1. 程式人生 > >Linux--Sys_Read系統呼叫過程分析

Linux--Sys_Read系統呼叫過程分析

注:

本片文章以Read函式的呼叫為例來講述一下系統對塊驅動層的一些處理, 哈哈。如果有不正確或者不完善的地方,歡迎前來拍磚留言或者發郵件到[email protected]進行討論,先行謝過。

一.Read函式經由的層次模型

首先來了解一下Read函式經由的層次模型:

clip_image002

從圖中可以看出,對於磁碟的一次讀請求,首先經過虛擬檔案系統層(vfs layer),其次是具體的檔案系統層(例如 ext2),接下來是 cache 層(page cache 層)、通用塊層(generic block layer)、IO 排程層(I/O scheduler layer)、塊裝置驅動層(block device driver layer),最後是物理塊裝置層(block device layer)。

下面摘抄一份文件,來對上面的各個層面的作用做一些簡述:

• 虛擬檔案系統層的作用:遮蔽下層具體檔案系統操作的差異,為上層的操作提供一個統一的介面。正是因為有了這個層次,所以可以把裝置抽象成檔案,使得操作裝置就像操作檔案一樣簡單。

• 在具體的檔案系統層中,不同的檔案系統(例如 ext2 和 NTFS)具體的操作過程也是不同的。每種檔案系統定義了自己的操作集合。關於檔案系統的更多內容,請參見參考資料。

• 引入 cache 層的目的是為了提高 linux 作業系統對磁碟訪問的效能。 Cache 層在記憶體中快取了磁碟上的部分資料。當資料的請求到達時,如果在 cache 中存在該資料且是最新的,則直接將資料傳遞給使用者程式,免除了對底層磁碟的操作,提高了效能。

• 通用塊層的主要工作是:接收上層發出的磁碟請求,並最終發出 IO 請求。該層隱藏了底層硬體塊裝置的特性,為塊裝置提供了一個通用的抽象檢視。

• IO 排程層的功能:接收通用塊層發出的 IO 請求,快取請求並試圖合併相鄰的請求(如果這兩個請求的資料在磁碟上是相鄰的)。並根據設定好的排程演算法,回撥驅動層提供的請求處理函式,以處理具體的 IO 請求。

• 驅動層中的驅動程式對應具體的物理塊裝置。它從上層中取出 IO 請求,並根據該 IO 請求中指定的資訊,通過向具體塊裝置的裝置控制器傳送命令的方式,來操縱裝置傳輸資料。

• 裝置層中都是具體的物理裝置。定義了操作具體裝置的規範。

二.系統呼叫的發起點sys_read

1. sys_read程式碼分析

Sys_read最終被註冊為系統API,在很多的系統模組中都可以看到該API的呼叫。

函式sys_read()的程式碼如下:

asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)

{

struct file *file;

ssize_t ret = -EBADF;

int fput_needed;

file = fget_light(fd, &fput_needed);

if (file) {

loff_t pos = file_pos_read(file);

ret = vfs_read(file, buf, count, &pos);

file_pos_write(file, pos);

fput_light(file, fput_needed);

}

return ret;

}

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)

{

ssize_t ret;

if (!(file->f_mode & FMODE_READ))

return -EBADF;

if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))

return -EINVAL;

if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))

return -EFAULT;

ret = rw_verify_area(READ, file, pos, count);

if (ret >= 0) {

count = ret;

if (file->f_op->read)

ret = file->f_op->read(file, buf, count, pos);

else

ret = do_sync_read(file, buf, count, pos);

if (ret > 0) {

fsnotify_access(file->f_path.dentry);

add_rchar(current, ret);

}

inc_syscr(current);

}

return ret;

}

從上面可以看到,呼叫Stack為sys_read()àvfs_read()àfile->f_op->read()。而file->f_op->read實際上就是具體的檔案系統向通用Block層註冊的一個函式指標,對於本文中講述的EXT2檔案系統來說,實際上就是do_sync_read。

三.Ext2檔案系統在sys_read呼叫過程中的角色

1. Ext2檔案系統file_operations介面的註冊過程

