2022-01-07

如何查看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的映射
                                                  * 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、前言 在 Linux设备驱动 中,内存使用 是一个逃不掉的话题。Linux内核 的内存管理庞大且复杂,要想理解...
    wipping的技术小栈阅读 4,543评论 0 3
  • 进程 创建 创建进程用fork()函数。fork()为子进程创建新的地址空间并且拷贝页表。子进程的虚拟地址空间...
    梅花怒阅读 1,960评论 0 7
  • 转自 https://blog.csdn.net/xiaojsj111/article/details/31422...
    han在路上阅读 837评论 0 0
  • Linux进程通信实现机制有很多,也有各自优缺点和适用场景,关于她们之间的对比,等各种通信机制一一介绍后,再来一个...
    batbattle阅读 4,136评论 3 13
  • 内存管理 内存管理包含: 物理内存管理; 虚拟内存管理; 两者的映射 除了内存管理模块, 其他都使用虚拟地址(包括...
    西山薄凉阅读 791评论 0 2