linux中的fork

         最近在读Robert love的《Linux Kernel Development》这本书,这本书对于整个linux kernel各个核心子系统进行整体的介绍,第三章提到了linux fork函数的原理和处理过程,但是没有进行详细的阐述和分析。后来在网上查看了不少的帖子和文章对于整体有了一个完整的了解,好记性不如烂笔头,为了加深自己的记忆和方便以后查找,就将自己的一些理解整理出来。

一、进程(process)的概念

       首先我们需要了解linu进程(process)的定义,在 The Linux Programming Interface 中定义为  A process is an instance  of an executing program.进程是正在执行的程序。但是进程不单单是我们在编辑工具中敲进去的代码,常常包含在运行期间其他的资源,包括执行期间相关联的数据(变量,内存空间,缓冲区间)以及进程执行的上下文(process context)。

       其中所谓进程上下文就是进程执行的环境,包括当一个进程执行时CPU所有寄存器中的值,进程的状态以及堆栈中的内容。

二、linux操作系统对于进程的管理

       现代操作系统举例,由于虚拟内存和虚拟处理器的机制,会给用户产生同时可以执行多个任务,也就是执行多个进程,那就存在进程的管理和调度。linux中通过Sched.h中定义的task_struct进行管理的。每个进程都有一个自己的进程表项,每个进程表都详细描述了该进程的状态(代码段地址,堆栈地址,文件句柄等等),kernel负责维护整个系统的列表task_list。

       由于多任务的操作、中断处理往往需要进行进程的调度,其中一些进程需要让出CPU资源,而另外一些进程将占用CPU资源进行工作。当CPU需要切换到另外一个进程的时候,就需要保存当前执行进程的所有状态,也就是进程的上下文,这样就方便当下一次执行该进程的时候CPU知道从何处或者何种状态开始。我们称为上下文的切换。类似与函数的调用,我们需要保存函数调用的现场,以便函数返回时恢复现场。

三、linux fork的工作原理

        在linux系统中,用户通过调用系统方法fork创建新的进程,其中调用fork的进程成为父进程,新产生的进程为子进程。

     1、fork函数原型:

        #include<unistd.h>  

          pid_d fork(void);  //返回值:如果成功调用该函数返回两个值,子进程返回0,父进程返回子进程的PID;如果调用失败则返回-1;

         fork函数调用一次分别给父进程和子进程返回两次值。这个和我们常见的调用一次返回一次的函数调用不同,在刚开始接触linux的时候对于这个概念的理解仅仅是在于对函数功能,并不了解fork的整个过程和工作原理。

      2、fork的工作原理:

       刚才提到linux中每一个进程都有一个自己的进程表项task_struct,其中记录的该进程的一些状态和数据。那调用fork之后子进程的进程表项中的数据项到底是如何赋值的。有兴趣的可以下载kernel的source code看一下/kernel/目录中fork.c文件中copy_process函数。

     调用fork的时候linux系统会创建一个新的进程,并且会为这个进程创建一个新的进程表项,该新子进程的和原父进程表项中大部分数据项是相同的,因为子进程copy了父进程的大部分上下文,特别是程序计数器Program counter的值是相同的的,用来指出计算机正在执行的指令。因此父进程和子进程的上下文中都记录了当前执行的位置,也就是执行fork之后就返回。由于子进程和父进程并没有同时占有CPU,当前CPU中的PC寄存器中保存的是父进程进程执行的值,而子进程的PC的值作为上下文保存在进程表项中。父进程继续执行,返回刚刚创建的新的子进程的PID。

      而在之后的某个时间由于进程的调度,刚创建的子进程被调度,子进程的上下文别切换到CPU中。其中的PC只出了当前进程执行的位置,并且子进程和父进程的PC一致,在执行完fork之后返回了0。

      其实调用fork之后的两次返回,就是独立的两个进程执行了同一段code,返回了两个不同的数据。这也就是为什么fork被称之为fork的原因,从同一地方开始,分叉为两个不同的进程。

四、实例分析

      从一个简单的例子来看一下fork的过程:

#include<unistd.h>
#include<stdio.h>

int main(void)
{
	int number = 12;
        pid_t pid = 0;
        printf("This is a test code\n");
        printf("start test code");// 没有换行符

        pid = fork();
        if(pid < 0)
            printf("creat error\n");
        else if(pid == 0)
        {
	  printf("    child process, pid=%d\n",getpid());
          number ++;
        }
        else if( pid > 0)
	{
 	  printf("   parent process,pid=%d\n",getpid());
	}
	printf("The number is %d\n",number);
      return 0;
}


执行的结果:

This is a test code
start test code   parent process,pid=5723
The number is 12
start test code   child process, pid=5724
The number is 13

从执行的结果来看成功创建了子进程,其中有两点需要进一步的分析:

1、缓冲区中的内容:

上面code中“start test code”这句打印分别在父进程和子进程中都输出了。由于print函数带有缓冲机制,它会将需要输出的内容放到标准输出缓冲中,只有当遇到"\n"或者缓冲区满的时候,系统才会将其中的内容输出。由于“start test code”没有"\n"没有即时输出,当遇到后续执行printf的时候因为有"\n"所以才输出。为什么子进程也会输出呢?调用fork函数创建子进程的时候,子进程copy了父进程大部分的进程表项,其中包括了缓冲区。所以在子进程中也输出了“start test code”。

2、变量的内容:

从输出的log信息中,我们可以看到父子进程的中number变量的数值是不相同的,虽然子进程在创建是copy了父进程的上下文信息,但他们却拥有不同的内存空间,是两个相互独立的进程。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值