转载前注明出处,欢迎转载分享
fork函数
Linux下创建新进程的系统调用是fork,其定义如下:
#include < sys/types.h >
#include < unistd.h >
pid_t fork ( void ) ;
该函数
每次调用返回两次
,其中:
在父进程中返回的是子进程的 PID
在子进程中返回值是0
fork函数调用失败则返回-1
于是我们可以用pid = fork() 创建子进程,通过 pid < 0,pid == 0,pid > 0 三种情况来判断进程执行情况。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include<
#include< int { } |
该函数运行的结果:
start fork
I am child process. my id is 8099
a process exit
I am parent process. my id is 8098
a process exit
--------------------------------------------------------------------------------------------------------------
fork函数执行后则创建了一个新的进程,这个新进程为原有进程( main 函数 )的子进程。执行的子进程返回值为0,所以执行pid == 0这条语句,getpid获取子进程的进程码为8099,之后执行父进程的时候fork返回的值为子进程的进程码,所以pid == 8099 则执行 pid > 0这条语句,getpid获取该进程( 父进程 )的进程码,即为8098.
以上是fork函数的基本知识。
详细见
参考资料:
=================================================================
fork的系统调用实现:
父进程调用fork,因为这是一个系统调用,会导致int软中断,进入内核空间;
内核根据系统调用号,
调用sys_fork系统调用,而sys_fork系统调用则是通过clone系统调用实现的,
会调用clone系统调用。
clone函数中会调用do_fork函数。
do_fork函数:
1.创建进程描述符指针struct task_struct *p;
2.p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace);
调用copy_process为子进程复制出一份进程信息,并对相应的标志位等进行设置。
3.最后,copy_process 函数做了一些清理工作,返回一个指向新建的子进程的指针给 do_fork 函数。回到do_fork函数中,如果 copy_process 函数执行成功,没有错误,那么将会唤醒新创建的子进程,让子进程运行。
自此,fork 函数调用成功执行。
-------------------------------------------------------------------------------------------------------------
copy_process函数参数说明:
- stack_start表示的是用户状态下栈的起始地址。
- regs是一个指向寄存器集合的指针,在其中保存了调用的参数。
- stack_size是用户态下的栈大小,一般是不必要的,设置为0。
- parent_tidptr和child_tidptr则分别是指向用户态下父进程和子进程的TID的指针。
补充:TID(thread id)可以理解为线程的ID。PID即为进程的ID。
-------------------------------------------------------------------------------------------------------------
copy_process()的实现:
第一步:p = dup_task_struct(current),其中dup_task_struct 函数将会为新进程创建一个内核栈内存、thread_iofo和task_struct内存,这里完全
copy父进程的内容,所以到目前为止,
父进程和子进程是没有任何区别的,可以说
此时父子进程的进程描述符是一致的。current 实际上是一个获取当前进程描述符的宏定义函数,返回当前调用系统调用的进程描述符,也就是父进程。
第二步:检查所有的进程数目是否已经超出了系统规定的最大进程数,如果没有的话,那么就开始设置进程描述符中的初始值, 从这开始,父进程和子进程就开始区别开了。
第三步:设置子进程的状态为 TASK_UNINTERRUPTIBLE(不可中断睡眠),从而 保证这个进程现在不能被投入运行,因为还有很多的标志位、数据等没有被设置。
第四步: 调用copy_flags函数更新task_struct结构中flags成员。 copy_flags(clone_flags, p);复制标志位(clone_flags)以及权限位(PE_SUPERPRIV(表示进程使用了超级用户权限))和其他的一些标志, 根据 clone_flags 集合中的值,共享或者复制父进程打开的文件,文件系统信息,信号处理函数,进程地址空间,命名空间等资源。这些资源通常情况下在一个进程内的多个线程才会共享,对于我们现在分析的 fork 系统调用来说,对于这些资源都会复制一份到子进程。
第五步:因为在 do_fork 函数中调用 copy_process 函数的时候, 参数pid的值为NULL,所以此时新建进程的 PID 其实还没有被分配。所以接下来的就是要给子进程分配一个PID,调用alloc_pid() 给子进程获取一个有效的并且是唯一的进程标识符PID。
第六步:
调用
sched_fork(p, clone_flags);
父子进程平分父进程剩余的时间片。
第七步:return p;
返回一个指向子进程的指针。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
*clone_flags:
* #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define |
问题:
子进程创建完成是什么时候被分配PID的和什么时候被放入调度队列的也就是什么时候被唤醒的?
答案:
什么时候分配PID?在copy_process函数执行过程中即可看到PID的分配。什么时候放入进程调度队列中的?首先想想,当调用copy_process函数,调用期间会执行让进程处于不可中断状态(task_uninterruptible),所以不会在copy_process期间就使得进程加入调度队列,当copy_process函数执行完毕,进程处于task_running状态,才会将进程PCB块加入到CPU可执行队列中,这个时候唤醒的进程有机会被调度。
参考资料: