W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
塊驅(qū)動(dòng), 象字符驅(qū)動(dòng), 必須使用一套注冊(cè)接口來(lái)使內(nèi)核可使用它們的設(shè)備. 概念是類似的, 但是塊設(shè)備注冊(cè)的細(xì)節(jié)是都不同的. 你有一整套新的數(shù)據(jù)結(jié)構(gòu)和設(shè)備操作要學(xué)習(xí).
大部分塊驅(qū)動(dòng)采取的第一步是注冊(cè)它們自己到內(nèi)核. 這個(gè)任務(wù)的函數(shù)是 register_blkdev(在 <linux/fs.h> 中定義):
int register_blkdev(unsigned int major, const char *name);
參數(shù)是你的設(shè)備要使用的主編號(hào)和關(guān)聯(lián)的名子(內(nèi)核將顯示它在 /proc/devices). 如果 major 傳遞為0, 內(nèi)核分配一個(gè)新的主編號(hào)并且返回它給調(diào)用者. 如常, 自 register_blkdev 的一個(gè)負(fù)的返回值指示已發(fā)生了一個(gè)錯(cuò)誤.
取消注冊(cè)的對(duì)應(yīng)函數(shù)是:
int unregister_blkdev(unsigned int major, const char *name);
這里, 參數(shù)必須匹配傳遞給 register_blkdev 的那些, 否則這個(gè)函數(shù)返回 -EINVAL 并且什么都不注銷.
在2.6內(nèi)核, 對(duì) register_blkdev 的調(diào)用完全是可選的. 由 register_blkdev 所進(jìn)行的功能已隨時(shí)間正在減少; 這個(gè)調(diào)用唯一的任務(wù)是 (1) 如果需要, 分配一個(gè)動(dòng)態(tài)主編號(hào), 并且 (2) 在 /proc/devices 創(chuàng)建一個(gè)入口. 在將來(lái)的內(nèi)核, register_blkdev 可能被一起去掉. 同時(shí), 但是, 大部分驅(qū)動(dòng)仍然調(diào)用它; 它是慣例.
雖然 register_blkdev 可用來(lái)獲得一個(gè)主編號(hào), 它不使任何磁盤(pán)驅(qū)動(dòng)器對(duì)系統(tǒng)可用. 有一個(gè)分開(kāi)的注冊(cè)接口你必須使用來(lái)管理單獨(dú)的驅(qū)動(dòng)器. 使用這個(gè)接口要求熟悉一對(duì)新結(jié)構(gòu), 這就是我們的起點(diǎn).
字符設(shè)備通過(guò) file_ 操作結(jié)構(gòu)使它們的操作對(duì)系統(tǒng)可用. 一個(gè)類似的結(jié)構(gòu)用在塊設(shè)備上; 它是 struct block_device_operations, 定義在 <linux/fs.h>. 下面是一個(gè)對(duì)這個(gè)結(jié)構(gòu)中的成員的簡(jiǎn)短的概覽; 當(dāng)我們進(jìn)入 sbull 驅(qū)動(dòng)的細(xì)節(jié)時(shí)詳細(xì)重新訪問(wèn)它們.
int (open)(struct inode inode, struct file filp);int (release)(struct inode inode, struct file filp);
就像它們的字符驅(qū)動(dòng)對(duì)等體一樣工作的函數(shù); 無(wú)論何時(shí)設(shè)備被打開(kāi)和關(guān)閉都調(diào)用它們. 一個(gè)字符驅(qū)動(dòng)可能通過(guò)啟動(dòng)設(shè)備或者鎖住門(mén)(為可移出的介質(zhì))來(lái)響應(yīng)一個(gè) open 調(diào)用. 如果你將介質(zhì)鎖入設(shè)備, 你當(dāng)然應(yīng)當(dāng)在 release 方法中解鎖.
int (ioctl)(struct inode inode, struct file *filp, unsigned int cmd, unsigned long arg);
實(shí)現(xiàn) ioctl 系統(tǒng)調(diào)用的方法. 但是, 塊層首先解釋大量的標(biāo)準(zhǔn)請(qǐng)求; 因此大部分的塊驅(qū)動(dòng) ioctl 方法相當(dāng)短.
int (media_changed) (struct gendisk gd);
被內(nèi)核調(diào)用來(lái)檢查是否用戶已經(jīng)改變了驅(qū)動(dòng)器中的介質(zhì)的方法, 如果是這樣返回一個(gè)非零值. 顯然, 這個(gè)方法僅適用于支持可移出的介質(zhì)的驅(qū)動(dòng)器(并且最好給驅(qū)動(dòng)一個(gè)"介質(zhì)被改變"標(biāo)志); 在其他情況下可被忽略.
struct gendisk 參數(shù)是內(nèi)核任何表示單個(gè)磁盤(pán); 我們將在下一節(jié)查看這個(gè)結(jié)構(gòu).
int (revalidate_disk) (struct gendisk gd);
revalidate_disk 方法被調(diào)用來(lái)響應(yīng)一個(gè)介質(zhì)改變; 它給驅(qū)動(dòng)一個(gè)機(jī)會(huì)來(lái)進(jìn)行需要的任何工作使新介質(zhì)準(zhǔn)備好使用. 這個(gè)函數(shù)返回一個(gè) int 值, 但是值被內(nèi)核忽略.
struct module *owner;
一個(gè)指向擁有這個(gè)結(jié)構(gòu)的模塊的指針; 它應(yīng)當(dāng)常常被初始化為 THIS_MODULE.
專心的讀者可能已注意到這個(gè)列表一個(gè)有趣的省略: 沒(méi)有實(shí)際讀或?qū)憯?shù)據(jù)的函數(shù). 在塊 I/O 子系統(tǒng), 這些操作由請(qǐng)求函數(shù)處理, 它們應(yīng)當(dāng)有它們自己的一節(jié)并且在本章后面討論. 在我們談?wù)摲?wù)請(qǐng)求之前, 我們必須完成對(duì)磁盤(pán)注冊(cè)的討論.
struct gendisk (定義于 <linux/genhd.h>) 是單獨(dú)一個(gè)磁盤(pán)驅(qū)動(dòng)器的內(nèi)核表示. 事實(shí)上, 內(nèi)核還使用 gendisk 來(lái)表示分區(qū), 但是驅(qū)動(dòng)作者不必知道這點(diǎn). struct gedisk 中有幾個(gè)成員, 必須被一個(gè)塊驅(qū)動(dòng)初始化:
int major;int first_minor;int minors;
描述被磁盤(pán)使用的設(shè)備號(hào)的成員. 至少, 一個(gè)驅(qū)動(dòng)器必須使用最少一個(gè)次編號(hào). 如果你的驅(qū)動(dòng)會(huì)是可分區(qū)的, 但是(并且大部分應(yīng)當(dāng)是), 你要分配一個(gè)次編號(hào)給每個(gè)可能的分區(qū). 次編號(hào)的一個(gè)普通的值是 16, 它允許"全磁盤(pán)"設(shè)備盒 15 個(gè)分區(qū). 一些磁盤(pán)驅(qū)動(dòng)使用 64 個(gè)次編號(hào)給每個(gè)設(shè)備.
char disk_name[32];
應(yīng)當(dāng)被設(shè)置為磁盤(pán)驅(qū)動(dòng)器名子的成員. 它出現(xiàn)在 /proc/partitions 和 sysfs.
struct block_device_operations *fops;
來(lái)自前一節(jié)的設(shè)備操作集合.
struct request_queue *queue;
被內(nèi)核用來(lái)管理這個(gè)設(shè)備的 I/O 請(qǐng)求的結(jié)構(gòu); 我們?cè)?請(qǐng)求處理"一節(jié)中檢查它.
int flags;
一套標(biāo)志(很少使用), 描述驅(qū)動(dòng)器的狀態(tài). 如果你的設(shè)備有可移出的介質(zhì), 你應(yīng)當(dāng)設(shè)置 GENHD_FL_REMOVABLE. CD-ROM 驅(qū)動(dòng)器可設(shè)置 GENHD_FL_CD. 如果, 由于某些原因, 你不需要分區(qū)信息出現(xiàn)在 /proc/partitions, 設(shè)置 GENHD_FL_SUPPRESS_PARTITIONS_INFO.
sector_t capacity;
這個(gè)驅(qū)動(dòng)器的容量, 以512-字節(jié)扇區(qū)來(lái)計(jì). sector_t 類型可以是 64 位寬. 驅(qū)動(dòng)不應(yīng)當(dāng)直接設(shè)置這個(gè)成員; 相反, 傳遞扇區(qū)數(shù)目給 set_capacity.
void *private_data;
塊驅(qū)動(dòng)可使用這個(gè)成員作為一個(gè)指向它們自己內(nèi)部數(shù)據(jù)的指針.
內(nèi)核提供了一小部分函數(shù)來(lái)使用 gendisk 結(jié)構(gòu). 我們?cè)谶@里介紹它們, 接著看 sbull 如何使用它們來(lái)使系統(tǒng)可使用它的磁盤(pán)驅(qū)動(dòng)器.
struct gendisk 是一個(gè)動(dòng)態(tài)分配的結(jié)構(gòu), 它需要特別的內(nèi)核操作來(lái)初始化; 驅(qū)動(dòng)不能自己分配這個(gè)結(jié)構(gòu). 相反, 你必須調(diào)用:
struct gendisk *alloc_disk(int minors);
minors 參數(shù)應(yīng)當(dāng)是這個(gè)磁盤(pán)使用的次編號(hào)數(shù)目; 注意你不能在之后改變 minors 成員并且期望事情可以正確工作. 當(dāng)不再需要一個(gè)磁盤(pán)時(shí), 它應(yīng)當(dāng)被釋放, 使用:
void del_gendisk(struct gendisk *gd);
一個(gè) gendisk 是一個(gè)被引用計(jì)數(shù)的結(jié)構(gòu)(它含有一個(gè) kobject). 有 get_disk 和 put_disk 函數(shù)用來(lái)操作引用計(jì)數(shù), 但是驅(qū)動(dòng)應(yīng)當(dāng)從不需要做這個(gè). 正常地, 對(duì) del_gendisk 的調(diào)用去掉了最一個(gè) gendisk 的最終的引用, 但是不保證這樣. 因此, 這個(gè)結(jié)構(gòu)可能繼續(xù)存在(并且你的方法可能被調(diào)用)在調(diào)用 del_gendisk 之后. 但是, 如果你刪除這個(gè)結(jié)構(gòu)當(dāng)沒(méi)有用戶時(shí)(即, 在最后的釋放之后, 或者在你的模塊清理函數(shù)), 你可確信你不會(huì)再收到它的信息.
分配一個(gè) gendisk 結(jié)構(gòu)不能使系統(tǒng)可使用這個(gè)磁盤(pán). 要做到這點(diǎn), 你必須初始化這個(gè)結(jié)構(gòu)并且調(diào)用 add_disk:
void add_disk(struct gendisk *gd);
這里記住一件重要的事情:一旦你調(diào)用add_disk, 這個(gè)磁盤(pán)是"活的"并且它的方法可被在任何時(shí)間被調(diào)用. 實(shí)際上, 這樣的第一個(gè)調(diào)用將可能發(fā)生, 即便在 add_disk 返回之前; 內(nèi)核將讀前幾個(gè)字節(jié)以試圖找到一個(gè)分區(qū)表. 因此你不應(yīng)當(dāng)調(diào)用 add_disk 直到你的驅(qū)動(dòng)被完全初始化并且準(zhǔn)備好響應(yīng)對(duì)那個(gè)磁盤(pán)的請(qǐng)求.
是時(shí)間進(jìn)入一些例子了. sbull 驅(qū)動(dòng)(從 O'Reilly 的 FTP 網(wǎng)站, 以及其他例子源碼)實(shí)現(xiàn)一套內(nèi)存中的虛擬磁盤(pán)驅(qū)動(dòng)器. 對(duì)每個(gè)驅(qū)動(dòng)器, sbull 分配(使用 vmalloc, 為了簡(jiǎn)單)一個(gè)內(nèi)存數(shù)組; 它接著使這個(gè)數(shù)組可通過(guò)塊操作來(lái)使用. 這個(gè) sbull 驅(qū)動(dòng)可通過(guò)分區(qū)這個(gè)驅(qū)動(dòng)器, 在上面建立文件系統(tǒng), 以及加載到系統(tǒng)層級(jí)中來(lái)測(cè)試.
象我們其他的例子驅(qū)動(dòng)一樣, sbull 允許一個(gè)主編號(hào)在編譯或者模塊加載時(shí)被指定. 如果沒(méi)有指定, 動(dòng)態(tài)分配一個(gè). 因?yàn)閷?duì) register_blkdev 的調(diào)用被用來(lái)動(dòng)態(tài)分配, sbull 應(yīng)當(dāng)這樣做:
sbull_major = register_blkdev(sbull_major, "sbull");
if (sbull_major <= 0)
{
printk(KERN_WARNING "sbull: unable to get major number\n");
return -EBUSY;
}
同樣, 象我們?cè)诒緯?shū)已展現(xiàn)的其他虛擬設(shè)備, sbull 設(shè)備由一個(gè)內(nèi)部結(jié)構(gòu)描述:
struct sbull_dev {
int size; /* Device size in sectors */
u8 *data; /* The data array */
short users; /* How many users */
short media_change; /* Flag a media change? */
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
struct timer_list timer; /* For simulated media changes */
};
需要幾個(gè)步驟來(lái)初始化這個(gè)結(jié)構(gòu), 并且使系統(tǒng)可用關(guān)聯(lián)的設(shè)備. 我們從基本的初始化開(kāi)始, 并且分配底層的內(nèi)存:
memset (dev, 0, sizeof (struct sbull_dev));
dev->size = nsectors*hardsect_size;
dev->data = vmalloc(dev->size);
if (dev->data == NULL)
{
printk (KERN_NOTICE "vmalloc failure.\n");
return;
}
spin_lock_init(&dev->lock);
重要的是在下一步之前分配和初始化一個(gè)自旋鎖, 下一步是分配請(qǐng)求隊(duì)列. 我們?cè)谶M(jìn)入請(qǐng)求處理時(shí)詳細(xì)看這個(gè)過(guò)程; 現(xiàn)在, 只需說(shuō)必要的調(diào)用是:
dev->queue = blk_init_queue(sbull_request, &dev->lock);
這里, sbull_request 是我們的請(qǐng)求函數(shù) -- 實(shí)際進(jìn)行塊讀和寫(xiě)請(qǐng)求的函數(shù). 當(dāng)我們分配一個(gè)請(qǐng)求隊(duì)列時(shí), 我們必須提供一個(gè)自旋鎖來(lái)控制對(duì)那個(gè)隊(duì)列的存取. 這個(gè)鎖由驅(qū)動(dòng)提供而不是內(nèi)核通常的部分, 因?yàn)? 常常, 請(qǐng)求隊(duì)列和其他的驅(qū)動(dòng)數(shù)據(jù)結(jié)構(gòu)在相同的臨界區(qū); 它們可能被同時(shí)存取. 如同任何分配內(nèi)存的函數(shù), blk_init_queue 可能失敗, 因此你必須在繼續(xù)之前檢查返回值.
一旦我們有我們的設(shè)備內(nèi)存和請(qǐng)求隊(duì)列, 我們可分配, 初始化, 并且安裝對(duì)應(yīng)的 gendisk 結(jié)構(gòu). 做這個(gè)工作的代碼是:
dev->gd = alloc_disk(SBULL_MINORS);
if (! dev->gd)
{
printk (KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
dev->gd->major = sbull_major;
dev->gd->first_minor = which*SBULL_MINORS;
dev->gd->fops = &sbull_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);
這里, SBULL_MINORS 是每個(gè) sbull 設(shè)備所支持的次編號(hào)的數(shù)目. 當(dāng)我們?cè)O(shè)置第一個(gè)次編號(hào)給每個(gè)設(shè)備, 我們必須考慮被之前的設(shè)備所用的全部編號(hào). 磁盤(pán)的名子被設(shè)置, 這樣第一個(gè)是 sbulla, 第二個(gè)是 sbullb, 等等. 用戶空間可接著添加分區(qū)號(hào)以便它們?cè)诘?2 個(gè)設(shè)備上的分區(qū)可能是 /dev/sbull3.
一旦所有的都被設(shè)置, 我們以對(duì) add_disk 的調(diào)用來(lái)結(jié)束. 我們的幾個(gè)方法將在 add_disk 返回時(shí)被調(diào)用, 因此我們負(fù)責(zé)做這個(gè)調(diào)用, 這是初始化我們的設(shè)備的最后一步.
如同我們之前提到的, 內(nèi)核對(duì)待每個(gè)磁盤(pán)如同一個(gè) 512-字節(jié)扇區(qū)的數(shù)組. 不是所有的硬件都使用那個(gè)扇區(qū)大小, 但是. 使一個(gè)有不同扇區(qū)大小的設(shè)備工作不是一件很難的事; 只要小心處理幾個(gè)細(xì)節(jié). sbull 設(shè)備輸出一個(gè) hardsect_size 參數(shù), 可被用來(lái)改變?cè)O(shè)備的"硬件"扇區(qū)大小. 通過(guò)看它的實(shí)現(xiàn), 你可見(jiàn)到如何添加這個(gè)支持到你自己的驅(qū)動(dòng).
這些細(xì)節(jié)中的第一個(gè)是通知內(nèi)核你的設(shè)備支持的扇區(qū)大小. 硬件扇區(qū)大小是一個(gè)在請(qǐng)求隊(duì)列的參數(shù), 而不是在 gendisk 結(jié)構(gòu). 這個(gè)大小通過(guò)調(diào)用 blk_queue_hardsect_size 設(shè)置的, 在分配隊(duì)列后馬上進(jìn)行:
blk_queue_hardsect_size(dev->queue, hardsect_size);
一旦完成那個(gè), 內(nèi)核堅(jiān)持你的設(shè)備的硬件扇區(qū)大小. 所有的 I/O 請(qǐng)求被正確對(duì)齊到一個(gè)硬件扇區(qū)的起始, 并且每個(gè)請(qǐng)求的長(zhǎng)度是一個(gè)整數(shù)的扇區(qū)數(shù). 你必須記住, 但是, 內(nèi)核一直以 512-字節(jié)扇區(qū)表述自己; 因此, 有必要相應(yīng)地轉(zhuǎn)換所有的扇區(qū)號(hào). 因此, 例如, 當(dāng) sbull 在它的 gendisk 結(jié)構(gòu)中設(shè)置設(shè)備的容量時(shí), 這個(gè)調(diào)用看來(lái)象:
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
KERNEL_SECTOR_SIZE 是一個(gè)本地定義的常量, 我們用來(lái)調(diào)整內(nèi)核的 512-字節(jié)和任何我們已被告知要使用的大小. 在我們查看 sbull 請(qǐng)求處理邏輯中會(huì)不時(shí)看到這類計(jì)算出來(lái).
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: