操作系统 fork与exec

前言

学习操作系统,首先便要学到process概念。process是什么?Process – a program in execution。七十年代UNIX最先提出多进程的构想,之后该构想便广泛用于linux与unix操作系统中。fork()函数给程序猿们提供了简便的多进程编程方式。

fork() creates new process

exec() used after a fork to replace the process’ memory space with a new program

下面就来看看fork()函数和exec()究竟干了什么。

操作系统 fork与exec

fork()

fork() 创建一个新的进程,该进程与主进程为父子关系。

Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。

"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。  

fork() 创建的子进程完全拷贝主进程,所以子进程的运行完全与主进程相同,且与主进程并行处理,竞争CPU的资源。

不同的是,主进程与子进程的fork()返回值不同,子进程中fork()的返回值是0,而主进程的fork()返回值是产生的子进程的进程id。

举个栗子:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
    int i;
    pid_t pid;

    //for another process
    pid  = fork();
    
    if(pid < 0){ //error
        printf("Fork failed");
        return 1;
    }

    else if(pid == 0){
        for(i = 0; i < 1000; i++){
            printf("This is a child\n");
        }
    }
    else{
        for(i = 0; i < 1000; i++){
            printf("process\n");
        }
    }
    return 0;
}

这样运行时,就会交替输出"This is a child"  与 "process", 可以看出子进程与主进程的并行处理。

这样的竞争状况是我们不希望看到的,所以下面修改一下

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
    int i;
    pid_t pid;

    //for another process
    pid  = fork();
    
    if(pid < 0){ //error
        printf("Fork failed");
        return 1;
    }

    else if(pid == 0){
        printf("This is a child\n");
    }
    else{
        wait(NULL);
        printf("Child complete\n");
    }
    return 0;
}

这样主进程会等到子进程运行完了再运行。

exec()

fork产生一个进程,该进程如果只是运行主进程的程序,那如果我们希望这个进程去干其他的活动,比如所我们在打字的时候同时听着歌,怎么办呢?这里就用到了exec()。

一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。)

来看个栗子

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
    int i;
    pid_t pid;

    //for another process
    pid = fork();
    
    if(pid < 0){ //error
        printf("Fork failed");
        return 1;
    }

    else if(pid == 0){ //run program.c
        execl("./program", "program", NULL);
    }
    else{
        wait(NULL);
        printf("Child complete\n");
    }
    return 0;
}

其中program.c

#include <stdio.h>
#include <ctype.h>

int main(){
    int i;
    for(i = 0; i < 10; i++){
        printf("A new program\n");
    }
    return 0;
}

这里子进程就是运行的program.c。

总结

在传统的Unix、linux环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。这些函数给程序猿的多进程编程提供了遍历,也增加了CPU的利用率。