Linux--Sys_Read系統呼叫過程分析
注:
本片文章以Read函式的呼叫為例來講述一下系統對塊驅動層的一些處理, 哈哈。如果有不正確或者不完善的地方,歡迎前來拍磚留言或者發郵件到[email protected]進行討論,先行謝過。
一.Read函式經由的層次模型
首先來了解一下Read函式經由的層次模型:
從圖中可以看出,對於磁碟的一次讀請求,首先經過虛擬檔案系統層(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
四.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如下:
五.通用Block層和IO Schedule層扮演的角色
這部分相對比較簡單,通過函式submit_bio()的呼叫直接可以找到,相關呼叫Stack如下:
六.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; } |
至此,整個流程分析完畢。
最終,彙總的流程圖如下:
注:
根據吳仲傑大哥描述,函式request_fn確實是塊驅動的入口。