1.fork 创建进程
函数原型为pid_t fork(void). 函数返回类型为pid_t实质为int 类型。
fork函数会生成一个新进程,该进程为子进程,调用fork函数的进程为父进程。fork函数调用一次,返回两次。若调用成功,子进程返回0,父进程返回子进程的pid.失败返回-1.fork在生成子进程时用到了写时拷贝技术;不执行一个父进程数据段、堆和栈的完全复制,因为fork之后父子进程共享这些区域,内核将他们的权限设为只读的。如果父子进程中任何一个试图修改这些区域,内核就会将区域所在页拷贝出来。
- 写时拷贝技术:
在进行fork复制进程时,并没有马上将父进程空间完全拷贝。而是使用了写时拷贝技术。fork之后,让父进程和子进程共享父进程页面(父子进程共享数据、堆区数据),并将这些数据的权限设置为只读。当父进程或子进程中任意一个试图修改某个页面时,再将这个页面所在页拷贝出来给子进程,然后将拷贝的页和原来页的权限设置为读写。
优点:这样延缓页面拷贝,提供fork复制效率。通常linux中的新进程都是通过fork+exec实现的。若fork后需要执行exec那么就不需要拷贝。
fork的实际开销是 复制父进程的页表以及给子进程创建唯一 一个的进程描述符。
2.fork实现原理
linux通过clone()系统调用实现fork(),这个调用通过一系列参数标明父子进程共享的资源。fork\vfork可根据自己的参数标志调用clone(),然后在有clone()调用do_fork().do_fork()其实已经完成创建的大部分工作,它定义在头文件fork.c中,调用copy_process函数,让进程运行。
- 首先给进程分配进程描述符,同时为进程创建一个内核栈、task_struct,这些字与当前进程值相同;
- 系统会检查这个新建进程,即当前拥有的进程数目有没有超过给他分配的资源限制。而且子进程会将自己与父进程分开,进程描述符里许多成员被清0或设为初始值。(注:进程描述符不是继承来的,进程描述符中大多信息是共享的)
- 将子进程设置为不会被运行状态, copy_process()调用copy_flags更新task_struct成员,表明进程是否拥有超级用户权限的标志被清0。设置进程还没有调用exec函数的标志。然后调用get_pid()函数为新进程获取有效pid.
- 根据传递给clone()的参数标志,copy_process()复制进程实体(共享打开的文件、文件系统信息、信息处理函数、地址空间等)。用父进程在内核上的存在的信息初始化子进程的现场信息,并将eax置0.
- 将父进程时间片分给子进程一半,进程状态设置为就绪。
3.vfork
除了不拷贝父进程的页表外, 与fork功能相同。子进程作为一个单独的线程在他的虚拟地址空间运行,父进程被阻塞,直到子进程退出或执行exit().子进程不能像地址空间写入数据。可把vfork当成fork使用。vfork()系统调用的实现通过clone()系统调用传递一个特殊标志进行。
- 在调用copy_process()时,task_struct的vfor_done成员设置为NULL;
- 在执行do_fork(),如果给定特殊标志,vfork_done会指向一个特定地址;
- 子进程先执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过vfork_done指针向他发送信号;
- 在调用mm_release()时,该函数用于进程执行内存地址空间,并检查vfork_done是否为空,如果不为空,则向父进程发送信号。
- 回到do_fork,父进程醒来并返回。
4.vfork与fork的区别
- vfork和fork都是创建一个新进程,vfork是fork的封装得来的;
- 使用fork创建的子进程他完全复制子进程资源。这样的子进程的独立性高,具有良好的并发性。vfork创建子进程时,操作系统并没有将父进程资源完全复制给子进程,vfork创建的进程共享父进程的地址空间,子进程完全在父进程的地址空间上。子进程对父进程地址空间修改数据时父进程是可以看见的。
- fork创建一个子进程取决于哪个进程先运行,vfork必须保证子进程先运行,当子进程调用exit或exec时,在调用exit或exec之前进程才有可能被运行。若子进程的调度还要依赖父进程的某个操作,就会导致死锁。
- 因为fork要将父进程中的代码给完全复制,所以系统开销很大(但fork的开销并不是所以情况都需要的,fork之后调用exec函数,就不会需要写时拷贝技术)。vfork并没将父进程资源全部复制,所以系统开销相对fork要小。
- vfrok创建一个进程时,以return 0结束为标志,释放局部变量。调用exit(0)是不会释放的。
- vfork一个子进程,先执行子进程,子进程沿用父进程中的变量,当以exit(0)结束后,父进程可仍沿用子进程中的变量。当以return 0 结束则释放局部变量,父进程再引用时则会为系统给的随机值。
命令补充:
查看进程堆栈命令: pstack pid
查看共享内存:ipcs -m
查看内存: cat /proc/meminfo
删除共享内存:ipcrm -m shmid
查看消息队列:ipcs -q
查看信号量:ipcs -s
查看进程: ps