进程的定义: 进程的经典定义就是一个执行中程序的实例,是计算机科学中最深刻、最成功的概念之一。

假象: 在现代系统上运行一个程序时,我们会得到一个假象,就好像我们的程序是系统当中运行的唯一程序一样。我们的程序好像独占的使用处理器和内存。处理器就好像是无间断地一条接一条的执行我们程序中的指令。最后,我们程序中的数据和代码好像是系统中内存的唯一对象。然而,这些都是假象,都是进程带给我们的。

真相: 关键在于进程是轮流使用处理器的。每个进程执行它的流一部分,然后被抢占(暂时挂起),然后轮到其他进程。对于一个运行在这些进程之一的上下文程序,它看上去像是在独占的使用处理器。即多个程序同时运行时,并不是某个程序从开始到结束连续的进行,而是某个程序执行一部分指令后,先暂时挂起,轮到另一个程序执行一部分,依此类推。

父进程与子进程: 父进程是通过fork函数创建一个新的子进程。

新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。

fork函数是有趣的,因为它只调用一次,却返回两次: 一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork返回子进程的PID。在子进程中,fork返回0.因为子进程的PID总是为非零,返回值就提供一个明确的方法来分辨程序是在父进程中执行还是在子进程中执行。

下面用两段程序代码做例子说明:

#include "csapp.h"

/* $begin fork */
/* $begin wasidefork */
int main(int argc, char *argv[])
{
    pid_t pid;
    int x = 1;

    pid = fork(); //line:ecf:forkreturn
    if (pid == 0) {  /* Child */
	printf("child : x=%d\n", ++x); //line:ecf:childprint
	fflush(stdout);
	return 0;
    }

    /* Parent */
    printf("parent: x=%d\n", --x); //line:ecf:parentprint
    fflush(stdout);
    return 0;
}/*fork.c*/
/* $end fork */
/* $end wasidefork */

先输入以下指令得到原fork.c对应编译后的的.o文件,即test.o。

java 子进程执行 进程 子进程_fork函数


再输入以下指令,执行该目标文件:

java 子进程执行 进程 子进程_fork函数_02


得到以下结果:

java 子进程执行 进程 子进程_fork函数_03


其中先输出parent=0,后输出child=0。

解析: 是因为指令执行到fork函数时,创建了一个子进程;
父进程优先执行(父进程中,fork返回子进程PID不为零),故执行-1操作,则优先输出parent=0;
后执行子进程(子进程中,fork返回PID为零),故执行if内语句进行+1操作,则后输出child=2。

具体流程图如下:

java 子进程执行 进程 子进程_父进程_04


即所谓一次调用,两次返回

另一个例子代码如下:

#include "csapp.h"

int main(){
    int x=1;
    if(fork()==0)  
        printf("p1: x=%d\n,++x");
    printf("p2: x=%d\n",--x);
    exit(0);
}

输入以下指令得到原文件对应的.o文件

java 子进程执行 进程 子进程_父进程_05


输入以下指令执行该目标文件

java 子进程执行 进程 子进程_父进程_06


即可得到以下结果:

java 子进程执行 进程 子进程_父进程_07


解析: 程序先执行了父进程,即-1操作,则先输出p2:x=0;

后进行子进程,即+1操作,后输出p1:x=2;

执行完if语句后,程序继续执行printf语句,即-1操作,最后输出p2:x=1.

一个进程,两个返回。具体流程图如下:

java 子进程执行 进程 子进程_fork函数_08