SD卡读写流程
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 bytesectors */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 */ #endifbio_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搬運數據,沒一次都有上限。總結
- 上一篇: stm32 sdio acmd41 无响
- 下一篇: STM32下SD卡驱动详解