1、调用dup_task_struct()为新进程创建一个内核栈,它的定义在kernel/fork.c文件中。该函数调用copy_process()函
数。然后让进程开始运行。从函数的名字dup就可知,此时,子进程和父进程的描述符是完全相同的。
2、检查这个新创建的的子进程后,当前用户所拥有的进程数目没有超过给他分配的资源的限制。
3、现在,子进程开始使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。
4、接下来,子进程的状态被设置为TASK_UNINTERRUPTIBLE以保证它不会投入运行。
5、调用copy_flags()以更新task_struct的flags成员,表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec函数的PF_FORKNOEXEC标志。
6、调用get_pid()为新进程获取一个有效的PID.
7、根据传递给clone()的参数标志,拷贝或共享打开的文件,文件系统信息,信号处理函数。进程地址空间和命名空间等。一般情况下,这些资源会被给定进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里.
8、让父进程和子进程平分剩余的时间片
9、最后,作扫尾工作并返回一个指向子进程的指针。
经过上面的操作,再回到do_fork()函数,如果copy_process()函数成功返回。新创建的子进程被唤醒并让其投入运行。内核有意选择子进程先运行。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销。如果父进程首先执行的话,有可能会开始向地址空间写入。
说完了fork,接下来说说他的兄弟---vfork(),兄弟就是兄弟,这像!两者功能相同,不同点在于vfork()不拷贝父进程的页表项。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec(),子进程不能向地址空间写入。按照刚才的方法,分析一下vfork(),它是通过向clone()系统调用传递一个特殊标志来进行的,过程如下:
1、在调用copy_process时,task_struct的vfor_done成员被设置为NULL
2、在执行do_fork()时,如果给定特别标志,则vfork_done会指向一个特殊地址。
3、子进程开始执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过vfork_done指针向它发送信号。
4、在调用mm_release()时,该函数用于进程退出内存地址空间,如果vfork_done不为空,会向父进程发送信号。
5、回到do_fork(),父进程醒来并返回。
上面步骤的顺利完成就意味着父子进程将会在各自的地址空间里运行。说句真的,通过研究发现这样的开销是降低了,但技术上不算咋优良。
如果说进程是80年代早上初升的太阳, 那不得不说的线程就是当前正午的烈日。线程机制提供了在同一程序内共享内存地址空间运行的一组线程。线程机制支持并发程序设计技术,可以共享打开的文件和其他资源。如果你的系统是多核心的,那多线程技术可保证系统的真正并行。然而,有一件令人奇怪的事情,在linux中,并没有线程这个概念,linux中所有的线程都当作进程来处理,换句话说就是在内核中并没有什么特殊的结构和算法来表示线程。那么,说了这多,到底在linux中啥是线程,我们说在linux中,线程仅仅是一个使用共享资源的进程。每个线程都拥有一个隶属于自己的task_struct.所以说线程本质上还是进程,只不过该进程可以和其他一些进程共享某些资源信息。
这样一说,后面就明白了也好解决了,两者既然属于同一类,那创建的方式也是一样的,但总要有不同啊,这个不同咋体现呢,这个好办,我们在调用clone()的时候传递一些参数标志来指明需要共享的资源就可以了:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);这段代码产生的结果和调用fork()差不多,只是父子俩共享地址空间,文件系统资源,文件描述符和信号处理程序。换个说法就是这里的父进程和子进程都叫做线程。也就是说clone()的参数决定了clone的行为,具体有哪些参数,我是个懒人,也不想说了。