1. 題外話
在蛻變成蝶的一系列學習當中,我們已經掌握了大部分Linux驅動的知識,在乾坤合一的分享當中,以綜合例項為主要講解,在一個月的蛻繭成蝶的學習探索當中,覺得資料結構,指標,連結串列等等佔據了程式碼的大部分框架,這些都需要我們平時多看程式碼,並且在相關知識點的時候需要在電腦上進行操作,這也讓自己受益匪淺,筆者在這期間受到了幾家IT學院的邀請錄製視訊,當兼職佈道師。但畢竟自己還是個學生,應該潛心學習,爭取更好的做一個IT的人才,所以都沒有接受,這裡很抱歉,並且會更加努力,好好鑽研,希望和大家一起共同進步~
2. 塊裝置與字元裝置I/O口操作異同
2.1 塊裝置只能以塊為單位接受輸入和返回輸出,而字元裝置則以位元組為單位。大多數裝置是字元裝置,因為它們不需要緩衝而且不以固定塊大小進行操作。
2.2 塊裝置對於I/O 請求有對應的緩衝區,因此它們可以選擇以什麼順序進行響應,字元裝置無須緩衝且被直接讀寫。對於儲存裝置而言調 讀寫的順序作用巨大,因為在讀寫連續的扇區比分離的扇區更快。
2.3 字元裝置只能被順序讀寫,而塊裝置可以隨機訪問。雖然塊裝置可隨機訪問,但是對於磁碟這類機械裝置而言,順序地組織塊裝置的訪問可以提高效能。
3. 塊裝置驅動結構
3.1 block_device_operations 結構體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
struct block device operations { int (*open)(struct inode *, struct file*); //開啟 int (*release)(struct inode *, struct file*); //釋放 //與字元裝置驅動類似,當裝置被開啟和關閉時將呼叫它們。 int (*ioctl)(struct inode *,struct file *,unsigned,unsigned long); //ioctl // ioctl()系統呼叫的實現,塊裝置包含大量的標準請求,這些標準請求由Linux 塊裝置層處理 long (*unlocked ioctl)(struct file *, unsigned, unsigned long); long (*compat ioctl)(struct file *, unsigned, unsigned long); int (*direct access)(struct block device *, sector t, unsigned long*); int (*media changed)(struct gendisk*); //介質被改變? //被核心呼叫來檢查是否驅動器中的介質已經改變,如果是,則返回一個非0 值,否則返回0 int (*revalidate disk)(struct gendisk*); //使介質有效 //revalidate_disk()函式被呼叫來響應一個介質改變,它給驅動一個機會來進行必要的工作以使新介質準備好。 int (*getgeo)(struct block device *, struct hd geometry*);//填充驅動器資訊 //根據驅動器的幾何資訊填充一個hd_geometry 結構體 struct module *owner; //模組擁有者 // 一個指向擁有這個結構體的模組的指標,它通常被初始化為THIS_MODULE }; |
3.2 gendisk 結構體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
struct gendisk { int major; /* 主裝置號 */ int first minor; /*第1個次裝置號*/ int minors; /* 最大的次裝置數,如果不能分割槽,則為1*/ char disk name [32]; /* 裝置名稱 */ struct hd struct **part; /* 磁碟上的分割槽資訊 */ struct block device operations *fops; /*塊裝置操作結構體*/ struct request queue *queue; /*請求佇列*/ void *private data; /*私有資料*/ sector t capacity; /*扇區數,512 位元組為1個扇區*/ int flags; char devfs name[64]; int number; struct device *driverfs dev; struct kobject kobj; struct timer rand state *random; int policy; atomic t sync io; /* RAID */ unsigned long stamp; int in flight; #ifdef CONFIG SMP struct disk stats *dkstats; #else struct disk stats dkstats; #endif }; |
3.3 gendisk的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//分配gendisk struct gendisk *alloc disk (int minors); // 增加gendisk void add disk(struct gendisk *gd); // 釋放gendisk void del gendisk (struct gendisk *gd); //gendisk 引用計數 // 設定gendisk 容量 void set capacity (struct gendisk *disk, sector t size); |
3.4 request 與bio 結構體
1) 請求
在Linux 塊裝置驅動中,使用request 結構體來表徵等待進行的I/O 請求,request 結構體的主要成員包括(只用於核心塊裝置層):
1 2 3 4 5 |
sector t hard sector; //第一個尚未傳輸的扇區 unsigned long hard nr sectors; //尚待完成的扇區數 unsigned int hard cur sectors; //當前I/O 操作中待完成的扇區數 |
2) 請求佇列
一個塊請求佇列是一個塊I/O 請求的佇列,請求佇列跟蹤的塊I/O 請求,它儲存用於描述這個裝置能夠支援的請求的型別資訊、它們的最大大小、多少不同的段可進入一個請求、硬體扇區大小、對齊要求等引數,其結果是:如果請求佇列被配置正確了,它不會交給該裝置一個不能處理的請求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
//request 佇列結構體 struct request queue { ... /* 保護佇列結構體的自旋鎖 */ spinlock t queue lock; spinlock t *queue lock; /* 佇列kobject */ struct kobject kobj; /* 佇列設定 */ unsigned long nr requests; /* 最大的請求數量 */ unsigned int nr congestion on; unsigned int nr congestion off; unsigned int nr batching; unsigned short max sectors; /* 最大的扇區數 */ unsigned short max hw sectors; unsigned short max phys segments; /* 最大的段數 */ unsigned short max hw segments; unsigned short hardsect size; /* 硬體扇區尺寸 */ unsigned int max segment size; /* 最大的段尺寸 */ unsigned long seg boundary mask; /* 段邊界掩碼 */ unsigned int dma alignment; /* DMA 傳送的記憶體對齊限制 */ struct blk queue tag *queue tags; atomic t refcnt; /* 引用計數 */ unsigned int in flight; unsigned int sg timeout; unsigned int sg reserved size; int node; struct list head drain list; struct request *flush rq; unsigned char ordered; }; |
3) 塊I/O
通常一個bio 對應一個I/O 請求,一個請求可以包含多個bio。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
struct bio { sector t bi sector; /* 要傳輸的第一個扇區 */ //標識這個 bio 要傳送的第一個 (512 位元組)扇區。 struct bio *bi next; /* 下一個bio */ struct block device *bi bdev; unsigned long bi flags; /* 狀態、命令等 */ unsigned long bi rw; /* 低位表示READ/WRITE,高位表示優先順序*/ unsigned short bi vcnt; /* bio vec 數量 */ unsigned short bi idx; /* 當前bvl vec 索引 */ /*不相鄰的物理段的數目*/ unsigned short bi phys segments; /*物理合並和DMA remap合併後不相鄰的物理段的數目*/ unsigned short bi hw segments; unsigned int bi size; /* 以位元組為單位所需傳輸的資料大小 */ //被傳送的資料大小,以位元組為單位,驅動中可以使用bio_sectors(bio)巨集獲得以扇區為單位的大小。 /* 為了明瞭最大的hw 尺寸,我們考慮這個bio 中第一個和最後一個虛擬的可合併的段的尺寸 */ unsigned int bi hw front size; unsigned int bi hw back size; unsigned int bi max vecs; /* 我們能持有的最大bvl vecs 數 */ struct bio vec *bi io vec; /* 實際的vec 列表 */ bio end io t *bi end io; atomic t bi cnt; void *bi private; bio destructor t *bi destructor; /* destructor */ }; |
3.5 塊裝置驅動註冊與登出
首先註冊她們自己到核心,其函式原型如下
1 |
int register blkdev (unsigned int major, const char *name);// major引數是塊裝置要使用的主裝置號,name為裝置名 |
與register_blkdev()對應的登出函式是unregister_blkdev(),其原型為:
1 2 |
int unregister blkdev (unsigned int major, const char *name); // 傳遞給register_blkdev() 的引數必須與傳遞給register_blkdev() 的引數匹配,否則這個函式返回-EINVAL |
4 Linux 塊裝置驅動的模組載入與解除安裝
4.1 需要完成的工作
- 分配、初始化請求佇列,繫結請求佇列和請求函式。
- 分配、初始化gendisk,給gendisk 的maj or、fops 、queue 等成員賦值,最後新增gendisk。
- 註冊塊裝置驅動。
4.2 塊裝置驅動的模組載入函式模板 (使用bl k_a llo c_que ue )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
static int init xxx init (void) { //分配gendisk xxx disks = alloc disk (1); if (!xxx disks) { goto out; } //塊裝置驅動註冊 if (register blkdev (XXX MAJOR, "xxx")) { err = - EIO; goto out; } // “請求佇列”分配 xxx queue = blk alloc queue (GFP KERNEL); if (!xxx queue) { goto out queue; } blk queue make request(xxx queue, &xxx make request); //繫結“製造請求”函式 blk queue hardsect size (xxx queue, xxx blocksize); //硬體扇區尺寸設定 //gendisk初始化 xxx disks->major = XXX MAJOR; xxx disks->first minor = 0; xxx disks->fops = &xxx op; xxx disks->queue = xxx queue; sprintf(xxx disks->disk name, "xxx%d", i); set capacity (xxx disks, xxx size); //xxx size 以512bytes 為單位 add disk (xxx disks); //新增gendisk return 0; out queue: unregister blkdev (XXX MAJOR, "xxx"); out: put disk(xxx disks); blk cleanup queue (xxx queue); return - ENOMEM; } |
4.3 塊裝置驅動的模組載入函式模板(使用bl k_ i nit_queue )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
static int init xxx init (void) { //塊裝置驅動註冊 if (register blkdev (XXX MAJOR, "xxx")) { err = - EIO; goto out; } //請求佇列初始化 xxx queue = blk init queue (xxx request, xxx lock); if (!xxx queue) { goto out queue; } blk queue hardsect size (xxx queue, xxx blocksize); //硬體扇區尺寸 設定 //gendisk初始化 xxx disks->major = XXX MAJOR; xxx disks->first minor = 0; xxx disks->fops = &xxx op; xxx disks->queue = xxx queue; sprintf(xxx disks->disk name, "xxx%d", i); set capacity (xxx disks, xxx size *2); add disk (xxx disks); //新增gendisk return 0; out queue: unregister blkdev (XXX MAJOR, "xxx"); out: put disk(xxx disks); blk cleanup queue (xxx queue); return - ENOMEM; } |
4.4 在塊裝置的open()函式中賦值private_data
1 2 3 4 5 6 7 8 9 10 |
static int xxx open (struct inode *inode, struct file *filp) { struct xxx dev *dev = inode->i bdev->bd disk->private data; filp->private data = dev; //賦值file 的private data ... return 0; } |
5 塊裝置的I/O請求處理
5.1 使用求情佇列
塊裝置驅動請求函式的原型為:
1 |
void request (request queue t *queue);//請求函式可以在沒有完成請求佇列中的所有請求的情況下返回,甚至它一個請求不完成都可以返回 |
下面給出了一個更復雜的請求函式,它進行了3 層遍歷:遍歷請求隊 列中的每個請求,遍歷請求中的每個bio,遍歷bio 中的每個段。請求函式遍歷請求、bio 和段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
static void xxx full request (request queue t *q) { struct request *req; int sectors xferred; struct xxx dev *dev = q->queuedata; /* 遍歷每個請求 */ while ((req = elv next request(q)) != NULL) { if (!blk fs request (req)) { printk (KERN NOTICE "Skip non-fs request\n"); end request (req, 0); continue; } sectors xferred = xxx xfer request (dev, req); if (!end that request first (req, 1, sectors xferred)) { blkdev dequeue request (req); end that request last (req); } } } /* 請求處理 */ static int xxx xfer request (struct xxx dev *dev,struct request *req) { struct bio *bio; int nsect = 0; /* 遍歷請求中的每個bio */ rq for each bio (bio, req) { xxx xfer bio (dev, bio); nsect += bio->bi size / KERNEL SECTOR SIZE; } return nsect; } /* bio 處理 */ static int xxx xfer bio (struct xxx dev *dev, struct bio *bio) { int i; struct bio vec *bvec; sector t sector = bio->bi sector; /* 遍歷每一段 */ bio for each segment(bvec, bio, i) { char *buffer = bio kmap atomic(bio, i, KM USER0); xxx transfer(dev, sector, bio cur sectors(bio), buffer, bio data dir (bio) == WRITE); sector += bio cur sectors(bio); bio kunmap atomic (bio, KM USER0); } return 0; } |
5.2 不適用請求佇列
有些裝置不需要使用請求佇列,其函式原型如下:
1 2 |
typedef int (make request fn) (request queue t *q, struct bio *bio); //bio 結構體表示一個或多個要傳送的緩衝區 |
在處理處理bio完成後應該使用bio_endio()函式通知處理結束,如下所示:
1 2 |
void bio endio (struct bio *bio, unsigned int bytes, int error); //引數bytes 是已經傳送的位元組數,它可以比這個bio 所代表的位元組數少 |
不管對應的I/O 處理成功與否,“製造請求”函式都應該返回0 。如果“製造請求” 函式返回一個非零值,bio 將被再次提交。下面程式碼所示為一個 “製造請求”函式的例子。
1 2 3 4 5 6 7 8 9 10 11 |
static int xxx make request (request queue t *q, struct bio *bio) { struct xxx dev *dev = q->queuedata; int status; status = xxx xfer bio (dev, bio); //處理bio bio endio (bio, bio->bi size, status); //通告結束 return 0; } |
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式