作者:刘昊昱
博客:http://blog.csdn.net/liuhaoyutz
编译环境: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结构。