赞
踩
首先,bash作为一个父进程,运行起来的程序是bash的子进程。
即,当我们在终端输入./main
运行一个可执行程序,bash会调用一个名为fork()
系统调用,然后陷入内核,CPU执行内核态的sys_fork()
函数,而sys_fork()
函数中调用了do_fork()
,其中do_fork()
会创建一个task_struct,然后将该task_struct加入到内核维护的进程的双向链表中。
然后,fork()调用结束,子进程诞生了,但是fork()做的是复制父进程,实际上我们当然不是期望父进程和子进程执行的指令和数据一模一样了,我们期望的是子进程执行自己的数据和指令,即main可执行程序的数据和指令。
然后,子进程调用exec()
族函数,继续陷入内核,执行sys_execve(),调用load_elf_binary()将main的存放在磁盘的数据和指令加载到内存中。
最终,真正意义上main进程诞生了,此时的子进程真正执行的是main的数据和指令。
用户态调用fork(),
出发系统调用,CPU转向内核,执行内核态的代码;
通过查询系统调用表,找到内核的sys_fork()
函数,进行调用;
而sys_fork()
实际上调用的是do_fork()
,该函数做的事情较多:
从slab分配器中分配一个task_struct实例
分配创建内核栈,并拷贝父进程内核栈,设置thread_info,特别的,父进程在陷入内核前,保存了的上下文也会被子进程进行拷贝,也就是说,如果系统调用结束,父进程和子进程返回用户 态时,返回的位置,以及执行的指令是一样的,有过多进程编程经验的朋友知道,父进程调用fork()创建子进程后,子进程和父进程的返回值不一样,但是都是从fork()函数位置返回的,这就是为什么看起来好像"一个函数可以返回两次一样",其实到了这里,fork()函数已经是两个进程中的函数了,不存在一个函数返回两次的问题。
copy_creds,拷贝父进程的权限
设置进程运行统计信息
sched_fork,设置进程调度相关信息,如将状态设置为TASK_NEW
复制父进程打开文件的信息
复制父进程文件目录信息
复制父进程信号相关信息
copy_mm 复制父进程内存管理信息
配置PID
建立进程间的亲缘关系
将上述task_struct进入到进程的双向链表中
(我们从上述过程可以看出,子进程的信息绝大部分拷贝自父进程,这样做实际上为了提高性能,因为task_struct中记录进程所有的信息,如果一个一个创建和分配的话势必会拉低整个系统效率,Linux惯用做法是COW)
用户态调用库函数execevp()
方法
发生系统调用,陷入内核,查找系统调用表,调用sys_execevp()
方法
sys_execevp()
中会调用load_elf_binary()
方法,该过程主要分为以下几个过程:
设置mmap_base的值
设置函数栈的 vm_area_struct
将ELF文件中的代码部分映射到内存中
设置堆的brk以及堆 vm_area_struct
将依赖的so映射到内存中的内存映射区域
设置mm_struct其他属性,如end_code,start_code,start_data,end_data
等
上面说到,子进程的虚拟地址空间完全和父进程的虚拟地址空间一样,都是映射到同一部分的物理内存上的。
fork()函数调用完成时,子进程和父进程共享同一份数据和指令。
当exec()系列函数调用后,替换掉子进程的代码和数据后,子进程就有了自己的一份代码,如下图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。