他们两个的实现, 其实重点都在 linux 中, 所以要比较 他们的区别, 要注意 以下流程 在 linux 内核中的区别
open - addr= mmap ( . . . ) ; - addr[ 0 ] - close
open - read - close
要将 "磁盘上的文件内容" 搞到 "用户能访问到的内存" 里面,需要关注以下数据及过程, mmap和read都做了这些工作
1. 硬盘中文件 的 "起始硬盘地址" 和 长度
2. "内核空间的虚拟内存地址" 和 对应的 "物理内存地址A" 和 "映射关系A"
3. "用户空间的虚拟内存地址" 和 对应的 "物理内存地址B" 和 "映射关系B"
4. 文件内容从 "起始硬盘地址" 到 "物理内存地址A" ,再到"物理内存地址B" 的过程
1.
mmap过程的"物理内存地址A" 和"物理内存地址B" 是相同的;
read过程的不同
2.
mmap过程的"物理内存地址A" 和"物理内存地址B" 对应的 page ( 页高速缓存) 是 vm_fault ( 其中有VMA) 管理的;
read过程的"物理内存地址A" 对应的page是 address_space 管理的, "物理内存地址B" 是用户程序的全局变量或局部变量, 是 VMA管理的数据段或栈段对应的page, 是linux_binprm ( 其中有VMA) 管理的
文件读写基本流程
读文件
1 、进程调用库函数向内核发起读文件请求;
2 、内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;
3 、调用该文件可用的系统调用函数read ( )
3 、read ( ) 函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
4 、在inode中,通过文件内容偏移量计算出要读取的页;
5 、通过inode找到文件对应的address_space;
6 、在address_space中访问该文件的页缓存树,查找对应的页缓存结点:
如果页缓存命中,那么直接返回文件内容;
如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重新进行第6 步查找页缓存;
7 、文件内容读取成功。
写文件
1 、进程调用库函数向内核发起读文件请求;
2 、内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;
3 、调用该文件可用的系统调用函数read ( )
3 、read ( ) 函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
4 、在inode中,通过文件内容偏移量计算出要读取的页;
5 、通过inode找到文件对应的address_space;
6. 在address_space中查询对应页的页缓存是否存在:
如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。
如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6 步。
7 一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:
手动调用sync ( ) 或者fsync ( ) 系统调用把脏页写回
pdflush进程会定时把脏页写回到磁盘
8. 文件写入成功
1. open时 通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode, 创建file结构体
2. lseek时, 通过文件内容偏移量计算出 页缓存地址 和 对应的 address_space
3. read 时
1. ( file-> f_op-> read) ->
generic_file_read-> generic_file_aio_read-> do_generic_file_read-> do_generic_mapping_read 会在页缓存中查找, 如果找不到, 进行2 , 如果找到, 直接返回
2. ( address_space-> a_ops-> readpage, 即ext3_files_read) -> mpage_readpage-> do_mpage_readpage-> mpage_bio_submit-> submit_bio-> generic_make_request 读取 文件内容到页缓存
3. 然后进行1
问题 : 缓存内存页 struct page 被放到了哪里?
被放到了 address_space ( 中的 radix_tree) 里
address_space 直接挂在了 file结构体下, 不用复杂管理
page 由 radix_tree 管理
fd-> file-> address_space-> radix_tree-> page
file-> f_mapping-> i_mapping-> i_data-> i_pages-> ( radix_tree_node, 即xa_node) -> slots[ 0 ] -> page
1. open时 通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode, 创建file结构体
2. lseek时, 通过文件内容偏移量计算出 要读取的磁盘页地址
3. 调用mmap, 参数为 fd >= 0 flags= MAP_SHARED 陷入内核sys_mmap
4. sys_mmap进入并返回
3. 通过 addr = get_unmapped_area 得到虚拟地址空间
4. 通过 file-> f_op-> mmap ( 具体是文件系统ext4 的 struct file_operations ext4_file_operations 中的 mmap成员ext4_file_mmap) 设置了 vma-> vm_ops = & ext4_file_vm_ops
5. 访存( 读) , 访存时发生异常( vector_dabt)
1. __vectors_start-> vector_dabt-> __dabt_usr-> dabt_helper-> v7_early_abort-> do_DataAbort-> do_page_fault
2. do_page_fault-> do_shared_fault-> __do_fault-> ( vma-> vm_ops-> fault, 即ext4_file_vm_ops中的 fault成员 : ext4_filemap_fault)
ext4_filemap_fault-> filemap_fault->
page = find_get_page ( mapping, offset) ;
fpin = do_async_mmap_readahead ( vmf, page) ;
3. do_page_fault-> do_shared_fault-> __do_fault-> finish_fault
finish_fault
alloc_set_pte ( vmf, page) ;
问题 : struct page 被放到了哪里?
被放到了 vm_fault ( 的page 成员) 里
vm_fault 由 VMA 管理
page 被直接挂到了 vm_fault 的成员里, 不用复杂管理
arch/ arm/ mm/ fault. c 中的 __do_page_fault 创建了 vma ( 创建时vma已经建立好联系了, 挂载到了链表和树中) , 并调用了 handle_mm_fault ( vma, . . . ) -> __handle_mm_fault ( vma, . . . )
在 __handle_mm_fault 中 创建了 vmf , 将 vma 封装到 vmf 中, 并让 handle_pte_fault-> do_fault-> do_shared_fault-> __do_fault-> ( vma-> vm_ops-> fault, 即ext4_filemap_fault) -> filemap_fault 去填充 vmf 的 page 结构体
read 操作时的关系图
一个进程的task_struct 中的 files 成员中的 fd_array 是 进程内所有 file 结构体的归属地
mmap 操作时的关系图
一个进程的task_struct 中的 mm 成员 中的 mm_rb ( 树根) 和 mmap ( 链表头) 是 进程内所有的VMA的 归属地
struct vm_fault
struct vm_area_struct * vma;
struct page * page;
我们 可以在 https:
可执行二进制文件 是 用 VMA 管理的
而 不可执行的文件( 包括二进制和文本文件)
可以用VMA管理, 也可以用 address_space 管理
地址空间 与 VMA
地址空间分为两种
1. 进程地址空间
代码段的 page 由 linux_binprm 管理 , linux_binprm 被 VMA 管理
文件内容的 page 由 vm_fault 管理 , vm_fault 被 VMA 管理
2. 文件内容地址空间
文件内容的 page 由 radix_tree 管理, radix_tree 被 address_space 管理
两种地址空间介绍
"进程地址空间" 管理了page,page 对应
1. 存在于可执行文件其被加载到内存的 segment : 代码段 数据段
2. 不存在于可执行文件其被加载到内存的 segment : 栈段 很多个堆段
3. mmap段 : mmap 机制中创建的地址空间(4 种,其中一种对应 文件内容)
page 在哪里
代码段的 page 由 linux_binprm 管理 , linux_binprm 被 VMA 管理
文件内容的 page 由 vm_fault 管理 , vm_fault 被 VMA 管理
"文件内容地址空间" 管理了page,page对应
1. read的文件内容
page 在哪里
文件内容的 page 由 radix_tree 管理, radix_tree 被 address_space 管理
VMA的创建
1. 可以从用户空间通过 mmap 创建 , 可以被用户访问
2. 可以从内核空间通过 异常处理函数 创建 , 可以被用户访问吗? TODO
文件 mmap 后 , 读文件
好像 VMA 是 用户进程空间, 也就是说 , 内核直接将 文件内容 弄到 用户虚拟地址 中了
是的, mmap 就是这么干的, 一套物理地址对应了 两套( 内核和用户的) 虚拟地址
文件系统相关的高速缓存
1. file-> f_mapping-> i_mapping-> i_data-> i_pages-> ( radix_tree_node, 即xa_node) -> slots[ 0 ] -> page
2. vm_fault-> page
read 函数流程导读 ext4
642 SYSCALL_DEFINE3 ( read, unsigned int , fd, char __user * , buf, size_t , count)
ksys_read
vfs_read
new_sync_read
call_read_iter
file-> f_op-> read_iter/ 即ext4_file_read_iter
generic_file_read_iter
generic_file_buffered_read
generic_file_buffered_read_get_pages
find_get_pages_contig
page_cache_sync_readahead
find_get_pages_contig
copy_page_to_iter