sbull 代码分析

sbull

Posted by cslqm on March 19, 2020

并非原书代码。

1
2
3
4
5
6
7
8
9
#ifndef _SBULL_H
#define _SBULL_H

#define SBULL_MAJOR    0
#define SBULL_HARDSECT    512

#define KERNEL_SECTOR_SIZE    512

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/bio.h>

#include "sbull.h"

static int sbull_major = SBULL_MAJOR;
static int hardsect_size = SBULL_HARDSECT;
static int nsectors = 2;
module_param(sbull_major, int, 0);
module_param(hardsect_size, int, 0);
module_param(nsectors, int, 0);

struct sbull_dev {
        int size;
        u8 *data;
        short users;
        spinlock_t lock;
        struct request_queue *queue;
        struct gendisk *gd;
};

static struct sbull_dev *device = NULL;


// 扇区的读写  完全简单的基于ram设备
static void sbull_transfer(struct sbull_dev *dev, unsigned long sector, unsigned long nsect, char *buffer, int write)
{
        unsigned long offset = sector*KERNEL_SECTOR_SIZE;
        unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;

        if ((offset+nbytes) > dev->size)
        {
                return;
        }

        if (write)
        {
                memcpy(dev->data+offset, buffer, nbytes);
        }
        else
        {
                memcpy(buffer, dev->data+offset, nbytes);
        }
}

static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
{
        int i;  //用来遍历bio_vec对象
        struct bio_vec *bvec;
        sector_t sector = bio->bi_sector;

        bio_for_each_segment(bvec, bio, i) ///bvec会遍历bio中每一个bio_vec对象  遍历一个bio中的每一个segment
        {
                char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); //获得的内核虚拟地址  底层函数直接映射了指定索引号为i的bio_vec中的缓冲
                sbull_transfer(dev,
                				 sector,                   // 开始扇区的索引号
                				 bio_cur_bytes(bio)>>9,    // 需要传输的扇区数   
                				 buffer,                   // 传输数据的缓冲区指针
                				 bio_data_dir(bio) == WRITE); // 传输方向,0表述从设备读,非0从设备写
                sector += bio_cur_bytes(bio)>>9;   //返回扇区数
                __bio_kunmap_atomic(bio, KM_USER0); // __bio_kmap_atomic()获得的内核虚拟地址
        }
        return 0;
}

static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
{
        struct bio *bio;
        int nsect = 0;

        // 遍历一个request中的所有的bio
        __rq_for_each_bio(bio, req) {
                sbull_xfer_bio(dev, bio);
                nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
        }
        return nsect;
}

// 队列处理函数
static void sbull_full_request(struct request_queue *q)
{
        struct request *req;
        int sectors_xferred;
        struct sbull_dev *dev = q->queuedata;

        while((req = blk_fetch_request(q)) != NULL) //提取请求 elv_next_reques 更新版 blk_fetch_request 
        {
                if (req->cmd_type != REQ_TYPE_FS) //判断是否为文件系统请求
                {
                        __blk_end_request_all(req, -EIO); // 请求失败
                        continue;
                }

                sectors_xferred = sbull_xfer_request(dev, req); //调用请求处理函数

                __blk_end_request_cur(req, 0);
        }
}

static int sbull_open(struct block_device *device, fmode_t mode)
{
        struct sbull_dev *dev = device->bd_disk->private_data;

        spin_lock(&dev->lock);
        dev->users++;    // 使用者增加
        spin_unlock(&dev->lock);
        return 0;
}

static int sbull_release(struct gendisk *disk, fmode_t mode)
{
        struct sbull_dev *dev = disk->private_data;

        spin_lock(&dev->lock);
        dev->users--;
        spin_unlock(&dev->lock);

        return 0;
}

static struct block_device_operations sbull_ops = {
        .owner = THIS_MODULE,
        .open = sbull_open,
        .release = sbull_release,
};

