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