通过分析块设备驱动的框架,知道如何来写驱动

1.之前我们学的都是字符设备驱动,先来回忆一下

字符设备驱动:

当我们的应用层读写(read()/write())字符设备驱动时,是按字节/字符来读写数据的,期间没有任何缓存区,因为数据量小,不能随机读取数据,例如:按键、LED、鼠标、键盘等

 

2.接下来本节开始学习块设备驱动

块设备:

块设备是i/o设备中的一类, 当我们的应用层对该设备读写时,是按扇区大小来读写数据的,若读写的数据小于扇区的大小,就会需要缓存区, 可以随机读写设备的任意位置处的数据,例如 普通文件(*.txt,*.c等),硬盘,U盘,SD卡,

 

3.块设备结构:

  • 段(Segments):由若干个块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。
  • 块  (Blocks):   由Linux制定对内核或文件系统等数据处理的基本单位。通常由1个或多个扇区组成。(对Linux操作系统而言)
  • 扇区(Sectors):块设备的基本单位。通常在512字节到32768字节之间,默认512字节

 

4.我们以txt文件为例,来简要分析下块设备流程:

比如:当我们要写一个很小的数据到txt文件某个位置时, 由于块设备写的数据是按扇区为单位,但又不能破坏txt文件里其它位置,那么就引入了一个“缓存区”,将所有数据读到缓存区里,然后修改缓存数据,再将整个数据放入txt文件对应的某个扇区中,当我们对txt文件多次写入很小的数据的话,那么就会重复不断地对扇区读出,写入,这样会浪费很多时间在读/写硬盘上,所以内核提供了一个队列的机制,再没有关闭txt文件之前,会将读写请求进行优化,排序,合并等操作,从而提高访问硬盘的效率

(PS:内核中是通过elv_merge()函数实现将队列优化,排序,合并,后面会分析到)

5.接下来开始分析块设备框架

当我们对一个*.txt写入数据时,文件系统会转换为对块设备上扇区的访问,也就是调用ll_rw_block()函数,从这个函数开始就进入了设备层.

5.1先来分析ll_rw_block()函数(/fs/buffer.c):

复制代码
void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
//rw:读写标志位,  nr:bhs[]长度,  bhs[]:要读写的数据数组
{
      int i; 
      for (i = 0; i < nr; i++) {
      struct buffer_head *bh = bhs[i];                 //获取nr个buffer_head
       ... ...
       if (rw == WRITE || rw == SWRITE) {
              if (test_clear_buffer_dirty(bh)) {
              ... ...
              submit_bh(WRITE, bh);                //提交WRITE写标志的buffer_head   
         continue;
              }}
       else {
              if (!buffer_uptodate(bh)) {
              ... ...
              submit_bh(rw, bh);               //提交其它标志的buffer_head
              continue;
              }}
              unlock_buffer(bh); }
}
复制代码

 

其中buffer_head结构体,就是我们的缓冲区描述符,存放缓存区的各种信息,结构体如下所示:

复制代码
struct buffer_head {
    unsigned long b_state;          //缓冲区状态标志 
    struct buffer_head *b_this_page;    //页面中的缓冲区 
    struct page *b_page;           //存储缓冲区位于哪个页面
    sector_t b_blocknr;           //逻辑块号
    size_t b_size;              //块的大小
    char *b_data;               //页面中的缓冲区

    struct block_device *b_bdev;     //块设备,来表示一个独立的磁盘设备

    bh_end_io_t *b_end_io;         //I/O完成方法
 
    void *b_private;             //完成方法数据
 
    struct list_head b_assoc_buffers;   //相关映射链表

    /* mapping this buffer is associated with */
    struct address_space *b_assoc_map;   
    atomic_t b_count;             //缓冲区使用计数 
};
复制代码

 

 

5.2然后进入submit_bh()中, submit_bh()函数如下:

复制代码
int submit_bh(int rw, struct buffer_head * bh)
{
       struct bio *bio;                    //定义一个bio(block input output),也就是块设备i/o
       ... ...
       bio = bio_alloc(GFP_NOIO, 1);      //分配bio
      /*根据buffer_head(bh)构造bio */
       bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);      //存放逻辑块号
       bio->bi_bdev = bh->b_bdev;                              //存放对应的块设备
       bio->bi_io_vec[0].bv_page = bh->b_page;           //存放缓冲区所在的物理页面
       bio->bi_io_vec[0].bv_len = bh->b_size;              //存放扇区的大小
       bio->bi_io_vec[0].bv_offset = bh_offset(bh);            //存放扇区中以字节为单位的偏移量

       bio->bi_vcnt = 1;                                    //计数值
       bio->bi_idx = 0;                                     //索引值
       bio->bi_size = bh->b_size;                         //存放扇区的大小

       bio->bi_end_io = end_bio_bh_io_sync;             //设置i/o回调函数
       bio->bi_private = bh;                               //指向哪个缓冲区
       ... ...
       submit_bio(rw, bio);                           //提交bio
       ... ...
}
复制代码

 

submit_bh()函数就是通过bh来构造bio,然后调用submit_bio()提交bio

 

5.3 submit_bio()函数如下:

void submit_bio(int rw, struct bio *bio)
{
       ... ...
       generic_make_request(bio);        
}

 

最终调用generic_make_request(),把bio数据提交到相应块设备的请求队列中,generic_make_request()函数主要是实现对bio的提交处理

 

5.4 generic_make_request()函数如下所示:

复制代码
void generic_make_request(struct bio *bio)
{
 if (current->bio_tail) {                   // current->bio_tail不为空,表示有bio正在提交
              *(current->bio_tail) = bio;     //将当前的bio放到之前的bio->bi_next里面
              bio->bi_next = NULL;    //更新bio->bi_next=0;
              current->bio_tail = &bio->bi_next; //然后将当前的bio->bi_next放到current->bio_tail里,使下次的bio就会放到当前bio->bi_next里面了

              return;    }

BUG_ON(bio->bi_next);
       do {
              current->bio_list = bio->bi_next;
              if (bio->bi_next == NULL)
                     current->bio_tail = &current->bio_list;
              else
                     bio->bi_next = NULL;

              __generic_make_request(bio);           //调用__generic_make_request()提交bio
              bio = current->bio_list;
       } while (bio);
       current->bio_tail = NULL; /* deactivate */
}  
复制代码

 

从上面的注释和代码分析到,只有当第一次进入generic_make_request()时, current->bio_tail为NULL,才能调用__generic_make_request().

__generic_make_request()首先由bio对应的block_device获取申请队列q,然后要检查对应的设备是不是分区,如果是分区的话要将扇区地址进行重新计算,最后调用q的成员函数make_request_fn完成bio的递交.

5.5 __generic_make_request()函数如下所示:

复制代码
 static inline void __generic_make_request(struct bio *bio)
{
request_queue_t *q;    
int ret;  
 ... ...
       do {
              q = bdev_get_queue(bio->bi_bdev);  //通过bio->bi_bdev获取申请队列q
              ... ...
              ret = q->make_request_fn(q, bio);             //提交申请队列q和bio
       } while (ret);
}
复制代码

 

这个q->make_request_fn()又是什么函数?到底做了什么,我们搜索下它在哪里被初始化的

如下图,搜索make_request_fn,它在blk_queue_make_request()函数中被初始化mfn这个参数

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值