Introduction
本文主要讨论在UNIX系统如何通过一系列系统调用函数创建与控制进程,创建进程时用到的系统调用函数主要有:fork()、exec()、wait()等,控制进程用到的系统调用函数主要有:kill()等。下面结合代码详细介绍上述系统调用函数功能。
一、创建进程
fork()可用于创建一个新的进程,更确切的说,它主要是用于在父进程中创建一个子进程,当调用此函数时,操作系统会为子进程分配其自己的地址空间、寄存器、PC等,子进程运行的代码是父进程代码的复制,但是执行不是从main函数开始,而是从fork()被调用的那行代码开始。fork()的返回值在父进程和子进程中也不一样,父进程中fork()的返回值为子进程的PID,而子进程中fork()的返回值为0. 当调用fork()函数时,因为CPU调度器的复杂性,父进程和子进程的执行顺序其实是不确定的,此时可以使用wait()函数来控制两个进程的执行,使其执行顺序由不确定变为确定。调用wait()函数可以使执行延迟,直到子进程完成执行。 除此之外,上面有提到fork()创建的子进程执行的代码是父进程的复制,如果要运行另一个程序可以调用exec()函数,当进程调用exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的main函数开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。使用上述fork()、wait()、exec()函数创建进程的实例代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
printf("Hello world! (pid: %d)\n",(int) getpid());
int rc = fork();
if (rc<0) {
printf("fork failed!");
} else if (rc==0){
printf("I'm child! (pid: %d)\n",(int) getpid());
char *myargs[4];
myargs[0] = strdup("./add_test");
myargs[1] = strdup("1");
myargs[2] = strdup("2");
myargs[3] = NULL;
execvp(myargs[0],myargs);
printf("this shouldn't print out.\n");
} else {
int rc_wait = wait(NULL);
printf("I'm father of %d (pid: %d)\n",rc,(int) getpid());
}
}
上述代码首先调用fork()创建子进程,在父进程分支中调用wait()控制执行顺序,等待子进程执行完后再执行父进程分支内容,子进程中调用了execvp()来执行另一个程序内容,execvp()是exec()函数的其中一个变种,详细解释可见exec函数族。上述子进程执行的程序add_test实现的功能为将终端输入的两个数相加并将结果打印出来,具体代码为:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
int a,b,c;
a = atoi(argv[1]);
b = atoi(argv[2]);
c = a+b;
printf("the add result is: %d+%d=%d\n",a,b,c);
return 0;
}
将进程的创建分离为fork()和exec()函数,可以在fork()和exec()之间执行一些代码,这些代码可以改变即将运行的程序环境,从而可以轻松构建各种有趣的功能。shell主要就是调用fork、exec()和wait()实现它的功能;当你在shell里敲下命令的那一刻,shell首先在文件系统中搜索可执行文件的位置,调用fork()函数创建子进程,然后调用exec()函数的变种执行命令,同时调用wait()等待子进程完成运行,当子进程结束运行,shell返回打印prompt等待下一个命令。
二、控制进程
在大多数UNIX shell中,不同的按键组合被设置成会向当前正在运行的进程传递特定的信号,比如说control-c发送SIGINT信号,control-z发送SIGTSTP信号。这个功能由信号子系统实现,信号子系统提供了丰富的基础设施向进程传递额外的事件,包括用单独的进程来接收和处理这些信号,以及发送信号给特定进程或者整个进程组的方式。对于进程来说,会使用signal()系统调用函数去捕获各种信号,这可以保证当给某个进程传递特定信号时,它会停止执行并运行一些特定的代码以响应这个信号。除此之外,还有专门的系统调用函数可以用来终止程序的运行:kill()。
虽然fork()函数十分常用,但这也不意味着它没有问题,还有一个也可用于创建进程的函数是spawn(),使用时可以对比一下哪一个更为适用
|