SD卡讀寫流程


本文主要介紹從用戶層讀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搬運數據,沒一次都有上限。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM