如何查看linux系统调用的函数实现 && 和进程创建相关的几个系统调用
描述
- 系统调用的调用号,在系统调用的前面,加上 __NR_ (NR前面2个下划线)即可,比如搜索 __NR_vfork.
- 系统调用的实现函数的定义,都是形如 SYSCALL_DEFINE(vfork,) 或者 SYSCALL_DEFINEx(vfork,),(这里的x表示参数个数),可以通过grep快速搜索实现定义
grep -rn "SYSCALL_DEFINE" | grep vfork
__do_fork的实现
- 不论是fork还是vfork内部最终都会调到 __do_fork
_do_fork
copy_process
dup_task_struct // 创建task_struct结构
copy_files // 创建 files_struct *files ,复制原 task 该结构体的内容,但并不是完全一样的, 具体不一样的细节没看明白
copy_mm // [重要] 将 struct mm_struct *mm 指向原 task 的指针
xx // 其它操作
- 上面引入了重要的数据结构 struce mm_struct ,该结构定义了一个进程的整个虚拟地址空间,它里面有一个结构体 struct vm_area_struct *mmap
struct vm_area_struct *mmap 是什么呢,它就是大名鼎鼎的这个进程所有VMA的列表。
- VMA是虚拟内存的中的连续的一块虚拟内存,这块连续内存出于相同目的创建,有相同权限,默认是4KB对齐的,可以通过/proc/PID/maps查看
do_execve的实现
do_execve
do_execveat_common
exec_binprm
search_binary_handler // 轮询名为formats的list,看谁可以处理这种格式的文件,会找到下面这个节点 elf_format
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
load_elf_binary (可以参考这个wiki,很详细 https://www.cnblogs.com/inevermore/p/4438944.html )
elf_map // 最终调用到 do_mmap , 也就是 mmap的核心处理,完成文件的映射
elf_entry = load_elf_interp // 获取ld.so的入口地址,如果有动态链接库就会有,如果没有就不会有,没有的话,elf_entry就会赋值为进程本身入口地址
start_thread(regs, elf_entry, bprm->p); // 传入执行入口地址(elf_entry,也就是ld.so的入口地址或者可执行程序本身的入口地址),同时传入了regs寄存器组,这个寄存器组是当前进程的寄存器组,也就是用户态寄存器组,这样自动同时自动切换到了用户态
do_mmap的实现
do_mmap
do_mmap_pgoff
找到一块可用的虚拟内存地址, 作为vma的起始地址
mmap_region
(由于MMAP有 MAP_FIXED 的场景,这种情况下,需要先 do_munmap )
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL); // 创建1个vma
vma->vm_mm = mm; // 设置这个vma的内存描述符 (struct mm_struct *),每个进程只有1个
vma->vm_pgoff = pgoff; // 设置这个vma的以page为单位的偏移
error = file->f_op->mmap(file, vma); // 通过调用文件系统本身的mmap钩子,来实现地址映射,常用的是ext4:
ext4_file_mmap()是ext4对应的mmap。
ext4_file_mmap
vma->vm_ops = &ext4_file_vm_ops; // 核心的一行,可以看到并没有发生实际的虚拟地址到文件的映射,只是设置了这个vma的操作函数
/*
static const struct vm_operations_struct ext4_file_vm_ops = {
.fault = filemap_fault,
.map_pages = filemap_map_pages,
.page_mkwrite = ext4_page_mkwrite,
};
*/
- 可以看到mmap执行本身最后并没有发生虚拟地址的映射,只是设置了vma的操作函数,其中有个重要的操作函数是61行的filemap_fault.这个函数是用于随后,
当实际访问这个虚拟地址时,由于没有映射,会触发缺页中断,OS就会找到这个 vma的.fault的处理函数,也就是61行的filemap_fault. 下面看下这个函数。
filemap_fault的实现 - 填充page cache
- 这个函数核心目的是,找到page cache,没有就创建1个,然后进行预取动作,我俗称填充动作(也就是将文件的内容从flash读取到page cache对应的物理页中)。
- 这个函数具体流程是,检查是否存在这个文件的page cache(page cache是啥,往下看)发起IO,将falsh内容,读取到page cache , 然后把文件对应的page cache,也就是 struct page * ,找到并赋值给 vmf->page (struct vm_fault* vmf)。
- linux的page cache:
linux的每个物理文件,在内存中都有一个对应的page cache,在linux中通过 struct page*来表示这个page cache.
主要作用:提供一个内存,并对这个内存区域进行预读,提高效率。这样相比直接读写flash效率更高
filemap_fault
page_cache_read // 若page cache不存在,则发起IO,将falsh内容,读取到 page cache。执行完后,再通过goto走到下面第7行的page cache存在的处理,即预读。
ext4_readpage (也就是mapping->a_ops->readpage这一行,它最终调到下面的函数)
ext4_read_inline_page
kaddr = kmap_atomic(page) // 将物理内存映射到内核的虚拟地址空间,得到映射后的地址kaddr
ext4_read_inline_data // 读取flash中的内容到这个地址kaddr
kunmap_atomic // 取消映射
do_async_mmap_readahead // 若page cache存在,则进行预读处理,也就是把flash中文件内存读取到 page cache 中
- 上面的4~6行具体是在将文件内容从flash读取到page所在的物理内存(page没忘吧, 就是page cache), 但是即便是OS也不能直接对物理内存进行写,因为也要通过MMU来访问物理内存,所以OS先通过kmap_atomic拿到1个和这个物理地址对应的虚拟地址kaddr,再通过这个地址kaddr去写其对应的物理地址,也就是这个page cache.
建立虚拟地址到page cache的映射
- 上面的filemap_fault只是填充了page cache,也就是填充了 struct page * , 但是还欠缺虚拟地址到这个page cache的映射,也就是建立这个页表,只有这个完成了,缺页异常的处理才算完整了。下面跟着流程,从缺页异常的开始,是怎么建立页表,并和上面的filemap_fault建立联系的,继续往下看
do_page_fault // 区分是用户态的 page_fault 还是内核态的 page_fault
__do_page_fault // 查找对应的vma,如果没有(会检查权限不够吗?)则触发 SEGV 段错误
handle_mm_fault
__handle_mm_fault
handle_pte_fault // 建立页表函数
do_anonymous_page // 处理匿名映射
do_fault // 处理匿名映射,这里就会逐渐调入
do_read_fault
__do_fault(,&fault_page)) // 返回出来这个文件的page cache
vma->vm_ops->fault // 这里就是走到了之前mmap的时候,注册的page fault的回调了,也就是走到了上面的那个ext4_file_vm_ops注册的filemap_fault. 从这里可以看到,通过这个来申请或填充这个page cache,并且返回出来这个page cache
do_set_pte(fault_page) // 建立vma的虚拟地址和page cache的映射
*