Ext2檔案系統的模組初始化函式會去註冊操作介面ext2_file_operations,呼叫Stack如下init_ext2_fs()à register_filesystem()àext2_get_sb()àext2_fill_super()àext2_iget(),其中函式ext2_iget()會獲取結構體file_operations的值。其中,介面的定義如下:

/*

* We have mostly NULL's here: the current defaults are ok for

* the ext2 filesystem.

*/

const struct file_operations ext2_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,

.unlocked_ioctl = ext2_ioctl,

#ifdef CONFIG_COMPAT

.compat_ioctl = ext2_compat_ioctl,

#endif

.mmap = generic_file_mmap,

.open = generic_file_open,

.release = ext2_release_file,

.fsync = ext2_sync_file,

.splice_read = generic_file_splice_read,

.splice_write = generic_file_splice_write,

};

const struct address_space_operations ext2_aops = {

.readpage = ext2_readpage,

.readpages = ext2_readpages,

.writepage = ext2_writepage,

.sync_page = block_sync_page,

.write_begin = ext2_write_begin,

.write_end = generic_write_end,

.bmap = ext2_bmap,

.direct_IO = ext2_direct_IO,

.writepages = ext2_writepages,

.migratepage = buffer_migrate_page,

.is_partially_uptodate = block_is_partially_uptodate,

};

const struct address_space_operations ext2_nobh_aops = {

.readpage = ext2_readpage,

.readpages = ext2_readpages,

.writepage = ext2_nobh_writepage,

.sync_page = block_sync_page,

.write_begin = ext2_nobh_write_begin,

.write_end = nobh_write_end,

.bmap = ext2_bmap,

.direct_IO = ext2_direct_IO,

.writepages = ext2_writepages,

.migratepage = buffer_migrate_page,

};

而函式ext2_iget()中的相關程式碼如下:

struct inode *ext2_iget (struct super_block *sb, unsigned long ino)

{

...

if (S_ISREG(inode->i_mode)) {

inode->i_op = &ext2_file_inode_operations;

if (ext2_use_xip(inode->i_sb)) {

inode->i_mapping->a_ops = &ext2_aops_xip;

inode->i_fop = &ext2_xip_file_operations;

} else if (test_opt(inode->i_sb, NOBH)) {

inode->i_mapping->a_ops = &ext2_nobh_aops;

inode->i_fop = &ext2_file_operations;

} else {

inode->i_mapping->a_ops = &ext2_aops;

inode->i_fop = &ext2_file_operations;

}

} else if (S_ISDIR(inode->i_mode)) {

inode->i_op = &ext2_dir_inode_operations;

inode->i_fop = &ext2_dir_operations;

if (test_opt(inode->i_sb, NOBH))

inode->i_mapping->a_ops = &ext2_nobh_aops;

else

inode->i_mapping->a_ops = &ext2_aops;

} else if (S_ISLNK(inode->i_mode)) {

if (ext2_inode_is_fast_symlink(inode))

inode->i_op = &ext2_fast_symlink_inode_operations;

else {

inode->i_op = &ext2_symlink_inode_operations;

if (test_opt(inode->i_sb, NOBH))

inode->i_mapping->a_ops = &ext2_nobh_aops;

else

inode->i_mapping->a_ops = &ext2_aops;

}

} else {

inode->i_op = &ext2_special_inode_operations;

if (raw_inode->i_block[0])

init_special_inode(inode, inode->i_mode,

old_decode_dev(le32_to_cpu(raw_inode->i_block[0])));

else

init_special_inode(inode, inode->i_mode,

new_decode_dev(le32_to_cpu(raw_inode->i_block[1])));

}

...

}

2. 系統Read過程呼叫在該層的Stack

image

四.Page Cache在Sys_read呼叫過程中所做的工作

1. Page Cache在Sys_read呼叫過程中所做的工作

從前面貼上的函式ext2_iget()的程式碼中中可以看到inode->i_mapping->a_ops = &ext2_aops,實際上這裡就是註冊了頁面快取的一些介面。

上一部分提到Ext2呼叫的結束點就是mappingàa_opsàreadpage(file, page),實際上執行的就是ext2_aops.readpage(file, page),也即ext2_readpage。

