操作系统 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()
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的利用率。