字符设备三部曲:
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
块设备:
major = register_blkdev(0, "ramblock");
struct gendisk ramblock_dis = alloc_disk(5);
add_disk(ramblock_dis);
如此看来,块设备驱动与字符设备驱动程序的编写流程是相似的,对于字符设备来说经过三部曲已经足够了,它主要目的就是绑定 file_operation 结构,应用程序能够经过系统调用调用到 file_operation 中函数就可以了。然而块设备要复杂的多,因为块设备的读写特性,导致我们需要为每一个块设备提供与一个请求队列,将内核挂起的I/O请求合并排序之后加入请求队列,请求队列还需要具有处理请求的能力。那么,整个块设备驱动程序框架大致分为以下四部分:
1、分配 gendisk 结构体
ramblock_dis = alloc_disk
2、构造一个 request_queue 请求队列
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
blk_init_queue 会初始化一个请求队列,并设置它的默认“构造请求函数”__make_request
__make_request 将内核挂起的请求能合并合并不能合并插入到队列尾
// 尝试合并
elv_merge(q, &req, bio);
// 如果合并不成,使用bio构造请求
init_request_from_bio(req, bio);
// 把请求放入队列
add_request(q, req);
// 执行队列
__generic_unplug_device(q);
// 调用队列的"处理函数"
q->request_fn(q);
q->request_fn 是 blk_init_queue 时提供的请求处理函数,例如:do_ramblock_request
ramblock_lock 是一个自旋锁:static DEFINE_SPINLOCK(ramblock_lock);
3、填充 gendisk
ramblock_dis->queue = ramblock_queue; // 绑定请求队列到 gendisk
major = register_blkdev(0, "ramblock");
ramblock_dis->major = major;
ramblock_dis->first_minor = 0;
sprintf(ramblock_dis->disk_name, "ramblock");
ramblock_dis->fops = &ramblock_fops;
set_capacity(ramblock_dis, RAMBLOCK_SIZE/512);//扇区个数
4、注册
add_disk(ramblock_dis);
简单看一下 alloc_disk 过程和 add_disk 过程:
struct gendisk *alloc_disk(int minors)
{
return alloc_disk_node(minors, -1);
}
EXPORT_SYMBOL(alloc_disk);
struct gendisk *alloc_disk_node(int minors, int node_id)
{
struct gendisk *disk;
disk = kmalloc_node(sizeof(struct gendisk),
GFP_KERNEL | __GFP_ZERO, node_id);
if (disk) {
disk->node_id = node_id;
disk->part_tbl->part[0] = &disk->part0;
// 分区数量 + 1
disk->minors = minors;
// disk->random = kzalloc(sizeof(struct timer_rand_state), GFP_KERNEL);
rand_initialize_disk(disk);
// 块设备类 disk->part0._dev.class = &block_class;
disk_to_dev(disk)->class = &block_class;
// 块设备属性
disk_to_dev(disk)->type = &disk_type;
// 初始化 device (之后 device_add)
device_initialize(disk_to_dev(disk));
INIT_WORK(&disk->async_notify,
media_change_notify_thread);
}
return disk;
}
所有的设备在内核中都得有一个 device 结构来描述,块设备也一样,它属于块设备类。
void add_disk(struct gendisk *disk)
{
struct backing_dev_info *bdi;
dev_t devt;
int retval;
disk->flags |= GENHD_FL_UP;
// *devt = MKDEV(disk->major, disk->first_minor + part->partno = 0);
retval = blk_alloc_devt(&disk->part0, &devt);
// disk->part0._dev.devt = devt;设备号
disk_to_dev(disk)->devt = devt;
disk->major = MAJOR(devt);
disk->first_minor = MINOR(devt);
// kobj_map(bdev_map, devt, range, module, probe, lock, data);
blk_register_region(disk_devt(disk), disk->minors, NULL,
exact_match, exact_lock, disk);
register_disk(disk);
blk_register_queue(disk);
bdi = &disk->queue->backing_dev_info;
bdi_register_dev(bdi, disk_devt(disk));
retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
"bdi");
WARN_ON(retval);
}
void register_disk(struct gendisk *disk)
{
struct device *ddev = disk_to_dev(disk);
struct block_device *bdev;
struct disk_part_iter piter;
struct hd_struct *part;
int err;
ddev->parent = disk->driverfs_dev;
dev_set_name(ddev, disk->disk_name);
/* delay uevents, until we scanned partition table */
dev_set_uevent_suppress(ddev, 1);
// 注册设备
if (device_add(ddev))
return;
#ifndef CONFIG_SYSFS_DEPRECATED
err = sysfs_create_link(block_depr, &ddev->kobj,
kobject_name(&ddev->kobj));
if (err) {
device_del(ddev);
return;
}
#endif
disk->part0.holder_dir = kobject_create_and_add("holders", &ddev->kobj);
disk->slave_dir = kobject_create_and_add("slaves", &ddev->kobj);
// bdev = bdget(part_devt(part)); 获得 block_device
// list_add(&bdev->bd_list, &all_bdevs);
bdev = bdget_disk(disk, 0);
bdev->bd_invalidated = 1;
err = blkdev_get(bdev, FMODE_READ);
blkdev_put(bdev, FMODE_READ);
exit:
/* announce disk after possible partitions are created */
dev_set_uevent_suppress(ddev, 0);
kobject_uevent(&ddev->kobj, KOBJ_ADD);
/* announce possible partitions */
disk_part_iter_init(&piter, disk, 0);
while ((part = disk_part_iter_next(&piter)))
kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
disk_part_iter_exit(&piter);
}
经过以上过程,块设备就被注册到内核中去了。我们看到有一个全局的链表 all_bdevs,每一个注册的 block_device 都被添加到其中。
有了驱动程序之后,我们在应用层读写文件,由文件系统转化为对扇区的读写,也就是调用 ll_rw_block , ll_rw_block 就会找到该请求对应的块设备,找到块设备的请求队列,利用请求队列的构造请求函数来将请求合并排队添加到请求队列中去,然后利用请求队列的请求处理函数处理请求。
应用 -> VFS -> 具体的文件系统 ll_ll_rw_block -> 块设备驱动 -> 硬件
内存模拟块设备:
/* 参考:
* drivers\block\xd.c
* drivers\block\z2ram.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
static struct gendisk *ramblock_disk;
static request_queue_t *ramblock_queue;
static int major;
static DEFINE_SPINLOCK(ramblock_lock);
#define RAMBLOCK_SIZE (1024*1024)
static unsigned char *ramblock_buf;
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512 */
geo->heads = 2;
geo->cylinders = 32;
geo->sectors = RAMBLOCK_SIZE/2/32/512;
return 0;
}
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
static void do_ramblock_request(request_queue_t * q)
{
static int r_cnt = 0;
static int w_cnt = 0;
struct request *req;
//printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
unsigned long offset = req->sector * 512;
/* 目的/源: */
// req->buffer
/* 长度: */
unsigned long len = req->current_nr_sectors * 512;
if (rq_data_dir(req) == READ)
{
//printk("do_ramblock_request read %d\n", ++r_cnt);
memcpy(req->buffer, ramblock_buf+offset, len);
}
else
{
//printk("do_ramblock_request write %d\n", ++w_cnt);
memcpy(ramblock_buf+offset, req->buffer, len);
}
end_request(req, 1);
}
}
static int ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */
/* 2. 设置 */
/* 2.1 分配/设置队列: 提供读写能力 */
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
ramblock_disk->queue = ramblock_queue;
/* 2.2 设置其他属性: 比如容量 */
major = register_blkdev(0, "ramblock"); /* cat /proc/devices */
ramblock_disk->major = major;
ramblock_disk->first_minor = 0;
sprintf(ramblock_disk->disk_name, "ramblock");
ramblock_disk->fops = &ramblock_fops;
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);
/* 3. 硬件相关操作 */
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
/* 4. 注册 */
add_disk(ramblock_disk);
return 0;
}
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
kfree(ramblock_buf);
}
module_init(ramblock_init);
module_exit(ramblock_exit);
MODULE_LICENSE("GPL");