16.1. 注冊

2018-02-24 15:50 更新

16.1.?注冊

塊驅(qū)動, 象字符驅(qū)動, 必須使用一套注冊接口來使內(nèi)核可使用它們的設(shè)備. 概念是類似的, 但是塊設(shè)備注冊的細(xì)節(jié)是都不同的. 你有一整套新的數(shù)據(jù)結(jié)構(gòu)和設(shè)備操作要學(xué)習(xí).

16.1.1.?塊驅(qū)動注冊

大部分塊驅(qū)動采取的第一步是注冊它們自己到內(nèi)核. 這個任務(wù)的函數(shù)是 register_blkdev(在 <linux/fs.h> 中定義):


int register_blkdev(unsigned int major, const char *name); 

參數(shù)是你的設(shè)備要使用的主編號和關(guān)聯(lián)的名子(內(nèi)核將顯示它在 /proc/devices). 如果 major 傳遞為0, 內(nèi)核分配一個新的主編號并且返回它給調(diào)用者. 如常, 自 register_blkdev 的一個負(fù)的返回值指示已發(fā)生了一個錯誤.

取消注冊的對應(yīng)函數(shù)是:


int unregister_blkdev(unsigned int major, const char *name); 

這里, 參數(shù)必須匹配傳遞給 register_blkdev 的那些, 否則這個函數(shù)返回 -EINVAL 并且什么都不注銷.

在2.6內(nèi)核, 對 register_blkdev 的調(diào)用完全是可選的. 由 register_blkdev 所進(jìn)行的功能已隨時間正在減少; 這個調(diào)用唯一的任務(wù)是 (1) 如果需要, 分配一個動態(tài)主編號, 并且 (2) 在 /proc/devices 創(chuàng)建一個入口. 在將來的內(nèi)核, register_blkdev 可能被一起去掉. 同時, 但是, 大部分驅(qū)動仍然調(diào)用它; 它是慣例.

16.1.2.?磁盤注冊

雖然 register_blkdev 可用來獲得一個主編號, 它不使任何磁盤驅(qū)動器對系統(tǒng)可用. 有一個分開的注冊接口你必須使用來管理單獨(dú)的驅(qū)動器. 使用這個接口要求熟悉一對新結(jié)構(gòu), 這就是我們的起點(diǎn).

16.1.2.1.?塊設(shè)備操作

字符設(shè)備通過 file_ 操作結(jié)構(gòu)使它們的操作對系統(tǒng)可用. 一個類似的結(jié)構(gòu)用在塊設(shè)備上; 它是 struct block_device_operations, 定義在 <linux/fs.h>. 下面是一個對這個結(jié)構(gòu)中的成員的簡短的概覽; 當(dāng)我們進(jìn)入 sbull 驅(qū)動的細(xì)節(jié)時詳細(xì)重新訪問它們.

int (open)(struct inode inode, struct file filp);int (release)(struct inode inode, struct file filp);
就像它們的字符驅(qū)動對等體一樣工作的函數(shù); 無論何時設(shè)備被打開和關(guān)閉都調(diào)用它們. 一個字符驅(qū)動可能通過啟動設(shè)備或者鎖住門(為可移出的介質(zhì))來響應(yīng)一個 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ū)動 ioctl 方法相當(dāng)短.

int (media_changed) (struct gendisk gd);
被內(nèi)核調(diào)用來檢查是否用戶已經(jīng)改變了驅(qū)動器中的介質(zhì)的方法, 如果是這樣返回一個非零值. 顯然, 這個方法僅適用于支持可移出的介質(zhì)的驅(qū)動器(并且最好給驅(qū)動一個"介質(zhì)被改變"標(biāo)志); 在其他情況下可被忽略.

struct gendisk 參數(shù)是內(nèi)核任何表示單個磁盤; 我們將在下一節(jié)查看這個結(jié)構(gòu).

int (revalidate_disk) (struct gendisk gd);
revalidate_disk 方法被調(diào)用來響應(yīng)一個介質(zhì)改變; 它給驅(qū)動一個機(jī)會來進(jìn)行需要的任何工作使新介質(zhì)準(zhǔn)備好使用. 這個函數(shù)返回一個 int 值, 但是值被內(nèi)核忽略.

struct module *owner;
一個指向擁有這個結(jié)構(gòu)的模塊的指針; 它應(yīng)當(dāng)常常被初始化為 THIS_MODULE.

專心的讀者可能已注意到這個列表一個有趣的省略: 沒有實(shí)際讀或?qū)憯?shù)據(jù)的函數(shù). 在塊 I/O 子系統(tǒng), 這些操作由請求函數(shù)處理, 它們應(yīng)當(dāng)有它們自己的一節(jié)并且在本章后面討論. 在我們談?wù)摲?wù)請求之前, 我們必須完成對磁盤注冊的討論.

16.1.2.2.?gendisk 結(jié)構(gòu)