static void setup_device(struct sbull_dev *dev)
{
        memset(dev, 0, sizeof(struct sbull_dev));
        dev->size = nsectors*hardsect_size;
        dev->data = vmalloc(dev->size); //在虚拟内存空间给出一块连续的内存区
        if (dev->data == NULL)
        {
                return;
        }

        spin_lock_init(&dev->lock);

        dev->queue = blk_init_queue(sbull_full_request, &dev->lock); //初始化
        if (dev->queue == NULL)
        {
                goto out_vfree;
        }

        blk_queue_logical_block_size(dev->queue, hardsect_size); // 设置扇区大小
        dev->queue->queuedata = dev; 

        dev->gd = alloc_disk(1); // 分区,1是不分区
        if (!dev->gd)
        {
                goto out_vfree;
        }

        dev->gd->major = sbull_major;
        dev->gd->first_minor = 0;
        dev->gd->fops = &sbull_ops;
        dev->gd->queue = dev->queue;
        dev->gd->private_data = dev;
        snprintf(dev->gd->disk_name, 6, "sbull");    
        /* 每个请求的大小都是扇区大小的整数倍,内核总是认为扇区大小是512字节,因此必须进行转换*/
        set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE)); //通过set_capacity()设置capacity成员,等于扇区数
        add_disk(dev->gd); //使用add_disk()注册gendisk结构体

        return;

out_vfree:
        if (dev->data)
        {
                vfree(dev->data);
        }
}

static int __init sbull_init(void)
{
        sbull_major = register_blkdev(sbull_major, "sbull"); // 注册块设备
        if (sbull_major <= 0)
        {
                return -EBUSY;    
        }

        device = kmalloc(sizeof(struct sbull_dev), GFP_KERNEL); // 申请一块连续内存
        if (device == NULL)
        {
                goto out_unregister;
        }

        setup_device(device);

        return 0;

out_unregister:
        unregister_blkdev(sbull_major, "sbull");
        return -ENOMEM;
}

static void sbull_exit(void)
{
        struct sbull_dev *dev = device;

        if (dev->gd)
        {
                del_gendisk(dev->gd); // 注销 gendisk 
                put_disk(dev->gd); 
        }

        if (dev->queue)
        {
                blk_cleanup_queue(dev->queue); // 使用blk_cleanup_queue()清除内存中的申请队列 
        }

        if (dev->data)
        {
                vfree(dev->data);
        }

        unregister_blkdev(sbull_major, "sbull"); //使用unregister_blkdev()卸载块设备  
        kfree(device);  //使用kfree()释放磁盘扇区缓存
}

module_init(sbull_init);
module_exit(sbull_exit);
MODULE_LICENSE("GPL");

部分使用函数解析

初始化请求队列

1
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

该函数的第1个参数是请求处理函数的指针,第2个参数是控制访问队列权限的自旋锁,这个函数会发生内存分配的行为,故它可能会失败,函数调用成 功时,它返回指向初始化请求队列的指针,否则,返回NULL。这个函数一般在块设备驱动的模块加载函数中调用。

清除请求队列

1
void blk_cleanup_queue(request_queue_t * q);

这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。

提取请求

1
struct request *elv_next_request(request_queue_t *queue);

上述函数用于返回下一个要处理的请求(由 I/O 调度器决定),如果没有请求则返回NULL。

去除请求

1
void blkdev_dequeue_request(struct request *req);

上述函数从队列中去除1个请求。如果驱动中同时从同一个队列中操作了多个请求,它必须以这样的方式将它们从队列中去除。

分配“请求队列”

1
request_queue_t *blk_alloc_queue(int gfp_mask);

对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的I/O调度,这个时候,应该使用上述函数分配1个“请求队列”,并使用如下函数来绑定“请求队列”和“制造请求”函数。

1
void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn);
1
2
void blk_queue_hardsect_size(request_queue_t *queue,
unsigned short max);

该函数用于告知内核块设备硬件扇区的大小,所有由内核产生的请求都是这个大小的倍数并且被正确对界。但是,内核块设备层和驱动之间的通信还是以512字节扇区为单位进行。