有關函式ext2_readpage()的呼叫Stack如下:

image

五.通用Block層和IO Schedule層扮演的角色

這部分相對比較簡單,通過函式submit_bio()的呼叫直接可以找到,相關呼叫Stack如下:

image

六.Driver所做的事情

哎呀,分析了半天還沒有看到塊裝置驅動的參與,不要急,這裡就來了,呵呵。

在塊裝置驅動中一般會呼叫通過Block層的匯出函式blk_init_queue()來註冊執行具體操作的函式,形如q->request_fn = rfn。

相關程式碼如下:

struct request_queue *

blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)

{

struct request_queue *q = blk_alloc_queue_node(GFP_KERNEL, node_id);

if (!q)

return NULL;

q->node = node_id;

if (blk_init_free_list(q)) {

kmem_cache_free(blk_requestq_cachep, q);

return NULL;

}

/*

* if caller didn't supply a lock, they get per-queue locking with

* our embedded lock

*/

if (!lock)

lock = &q->__queue_lock;

q->request_fn = rfn;

q->prep_rq_fn = NULL;

q->unplug_fn = generic_unplug_device;

q->queue_flags = (1 << QUEUE_FLAG_CLUSTER);

q->queue_lock = lock;

blk_queue_segment_boundary(q, 0xffffffff);

blk_queue_make_request(q, __make_request);

blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);

blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);

blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);

q->sg_reserved_size = INT_MAX;

blk_set_cmd_filter_defaults(&q->cmd_filter);

/*

* all done

*/

if (!elevator_init(q, NULL)) {

blk_queue_congestion_threshold(q);

return q;

}

blk_put_queue(q);

return NULL;

}

至此,整個流程分析完畢。

最終,彙總的流程圖如下:

image

注:

根據吳仲傑大哥描述,函式request_fn確實是塊驅動的入口。

相關推薦

Linux--Sys_Read系統呼叫過程分析

注: 本片文章以Read函式的呼叫為例來講述一下系統對塊驅動層的一些處理, 哈哈。如果有不正確或者不完善的地方,歡迎前來拍磚留言或者發郵件到[email protected]進行討論,先行謝過。 一.Read函式經由的層次模型 首先來了解一下Read函式經由的

Linux系統呼叫過程分析

Linux系統呼叫過程分析 參考: 《Linux核心設計與實現》 0 摘要 linux的系統呼叫過程:層次例如以下:使用者程式------>C庫(即API):INT 0x80 ----->system_call------->系

Linux核心:基於int指令的經典系統呼叫過程分析

眾所周知,程式碼執行可以存在多種不同的特權級別,而針對Linux系統,即為:使用者模式(user mode)和核心模式(kernel mode)。在使用者模式下,CPU的功能空間受到極大的限制,是沒有權利訪問多少系統資源的,諸多關鍵資源的使用無法直接調配,如:硬

窺探 kernel,just for fun --- 系統呼叫過程分析

郵箱:[email protected] 過程分析: 1、系統呼叫需要一個使用者空間到核心空間的轉換,不同的平臺有不同的指令來完成這樣的轉換,這個指令也叫做作業系統陷入(operating systemtrap)指令。在linux中對於x86

Linux核心分析(五)系統呼叫過程解析

禹曉博+ 原創作品轉載請註明出處 + 歡迎加入《Linux核心分析》MOOC網易雲課堂學習 一、系統呼叫流程分析         系統呼叫系統呼叫就是使用者空間應用程式和核心提供的服務之間的一個介面。由於服務是在核心中提供的,因此無法執行直接呼叫;相反,我們必須使用一個程序

linux 系統啟動過程分析

系統root 密碼丟失故障 linux啟動順序主板BIOS加電自檢 檢查硬件--> 讀取硬盤引導扇區(MBR)--> 啟動引導程序(grub)--> 選擇系統--> 加載系統內核(kernel shell)--> 啟動系統讀取相應的默認設置(環境變量,運行級別)--

arm linux 系統呼叫過程

在Linux下系統呼叫是用軟中斷實現的,下面以一個簡單的open例子簡要分析一下應用層的open是如何呼叫到核心中的sys_open的。 t8.c 1: #include <stdio.h> 2: #include <sys/types.h> 3:

