抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

fork和exec简介

在linux中启动新一个程序有两种方式:

  • Fork(分叉)
    • 创建一个新的进程,该进程几乎是前一个进程的拷贝
    • 子进程和父进程使用相同的代码段,子进程复制父进程的堆栈段和数据段
    • 子进程对父进程的复制采用 写时复制 的策略,所有实际上物理空间还是共享着的,直到一个进程写了数据,才将有差别的”页”从物理地址上分开
  • Exec
    • 启动另外的进程以取代当前运行的进程
    • 调用exec的进程将会死亡,废弃原有的数据段和堆栈段,分配新的数据段和堆栈段,但是进程号是同一个 。所以对于系统来说还是同一个进程,但不是同一个程序了

结论:

fork时将父进程的虚拟空间复制给子进程一份,但此时父子进程使用的是同一块物理空间。当对这块共享地址读写时,才为子进程生成一块独立的不同于父进程的物理空间,称为写时复制(copy on write, cow)。当然cow后子进程虚拟空间不变与fork时父进程的虚拟空间相同,正文段,数据段,堆,栈这四个部分内容相同(即变量地址、函数地址等与父进程相同)。也因此父子进程存在 地址相同但值不同 的情况,因为用户看到的是进程的虚地址。

fork, vfork, clone的区别与联系

  • fork
    • fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
    • 子进程会复制父进程的task_struct,系统堆栈和页表
  • vfork
    • vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
    • vfork创造出来的是轻量级进程,也叫线程,是共享资源的进程
  • clone
    • 可以通过传递不同的参数来选择性的复制父进程的资源

写时复制(copy on write, COW)

当子进程改变了变量时,才会通过写时复制的方法为涉及的页表建立新的副本。即当子进程或则进程尝试写一个共享的页帧时就产生一个异常(page_fault, int14)中断,此时CPU会执行相应的异常处理函数do_wp_page()

do_wp_page()会对这块导致写入异常中断的物理页面进行取消共享操作,为写进程复制一新的物理页面,使父进程和子进程各自拥有一块内容相同的物理页面。最后,从异常处理函数中返回时,CPU就会重新执行刚才导致异常的写入操作指令,使进程继续执行下去。

需要注意的是,每个进程有自己独立的虚拟地址,复制后子进程段、变量的地址空间(虚地址空间)和父进程是一样的,但是映射到的物理空间不同,所以存在 地址相同但值不同 的情况。因为用户看到的是进程的虚地址。

fork函数的执行过程

  • 触发sys_clone系统调用
  • 执行do_fork()
    • 执行copy_process()
      • 执行dup_task_struct()copy_file()copy_thread()copy_mm
    • wake_up_new_task(task_struct p)
    • put_pid(struct pid)
  • 执行ret_from_fork()

实战

  • 一般使用fork()函数新建一个进程,然后让进程去执行exec调用
    • 由于写时复制策略,fork()后会优先执行子进程(当然有时也不会),如果子进程直接exec将可执行文件载入地址空间开始执行,它就无需赋值了,效率提高
    • 如果不先执行子进程,则父进程很可能会产生写操作导致复制开始
  • 在命令中修改文件描述符时(如重定向,关闭fd等),命令执行结束时描述符就会自动还原。可以通过exec执行
    • exec 2>/dev/null,那么直到shell退出,2(标准错误)都一直会为/dev/null

评论