Linux块设备驱动开发简介

本文基于Linux-4.14

文件系统框架

Linux内核的文件系统框架图如下所示:
Linux块设备驱动开发简介_第1张图片

gendisk对象

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系统调用的定义,那么块设备是怎么进行读写的呢?
实际上块设备的访问方式有两种:

  • 第一种是通过/dev下的设备文件来访问
  • 第二种就是通过文件系统的read、write来访问

一般第一种方式我们大多是用来ioctl操作,获取块设备的属性;第二种操作才是用来读写访问的。通过文件系统来访问块设备,文件系统的最底层和块设备的关联的就是request_queue,每一个gendisk对象都有一个request_queue对象:gendisk->request_queue。它是文件系统和底层块设备驱动之间IO的桥梁,在下一个小节介绍。

request_queue对象

每个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驱动架构(块设备的一种)

前面介绍了块设备的实现框架层,那么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

你可能感兴趣的:(内核笔记,块设备驱动,文件系统)