LDD3原始碼分析之塊裝置驅動程式
作者:劉昊昱
編譯環境:Ubuntu 10.10
核心版本:2.6.32-38-generic-pae
LDD3原始碼路徑:examples/sbull
一、塊裝置驅動基本操作
我們從init函式開始分析,sbull的init函式內容如下:
403static int __init sbull_init(void) 404{ 405 int i; 406 /* 407 * Get registered. 408 */ 409 sbull_major = register_blkdev(sbull_major, "sbull"); 410 if (sbull_major <= 0) { 411 printk(KERN_WARNING "sbull: unable to get major number\n"); 412 return -EBUSY; 413 } 414 /* 415 * Allocate the device array, and initialize each one. 416 */ 417 Devices = kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL); 418 if (Devices == NULL) 419 goto out_unregister; 420 for (i = 0; i < ndevices; i++) 421 setup_device(Devices + i, i); 422 423 return 0; 424 425 out_unregister: 426 unregister_blkdev(sbull_major, "sbd"); 427 return -ENOMEM; 428}
409行,呼叫register_blkdev(sbull_major,"sbull")函式,向kernel註冊該驅動程式對應的block裝置,register_blkdev函式的第一個引數指定裝置對應的主裝置號,如果第一個引數傳遞的值為0,kerenl將自動分配一個主裝置號並通過register_blkdev函式的返回值返回。sbull_major的定義如下:
28static int sbull_major = 0;
29module_param(sbull_major, int, 0);
可見,sbull_major的預設值為0,使用者可以通過引數指定其他值。
register_blkdev函式的第二個引數指定裝置名,將出現在/proc/devices/目錄下。
417行,呼叫kmalloc為裝置分配記憶體空間。sbull使用struct sbull_dev代表一個裝置,共有ndevices個裝置。struct sbull_dev定義如下:
66/* 67 *The internal representation of our device. 68*/ 69struct sbull_dev { 70 int size; /* Device size in sectors */ 71 u8 *data; /*The data array */ 72 short users; /*How many users */ 73 short media_change; /*Flag a media change? */ 74 spinlock_t lock; /*For mutual exclusion */ 75 struct request_queue *queue; /*The device request queue */ 76 struct gendisk *gd; /*The gendisk structure */ 77 struct timer_list timer; /*For simulated media changes */ 78};
ndevices變數定義如下:
34static int ndevices = 4;
35module_param(ndevices, int, 0);
可見,ndevices預設為4,使用者可以通過引數指定其他值。
用kmalloc分配的記憶體空間儲存在Devices變數中,它也是一個struct sbull_dev變數:
80static struct sbull_dev *Devices = NULL;
420-421行,迴圈呼叫setup_device函式,對每個sbull裝置進行初始化。setup_device函式定義如下:
324/*
325 * Set up our internal device.
326 */
327static void setup_device(structsbull_dev *dev, int which)
328{
329 /*
330 * Get some memory.
331 */
332 memset (dev, 0, sizeof (struct sbull_dev));
333 dev->size = nsectors*hardsect_size;
334 dev->data = vmalloc(dev->size);
335 if (dev->data == NULL) {
336 printk (KERN_NOTICE "vmalloc failure.\n");
337 return;
338 }
339 spin_lock_init(&dev->lock);
340
341 /*
342 * The timer which "invalidates" the device.
343 */
344 init_timer(&dev->timer);
345 dev->timer.data = (unsigned long) dev;
346 dev->timer.function = sbull_invalidate;
347
348 /*
349 * The I/O queue, depending on whether we are using our own
350 * make_request function or not.
351 */
352 switch (request_mode) {
353 case RM_NOQUEUE:
354 dev->queue = blk_alloc_queue(GFP_KERNEL);
355 if (dev->queue == NULL)
356 goto out_vfree;
357 blk_queue_make_request(dev->queue, sbull_make_request);
358 break;
359
360 case RM_FULL:
361 dev->queue = blk_init_queue(sbull_full_request, &dev->lock);
362 if (dev->queue == NULL)
363 goto out_vfree;
364 break;
365
366 default:
367 printk(KERN_NOTICE "Bad request mode %d, using simple\n",request_mode);
368 /* fall into.. */
369
370 case RM_SIMPLE:
371 dev->queue = blk_init_queue(sbull_request, &dev->lock);
372 if (dev->queue == NULL)
373 goto out_vfree;
374 break;
375 }
376 blk_queue_hardsect_size(dev->queue, hardsect_size);
377 dev->queue->queuedata = dev;
378 /*
379 * And the gendisk structure.
380 */
381 dev->gd = alloc_disk(SBULL_MINORS);
382 if (! dev->gd) {
383 printk (KERN_NOTICE "alloc_disk failure\n");
384 goto out_vfree;
385 }
386 dev->gd->major = sbull_major;
387 dev->gd->first_minor = which*SBULL_MINORS;
388 dev->gd->fops = &sbull_ops;
389 dev->gd->queue = dev->queue;
390 dev->gd->private_data = dev;
391 snprintf (dev->gd->disk_name, 32, "sbull%c", which +'a');
392 set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
393 add_disk(dev->gd);
394 return;
395
396 out_vfree:
397 if (dev->data)
398 vfree(dev->data);
399}
332行,將sbull_dev空間清零。
333行,dev->size是裝置以sector為單位的大小,nsectors是裝置有多少個sector,hardsect是裝置對應的每個sector的大小。nsectors和hardsect_size變數定義如下:
30static int hardsect_size = 512;
31module_param(hardsect_size, int, 0);
32static int nsectors = 1024; /* How bigthe drive is */
33module_param(nsectors, int, 0);
可見,nsectors預設值為1024,hardsect_size預設值為512,它們可以通過引數指定其他值。
334行,為dev->data分配dev->size的記憶體空間。
339行,初始化dev->lock,它是一個spin_lock。
341-346行,初始化定時器dev->timer,定時器函式為sbull_invalidate。sbull_dev被假設是可插撥裝置,當最後一個使用者關閉裝置時(即在sbull_release函式中),dev->timer被設定為30秒後觸發定時器中斷,如果沒有使用者在30秒內開啟裝置,裝置上的內容將被清空,並將dev->media_change標誌位設定為1,通知核心裝置已經被撥出。sbull_invalidate函式定義如下:
261/*
262 * The "invalidate" function runsout of the device timer; it sets
263 * a flag to simulate the removal of themedia.
264 */
265void sbull_invalidate(unsigned longldev)
266{
267 struct sbull_dev *dev = (struct sbull_dev *) ldev;
268
269 spin_lock(&dev->lock);
270 if (dev->users || !dev->data)
271 printk (KERN_WARNING "sbull: timer sanity check failed\n");
272 else
273 dev->media_change = 1;
274 spin_unlock(&dev->lock);
275}
273行,dev->media_change設定為1,表示裝置被撥出。
回到setup_device函式:
348-375行,初始化請求佇列,這裡分為三種情況,一是使用自己的make_request,二是完整的request,三是簡單的request。
376行,呼叫blk_queue_hardsect_size函式通知kernel sbull_dev支援的sector大小。
381行,呼叫alloc_disk函式建立一個struct gendisk物件,賦值給dev->gd。struct gendisk代表一個單獨的磁碟裝置,實際上,一個磁碟分割槽也是用structgendisk表示,但是驅動開發者不用關心這些細節。alloc_disk函式用於建立並初始化一個gendisk物件,其引數表示該磁碟物件使用的次裝置號的個數。
386-390行,對dev->gd的各個成員進行初始化。我們需要注意的是dev->gd->fops被賦值為sbull_ops。struct gendisk的fops成員變數是struct block_device_operations指標變數,它是對該塊裝置進行操作的函式集合。sbull_ops定義如下:
311/*
312 * The device operations structure.
313 */
314static struct block_device_operationssbull_ops = {
315 .owner = THIS_MODULE,
316 .open = sbull_open,
317 .release = sbull_release,
318 .media_changed =sbull_media_changed,
319 .revalidate_disk = sbull_revalidate,
320 .ioctl = sbull_ioctl
321};
我們後面會依次分析sbull_ops的成員函式。
392行,呼叫set_capacity函式,記錄裝置大小,以sector為單位。
393行,呼叫add_disk(dev->gd)向kernel註冊dev->gd。該函式執行後,裝置就“活”了,其操作函式隨時可能被呼叫。
至此,setup_device函式我們就分析完了。同時,sbull_init函式也就分析完了。
前面我們提到過struct gendisk代表一個磁碟裝置,而gendisk.fops成員則是對該磁碟進行操作的函式集合。在sbull驅動中,gendisk.fops被設定為sbull_ops,下面我們就來分析一下sbull_ops中的成員函式。
sbull_ops.open被設定為sbull_open,當sbull塊裝置被開啟時,該函式被呼叫,其定義如下:
206static int sbull_open(struct inode*inode, struct file *filp)
207{
208 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
209
210 del_timer_sync(&dev->timer);
211 filp->private_data = dev;
212 spin_lock(&dev->lock);
213 if (! dev->users)
214 check_disk_change(inode->i_bdev);
215 dev->users++;
216 spin_unlock(&dev->lock);
217 return 0;
218}
為了模擬可插撥裝置,sbull驅動程式必須知道什麼時候最後一個使用者關閉裝置。為此,sbull驅動程式需要維護一個使用者計數,在open和release函式中,對這個使用者計數進行增減操作。
塊裝置的open操作與字元裝置的open操作看起來非常像,它們都是以相應的inode和file結構體指標為引數,當一個inode指向一個塊裝置時,inode.i_bdev->bd_disk指向對應的gendisk結構體。
208行,通過gendisk.private_data得到sbull_dev指標。
210行,刪除定時器。
213-214行,如果dev->users為0,則呼叫kernel提供的函式check_disk_change,檢查裝置狀態是否發生變化。
215行,使用者數加1。
sbull_ops.release被設定為sbull_release,該函式定義如下:
220static int sbull_release(struct inode*inode, struct file *filp)
221{
222 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
223
224 spin_lock(&dev->lock);
225 dev->users--;
226
227 if (!dev->users) {
228 dev->timer.expires = jiffies + INVALIDATE_DELAY;
229 add_timer(&dev->timer);
230 }
231 spin_unlock(&dev->lock);
232
233 return 0;
234}
sbull_release函式與sbull_open函式的作用正好相反,當一個使用者關閉sbull裝置時,sbull_release函式被呼叫,它用於減少使用者計數,如果使用者計數為0,則啟動定時器。
225行,使用者計數減1。
227-230行,如果使用者計數為0,則啟動去時器,並設定定時器在INVALIDATE_DELAY即30秒鐘後觸發。
struct block_device_operations的成員函式media_changed和revalidate_disk是為可插撥裝置準備的,如果我們的驅動程式對應的裝置是不可插撥的裝置,則可以忽略這兩個函式。因為我們的sbull裝置要模擬一個可插撥裝置,所以我們實現了這兩個函式:sbull_ops.media_changed對應sbull_media_changed函式,sbull_ops.revalidate_disk對應sbull_revalidate函式。
先來看sbull_media_changed,該函式定義如下:
236/*
237 * Look for a (simulated) media change.
238 */
239int sbull_media_changed(struct gendisk*gd)
240{
241 struct sbull_dev *dev = gd->private_data;
242
243 return dev->media_change;
244}
media_changed函式用來探測可插撥裝置是否已經發生變化,如果是,該函式返回一個非0值,如果沒有變化,返回0。sbull_media_changed函式返回dev->media_change標誌。前面我們分析了定時器處理函式sbull_invalidate,在該函式中,如果sbull裝置被最後一個使用者關閉30秒鐘後沒有被再開啟,則將dev->media_change設定為1,表示裝置被撥出。
sbull_revalidate函式定義如下:
246/*
247 * Revalidate. WE DO NOT TAKE THE LOCK HERE, for fear ofdeadlocking
248 * with open. That needs to be reevaluated.
249 */
250int sbull_revalidate(struct gendisk *gd)
251{
252 struct sbull_dev *dev = gd->private_data;
253
254 if (dev->media_change) {
255 dev->media_change = 0;
256 memset (dev->data, 0, dev->size);
257 }
258 return 0;
259}
revalidate函式在可插撥裝置發生變化時會被kernel呼叫,它的作用是用來為驅動新插入的裝置(如果有的話)做任何需要的準備工作。呼叫完revalidate函式後,kernel會嘗試讀取裝置分割槽表,並重新啟動裝置。
在sbull_revalidate函式中,我們只是將dev->media_change設定為0,並清空dev->data的內容,以此來模擬插入了一個空白磁碟。
下面我們來看sbull_ops. sbull_ioctl函式,該函式定義如下:
277/*
278 * The ioctl() implementation
279 */
280
281int sbull_ioctl (struct inode *inode,struct file *filp,
282 unsigned int cmd, unsignedlong arg)
283{
284 long size;
285 struct hd_geometry geo;
286 struct sbull_dev *dev = filp->private_data;
287
288 switch(cmd) {
289 case HDIO_GETGEO:
290 /*
291 * Get geometry: since we are a virtual device, we have to make
292 * up something plausible. So weclaim 16 sectors, four heads,
293 * and calculate the corresponding number of cylinders. We set the
294 * start of data at sector four.
295 */
296 size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);
297 geo.cylinders = (size & ~0x3f) >> 6;
298 geo.heads = 4;
299 geo.sectors = 16;
300 geo.start = 4;
301 if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))
302 return -EFAULT;
303 return 0;
304 }
305
306 return -ENOTTY; /* unknowncommand */
307}
塊裝置驅動程式可以提供ioctl方法執行對裝置的控制,在ioctl命令到達塊裝置驅動程式之前,kernel的塊裝置子系統可能已經處理了大部分ioctl命令。所以,塊裝置驅動程式不需要實現太多的ioctl命令處理。
sbull_ioctl只實現了一個ioctl命令處理,用於返回裝置geometry。
二、request處理
塊裝置驅動的效能在很大程度上能決定整個系統的效能,因此,Linux kernel在實現塊裝置子系統時,始終考慮到效能的因素,它盡最大可能讓塊裝置驅動程式不必關心操作塊裝置的細節,這使得更快速的I/O操作成為可能,同時,塊裝置子系統也不必為塊裝置驅動程式匯出大量複雜的API介面。
對一個塊裝置驅動程式來說,最重要函式就是它的request函式,該函式是執行“真正的”工作的地方,或者說至少是“真正的”工作開始執行的地方。塊裝置驅動的request函式具有如下格式:
void request(request_queue_t *queue);
當kernel認為需要驅動程式執行read、write或其它針對塊裝置的操作時,request函式就會被呼叫。request函式不必完成所有的請求操作,而是啟動對請求操作的處理,保證該請求最終被驅動程式完成。
每個裝置都有一個請求佇列(request queue),request函式在request queue被建立的時候就和該request queue關聯在一起。例如,在sbull驅動中,建立request queue的語句如下(在setup_device函式中):
371 dev->queue = blk_init_queue(sbull_request, &dev->lock);
blk_init_queue函式用於建立和初始化requestqueue,sbull_request就是對應的request函式。dev->lock是呼叫request函式前必須獲得的自旋鎖。
下面我們來看sbull驅動程式是怎麼處理request的。
在setup_device函式中,sbull驅動程式提供了幾種不同的處理request的函式,最簡單的也是預設的request處理函式是sbull_request函式。該函式定義如下:
101/*
102 * The simple form of the requestfunction.
103 */
104static voidsbull_request(request_queue_t *q)
105{
106 struct request *req;
107
108 while ((req = elv_next_request(q)) != NULL) {
109 struct sbull_dev *dev = req->rq_disk->private_data;
110 if (! blk_fs_request(req)) {
111 printk (KERN_NOTICE "Skipnon-fs request\n");
112 end_request(req, 0);
113 continue;
114 }
115 // printk (KERN_NOTICE"Req dev %d dir %ld sec %ld, nr %d f %lx\n",
116 // dev - Devices, rq_data_dir(req),
117 // req->sector,req->current_nr_sectors,
118 // req->flags);
119 sbull_transfer(dev, req->sector, req->current_nr_sectors,
120 req->buffer,rq_data_dir(req));
121 end_request(req, 1);
122 }
123}
106行,定義一個struct request指標變數req,struct request結構體我們後面會詳細分析,這裡我們只需要知道它代表一個block I/O request。
108行,呼叫elv_next_request函式從request佇列中取得第一個未處理的request。注意elv_next_request函式並沒有將該request從request佇列中刪除,如果沒有對它執行相應操作,兩次呼叫elv_next_request函式將返回同一個request。
110-114行,呼叫blk_fs_request函式判斷當前request是不是執行真正讀寫操作的request,如果不是,呼叫end_request函式表示完成對該request的處理,注意這112行呼叫end_request時,第二個引數傳遞的是0,表示我們沒有成功完成該request。
119行,如果request是執行讀寫操作的request,呼叫sbull_transfer函式執行讀寫操作。後面我們再分析sbull_transfer函式。
121行,完成request要求的讀寫操作後,呼叫end_request,第二個引數傳遞1,表示該request成功完成。
sbull_transfer函式定義如下:
82/*
83 *Handle an I/O request.
84*/
85static void sbull_transfer(struct sbull_dev*dev, unsigned long sector,
86 unsigned long nsect, char *buffer, int write)
87{
88 unsigned long offset = sector*KERNEL_SECTOR_SIZE;
89 unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
90
91 if((offset + nbytes) > dev->size) {
92 printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset,nbytes);
93 return;
94 }
95 if(write)
96 memcpy(dev->data + offset, buffer, nbytes);
97 else
98 memcpy(buffer, dev->data + offset, nbytes);
99}
該函式的第一個引數代表sbull_dev裝置,第二個引數指定讀寫操作從哪個sector開始,第三個引數指定讀寫操作涉及到多少個sector,第四個引數指定讀/寫緩衝區,第五個引數指定是執行讀還是寫操作。
88行,計算讀/寫操作的起始位置。
89行,計算讀/寫操作的位元組數。
91-94行,判斷讀/寫操作是否越界。
95-98行,呼叫memcpy函式執行讀/寫操作。
setup_device函式的第361行:
361 dev->queue = blk_init_queue(sbull_full_request, &dev->lock);
可以看出,完整版本的request處理函式是sbull_full_request,該函式定義如下:
163/*
164 * Smarter request function that"handles clustering".
165 */
166static voidsbull_full_request(request_queue_t *q)
167{
168 struct request *req;
169 int sectors_xferred;
170 struct sbull_dev *dev = q->queuedata;
171
172 while ((req = elv_next_request(q)) != NULL) {
173 if (! blk_fs_request(req)) {
174 printk (KERN_NOTICE "Skipnon-fs request\n");
175 end_request(req, 0);
176 continue;
177 }
178 sectors_xferred = sbull_xfer_request(dev, req);
179 if (! end_that_request_first(req, 1, sectors_xferred)) {
180 blkdev_dequeue_request(req);
181 end_that_request_last(req);
182 }
183 }
184}
172行,呼叫elv_next_request函式從request佇列中取得第一個未處理的request。
173-177行,呼叫blk_fs_request函式判斷當前request是不是執行真正讀寫操作的request,如果不是,呼叫end_request函式表示完成對該request的處理,注意175行呼叫end_request時,第二個引數傳遞的是0,表示我們沒有成功完成該request。
178行,呼叫sbull_xfer_request(dev,req)函式,該函式處理整個request,執行真正的I/O操作,該函式定義如下:
146/*
147 * Transfer a full request.
148 */
149static int sbull_xfer_request(structsbull_dev *dev, struct request *req)
150{
151 struct bio *bio;
152 int nsect = 0;
153
154 rq_for_each_bio(bio, req) {
155 sbull_xfer_bio(dev, bio);
156 nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
157 }
158 return nsect;
159}
154-157行,rq_for_each_bio是一個巨集,用於遍歷request包含的所有bio結構。對於每個bio結構,155行呼叫sbull_xfer_bio (dev,bio),該函式處理單個bio結構,其定義如下:
126/*
127 * Transfer a single BIO.
128 */
129static int sbull_xfer_bio(structsbull_dev *dev, struct bio *bio)
130{
131 int i;
132 struct bio_vec *bvec;
133 sector_t sector = bio->bi_sector;
134
135 /* Do each segment independently. */
136 bio_for_each_segment(bvec, bio, i) {
137 char *buffer =__bio_kmap_atomic(bio, i, KM_USER0);
138 sbull_transfer(dev, sector, bio_cur_sectors(bio),
139 buffer, bio_data_dir(bio) ==WRITE);
140 sector += bio_cur_sectors(bio);
141 __bio_kunmap_atomic(bio, KM_USER0);
142 }
143 return 0; /* Always "succeed" */
144}
136行,呼叫bio_for_each_segment巨集,遍歷bio的bi_io_vec陣列的每個成員(即一個bio_vec結構)。bio_for_each_segment巨集會將當前的bio_vce賦值給第一個引數bvec,將當前segment號賦值給第三個引數i。
137行,呼叫__bio_kmap_atomic將bio_vec資料的虛擬地址轉換為實體地址,儲存在buffer指標變數中。
138-139行,呼叫sbull_transfer函式,該函式我們在前面分析sbull_request函式時已經分析過。sbull_transfer函式定義如下:
82/*
83 *Handle an I/O request.
84*/
85static void sbull_transfer(struct sbull_dev*dev, unsigned long sector,
86 unsigned long nsect, char *buffer, int write)
87{
88 unsigned long offset = sector*KERNEL_SECTOR_SIZE;
89 unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
90
91 if((offset + nbytes) > dev->size) {
92 printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset,nbytes);
93 return;
94 }
95 if(write)
96 memcpy(dev->data + offset, buffer, nbytes);
97 else
98 memcpy(buffer, dev->data + offset, nbytes);
99}
該函式的第一個引數代表sbull_dev裝置,第二個引數指定讀寫操作從哪個sector開始,第三個引數指定讀寫操作涉及到多少個sector,第四個引數指定讀/寫緩衝區,第五個引數指定是執行讀還是寫操作。
88行,計算讀/寫操作的起始位置。
89行,計算讀/寫操作的位元組數。
91-94行,判斷讀/寫操作是否越界。
95-98行,呼叫memcpy函式執行讀/寫操作。
回到sbull_xfer_bio函式:
140行,更新sector值。
141行,取消地址對映。
至此,sbull_xfer_bio函式我們就分析完了。
回到sbull_xfer_request函式:
156行,更新nsect值,記錄傳輸的sector數。
158行,返回傳輸的sector個數。
回到sbull_full_request函式:
179行,處理完request後,呼叫end_that_request_first函式通過block子系統該request處理完畢。end_that_request_first函式的返回值表明該request的所有sector都被傳輸完成與否,返回0表明該request所有的sector都被傳輸完成,request處理結束。
180行,如果request的所有sector都被傳輸完成,request處理結束,則呼叫blkdev_dequeue_request(req)函式將request從request佇列中刪除。
181行,呼叫end_that_request_last(req)函式,該函式通知等待該request結束的程序,並回收request結構。