通过分析块设备驱动的框架,知道如何来写驱动
2017-10-11 18:09
507 查看
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):
其中buffer_head结构体,就是我们的缓冲区描述符,存放缓存区的各种信息,结构体如下所示:
5.2然后进入submit_bh()中, submit_bh()函数如下:
submit_bh()函数就是通过bh来构造bio,然后调用submit_bio()提交bio
5.3 submit_bio()函数如下:
最终调用generic_make_request(),把bio数据提交到相应块设备的请求队列中,generic_make_request()函数主要是实现对bio的提交处理
5.4 generic_make_request()函数如下所示:
从上面的注释和代码分析到,只有当第一次进入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()函数如下所示:
字符设备驱动:
当我们的应用层读写(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 = ¤t->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); }
相关文章推荐
- 通过分析块设备驱动的框架,知道如何来写驱动
- 基于事件驱动的领域模型实现框架 - 分析框架如何解决各种典型业务逻辑场景
- 物联网平台机智云Android开源框架入门之旅(三)分析设备详情界面的中如何发送各种指令到云端。
- 物联网平台机智云Android开源框架入门之旅(三)分析设备详情界面的中如何发送各种指令到云端。
- 通过上节的块设备驱动分析,本节便通过内存来模拟块设备驱动
- platform设备驱动框架搭建分析
- 通过分析蜘蛛侠论坛中的版块管理功能来介绍该如何使用我开发出来的ROM框架
- spi驱动框架全面分析,从master驱动到设备驱动
- 物联网平台机智云Android开源框架入门之旅(二)详细分析在设备列表的代码块,如何修改设备默认图片。
- 如何通过供应商和设备ID查找未知设备驱动
- 22.Linux-块设备驱动之框架详细分析(详解)
- .Linux-块设备驱动之框架详细分析(详解)
- 物联网平台机智云Android开源框架入门之旅(二)详细分析在设备列表的代码块,如何修改设备默认图片。
- 学习笔记 --- LINUX块设备驱动框架分析
- 22.Linux-块设备驱动之框架详细分析(详解)
- linux设备驱动--globalmem字符设备框架分析
- ramdisk驱动程序分析-2.6内核--块设备驱动框架(1)
- 11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结
- spi驱动框架全面分析,从master驱动到设备驱动
- LCD设备驱动框架分析(数据结构)