struct gendisk (定義于 <linux/genhd.h>) 是單獨(dú)一個磁盤驅(qū)動器的內(nèi)核表示. 事實(shí)上, 內(nèi)核還使用 gendisk 來表示分區(qū), 但是驅(qū)動作者不必知道這點(diǎn). struct gedisk 中有幾個成員, 必須被一個塊驅(qū)動初始化:

int major;int first_minor;int minors;
描述被磁盤使用的設(shè)備號的成員. 至少, 一個驅(qū)動器必須使用最少一個次編號. 如果你的驅(qū)動會是可分區(qū)的, 但是(并且大部分應(yīng)當(dāng)是), 你要分配一個次編號給每個可能的分區(qū). 次編號的一個普通的值是 16, 它允許"全磁盤"設(shè)備盒 15 個分區(qū). 一些磁盤驅(qū)動使用 64 個次編號給每個設(shè)備.

char disk_name[32];
應(yīng)當(dāng)被設(shè)置為磁盤驅(qū)動器名子的成員. 它出現(xiàn)在 /proc/partitions 和 sysfs.

struct block_device_operations *fops;
來自前一節(jié)的設(shè)備操作集合.

struct request_queue *queue;
被內(nèi)核用來管理這個設(shè)備的 I/O 請求的結(jié)構(gòu); 我們在"請求處理"一節(jié)中檢查它.

int flags;
一套標(biāo)志(很少使用), 描述驅(qū)動器的狀態(tài). 如果你的設(shè)備有可移出的介質(zhì), 你應(yīng)當(dāng)設(shè)置 GENHD_FL_REMOVABLE. CD-ROM 驅(qū)動器可設(shè)置 GENHD_FL_CD. 如果, 由于某些原因, 你不需要分區(qū)信息出現(xiàn)在 /proc/partitions, 設(shè)置 GENHD_FL_SUPPRESS_PARTITIONS_INFO.

sector_t capacity;
這個驅(qū)動器的容量, 以512-字節(jié)扇區(qū)來計(jì). sector_t 類型可以是 64 位寬. 驅(qū)動不應(yīng)當(dāng)直接設(shè)置這個成員; 相反, 傳遞扇區(qū)數(shù)目給 set_capacity.

void *private_data;
塊驅(qū)動可使用這個成員作為一個指向它們自己內(nèi)部數(shù)據(jù)的指針.

內(nèi)核提供了一小部分函數(shù)來使用 gendisk 結(jié)構(gòu). 我們在這里介紹它們, 接著看 sbull 如何使用它們來使系統(tǒng)可使用它的磁盤驅(qū)動器.

struct gendisk 是一個動態(tài)分配的結(jié)構(gòu), 它需要特別的內(nèi)核操作來初始化; 驅(qū)動不能自己分配這個結(jié)構(gòu). 相反, 你必須調(diào)用:


struct gendisk *alloc_disk(int minors); 

minors 參數(shù)應(yīng)當(dāng)是這個磁盤使用的次編號數(shù)目; 注意你不能在之后改變 minors 成員并且期望事情可以正確工作. 當(dāng)不再需要一個磁盤時, 它應(yīng)當(dāng)被釋放, 使用:


void del_gendisk(struct gendisk *gd);

一個 gendisk 是一個被引用計(jì)數(shù)的結(jié)構(gòu)(它含有一個 kobject). 有 get_disk 和 put_disk 函數(shù)用來操作引用計(jì)數(shù), 但是驅(qū)動應(yīng)當(dāng)從不需要做這個. 正常地, 對 del_gendisk 的調(diào)用去掉了最一個 gendisk 的最終的引用, 但是不保證這樣. 因此, 這個結(jié)構(gòu)可能繼續(xù)存在(并且你的方法可能被調(diào)用)在調(diào)用 del_gendisk 之后. 但是, 如果你刪除這個結(jié)構(gòu)當(dāng)沒有用戶時(即, 在最后的釋放之后, 或者在你的模塊清理函數(shù)), 你可確信你不會再收到它的信息.

分配一個 gendisk 結(jié)構(gòu)不能使系統(tǒng)可使用這個磁盤. 要做到這點(diǎn), 你必須初始化這個結(jié)構(gòu)并且調(diào)用 add_disk:


void add_disk(struct gendisk *gd); 

這里記住一件重要的事情:一旦你調(diào)用add_disk, 這個磁盤是"活的"并且它的方法可被在任何時間被調(diào)用. 實(shí)際上, 這樣的第一個調(diào)用將可能發(fā)生, 即便在 add_disk 返回之前; 內(nèi)核將讀前幾個字節(jié)以試圖找到一個分區(qū)表. 因此你不應(yīng)當(dāng)調(diào)用 add_disk 直到你的驅(qū)動被完全初始化并且準(zhǔn)備好響應(yīng)對那個磁盤的請求.

16.1.3.?在 sbull 中的初始化

