进程

进程

在 Linux 系统中,每一个进程都有自己的 ID,就如同人的身份证一样。linux 中有一个数据类 pid_t。该数据用来定义进程的 ID。其实就是一个非负的整数。进程的状态:运行态,等待态,结束态,就绪,挂起和僵尸状态。进程就是在这几个状态间来回切换。

进程创建

fork

首先来看下如何创建新的进程,这里需要用到 fork 函数。使用 fork 函数需要用到 <sys/types.h><unistd.h> 头文件,函数的返回类型为 pid_t。表示一个非负的整数。

void get_pid()
{
pid_t pid;
if ((pid=fork())<0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the child process!\n");
}
else
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the parent process!\n");
}
exit(0);
}
// the pid is 3738in the parent process!
// the pid is 0in the child process!

从结果可以看到返回了两条打印,分别是父进程和子进程。原因在于调用 fork 函数后,就会出现分叉。在子进程中 fork 函数返回 0, 在父进程中,fork 函数返回子进程的 ID。也就是 fork 函数会复制一个进程给子进程。为什么 pid 的值在父子进程中返回的值不一样呢。原因在于父子进程相当与链表,进程形成了链表,父亲进程的 pid 意味这指向子进程的 id,但是子进程没有 id,所以其 pid=0。

资源复制

在开发过程中,可以根据返回值的不同,对父进程和子进程执行不同的代码。我们来把代码变动下来看下父进程和子进程的关系。增加了一个全局变量 gvar 和局部变量 var。

int gvar=2;
void get_pid()
{
pid_t pid;
int var=5;
if ((pid=fork())<0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
gvar--;
var++;
printf("the pid is %d, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the child process!\n");
}
else
{
printf("the pid is %d, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the parent process!\n");
}
exit(0);
}

运行结果,在子进程中,对 gvar 和 var 分别进行减一和加一的操作。但是在父进程中,gvar 和 var 仍然是原值且并没有被改动。原因在于 fork 函数胡复制父进程的所有资源,包括内存等。因此父子进程属于不同的内存空间。当修改的时候自然没有同步。

// the pid is 4694, gvar=2,var=5 in the parent process!
// the pid is 0, gvar=1,var=6 in the child process!

vfork

除了 fork 外,还有一个 vfork 函数,和 fork 一样都是系统调用函数。两者的区别在于 vfork 在创建子进程的时候不会复制父进程的所有资源,父子进程共享地址空间。因此子进程中修改的所有变量在父进程也会被修改,因为同属一个地址空间。

int get_pid__vfork()
{
pid_t pid;
int var =5;
printf("process id:%d\n",getpid());
printf("gvar=%d var=%d\n",gvar,var);
if((pid=vfork())<0)
{
printf("error");
return 1;
}
else if(pid == 0)
{
gvar--;
var++;
printf("The child process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
exit(0);
}
else
{
printf("the pareent process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
return 0;
}
}

执行结果:

process id:4821
gvar=2 var=5
The child process id:4822
gvar=1 var=6
the pareent process id:4821
gvar=1 var=6

脚本执行

在 fork 和 vfork 中,子进程和父进程都是运行同样的代码。那么如果想让子进程执行不同的操作。就需要用到 execv 函数。在 test1.c 中的代码:

void main(int argc,char* argv[]) {
execve("test2",argv,environ);
}

test2.c 中的代码:

int main() {
puts("welcome!");
return 0;
}

首先编译这 2 个文件,得到两个可执行的文件 test1 和 test2。然后运行./test1. 得到的结果如下:test1.c 中执行了 test2.c 的代码。

进程等待

进程等待就是为了同步父进程和子进程。等待调用 wait 函数,wait 函数的工作原理是首先判断子进程是否存在,如果创建失败,子进程不存在,则会直接退出进程。并且提示相关错误信息。如果创建成功,wait 函数会将父进程挂起,直到子进程结束,并且返回结束时的状态和最后结束的子进程 PID。来看下具体的代码实现。

oid exit_s(int status)
{
if(WIFEXITED(status)){
printf("normal exist,status=%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("signal exit!status=%d\n",WTERMSIG(status));
}
}
void wait_function_test()
{
pid_t pid,pid1;
int status;
int ret;
int pr;
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
printf("the pid of child is %d\n",getpid());
printf("The child process\n");
sleep(3);
exit(2);
}
else
{
printf("the pid of parent is %d\n",getpid());
printf("I am waiting for child process to exit\n");
pr=wait(&status);
if (pr > 0){
printf("I catched a child process with pid of %d\n",pr);
}
exit_s(status);
}
}

在子进程中调用 sleep(3) 睡眠 3 秒钟。只有子进程从睡眠中苏醒过来,才能正常退出并被父进程捕捉到。在此期间父进程会继续等待下去。在 wait 函数中会将子进程的状态保存在 status 中。在 exit_s 中调用了几个宏:

  • WIFEXITED:当子进程正常退出时,返回真值

  • WEXITSTATUS:返回子进程正常退出时的状态。只有当 WIFEXITED 为真值的时候。

  • WTERMSIG:用于子进程被信号终止的情况。返回此信号类型