儲存筆記--塊裝置驅動的註冊
塊裝置的註冊
1。塊裝置模型示意圖
一個塊是一個固定大小的資料塊, 大小由核心決定. 塊常常是 4096 位元組, 但是這個值可依賴體系和使用的檔案系統而變化. 一個扇區, 相反, 是一個小塊, 它的大小常常由底層的硬體決定. 核心期望處理實現 512-位元組扇區的裝置。
2。註冊
2.1 塊驅動的註冊
int register_blkdev(unsigned int major, const char *name);
引數是你的裝置要使用的主編號和關聯的名子(核心將顯示它在 /proc/devices). 如果 major 傳遞為0, 核心分配一個新的主編號並且返回它給呼叫者. 如常, 自 register_blkdev 的一個負的返回值指示已發生了一個錯誤.
取消註冊的對應函式是:
int unregister_blkdev(unsigned int major, const char *name);2.2 磁碟註冊
磁碟操作函式 struct block_device_operations。
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
就像它們的字元驅動對等體一樣工作的函式; 無論何時裝置被開啟和關閉都呼叫它們. 一個字元驅動可能通過啟動裝置或者鎖住門(為可移出的介質)來響應一個 open 呼叫. 如果你將介質鎖入裝置, 你當然應當在 release 方法中解鎖.int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
實現 ioctl 系統呼叫的方法. 但是, 塊層首先解釋大量的標準請求; 因此大部分的塊驅動 ioctl 方法相當短.int (*media_changed) (struct gendisk *gd);
被核心呼叫來檢查是否使用者已經改變了驅動器中的介質的方法, 如果是這樣返回一個非零值. 顯然, 這個方法僅適用於支援可移出的介質的驅動器(並且最好給驅動一個”介質被改變”標誌); 在其他情況下可被忽略.
struct gendisk 引數是核心任何表示單個磁碟; 我們將在下一節檢視這個結構.int (*revalidate_disk) (struct gendisk *gd);
revalidate_disk 方法被呼叫來響應一個介質改變; 它給驅動一個機會來進行需要的任何工作使新介質準備好使用. 這個函式返回一個 int 值, 但是值被核心忽略.struct module *owner;
一個指向擁有這個結構的模組的指標; 它應當常常被初始化為 THIS_MODULE.磁碟結構體 gendisk
struct gendisk 是單獨一個磁碟驅動器的核心表示. 事實上, 核心還使用 gendisk 來表示分割槽, 必須被一個塊驅動初始化:
int major;
int first_minor;
int minors;
描述被磁碟使用的裝置號的成員. 至少, 一個驅動器必須使用最少一個次編號. 如果你的驅動會是可分割槽的, 但是(並且大部分應當是), 你要分配一個次編號給每個可能的分割槽. 次編號的一個普通的值是 16, 它允許”全磁碟”裝置盒 15 個分割槽. 一些磁碟驅動使用 64 個次編號給每個裝置.char disk_name[32];
應當被設定為磁碟驅動器名子的成員. 它出現在 /proc/partitions 和 sysfs.struct block_device_operations *fops;
來自前一節的裝置操作集合.struct request_queue *queue;
被核心用來管理這個裝置的 I/O 請求的結構; 我們在”請求處理”一節中檢查它.int flags;
一套標誌(很少使用), 描述驅動器的狀態. 如果你的裝置有可移出的介質, 你應當設定 GENHD_FL_REMOVABLE. CD-ROM 驅動器可設定 GENHD_FL_CD. 如果, 由於某些原因, 你不需要分割槽資訊出現在 /proc/partitions, 設定 GENHD_FL_SUPPRESS_PARTITIONS_INFO.sector_t capacity;
這個驅動器的容量, 以512-位元組扇區來計. sector_t 型別可以是 64 位寬. 驅動不應當直接設定這個成員; 相反, 傳遞扇區數目給 set_capacity.void *private_data;
塊驅動可使用這個成員作為一個指向它們自己內部資料的指標.
核心提供了一小部分函式來使用 gendisk 結構. 我們在這裡介紹它們, 接著看 sbull 如何使用它們來使系統可使用它的磁碟驅動器.struct gendisk 是一個動態分配的結構, 它需要特別的核心操作來初始化; 驅動不能自己分配這個結構. 相反, 你必須呼叫:
struct gendisk *alloc_disk(int minors);
minors 引數應當是這個磁碟使用的次編號數目; 注意你不能在之後改變 minors 成員並且期望事情可以正確工作. 當不再需要一個磁碟時, 它應當被釋放, 使用:void del_gendisk(struct gendisk *gd);
一個 gendisk 是一個被引用計數的結構(它含有一個 kobject). 有 get_disk 和 put_disk 函式用來操作引用計數, 但是驅動應當從不需要做這個. 正常地, 對 del_gendisk 的呼叫去掉了最一個 gendisk 的最終的引用, 但是不保證這樣. 因此, 這個結構可能繼續存在(並且你的方法可能被呼叫)在呼叫 del_gendisk 之後. 但是, 如果你刪除這個結構當沒有使用者時(即, 在最後的釋放之後, 或者在你的模組清理函式), 你可確信你不會再收到它的資訊.分配一個 gendisk 結構不能使系統可使用這個磁碟. 要做到這點, 你必須初始化這個結構並且呼叫 add_disk:
void add_disk(struct gendisk *gd);
這裡記住一件重要的事情:一旦你呼叫add_disk, 這個磁碟是”活的”並且它的方法可被在任何時間被呼叫. 實際上, 這樣的第一個呼叫將可能發生, 即便在 add_disk 返回之前; 核心將讀前幾個位元組以試圖找到一個分割槽表. 因此你不應當呼叫 add_disk 直到你的驅動被完全初始化並且準備好響應對那個磁碟的請求.
3。初始化
- 虛擬塊裝置
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 */
}; 註冊
sbull_major = register_blkdev(sbull_major, “sbull”);
if (sbull_major <= 0)
{
printk(KERN_WARNING “sbull: unable to get major number\n”);
return -EBUSY;
}分配記憶體
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);
dev->queue = blk_init_queue(sbull_request, &dev->lock);
這裡, sbull_request 是我們的請求函式 – 實際進行塊讀和寫請求的函式. 當我們分配一個請求佇列時, 我們必須提供一個自旋鎖來控制對那個佇列的存取. 這個鎖由驅動提供而不是核心通常的部分, 因為, 常常, 請求佇列和其他的驅動資料結構在相同的臨界區; 它們可能被同時存取. 如同任何分配記憶體的函式, blk_init_queue 可能失敗, 因此你必須在繼續之前檢查返回值. - 並且安裝對應的 gendisk
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 裝置所支援的次編號的數目. 當我們設定第一個次編號給每個裝置, 我們必須考慮被之前的裝置所用的全部編號. 磁碟的名子被設定, 這樣第一個是 sbulla, 第二個是 sbullb, 等等. 使用者空間可接著新增分割槽號以便它們在第 2 個裝置上的分割槽可能是 /dev/sbull3.