是時間進(jìn)入一些例子了. sbull 驅(qū)動(從 O'Reilly 的 FTP 網(wǎng)站, 以及其他例子源碼)實(shí)現(xiàn)一套內(nèi)存中的虛擬磁盤驅(qū)動器. 對每個驅(qū)動器, sbull 分配(使用 vmalloc, 為了簡單)一個內(nèi)存數(shù)組; 它接著使這個數(shù)組可通過塊操作來使用. 這個 sbull 驅(qū)動可通過分區(qū)這個驅(qū)動器, 在上面建立文件系統(tǒng), 以及加載到系統(tǒng)層級中來測試.

象我們其他的例子驅(qū)動一樣, sbull 允許一個主編號在編譯或者模塊加載時被指定. 如果沒有指定, 動態(tài)分配一個. 因?yàn)閷?register_blkdev 的調(diào)用被用來動態(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;
}

同樣, 象我們在本書已展現(xiàn)的其他虛擬設(shè)備, sbull 設(shè)備由一個內(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 */  
};  

需要幾個步驟來初始化這個結(jié)構(gòu), 并且使系統(tǒng)可用關(guān)聯(lián)的設(shè)備. 我們從基本的初始化開始, 并且分配底層的內(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);

重要的是在下一步之前分配和初始化一個自旋鎖, 下一步是分配請求隊(duì)列. 我們在進(jìn)入請求處理時詳細(xì)看這個過程; 現(xiàn)在, 只需說必要的調(diào)用是:


dev->queue = blk_init_queue(sbull_request, &dev->lock); 

這里, sbull_request 是我們的請求函數(shù) -- 實(shí)際進(jìn)行塊讀和寫請求的函數(shù). 當(dāng)我們分配一個請求隊(duì)列時, 我們必須提供一個自旋鎖來控制對那個隊(duì)列的存取. 這個鎖由驅(qū)動提供而不是內(nèi)核通常的部分, 因?yàn)? 常常, 請求隊(duì)列和其他的驅(qū)動數(shù)據(jù)結(jié)構(gòu)在相同的臨界區(qū); 它們可能被同時存取. 如同任何分配內(nèi)存的函數(shù), blk_init_queue 可能失敗, 因此你必須在繼續(xù)之前檢查返回值.

一旦我們有我們的設(shè)備內(nèi)存和請求隊(duì)列, 我們可分配, 初始化, 并且安裝對應(yīng)的 gendisk 結(jié)構(gòu). 做這個工作的代碼是:


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 是每個 sbull 設(shè)備所支持的次編號的數(shù)目. 當(dāng)我們設(shè)置第一個次編號給每個設(shè)備, 我們必須考慮被之前的設(shè)備所用的全部編號. 磁盤的名子被設(shè)置, 這樣第一個是 sbulla, 第二個是 sbullb, 等等. 用戶空間可接著添加分區(qū)號以便它們在第 2 個設(shè)備上的分區(qū)可能是 /dev/sbull3.

一旦所有的都被設(shè)置, 我們以對 add_disk 的調(diào)用來結(jié)束. 我們的幾個方法將在 add_disk 返回時被調(diào)用, 因此我們負(fù)責(zé)做這個調(diào)用, 這是初始化我們的設(shè)備的最后一步.

16.1.4.?注意扇區(qū)大小

如同我們之前提到的, 內(nèi)核對待每個磁盤如同一個 512-字節(jié)扇區(qū)的數(shù)組. 不是所有的硬件都使用那個扇區(qū)大小, 但是. 使一個有不同扇區(qū)大小的設(shè)備工作不是一件很難的事; 只要小心處理幾個細(xì)節(jié). sbull 設(shè)備輸出一個 hardsect_size 參數(shù), 可被用來改變設(shè)備的"硬件"扇區(qū)大小. 通過看它的實(shí)現(xiàn), 你可見到如何添加這個支持到你自己的驅(qū)動.

這些細(xì)節(jié)中的第一個是通知內(nèi)核你的設(shè)備支持的扇區(qū)大小. 硬件扇區(qū)大小是一個在請求隊(duì)列的參數(shù), 而不是在 gendisk 結(jié)構(gòu). 這個大小通過調(diào)用 blk_queue_hardsect_size 設(shè)置的, 在分配隊(duì)列后馬上進(jìn)行:


blk_queue_hardsect_size(dev->queue, hardsect_size); 

一旦完成那個, 內(nèi)核堅(jiān)持你的設(shè)備的硬件扇區(qū)大小. 所有的 I/O 請求被正確對齊到一個硬件扇區(qū)的起始, 并且每個請求的長度是一個整數(shù)的扇區(qū)數(shù). 你必須記住, 但是, 內(nèi)核一直以 512-字節(jié)扇區(qū)表述自己; 因此, 有必要相應(yīng)地轉(zhuǎn)換所有的扇區(qū)號. 因此, 例如, 當(dāng) sbull 在它的 gendisk 結(jié)構(gòu)中設(shè)置設(shè)備的容量時, 這個調(diào)用看來象:


set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));

KERNEL_SECTOR_SIZE 是一個本地定義的常量, 我們用來調(diào)整內(nèi)核的 512-字節(jié)和任何我們已被告知要使用的大小. 在我們查看 sbull 請求處理邏輯中會不時看到這類計(jì)算出來.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號