Linux fsync和fdatasync系統呼叫實現分析(Ext4檔案系統

參考:https://blog.csdn.net/luckyapple1028/article/details/61413724 在Linux系統中,對檔案系統上檔案的讀寫一般是通過頁快取(page cache)進行的(DirectIO除外),這樣設計的可以延時磁碟IO的操作,從而可以減少磁碟讀

Linux 驅動 tty終端 呼叫過程分析

核心版本: 2.6.32.2 仍在修改中 2018.12.21 需進一步瞭解核心啟動過程中start_kernel以及module_init呼叫 module_init(serial8250_init); module_init(tty_init); console_init在start

Linux系統呼叫系統呼叫過程

對於日常使用的應用也不是脫離了硬體進行執行的,為了方便使用,就出現了作業系統,如果作業系統不是開放的,那就失去了作業系統的意義,為了方便使用作業系統,作業系統預留出了一些介面,這些介面就是系統呼叫函式。 當然系統呼叫函式肯定不同於庫函式,接下來我將講解Linux中的系統呼叫過程。 下圖是軟硬

arm linux 系統呼叫過程

在Linux下系統呼叫是用軟中斷實現的,下面以一個簡單的open例子簡要分析一下應用層的open是如何呼叫到核心中的sys_open的。 t8.c 1: #include <stdio.h> 2: #include <sys/types.

【轉載】Linux系統啟動過程分析

經過對Linux系統有了一定了解和熟悉後,想對其更深層次的東西做進一步探究。這當中就包括系統的啟動流程、檔案系統的組成結構、基於動態庫和靜態庫的程式在執行時的異同、協議棧的架構和原理、驅動程式的機制等等。        本人在綜合了現有網上大家智慧的基礎上,結合對2.6

嵌入式Linux系統啟動過程分析

一 啟動 嵌入式系統在啟動時,引導程式碼、作業系統的執行和應用程式的載入主要有兩種架構,一種是直接從Nor Flash啟動的架構,另一種是直接從Nand Flash啟動的架構。 1 從Nor Flash啟動 NorFlash具有晶片內執行(XIP,eXecute In P

linux系統呼叫版串列埠分析&原始碼實戰

[toc] --- ## 前言 * **目前不涉及驅動原始碼** ## 參考 * [linux手冊之termios](https://man7.org/linux/man-pages/man3/termios.3.html) * [本文連結](https://www.cnblogs.com/lizhu

Linux進程啟動過程分析do_execve(可執行程序的加載和運行)---Linux進程的管理與調度(十一)

[] flag 表示 conn nali 最終 roc 不同的 recursion execve系統調用 execve系統調用 我們前面提到了, fork, vfork等復制出來的進程是父進程的一個副本, 那麽如何我們想加載新的程序, 可以通過execve來加載和啟動新的程

Linux 檔案系統呼叫open七日遊(三)

接著上回,當對“.”和“..”處理完成後就直接返回進入下一個子路徑迴圈了,但如果當前子路徑不是“.”或“..”呢? 【fs/namei.c】 sys_open > do_sys_open > do_filp_open >&

Linux檔案系統呼叫open 七日遊 (六)

還記得在上一個場景中,build_open_flags裡面有一個對標誌位O_PATH的判斷麼?現在我們就來看看這個標誌位是幹啥的: 【場景二】open(pathname,O_PATH)     這個O_PATH似乎是不常用的,咱們先看看它的使用

linux檔案系統呼叫 open 七日遊(四)

現在,我們的“路徑行走”只剩下最後一個小問題需要處理了——符號連結。 【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat &g

Java中的方法呼叫過程分析

假設呼叫x.f(args),隱式引數x宣告為類C的一個例項物件: 1.編譯器檢視物件的宣告型別和方法名。例如,可能存在方法f(int)和方法f(String)。編譯器將會一一列舉出所有該類中名為f的方法和其超類中訪問屬性為public且名為f的方法。 2.編譯器將檢視呼叫方法時提供的引數型別

Linux系統呼叫 網路連線狀態 磁碟I/O 可疑行為監控/日誌收集 SHELL命令執行流程

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!