天天看點

跟蹤系統調用之旅-續2

函數generic_file_read()通過執行宏init_sync_kiocb來初始化描述符kiocb,并設定一個同步操作對象的有關字段。具體地說就是,改宏設定ki_key字段為KIOCB_SYNC_KEY,ki_filp字段為filp、ki_obj字段為current。

 然後将剛填好的iovec和kiocb描述符位址傳給__ generic_file_aio_read()。函數__ generic_file_aio_read()是所有檔案系統實作同步和異步操作所使用的通用例程。

跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2

如果是直接 io (filp->f_flags 被設定了 O_DIRECT 标志,即不經過 cache)的方式,則調用 generic_file_direct_IO 函數;如果是 page cache 的方式,則調用 do_generic_file_read 函數。函數 do_generic_file_read 僅僅是一個包裝函數,它又調用 do_generic_mapping_read 函數,此函數在檔案include/linux/fs.h中。

跟蹤系統調用之旅-續2

do_generic_mapping_read函數在mm/filemap.c檔案中,do_generic_mapping_read函數主要流程如下:

根據檔案目前的讀寫位置,在 page cache 中找到緩存請求資料的 page

如果該頁已經最新,将請求的資料拷貝到使用者空間

否則, Lock 該頁

調用 readpage 函數向磁盤發出添頁請求(當下層完成該 IO 操作時會解鎖該頁),代碼:

跟蹤系統調用之旅-續2

再一次 lock 該頁,操作成功時,說明資料已經在 page cache 中了,因為隻有 IO 操作完成後才可能解鎖該頁。此處是一個同步點,用于同步資料從磁盤到記憶體的過程。

解鎖該頁

到此為止資料已經在 page cache 中了,再将其拷貝到使用者空間中(之後 read 調用可以在使用者空間傳回了)

readpage 函數在include/linux/fs.h,變量 mapping 為 inode 對象中的 address_space 對象,address_space 對象是嵌入在 inode 對象之中的,那麼不難想象: address_space 對象成員 a_ops 的初始化工作将會在初始化 inode 對象時進行,如下圖:

跟蹤系統調用之旅-續2

變量 ext2_aops 或者變量 ext2_nobh_aops的定義如下:

跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2

readpage 成員都指向函數 ext2_readpage,so: 函數 do_generic_mapping_read 最終調用 ext2_readpage 函

數處理讀資料請求。ext2_readpage函數在fs/ext2/inode.c檔案中

跟蹤系統調用之旅-續2

mpage_readpage()函數在fs/mpage.c

跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2

該函數首先調用函數 do_mpage_readpage 函數建立了一個 bio 請求,該請求指明了要讀取的資料塊所在磁盤的位置、資料塊的數量以及拷貝該資料的目标位置——緩存區中 page 的資訊。然後調用 mpage_bio_submit 函數處理請求。 mpage_bio_submit 函數則調用 submit_bio 函數處理該請求,後者最終将請求傳遞給函數 generic_make_request ,并由 generic_make_request 函數将請求送出給通用塊層處理。

跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2
跟蹤系統調用之旅-續2

__generic_make_request根據bio中儲存的裝置号取得請求隊列q,檢查目前IO排程器是否可用,如果可以,繼續;否則等待排程器可用,然後調用q->make_request_fn所指函數即将該請求加入到請求隊列。

對 make_request_fn 函數的調用可以認為是 IO 排程層的入口,該函數用于向請求隊列中添加請求。該函數是在建立請求隊列時指定的,代碼如下(blk_init_queue 函數中):

跟蹤系統調用之旅-續2

函數 blk_queue_make_request 将函數 __make_request 的位址賦予了請求隊列 q 的 make_request_fn 成員

__make_request 函數的主要工作為:

檢測請求隊列是否為空,若是,延緩驅動程式處理目前請求(其目的是想積累更多的請求,這樣就有機會對相鄰的請求進行合并,進而提高處理的性能),并跳到3,否則跳到2

試圖将目前請求同請求隊列中現有的請求合并,如果合并成功,則函數傳回,否則跳到3

該請求是一個新請求,建立新的請求描述符,并初始化相應的域,并将該請求描述符加入到請求隊列中,函數傳回

将請求放入到請求隊列中後,何時被處理就由 IO 排程器的排程算法決定了(有關 IO 排程器的算法内容請參見參考資料)。一旦該請求能夠被處理,便調用請求隊列中成員 request_fn 所指向的函數處理。

        request_fn 函數是塊裝置驅動層的入口。它是在驅動程式建立請求隊列時由驅動程式傳遞給 IO 排程層的。IO 排程層通過回調 request_fn 函數的方式,把請求交給了驅動程式。而驅動程式從該函數的參數中獲得上層發出的 IO 請求,并根據請求中指定的資訊操作裝置控制器(這一請求的發出需要依據實體裝置指定的規範進行)。

       當裝置完成了 IO 請求之後,通過中斷的方式通知 cpu ,而中斷處理程式又會調用 request_fn 函數進行處理。當驅動再次處理該請求時,會根據本次資料傳輸的結果通知上層函數本次 IO 操作是否成功,如果成功,上層函數解鎖 IO 操作所涉及的頁面(在 do_generic_mapping_read 函數中加的鎖)。該頁被解鎖後, do_generic_mapping_read() 函數就可以再次成功獲得該鎖(資料的同步點),并繼續執行程式了。之後,函數 sys_read 可以傳回了。最終 read 系統調用也可以傳回了。

       至此, read 系統調用從發出到結束的整個處理過程就全部結束了。

繼續閱讀