fork是复制进程,那么首先要清楚进程是什么?
进程是一个正在运行的程序,是资源分配的最小单位,系统管理进程是依靠对进程控制块(PCB)的管理完成的,每个进程的产生分两步,一是:分配PCB,二是准备进程实体,如分配内存空间等。
fork() 创建进程,1、fork()调用一次,返回2次,子进程的返回值是0,父进程的返回值是新子进程的进程ID。 2、文件共享 在fork之前父进程打开的文件子进程才能使用,一个进程打开的文件描述符是在PCB中记录的,父进程调用fork()创建子进程的过程中,子进程的PCB是拷贝父进程的PCB,父进程的所有打开的文件描述符都被复制到子进程中。父子进程每个相同的打开描述符共享一个文件表项。文件描述符的引用计数count+1,不仅如此,父进程的用户根目录、当前工作目录等变量的引用计数均+1
pthread_creat()创建线程
vfork()创建一个新进程,子进程去调用exec,并不将父进程的地址空间完全复制到子进程中去,因为子进程会立即调用exec(或者exit),于是就不会访问该地址空间并保证子进程先运行,直到子进程调用exec或者exit子后,父进程才会运行。
fork() ,pthread_creat(), vfork()的系统调用分别是sys_fork(),sys_clone(), sys_vfork(),它们的底层都用的是do_fork(),只是传的参数,和标志不同
我们对sys_fork()的do_fork()转到定义:
do_fork():1、定义PCB指针struct task_struct *p; 2、分配PID,cat /proc/sys/kernel/pid_max命令可以查看一个系统支持的最大进程数,进程数的范围0~32768,理论值。 3、调用copy_process方法,创建子进程的task_struct.
copy_process(),完成创建子进程的PCB
1、分配PCB,继承父进程的PCB中的值,只是将特有的信息改过来。每个进程都有task_thread,thread_info结构体保存的是进程上下文的信息。要修改thread_info *info,子进程的task_struct的成员struct thread_info *info指向自己的struct thread_info,而且struct thread_info结构体的成员struct task_struct *p指向子进程自己的struct task_struct.即下面代码用红色标记的部分。
2、其中copy_files()复制父进程打开的文件描述符
3、其中copy_mm()复制地址空间,struct mm_struct *mm,*active_mm, mm表示:进程所拥有的内存空间的描述符,对于内核线程的mm为NULL,active_mm表示:进程运行时所使用的进程描述符。
1、判断是否设置了CLONE_VM标志,如果设置,创建线程,新线程共享父进程的地址空间,将mm_users加1,然后mm=oldmm,把父进程的mm_struct指针赋给子进程的mm_struct.
如果没有设置,当前进程分配一个新的内存描述符,mm=allocate_mm(), 将它的地址放在子进程的mm中。再把父进程(*oldmm)的内容拷进(*mm)中
2、dup_mmap(mm, oldmm)
复制线性区和页表,设置mm的一些属性,改变父进程的私有,可写的页为只读的,以使写时拷贝技术生效。
4、除此之外,我们还要复制父进程的内核栈,copy_thread()
调用copy_thread,用发出clone系统调用时CPU寄存器的值(它们保存在父进程的内核栈中)
来初始化子进程的内核栈。不过,copy_thread把eax寄存器对应字段的值(这是fork和clone系统调用在子进程中的返回值)
强行置为0。子进程描述符的thread.esp字段初始化为子进程内核栈的基地址。ret_from_fork的地址存放在thread.eip中。
如果父进程使用IO权限位图。则子进程获取该位图的一个拷贝。
最后,如果CLONE_SETTLS标志被置位,则子进程获取由CLONE系统调用的参数tls指向的用户态数据结构所表示的TLS段。
这就是为什么父子进程沿着统一位置执行,以及子进程的返回值是0。