块设备驱动程序
一个块设备驱动程序主要通过传输固定大小的随机数据来访问设备。
块驱动程序是在核心内存和其他存储介质之间的管道,因此他们可以认为是虚拟内存子系统的组成部分。
一些概念
一个数据块指的是固定大小的数据,而大小的值有内核决定
与数据块对应的是扇区,它是由底层硬件决定大小的一个块。
无论何时内核向用户提供一个扇区编号,该扇区的大小就是512字节。
注册
注册的目的:使内核知道设备的存在
注册块设备驱动程序
注册到内核
int register_blkdev(unsigned int major, const char *name);
参数为主设备号和设备名字
注销函数:
int unregister_blkdev(unsigned int major, const char *name);
2.6中register_blkdev所做的事情:
如果需要的话分配一个动态的主设备号。
在/proc/devices 中创建一个入口项。
注册磁盘
虽然register_blkdev能获得主设备号,但是它并不能让系统使用任何磁盘
块设备操作
block_device_operations结构告诉系统对他们的操作接口。
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
实现ioctl系统函数的调用。
int (*media_changed) (struct gendisk *gd);
检查用户是否更换了驱动器介质
该函数只适合于移动介质
int (*revalidate_disk) (struct gendisk *gd);
当介质变更时该函数做出响应,告诉驱动程序完成响应的工作,以便使用新的介质
struct module *owner;
指向该结构体的模块指针。
这里面没有函数负责数据的读写,在块设备中,这些操作是由request函数处理的。
Gendisk结构
内核使用gendisk结构表示一个独立的磁盘设备。
Gendisk结构的许多成员需要由驱动程序初始化。
int major;
int first_minor;
int minors;
一个驱动器至少使用一个次设备号,如果驱动器是可分区的那么用户需要为每个可能的分区都分配一个次设备号。Minors常取16
char disk_name[32];
设置磁盘驱动器的名字.
struct block_device_operations *fops;
设置前一节描述的各种设备操作
struct request_queue *queue;
内核使用该结构为设备管理IO请求
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结构提供的一些函数:
struct gendisk 是一个动态分配的结构, 它需要特殊的内核操作来初始化; 驱动不能自己分配这个结构. 而必须调用:
struct gendisk *alloc_disk(int minors);
minors 参数表示次设备号,此后用户不能改变该成员。
不需要一个磁盘时,调用下面的函数卸载磁盘:
void del_gendisk(struct gendisk *gd);
gendisk 是一个引用计数的结构(它含有一个 kobject). get_disk 和 put_disk 负责引用计数, 但是驱动不能直接使用这两个函数.
分配一个 gendisk 结构不能使系统可使用这个磁盘. 要做到这点, 必须初始化这个结构并且调用 add_disk:
void add_disk(struct gendisk *gd);
驱动程序完全初始化并能相应对磁盘的请求前请不要调用add_disk.
下面这几点可以结合最后面的示例代码(模拟块设备程序)来理解,我在里面添加了很详细的注释
Sbull的初始化
对扇区大小的说明
块设备操作
Open和release函数
对可移动介质的支持
Ioctl函数
请求处理
Request函数简介
一个简单的request函数
请求队列
请求过程
本质上 ,一个request结果是作为一个bio结构的链表实现的
Bio结构是在底层对部分设备IO请求的描述
sector_t bi_sector;
这个 bio 要被传送的第一个(512字节)扇区.
unsigned int bi_size;
被传送的数据大小, 以字节计. 相反, 常常更易使用 bio_sectors(bio), 一个给定以扇区计的大小的宏.
unsigned long bi_flags;
一组描述 bio 的标志;
unsigned short bio_phys_segments;
unsigned short bio_hw_segments;
包含在这个 BIO 中的物理段的数目, 和在 DMA 映射完成后被硬件看到的段数目,
一个 bio 的核心, 是一个称为 bi_io_vec 的数组:
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
图 0‑1bio结构
为了能追踪正在处理的请求的当前状态,块设备也在bio结构中维护了一系列指针。提供了一些宏来访问这个状态。
struct page *bio_page(struct bio *bio);
返回一个指向页结构的指针, 表示下一个被传送的页.
int bio_offset(struct bio *bio);
返回页内的被传送的数据的偏移.
int bio_cur_sectors(struct bio *bio);
返回要被传送出当前页的扇区数.
char *bio_data(struct bio *bio);
返回一个内核逻辑地址, 指向被传送的数据.
char *bio_kmap_irq(struct bio *bio, unsigned long *flags);
void bio_kunmap_irq(char *buffer, unsigned long *flags);
这个函数可以为任何缓冲区返回内核虚拟地址。
屏蔽请求
void blk_queue_ordered(request_queue_t *queue, int flag);
指出驱动程序要实现屏蔽的请求
屏蔽请求只是检查request结构中的相关标志,为此内核提供了一个宏:
int blk_barrier_rq(struct request *req);
不可重试的请求
如果驱动程序要重试一个失败的请求,调用:
int blk_noretry_request(struct request *req);
如果返回非0 ,驱动程序忽略这个请求并返回错误值,不在重试。
请求完成函数
当设备完成一个IO请求的部分或者全部扇区时,必须调用下面的函数通知块设备子系统:
int end_that_request_first(struct request *req, int success, int count);
void end_that_request_last(struct request *req);
1
2 /*
3 * sbull.h -- definitions for the char module
4 *
5 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
6 * Copyright (C) 2001 O'Reilly & Associates
7 *
8 * The source code in this file can be freely used, adapted,
9 * and redistributed in source or binary form, so long as an
10 * acknowledgment appears in derived source files. The citation
11 * should list that the code comes from the book "Linux Device
12 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
13 * by O'Reilly & Associates. No warranty is attached;
14 * we cannot take responsibility for errors or fitness for use.
15 *
16 */
17
18
19 #include <linux/ioctl.h>
20
21 /* Multiqueue only works on 2.4 */
22 #ifdef SBULL_MULTIQUEUE
23 # warning "Multiqueue only works on 2.4 kernels"
24 #endif
25
26 /*
27 * Macros to help debugging
28 */
29
30 #undef PDEBUG /* undef it, just in case */
31 #ifdef SBULL_DEBUG
32 # ifdef __KERNEL__
33 /* This one if debugging is on, and kernel space */
34 # define PDEBUG(fmt, args...) printk( KERN_DEBUG "sbull: " fmt, ## args)
35 # else
36 /* This one for user space */
37 # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
38 # endif
39 #else
40 # define PDEBUG(fmt, args...) /* not debugging: nothing */
41 #endif
42
43 #undef PDEBUGG
44 #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */
45
46
47 #define SBULL_MAJOR 0 /* dynamic major by default */
48 #define SBULL_DEVS 2 /* two disks */
49 #define SBULL_RAHEAD 2 /* two sectors */
50 #define SBULL_SIZE 2048 /* two megs each */
51 #define SBULL_BLKSIZE 1024 /* 1k blocks */
52 #define SBULL_HARDSECT 512 /* 2.2 and 2.4 can used different values */
53
54 #define SBULLR_MAJOR 0 /* Dynamic major for raw device */
55 /*
56 * The sbull device is removable: if it is left closed for more than
57 * half a minute, it is removed. Thus use a usage count and a
58 * kernel timer
59 */
60
61 typedef struct Sbull_Dev {
62 int size;
63 int usage;
64 struct timer_list timer;
65 spinlock_t lock;
66 u8 *data;
67 #ifdef SBULL_MULTIQUEUE
68 request_queue_t *queue;
69 int busy;
70 #endif
71 } Sbull_Dev;
72
1 /*
2 * Sample disk driver, from the beginning.
3 */
4
5 #include <linux/config.h>
6 #include <linux/module.h>
7 #include <linux/moduleparam.h>
8 #include <linux/init.h>
9
10 #include <linux/sched.h>
11 #include <linux/kernel.h> /* printk() */
12 #include <linux/slab.h> /* kmalloc() */
13 #include <linux/fs.h> /* everything... */
14 #include <linux/errno.h> /* error codes */
15 #include <linux/timer.h>
16 #include <linux/types.h> /* size_t */
17 #include <linux/fcntl.h> /* O_ACCMODE */
18 #include <linux/hdreg.h> /* HDIO_GETGEO */
19 #include <linux/kdev_t.h>
20 #include <linux/vmalloc.h>
21 #include <linux/genhd.h>
22 #include <linux/blkdev.h>
23 #include <linux/buffer_head.h> /* invalidate_bdev */
24 #include <linux/bio.h>
25
26 MODULE_LICENSE("Dual BSD/GPL");
27
28 static int sbull_major = 0;
29 module_param(sbull_major, int, 0);
30 static int hardsect_size = 512;
31 module_param(hardsect_size, int, 0);
32 static int nsectors = 1024; /* How big the drive is */
33 module_param(nsectors, int, 0);
34 static int ndevices = 4;
35 module_param(ndevices, int, 0);
36
37 /*
38 * The different "request modes" we can use.
39 */
40 enum {
41 RM_SIMPLE = 0, /* The extra-simple request function */
42 RM_FULL = 1, /* The full-blown version */
43 RM_NOQUEUE = 2, /* Use make_request */
44 };
45 static int request_mode = RM_SIMPLE;
46 module_param(request_mode, int, 0);
47
48 /*
49 * Minor number and partition management.
50 */
51 #define SBULL_MINORS 16
52 #define MINOR_SHIFT 4
53 #define DEVNUM(kdevnum) (MINOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT
54
55 /*
56 * We can tweak our hardware sector size, but the kernel talks to us
57 * in terms of small sectors, always.
58 */
59 #define KERNEL_SECTOR_SIZE 512
60
61 /*
62 * After this much idle time, the driver will simulate a media change.
63 */
64 #define INVALIDATE_DELAY 30*HZ
65
66 /*
67 * The internal representation of our device.
68 */
69 struct sbull_dev { //描述sbull设备的结构体
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 };
79
80 static struct sbull_dev *Devices = NULL;
81
82 /*
83 * Handle an I/O request.
84 */
85 static 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 }
100
101 /*
102 * The simple form of the request function.
103 * 块设备的驱动程序的核心是它的请求函数,至少如设备的启动都是在这个函数里面完成的...
104 * 编写一个简单的request如下,但是驱动程序如果要在很高的层析上控制非常复杂的硬件,它将不再这么简单
105 *
106 * 内核需要驱动程序处理读取写入以及其他的设备操作时,就会调用该函数
107 * request是在一个原子上下文运行的,该锁是由内核控制的,拥有锁时防止内核为设备安排其他请求
108 *
109 */
110 static void sbull_request(request_queue_t *q)
111 { // 每一个设备都需要一个请求队列,因为对磁盘的数据实际的传入和传出的时间 与内核请求的时间相差很大
112 // 驱动程序所需要知道的任何信息都敖汉在通过请求队列传给我们的结构中
113 // 请求队列保存了表述设备所能处理的请求的参数:最大尺寸,在同一个请求所能包含的独立段的数目、硬件扇区大小、对齐需求等等
114 // 请求队列还实现了插件接口,使多规格IO调度器的使用称为可能
115 struct request *req;
116
117 while ((req = elv_next_request(q)) != NULL) {
118 // elv_next_request 返回队列中第一个未完成的请求,如果没有请求返回null
119 // 这个函数并不从队列中删除请求,如果不加以干涉而两次调用该函数,则两次放回相同的结构
120 struct sbull_dev *dev = req->rq_disk->private_data;
121 if (! blk_fs_request(req)) {
122 // blk_fs_request告诉用户该请求是否是一个文件系统请求——移动块设备数据请求
123 printk (KERN_NOTICE "Skip non-fs request\n");
124 end_request(req, 0);
125 continue;
126 }
127 // printk (KERN_NOTICE "Req dev %d dir %ld sec %ld, nr %d f %lx\n",
128 // dev - Devices,
129 // rq_data_dir(req),传输方向,0表述从设备读,非0表示写
130 // req->sector,开始扇区的索引号
131 // req->current_nr_sectors, 需要传输的扇区数
132 // req->flags);
133 // sbull_transfer 传输的数据的缓冲区指针
134 // 实现数据的真正移动
135 sbull_transfer(dev, req->sector, req->current_nr_sectors,
136 req->buffer, rq_data_dir(req));
137 end_request(req, 1);
138 }
139 }
140
141
142 /*
143 * Transfer a single BIO.
144 * 该函数遍历了bio结构中的每个段,获得内核虚拟地址以访问缓冲区,然后调用前面介绍过的sbull_transfer函数,以完成数据的拷贝.
145 */
146 static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
147 {
148 int i;
149 struct bio_vec *bvec;
150 sector_t sector = bio->bi_sector;
151
152 /* Do each segment independently. */
153 bio_for_each_segment(bvec, bio, i) {
154 char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
155 sbull_transfer(dev, sector, bio_cur_sectors(bio),
156 buffer, bio_data_dir(bio) == WRITE);
157 sector += bio_cur_sectors(bio);
158 __bio_kunmap_atomic(bio, KM_USER0);
159 }
160 return 0; /* Always "succeed" */
161 }
162
163 /*
164 * Transfer a full request.
165 * 实际执行请求的函数
166 */
167 static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
168 {
169 struct bio *bio;
170 int nsect = 0;
171
172 rq_for_each_bio(bio, req) { // rq_for_each_bio宏 只是遍历了请求中的每个biio结构,并提供可可以传递给sbull_xfer_bio函数用于传输的指针
173 sbull_xfer_bio(dev, bio);
174 nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
175 }
176 return nsect;
177 }
178
179
180
181 /*
182 * Smarter request function that "handles clustering".
183 * 这个函数将注册一个bio请求函数
184 */
185 static void sbull_full_request(request_queue_t *q)
186 {
187 struct request *req;
188 int sectors_xferred;
189 struct sbull_dev *dev = q->queuedata;
190
191 while ((req = elv_next_request(q)) != NULL) {
192 if (! blk_fs_request(req)) {
193 printk (KERN_NOTICE "Skip non-fs request\n");
194 end_request(req, 0);
195 continue;
196 }
197 sectors_xferred = sbull_xfer_request(dev, req);
198 if (! end_that_request_first(req, 1, sectors_xferred)) {
199 blkdev_dequeue_request(req);
200 end_that_request_last(req);
201 }
202 }
203 }
204
205
206
207 /*
208 * The direct make request version.
209 */
210 static int sbull_make_request(request_queue_t *q, struct bio *bio)
211 {
212 struct sbull_dev *dev = q->queuedata;
213 int status;
214
215 status = sbull_xfer_bio(dev, bio);
216 bio_endio(bio, bio->bi_size, status);
217 return 0;
218 }
219
220
221 /*
222 * Open and close.
223 * // sbull必须知道最后一个用户何时关闭了设备
224 */
225
226 static int sbull_open(struct inode *inode, struct file *filp)
227 {
228 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
229
230 del_timer_sync(&dev->timer);
231 filp->private_data = dev;
232 spin_lock(&dev->lock);
233 if (! dev->users)
234 check_disk_change(inode->i_bdev);
235 dev->users++;
236 spin_unlock(&dev->lock);
237 return 0;
238 }
239 /*
240 * 减少用户基数,并启用介质移除定时器
241 */
242 static int sbull_release(struct inode *inode, struct file *filp)
243 {
244 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
245
246 spin_lock(&dev->lock);
247 dev->users--;
248
249 if (!dev->users) {
250 dev->timer.expires = jiffies + INVALIDATE_DELAY; //30秒的定时器,如果这个时段内设备没有被打开则移除设备
251 add_timer(&dev->timer);
252 }
253 spin_unlock(&dev->lock);
254
255 return 0;
256 }
257
258 /*
259 * Look for a (simulated) media change.
260 */
261 int sbull_media_changed(struct gendisk *gd)
262 {
263 struct sbull_dev *dev = gd->private_data;
264
265 return dev->media_change;
266 }
267
268 /*
269 * Revalidate. WE DO NOT TAKE THE LOCK HERE, for fear of deadlocking
270 * with open. That needs to be reevaluated.
271 * 调用此函数内核将试着重新读取分区表,在这里这个函数只是简单地重置了media_change的标志位,并清除内存空间以模拟插入一张磁盘
272 */
273 int sbull_revalidate(struct gendisk *gd)
274 {
275 struct sbull_dev *dev = gd->private_data;
276
277 if (dev->media_change) {
278 dev->media_change = 0;
279 memset (dev->data, 0, dev->size);
280 }
281 return 0;
282 }
283
284 /*
285 * The "invalidate" function runs out of the device timer; it sets
286 * a flag to simulate the removal of the media.
287 */
288 void sbull_invalidate(unsigned long ldev)
289 {
290 struct sbull_dev *dev = (struct sbull_dev *) ldev;
291
292 spin_lock(&dev->lock);
293 if (dev->users || !dev->data)
294 printk (KERN_WARNING "sbull: timer sanity check failed\n");
295 else
296 dev->media_change = 1;
297 spin_unlock(&dev->lock);
298 }
299
300 /*
301 * The ioctl() implementation
302 * ioctl函数在这里只处理了一个命令,对设备物理信息的查询请求
303 * 这里由于是虚拟设备,因此只提供了一些虚拟信息
304 */
305
306 int sbull_ioctl (struct inode *inode, struct file *filp,
307 unsigned int cmd, unsigned long arg)
308 {
309 long size;
310 struct hd_geometry geo;
311 struct sbull_dev *dev = filp->private_data;
312
313 switch(cmd) {
314 case HDIO_GETGEO:
315 /*
316 * Get geometry: since we are a virtual device, we have to make
317 * up something plausible. So we claim 16 sectors, four heads,
318 * and calculate the corresponding number of cylinders. We set the
319 * start of data at sector four.
320 */
321 size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);
322 geo.cylinders = (size & ~0x3f) >> 6;
323 geo.heads = 4;
324 geo.sectors = 16;
325 geo.start = 4;
326 if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))
327 return -EFAULT;
328 return 0;
329 }
330
331 return -ENOTTY; /* unknown command */
332 }
333
334
335
336 /*
337 * The device operations structure.
338 */
339 static struct block_device_operations sbull_ops = {
340 .owner = THIS_MODULE,
341 .open = sbull_open,
342 .release = sbull_release,
343 .media_changed = sbull_media_changed,
344 .revalidate_disk = sbull_revalidate,
345 .ioctl = sbull_ioctl
346 };
347
348
349 /*
350 * Set up our internal device.初始化sbull_dev结构
351 */
352 static void setup_device(struct sbull_dev *dev, int which)
353 {
354 /*
355 * Get some memory.
356 */
357 memset (dev, 0, sizeof (struct sbull_dev));
358 dev->size = nsectors*hardsect_size;
359 dev->data = vmalloc(dev->size);
360 if (dev->data == NULL) {
361 printk (KERN_NOTICE "vmalloc failure.\n");
362 return;
363 }
364 spin_lock_init(&dev->lock);
365
366 /*
367 * The timer which "invalidates" the device.
368 */
369 init_timer(&dev->timer);
370 dev->timer.data = (unsigned long) dev;
371 dev->timer.function = sbull_invalidate;
372
373 /*
374 * The I/O queue, depending on whether we are using our own
375 * make_request function or not.
376 */
377 switch (request_mode) {
378 case RM_NOQUEUE:
379 dev->queue = blk_alloc_queue(GFP_KERNEL);
380 if (dev->queue == NULL)
381 goto out_vfree;
382 blk_queue_make_request(dev->queue, sbull_make_request);
383 break;
384
385 case RM_FULL:
386 dev->queue = blk_init_queue(sbull_full_request, &dev->lock); 创建iqngqiu队列
387 if (dev->queue == NULL)
388 goto out_vfree;
389 break;
390
391 default:
392 printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode);
393 /* fall into.. */
394
395 case RM_SIMPLE:
396 dev->queue = blk_init_queue(sbull_request, &dev->lock);
397 //sbull_request是请求函数,负责执行块设备的读写请求。
398 if (dev->queue == NULL)
399 goto out_vfree;
400 break;
401 }
402 blk_queue_hardsect_size(dev->queue, hardsect_size); //设置扇区大小
403 dev->queue->queuedata = dev;
404 /*
405 * And the gendisk structure.
406 * 拥有了设备内存和请求队列,就可以分配、初始化及安装相应的gendisk结构了.
407 */
408 dev->gd = alloc_disk(SBULL_MINORS);//SBULL_MINORS是每个sbull设备所支持的次设备号的数量
409 if (! dev->gd) {
410 printk (KERN_NOTICE "alloc_disk failure\n");
411 goto out_vfree;
412 }
413 dev->gd->major = sbull_major;
414 dev->gd->first_minor = which*SBULL_MINORS;
415 dev->gd->fops = &sbull_ops;
416 dev->gd->queue = dev->queue;
417 dev->gd->private_data = dev;
418 snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
419 set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));//每个请求的大小都是扇区大小的整数倍,内核总是认为扇区大小是512字节,因此必须进行转换
420 //KERNEL_SECTOR_SIZE是本地定义的一个常量,使用该常量进行内核512自己上去到实际扇区大小的转换。
421
422 add_disk(dev->gd);//结束设置过程
423 return;
424
425 out_vfree:
426 if (dev->data)
427 vfree(dev->data);
428 }
429
430
431
432 static int __init sbull_init(void)
433 {
434 int i;
435 /*
436 * Get registered.
437 */
438 sbull_major = register_blkdev(sbull_major, "sbull"); //动态分配主设备号
439 if (sbull_major <= 0) {
440 printk(KERN_WARNING "sbull: unable to get major number\n");
441 return -EBUSY;
442 }
443 /*
444 * Allocate the device array, and initialize each one.
445 */
446 Devices = kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL);
447 if (Devices == NULL)
448 goto out_unregister;
449 for (i = 0; i < ndevices; i++)
450 setup_device(Devices + i, i);//初始化sbull_dev结构
451
452 return 0;
453
454 out_unregister:
455 unregister_blkdev(sbull_major, "sbd");
456 return -ENOMEM;
457 }
458
459 static void sbull_exit(void)
460 {
461 int i;
462
463 for (i = 0; i < ndevices; i++) {
464 struct sbull_dev *dev = Devices + i;
465
466 del_timer_sync(&dev->timer);
467 if (dev->gd) {
468 del_gendisk(dev->gd);
469 put_disk(dev->gd);
470 }
471 if (dev->queue) {
472 if (request_mode == RM_NOQUEUE)
473 blk_put_queue(dev->queue);
474 else
475 blk_cleanup_queue(dev->queue);// 删除请求队列
476 }
477 if (dev->data)
478 vfree(dev->data);
479 }
480 unregister_blkdev(sbull_major, "sbull");
481 kfree(Devices);
482 }
483
484 module_init(sbull_init);
485 module_exit(sbull_exit);
486