本文基于Linux-4.14
Linux中用一个gendisk对象结构体表示一个磁盘分区,这个结构体对象中会包含该分区对应的设备文件的主设备号,次设备号,以及对应的gendisk->fops操作函数,这个块设备操作方法结构体如下所示:
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*rw_page)(struct block_device *, sector_t, struct page *, bool);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
unsigned int (*check_events) (struct gendisk *disk,
unsigned int clearing);
/* ->media_changed() is DEPRECATED, use ->check_events() instead */
int (*media_changed) (struct gendisk *);
void (*unlock_native_capacity) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
/* this callback is with swap_lock and sometimes page table lock held */
void (*swap_slot_free_notify) (struct block_device *, unsigned long);
struct module *owner;
const struct pr_ops *pr_ops;
};
和字节设备cdev类似,cdev有用自己的操作函数file_operations; gendisk(block device)拥有自己的block_device_operations。实现这个结构体并且注册后,会在/dev目录下产生一个设备文件。对于驱动的实现需要使用如下三个关键API:
block/genhd.c:
struct gendisk *alloc_disk(int minors);//申请gendisk对象
void add_disk(struct gendisk *disk); //注册gendisk对象
void del_gendisk(struct gendisk *gp);//注销gendisk对象
此时你可能会产生一个疑问,上面的fops中并没有read、write系统调用的定义,那么块设备是怎么进行读写的呢?
实际上块设备的访问方式有两种:
一般第一种方式我们大多是用来ioctl操作,获取块设备的属性;第二种操作才是用来读写访问的。通过文件系统来访问块设备,文件系统的最底层和块设备的关联的就是request_queue,每一个gendisk对象都有一个request_queue对象:gendisk->request_queue。它是文件系统和底层块设备驱动之间IO的桥梁,在下一个小节介绍。
每个gendisk对象都会绑定一个request_queue对象,通过gendisk->request_queue可以访问对应的request_queue。文件系统通过把submit_bio请求发送给request_queue,底层块设备从request_queue读取request进行读写操作。在这一套运行机制下,底层设备块驱动只需要注册一个回调到对应的request_queue中,然后从request_queue中接收请求并处理,即可达到读写的目的。相关的操作API如下:
block/blk-core.c:
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) //注册一个request请求处理回调函数到request_queue中
{
return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);
}
EXPORT_SYMBOL(blk_init_queue);
struct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
struct request_queue *q;
q = blk_alloc_queue_node(GFP_KERNEL, node_id);
if (!q)
return NULL;
q->request_fn = rfn;
if (lock)
q->queue_lock = lock;
if (blk_init_allocated_queue(q) < 0) {
blk_cleanup_queue(q);
return NULL;
}
return q;
}
EXPORT_SYMBOL(blk_init_queue_node);
除了注册回调函数,block驱动层还需要维护request_queue的读取操作,一般会用一个线程去读取request_queue中的request请求,在这个内核线程中使用如下通用相关API:
struct request *blk_fetch_request(struct request_queue *q); //获取并处理一个request
void blk_start_queue(struct request_queue *q); //开始一个request queue
void blk_stop_queue(struct request_queue *q) ; //停止一个request queue
前面介绍了块设备的实现框架层,那么mmc驱动属于块设备的一种,我们通过介绍它来讲解gendisk是如何生成的,以及request_queue是如何维护的。
mmc驱动框架代码目录drivers/mmc/core,其中mmc设备注册gendisk的操作:
static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
struct device *parent,
sector_t size,
bool default_ro,
const char *subname,
int area_type)
{
......
md->disk = alloc_disk(perdev_minors);
if (md->disk == NULL) {
ret = -ENOMEM;
goto err_kfree;
}
spin_lock_init(&md->lock);
INIT_LIST_HEAD(&md->part);
md->usage = 1;
ret = mmc_init_queue(&md->queue, card, &md->lock, subname, area_type); //初始化绑定在该块设备上的request_queue,实现在后面介绍
if (ret)
goto err_putdisk;
md->queue.blkdata = md;
md->disk->major = MMC_BLOCK_MAJOR;
md->disk->first_minor = devidx * perdev_minors;
md->disk->fops = &mmc_bdops;
md->disk->private_data = md;
md->disk->queue = md->queue.queue;
mmc设备驱动框架注册request_queue初始化和处理函数的接口:
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname, int area_type)
{
......
mq->queue = blk_alloc_queue(GFP_KERNEL); //申请一个request_queue
if (!mq->queue)
return -ENOMEM;
mq->queue->queue_lock = lock;
mq->queue->request_fn = mmc_request_fn; //关键实现:设置request处理的回调函数
mq->queue->init_rq_fn = mmc_init_request;
mq->queue->exit_rq_fn = mmc_exit_request;
mq->queue->cmd_size = sizeof(struct mmc_queue_req);
mq->queue->queuedata = mq;
mq->qcnt = 0;
ret = blk_init_allocated_queue(mq->queue); //初始化request_queue,其中会把对应的回调函数使能
if (ret) {
blk_cleanup_queue(mq->queue);
return ret;
}
blk_queue_prep_rq(mq->queue, mmc_prep_request);
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, mq->queue);
if (mmc_can_erase(card))
mmc_queue_setup_discard(mq->queue, card);
blk_queue_bounce_limit(mq->queue, limit);
blk_queue_max_hw_sectors(mq->queue,
min(host->max_blk_count, host->max_req_size / 512));
blk_queue_max_segments(mq->queue, host->max_segs);
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
if (host->inlinecrypt_support)
queue_flag_set_unlocked(QUEUE_FLAG_INLINECRYPT, mq->queue);
除了完成上面的初始化和注册功能,mmc框架还是需要实现request_queue的管理,上面介绍已经通过request_fn注册了回调函数,那么需要开启一个线程定期读取并且触发request的处理:
static int mmc_queue_thread(void *d)
{
struct mmc_queue *mq = d;
struct request_queue *q = mq->queue;
struct mmc_context_info *cntx = &mq->card->host->context_info;
struct sched_param scheduler_params = {0};
scheduler_params.sched_priority = 1;
sched_setscheduler(current, SCHED_FIFO, &scheduler_params);
current->flags |= PF_MEMALLOC;
down(&mq->thread_sem);
do {
struct request *req;
spin_lock_irq(q->queue_lock);
set_current_state(TASK_INTERRUPTIBLE);
req = blk_fetch_request(q); //在这里会调用上面注册的回调函数request_fn,为什么不直接在这里调用request_fn?
//因为注册到框架的request_fn函数,除了block驱动层会用,block框架层也需要用。
mq->asleep = false;
cntx->is_waiting_last_req = false;
cntx->is_new_req = false;
if (!req) {
/*
* Dispatch queue is empty so set flags for
* mmc_request_fn() to wake us up.
*/
if (mq->qcnt)
cntx->is_waiting_last_req = true;
else
mq->asleep = true;
}
spin_unlock_irq(q->queue_lock);
if (req || mq->qcnt) {
set_current_state(TASK_RUNNING);
mmc_blk_issue_rq(mq, req);
cond_resched();
} else {
if (kthread_should_stop()) {
set_current_state(TASK_RUNNING);
break;
}
up(&mq->thread_sem);
schedule();
down(&mq->thread_sem);
}
} while (1);
up(&mq->thread_sem);
return 0;
}
kernel doc - Documentation/block
Linux块设备IO子系统(一) _驱动模型 - https://www.cnblogs.com/xiaojiang1025/p/6500557.html