浅析fork系统调用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gc348342215/article/details/70225107
转载前注明出处,欢迎转载分享
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 三种情况来判断进程执行情况。
 C Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include< sys/types.h >
#include< unistd.h >

int main()
{
    pid_t pid;
    printf(
"start fork\n");
    pid = fork(); 
//调用fork函数创建子进程

    
if ( pid < 0 )
        printf(
"error in fork");
    
if ( pid == 0 )
        printf(
"I am child process . my id is %d \n", getpid() );//getpid函数返回的是当前进程的进程码
    if ( pid > 0)
        printf(
"I am parent process. my id is %d\n", getpid() );
    printf( 
"a process exit\n." );
    
return 0;
}

该函数运行的结果:
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_tidptrchild_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  CSIGNAL             0x000000ff      
#define  CLONE_VM            0x00000100      
#define  CLONE_FS           0x00000200      
#define  CLONE_FILES       0x00000400      
#define  CLONE_SIGHAND  0x00000800      
#define  CLONE_PTRACE      0x00002000      
#define  CLONE_VFORK       0x00004000      
#define  CLONE_PARENT     0x00008000      
#define  CLONE_THREAD     0x00010000      
#define  CLONE_NEWNS     0x00020000      
#define  CLONE_SYSVSEM  0x00040000      
#define  CLONE_SETTLS       0x00080000      
#define  CLONE_PARENT_SETTID      0x00100000      
#define  CLONE_CHILD_CLEARTID    0x00200000      
#define  CLONE_DETACHED              0x00400000      
#define  CLONE_UNTRACED             0x00800000      
#define  CLONE_CHILD_SETTID        0x01000000      
#define  CLONE_STOPPED             0x02000000      
#define  CLONE_NEWUTS                0x04000000      
#define  CLONE_NEWIPC                 0x08000000      
#define  CLONE_NEWUSER              0x10000000      
#define  CLONE_NEWPID                 0x20000000      
#define  CLONE_NEWNET                0x40000000      
#define  CLONE_IO                          0x80000000      

问题:
子进程创建完成是什么时候被分配PID的和什么时候被放入调度队列的也就是什么时候被唤醒的?

答案:
什么时候分配PID?在copy_process函数执行过程中即可看到PID的分配。什么时候放入进程调度队列中的?首先想想,当调用copy_process函数,调用期间会执行让进程处于不可中断状态(task_uninterruptible),所以不会在copy_process期间就使得进程加入调度队列,当copy_process函数执行完毕,进程处于task_running状态,才会将进程PCB块加入到CPU可执行队列中,这个时候唤醒的进程有机会被调度。

参考资料:


猜你喜欢

转载自blog.csdn.net/gc348342215/article/details/70225107