分享

SD卡读写流程

 海漩涡 2016-03-25

SD卡读写流程

2012-12-28 15:34 by 至上, 5131 阅读, 0 评论, 收藏编辑

转自:http://www.cnblogs.com/autum/archive/2012/12/28/sdstream.html

本文主要介绍从用户层读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函数
/* Start the actual read. The read will unlock the page. */
error = mapping->a_ops->readpage(filp, page);
接着会.readpage = fat_readpage,就是直接调用fat_readpage函数。
文件系统在操作设备的时候,有一个预读取的动作。一般我们需要的数据是通过预读取读进内存的,这个时候调用的就是fat_readpages操作函数。
static const struct address_space_operations fat_aops = {
.readpage = fat_readpage,
.readpages = fat_readpages,
.writepage = fat_writepage,
.writepages = fat_writepages,
.sync_page = block_sync_page,
.write_begin = fat_write_begin,
.write_end = fat_write_end,
.direct_IO = fat_direct_IO,
.bmap = _fat_bmap
};
复制代码
复制代码
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字节,因为blocks_per_page 8,就是一页中有8个块。
复制代码

上文提到文件小于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搬运数据,没一次都有上限。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多