SD卡读写流程
2012-12-28 15:34 至上 阅读(17154) 评论(0) 编辑 收藏 举报本文主要介绍从用户层读sd卡和写sd卡中间执行的过程。有对内核普遍性的介绍,和sd卡驱动个性的描述,强调把内核与驱动分开来看。同时提出内核需要驱动提供的参数,数据。
一 SD卡使用流程框图
说简单点:就是完成SD卡与内存之间的数据交互。但是涉及到了设备管理与文件管理。用户操作就是用户进程的read/write系统调用,应该说是 fread/fwrite,表示读某个文件,再不是读sd卡这个设备文件,也就是说你不需要在sd驱动中实现read/write函数,因为用不到啊。系 统调用后进入fat文件系统中的read/write,如果你问我为什么会进入fat中,因为sd卡上的文件组织使用的是fat32文件系统,并且在 mount的时候明确了是fat。这是与字符设备差异的地方。
VFS层>具体的文件系统层(fat32体现文件系统的差异)>cache层(这是内存的一部分,用于缓存磁盘文件,读文件的时候现在内存找,命中就不用再读了)>通用块设备层(制造bio,bio是一次请求的容器,包含了内存中为将要读取文件预留的空间(bio_vec),还有文件在磁盘上的扇区)>IO调度层(以前的磁盘文件随意访问影响速度,这是由硬件决定的,所以有了对请求的调度,如果2个磁盘文件在磁盘中的位置相近,就把请求调整到一起,现在的sd卡等就不需要了)>块设备驱动层(从请求队列中一个接着一个的取request,并且执行,使用dma搬运数据)
二 SD卡使用流程图
三 文件在内存中的缓存
从物理地址上讲,文件缓存是一直在内存中的(超级块信息缓存在内核数据区,节点位图,设备块位图保存在缓冲区(缓冲区是内核空间与用户空间中间的一部分内存),文件缓存在page cache中),不会因为进程切换而切换,就是说所有进程共享这部分内容。普通文件是共享的,可以操作,可执行文件是要拷贝到进程空间执行的。这样做的好处是不用每次都去外存中读取文件,提高了整体的性能。都是页操作的,不错页管理与页中的内容无关,缓冲区还是你按照块操作的,这个块是文件块的意思,一般是1K,大小与操作系统有关,块IO也就是这个意思。在用户空间,使用缺页机制,经常会按页操作,不会与块有关。就是说块机制用于与外存文件交互,页机制用于其他内存管理,当然了缓冲区也有页的概念,这就与线性地址寻址有关了。
四 准备SD卡,准备具体分析流程
准备一个SD卡,在其内放置一个文件test.txt,内容是:this is just a test!!!文件大小22字节,占空间4KB。这是在SD卡中占的空间,称之为一簇,但是大家知道,在内存中是不会占这么大的,可能比22字节大一点,保证字节对齐。从SD卡中取数据必须以扇区为单位,就是512字节,那么系统在执行的时候是读了一个扇区,还是8个扇区呢?因为后面根本没有数据。这个问题后面会解答。
这个是test文件的信息,使用winhex工具时可以查看的,有创建时间,文件大小,所在簇号等信息。这些信息很重要,比如说当你在SD卡目录,cd打开一个目录,看到很多文件名,那么就代表这些文件在内存中或者cache中吗,显然不是,但是关于这些文件的信息比如在内存中,当你打开某一个文件时,会用到这些信息,使用文件大小确定读得扇区数,通过扇区号,确定文件在SD卡中的位置,这样才能找到文件。那么这些文件信息一开始就在内存中吗,是的,在mount的时候,就读进来了。或许只是部分,但是通过目录是可以找到的。在图片中最后的16 00 00 00 ,是文件大小22字节,前面一点是03 00,是表示文件在第三簇,然后推算到扇区号是7528+4+4=7536.怎么找到的就不说了,我的另外一篇博客中有写。
这副图就是在在7536号扇区中显示的,是文件的内容。这次的测试就是把这些内容读到内存中去。详细说明其过程。
五 上代码
从readpage函数开始分析,因为第一次肯定不在cache中,需要从sd卡中读。也是产生bio结构体的最直接的函数。表示一次磁盘连续IO操作的结构体为bio,bio在通用块层制造,构造好之后传递给IO调度层,进而传递给设备驱动来处理,磁盘操作最小的单位是扇区,所以bio要以起始扇区号和长度来描述一次IO操作。
当访问的sd卡中两个文件连续的 时候,两个bio会合成一个request,一般一个bio就是一次request,有时候一个request也包含多个bio。表示请求一次文件操作,不过,当文刚说到了内存中不连续的块,如果一次get_block分配到的几个块是连续的,就表示为一个段,所以bio_vec用来表示几个连续的文件块就是段了,当然了,如果文件大于4K,就会有多个页,就不是图中仅有的8个数据块了(页是4K,块大小与操作系统有关,感觉还是1K的多)。我还有个问题:如果两个块属于不同的物理页,但是物理地址上是连续的,可以组成一个段吗?貌似是不可以的。
const struct file_operations fat_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.mmap = generic_file_mmap,
.release = fat_file_release,
.ioctl = fat_generic_ioctl,
.fsync = file_fsync,
.splice_read = generic_file_splice_read,
};
sys_read操作先调用do_sync_read,然后在do_sync_read中调用generic_file_aio_read。
在generic_file_aio_read中先到缓存中去找,找到就直接拷贝文件到用户空间,找不到就要到磁盘上去运输,会调用read_page函数
接着会.readpage = fat_readpage,就是直接调用fat_readpage函数。
文件系统在操作设备的时候,有一个预读取的动作。一般我们需要的数据是通过预读取读进内存的,这个时候调用的就是fat_readpages操作函数。
static int fat_readpages(struct file *file, struct address_space *mapping, struct list_head *pages, unsigned nr_pages)
//注意他的最后一个参数fat_get_block是一个函数指针。他的作用是建立从文件块号到设备扇区号的映射。 { printk("*****you are in fat_readpages*****\n"); return mpage_readpages(mapping, pages, nr_pages, fat_get_block);
//由这个函数制造bio,这个函数属于通用块层 }
先分析一下参数,file是fopen函数返回的一个结构体,address_space数据结构是用来描述一个文件在页高速缓存中的信息,每个文件可以有多个虚拟地址,但是只能在物理内存中有一份,意思
就是多个进程共享该文件,所以address_space结构体与虚拟地址没有关系,只是描述文件存储本身,pages和nr_pages是文件使用的第一页信息和文件在内存中占了几页,这样一看这些页是连续
的,从虚拟地址的角度看确实是的,但是在内存存储上,即使是一页,也不一定是连续的,比如说一页是4K,一个块是512Byte,那么一个页就由8个块组成,这8个块是可连续也可不连续的。如果每
次都是这样分配,页高速缓存机制(内存中一部分)也不会给内存带来内存碎片,这样的组织方式还是可以接受的。结论就是,文件在内存中是以页为单位的,就是4K为单位的,而在sd卡中文件是以
簇为单位的,簇的大小也是4K,扇区与块都是512Byte,这个巧合不能说明什么,因为Nand等其他块设备不一定是这样的。问题就来了,在sd卡中,以4K为单位存储文件没问题,但是内存有限,当
一个22字节的文件在内存中占用4K空间的时候,你能接受吗?不过从文件统计来讲,大于4K的文件多还是小于4K的文件多呢,这也说不清楚,但是从回写的角度看,把一页内容直接回写到sd卡中,
确实很方便,如果一页中还有其他内容,显然不方便操作。浪费一点就一点吧,毕竟页高速缓存是可以缩放的,并不严格规定大小,随着内存的使用而改变,在内存充足的情况下还是可以接受的。
要制造一个bio,需要提供三个信息:/×缓冲块大小可以是1K,2K等小于页大于扇区,与操作系统有关,文件小与1K,只需要申请一个文件块,不要申请一个页,所以是块IO而不是页IO是有
理由的×/
- 设备信息,关系到生成的bio结构体提交给谁,就是给那个设备,并不是说fat文件系统制造的就一定要给sd卡。/×文件路径决定了设备信息,文件的i节点就有设备信息×/
- 内存信息,如果是读sd卡,就要指定读到内存的那个地方,如果是写,就要说明数据来自内存的那个地方。/×fat_get_block×/
- sd卡信息,如果是读,文件在sd卡的那个扇区,大小多少。如果是写,又要写在那个扇区,占多大空间。/*fat_readpages*/
只有提供了这些信息,才能完成一次传输。然后分析一下参数喽。
/kernel/fs/mpage.c
mpage_readpages(struct address_space *mapping, struct list_head *pages, unsigned nr_pages, get_block_t get_block) { struct bio *bio = NULL; unsigned page_idx; sector_t last_block_in_bio = 0; struct buffer_head map_bh; unsigned long first_logical_block = 0; map_bh.b_state = 0; map_bh.b_size = 0; for (page_idx = 0; page_idx < nr_pages; page_idx++) {
//是一页一页循环读取的,是按页操作的,每次读都是4K内容。如果是读22字节的文件,显然4K空间内只有一个扇区是有内容的,后面的填零就行了啊。省去不少读的时间,读空
也是读啊。这个循环把每一页的地址信息都放到bio_vec中去,形成一个大的bio,并且通过get_block找到sd卡中想要操作的文件的扇区号和占用扇区个数。 struct page *page = list_entry(pages->prev, struct page, lru); prefetchw(&page->flags); list_del(&page->lru); if (!add_to_page_cache_lru(page, mapping, page->index, GFP_KERNEL)) { bio = do_mpage_readpage(bio, page, nr_pages - page_idx,7 &last_block_in_bio, &map_bh, &first_logical_block, get_block); } page_cache_release(page); } BUG_ON(!list_empty(pages)); if (bio) mpage_bio_submit(READ, bio); return 0; }
struct bio中的数据分析:
struct bio { sector_t bi_sector; /* device address in 512 byte sectors */ struct bio *bi_next; /* request queue link */ struct block_device *bi_bdev; unsigned long bi_flags; /* status, command, etc */ unsigned long bi_rw; /* bottom bits READ/WRITE, * top bits priority */ unsigned short bi_vcnt; /* how many bio_vec's */ unsigned short bi_idx; /* current index into bvl_vec */ /* Number of segments in this BIO after * physical address coalescing is performed. */ unsigned int bi_phys_segments; unsigned int bi_size; /* residual I/O count */ /* * To keep track of the max segment size, we account for the * sizes of the first and last mergeable segments in this bio. */ unsigned int bi_seg_front_size; unsigned int bi_seg_back_size; unsigned int bi_max_vecs; /* max bvl_vecs we can hold */ unsigned int bi_comp_cpu; /* completion CPU */ atomic_t bi_cnt; /* pin count */ struct bio_vec *bi_io_vec; /* the actual vec list */ bio_end_io_t *bi_end_io; void *bi_private; #if defined(CONFIG_BLK_DEV_INTEGRITY) struct bio_integrity_payload *bi_integrity; /* data integrity */ #endif bio_destructor_t *bi_destructor; /* destructor */ /* * We can inline a number of vecs at the end of the bio, to avoid * double allocations for a small number of bio_vecs. This member * MUST obviously be kept at the very end of the bio. */ struct bio_vec bi_inline_vecs[0];
//多个内存片段数组(片段是几个连续块的集合(一个页内的块)) };
struct bio_vec { struct page *bv_page; unsigned int bv_len; unsigned int bv_offset; };
/kernel/fs/mpage.c mapge_readpages make struct bio ------do_mpage_readpage------ blocks_per_page = 8 //内存中的文件块大小是512Bytes与扇区大小一致 you are into __make_request
mpage.c mapge_readpages make struct bio ------do_mpage_readpage------ blocks_per_page = 8 ------do_mpage_readpage------ blocks_per_page = 8 ------do_mpage_readpage------ blocks_per_page = 8 you are into __make_request [sepmmc_request], into [sepmmc_start_cmd], cmd:18 blksz=512, blocks=21 [sepmmc_dma_transfer], seg_num = 3 [sepmmc_dma_transfer], cur_seg=0, bus_addr=0x42103000, seg_len=0x1000 [dma_read], bus_addr: 0x42103000, blk_size: 0x1000 [sepmmc_command_done] [dma_chan_for_sdio1_irq_handler], cur_seg=1, bus_addr=0x428d1000, seg_len=0x1000 [dma_read], bus_addr: 0x428d1000, blk_size: 0x1000 [dma_chan_for_sdio1_irq_handler], cur_seg=2, bus_addr=0x428e0000, seg_len=0xa00 [dma_read], bus_addr: 0x428e0000, blk_size: 0xa00 [sepmmc_data_transfer_over] [sepmmc_start_cmd], cmd:12 [dma_chan_for_sdio1_irq_handler], up to the last segment [sepmmc_command_done] [sepmmc_request], exit
这是把一个12KB的文件cp到UBI文件系统中,可以看书只拷贝了10.5KB大小,说明了文件不足10.KB。只发了一次命令,说明文件在sd卡中存储是连续的,同时在这个request中
只有一个bio,这个bio中有三个bio_vec,各对应一个缓冲区片段,每个片段最大是4K,就是不能超过一页。奇怪的是这些页地址并不连续,好奇怪。
上文提到文件小于512字节的在sd卡中也占一个簇4KB,但是在读得时候,只读一个扇区,因为其他扇区没有用内容。
[sepmmc_request], into [sepmmc_start_cmd], cmd:18 blksz=512, blocks=256 [sepmmc_dma_transfer], seg_num = 13 [sepmmc_dma_transfer], cur_seg=0, bus_addr=0x4424a000, seg_len=0x2000 [dma_read], bus_addr: 0x4424a000, blk_size: 0x2000 [sepmmc_command_done] [dma_chan_for_sdio1_irq_handler], cur_seg=1, bus_addr=0x4422c000, seg_len=0x3000 [dma_read], bus_addr: 0x4422c000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=2, bus_addr=0x4422f000, seg_len=0x1000 [dma_read], bus_addr: 0x4422f000, blk_size: 0x1000 [dma_chan_for_sdio1_irq_handler], cur_seg=3, bus_addr=0x441e8000, seg_len=0x3000 [dma_read], bus_addr: 0x441e8000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=4, bus_addr=0x441eb000, seg_len=0x1000 [dma_read], bus_addr: 0x441eb000, blk_size: 0x1000 [dma_chan_for_sdio1_irq_handler], cur_seg=5, bus_addr=0x44240000, seg_len=0x3000 [dma_read], bus_addr: 0x44240000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=6, bus_addr=0x44243000, seg_len=0x3000 [dma_read], bus_addr: 0x44243000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=7, bus_addr=0x44246000, seg_len=0x2000 [dma_read], bus_addr: 0x44246000, blk_size: 0x2000 [dma_chan_for_sdio1_irq_handler], cur_seg=8, bus_addr=0x44220000, seg_len=0x3000 [dma_read], bus_addr: 0x44220000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=9, bus_addr=0x44223000, seg_len=0x3000 [dma_read], bus_addr: 0x44223000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=10, bus_addr=0x44226000, seg_len=0x2000 [dma_read], bus_addr: 0x44226000, blk_size: 0x2000 [dma_chan_for_sdio1_irq_handler], cur_seg=11, bus_addr=0x441e0000, seg_len=0x3000 [dma_read], bus_addr: 0x441e0000, blk_size: 0x3000 [dma_chan_for_sdio1_irq_handler], cur_seg=12, bus_addr=0x441e3000, seg_len=0x3000 [dma_read], bus_addr: 0x441e3000, blk_size: 0x3000 [sepmmc_data_transfer_over] [sepmmc_start_cmd], cmd:12
在读取一个几M大的文件的时候,发一次命令只读取128K,可能与sd卡多块读上线有关,dma传输最大是12kB,一个片段(经过整合过的,通常一个片段最大是4KB,不会超过一页
如果连续时可以整合的merge)
[sepmmc_request], into [sepmmc_start_cmd], cmd:17 blksz=512, blocks=1 [sepmmc_dma_transfer], seg_num = 1 [sepmmc_dma_transfer], cur_seg=0, bus_addr=0x428ee000, seg_len=0x200 [dma_read], bus_addr: 0x428ee000, blk_size: 0x200 [sepmmc_command_done] [sepmmc_data_transfer_over] [sepmmc_request], exit ------do_mpage_readpage------ blocks_per_page = 8 ------do_mpage_readpage------ blocks_per_page = 8 you are into __make_request [sepmmc_request], into [sepmmc_start_cmd], cmd:18 blksz=512, blocks=32 [sepmmc_dma_transfer], seg_num = 2 [sepmmc_dma_transfer], cur_seg=0, bus_addr=0x428e9000, seg_len=0x3000 [dma_read], bus_addr: 0x428e9000, blk_size: 0x3000 [sepmmc_command_done] [dma_chan_for_sdio1_irq_handler], cur_seg=1, bus_addr=0x428ec000, seg_len=0x1000 [dma_read], bus_addr: 0x428ec000, blk_size: 0x1000 [sepmmc_data_transfer_over] [sepmmc_start_cmd], cmd:12 [dma_chan_for_sdio1_irq_handler], up to the last segment [sepmmc_command_done] [sepmmc_request], exit mpage.c mapge_readpages make struct bio ------do_mpage_readpage------ blocks_per_page = 8 ------do_mpage_readpage------ blocks_per_page = 8 you are into __make_request [sepmmc_request], into [sepmmc_start_cmd], cmd:18 blksz=512, blocks=9 [sepmmc_dma_transfer], seg_num = 2 [sepmmc_dma_transfer], cur_seg=0, bus_addr=0x428f1000, seg_len=0x1000 [dma_read], bus_addr: 0x428f1000, blk_size: 0x1000 [sepmmc_command_done] [dma_chan_for_sdio1_irq_handler], cur_seg=1, bus_addr=0x428f0000, seg_len=0x200 [dma_read], bus_addr: 0x428f0000, blk_size: 0x200 [dma_chan_for_sdio1_irq_handler], up to the last segment [sepmmc_data_transfer_over] [sepmmc_start_cmd], cmd:12 [sepmmc_command_done] [sepmmc_request], exit
这是一个文件在sd卡中存放的簇号不连续(修改过的文件都这样),在读取这个文件的时候,是两个request,发两次读命令。一般都是一个bio对应一个request,不过在请求
队列里面对源地址(sd卡中的文件块号)相连的两个bio(就是两个request)合并为一个request,这就造成了一个request拥有多个bio,但是在dma传输的时候不需要区分
是几个bio,直接拿bio_vec里面的信息生成目的地址信息(内存中的物理地址,及其长度),这个时候的bio_vec也是合并过的,你会发现,dma生成的片段大小会超过4K,也
就是超过正常的一个bio_vec的大小,但是有会小于一个值(比如说12K),这是由于dma一次性可传输的块大小决定的,就是说dma搬运数据,没一次都有上限。