接前一篇文章:Linux内核有什么之块设备驱动有什么第一回 —— 概述及框架
本文内容参考:
34 | 块设备(上):如何建立代理商销售模式?-趣谈Linux操作系统-极客时间
Linux内核——块设备总结_linux do_open-CSDN博客
【Linux驱动】块设备驱动(一)—— 注册块设备_创建块设备-CSDN博客
特此致谢!
上一回起了头,对于块设备进行了概述,并给出了块设备子系统的框架:
本回限闲言少叙,直接开始解析块设备驱动的代码和机制。
块设备驱动的一般流程
要讲块设备的驱动流程,还得先打字符设备驱动那说起,两者对照着更好理解。
字符设备的核心结构为struct cdev,其在include/linux/cdev.h中定义,代码如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
字符设备驱动的一般初始化流程如下:
(0)加载设备驱动
static int __init xxx_init(void)
{
……
}
module_init(xxx_init);
注:下边代码凡是带缩进的,都是在xxx_init()中的。
(1)构建设备号
dev_t xxx_dev_no = MKDEV(xxx_major, 0);
(2)初始化并准备file_operations结构中的成员
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
……
copy_to_user(buf, ……, ……);
……
}
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
……
copy_from_user(……, buf, ……);
……
}
long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
……
switch (cmd) {
case XXX_CMD1:
……
break;
case XXX_CMD2:
……
break;
default:
return -ENOTTY;
}
return 0;
}
static int xxx_open(struct inode *inode, struct file *filp)
{
filp->private_data = ……;
……
}
static void xxx_release(struct inode *inode, struct file *filp)
{
return 0;
}
struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.unlocked_ioctl = xxx_ioctl,
.open = xxx_open,
.release = xxx_close,
……
};
(3)初始化cdev
cdev_init(&xxx_dev.cdev, xxx_fops);
xxx_dev.cdev.owner = THIS_MODULE;
(4)获取字符设备号
if (xxx_major) {
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
} else {
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
}
(5)注册设备
ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1);
讲完了字符设备驱动的初始化流程,该来看一下块设备驱动的初始化流程了。
块设备的核心结构为struct block_device(说实在的,从这名字上就想怼这帮做内核的。至少两边要对应起来嘛,要全称就都用全称:struct char_device、struct block_device;要简称就都用简称:struct cdev、struct bdev或者struct blkdev。结果弄了一个字符设备用简称,块设备用全称,不伦不类),其在include/linux/blk_types.h中定义,代码如下:
struct block_device {
sector_t bd_start_sect;
sector_t bd_nr_sectors;
struct gendisk * bd_disk;
struct request_queue * bd_queue;
struct disk_stats __percpu *bd_stats;
unsigned long bd_stamp;
bool bd_read_only; /* read-only policy */
u8 bd_partno;
bool bd_write_holder;
bool bd_has_submit_bio;
dev_t bd_dev;
struct inode *bd_inode; /* will die */
atomic_t bd_openers;
spinlock_t bd_size_lock; /* for bd_inode->i_size updates */
void * bd_claiming;
void * bd_holder;
const struct blk_holder_ops *bd_holder_ops;
struct mutex bd_holder_lock;
/* The counter of freeze processes */
int bd_fsfreeze_count;
int bd_holders;
struct kobject *bd_holder_dir;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
struct super_block *bd_fsfreeze_sb;
struct partition_meta_info *bd_meta_info;
#ifdef CONFIG_FAIL_MAKE_REQUEST
bool bd_make_it_fail;
#endif
bool bd_ro_warned;
/*
* keep this out-of-line as it's both big and not needed in the fast
* path
*/
struct device bd_device;
} __randomize_layout;
上回书说过,块设备驱动比字符设备要复杂得“多地多”(单田芳评书的味道~)。从上边这俩设备的核心结构体定义上,你就能看出来块设备复杂了多少。因此,在流程上也有较大的区别。具体来对照字符设备驱动流程来看一下。块设备驱动的一般初始化流程如下:
(0)加载设备驱动
static int __init xxx_init(void)
{
……
}
module_init(xxx_init);
这一步还是一样。
(1)初始化并准备block_device_operations结构中的成员
static int xxx_open(struct block_device *bdev, fmode_t mode)
{
struct xxx_dev *dev = bdev->bd_disk->private_data;
……
return 0;
}
static void xxx_release(struct gendisk *disk, fmode_t mode)
{
struct xxx_dev *dev = disk->private_data;
……
}
static int xxx_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
……
}
struct block_device_operations xxx_ops = {
.open = xxx_open,
.release = xxx_release,
.ioctl = xxx_ioctl,
……
}
块设备驱动的open函数与字符设备驱动的对等体不太相似。前者不以相关的inodehe file结构体指针作为参数,因为file和inode概念位于文件系统层中。
static int xxx_open(struct inode *inode, struct file *filp) { filp->private_data = ……; …… }
(2)块设备注册
if (register_blkdev(XXX_MAJOR, "xxx")) {
error = -EIO;
goto out;
}
(3)请求队列初始化
xxx_queue = blk_init_queue(xxx_request, xxx_lock);
if (!xxx_queue)
goto out_queue;
blk_queue_max_hw_sectors(xxx_queue, 255);
blk_queue_logical_block_size(xxx_queue, 512);
(4)gendisk初始化
xxx_disks->major = XXX_MAJOR;
xxx_disks->first_minor = 0;
xxx_disks->fops = &xxx_op;
xxx_disks->queue = xxx_queue;
sprintf(xxx_disks, xxx_size * 2);
add_disk(xxx_disks); // 添加gendisk
至此,借着与字符设备初始化流程的对比,块设备初始化的一般流程就基本清楚了。当然,这只是一个总体的概况,接下来会对于此流程以及整个块设备驱动的细节进行深入解析。