Linux进程创建、等待、终止以及vfork&return详解

本文详细介绍了进程控制的基础概念,包括进程创建、等待及终止的方法。通过具体代码示例展示了如何使用fork()和vfork()创建子进程,如何使用wait()和waitpid()避免僵尸进程的产生,以及不同方式下进程终止的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程控制:
进程创建:

利用fork()创建一个子进程:

#include<stdio.h>
#include<unistd.h>

int main(){
  pid_t id = fork();
  if(id < 0){ 
    //fork()失败
    perror("fork:");
  }else if(id == 0){ 
    //child
    printf("child pid:%d fork:%d\n",getpid(),id);
  }else{
    //father
    printf("father pid:%d fork:%d\n",getpid(),id);
  }
  return 0;
}

输出结果:

[DELL@localhost lesson2]$ ./process 
father pid:4511 fork:4512
child pid:4512 fork:0
[DELL@localhost lesson2]$ 
进程等待:

当子进程退出,父进程并没有查看子进程的退出状态时,便会产生“僵尸进程”,僵尸进程会造成系统资源泄露,危害很大,当然,我们一般创建一个子进程来让子进程完成一定的逻辑功能,当子进程完成逻辑功能返回后,父进程应该查看子进程的退出状态来得知子进程执行逻辑功能的成功与否,于是便有了进程等待的概念,我们一般是通过父进程调用wait/waitpid等待子进程调用完成后查看子进程的返回状态从而避免僵尸进程。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
//输出型参数status,由操作系统填充,获取子进程的退出状态,不关心可置为NULL
//status指向的int型空间可以看作位图,以下只研究低16位比特:

status

//当进程正常退出时,低8位为0,高8位表示退出状态,进程异常退出时,高8位未启用,低7位表示终止信号,第7位为core dump标志位。
//返回值,调用成功返回被等待进程pid,失败返回-1


pid_t waitpid(pid_t pid, int *status, int options);
//参数pid>0等待pid指定的子进程,若为-1等待任意一个进程,<-1等待进程组识别码为pid绝对值的任何子进程,==0等待进程组识别码与当前进程相同的任何子进程。
//参数status,与wait参数类似,此外,在<sys/wait.h>中为我们提供了WIFEXITED、WEXITSTATUS宏,其中WIFEXITED宏用于查看子进程是否正常退出,若正常退出WIFEXITED(status)为真,若WIFEXITED为真,WEXITSTATUS(status)返回子进程的退出码,若进程被信号杀死,可用WTERMSIG(status)可获得信号量。
//参数options,若传入WNOHANG,则pid指定的子进程没有退出,waitpid()函数不予等待返回0.若不想使用可以置为0
//返回值,等到进程时返回被等待子进程pid,若设置了WONHANG,则当要等待进程没有退出时返回0,若指定pid子进程不存在或调用出错时,返回-1,此时errno会被设置成相应的值。

代码:

//阻塞式等待
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>

int main(){

  pid_t creat_id = fork();
  if(creat_id < 0){
    perror("fork:");
    return -1;
  }else if(creat_id == 0){
    //child
    printf("child PID:%d is begin\n",getpid());
    sleep(10);
    printf("child is end\n");
  }else{
    //father
    int status;
    printf("father is begin wait...\n");
    pid_t wait_id = wait(&status);//阻塞式等待
    if(wait_id == -1){
      perror("wait:");
      return -1;
    }else if(wait_id == creat_id){
      printf("father is wait end\n");
      if(WIFEXITED(status)){
        printf("正常退出,退出码:%d\n",WEXITSTATUS(status));
      }else{
        printf("异常退出,退出码:%d\n",WTERMSIG(status));
      }
    }else{
        printf("等到的不是不是本次创建的子进程\n");
    }
  }
  return 0;
}



//非阻塞式等待
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>

int main(){
  pid_t creat_id = fork();
  if(creat_id < 0){
    perror("fork:");
    return -1;
  }else if(creat_id == 0){
    //child
    printf("child PID: %d is begin\n",getpid());
    sleep(20);
    printf("child is end\n");
  }else{
    //father
    int status;
    pid_t wait_id;
    do{
      wait_id = waitpid(-1,&status,WNOHANG);//非阻塞式等待
      if(wait_id == 0){
        printf("waiting...\n");
      }
      sleep(2);
    }while(wait_id == 0);
    if(wait_id == -1){
      perror("wait:");
      return -1;
    }else if(WIFEXITED(status)&&(wait_id == creat_id)){
      printf("正常退出 PID:%d,退出码:%d\n",wait_id,WEXITSTATUS(status));
    }else if(wait_id == creat_id){
      printf("异常退出,PID:%d,退出码:%d\n",wait_id,WTERMSIG(status));
    }else{
      printf("等到的进程不是本次创建的进程.\n");
    }
  }
  return 0;
}

结果:

阻塞式等待结果:
[DELL@localhost lesson2]$ ./process 
father is begin wait...
child PID:4208 is begin
child is end
father is wait end
正常退出,退出码:0
[DELL@localhost lesson2]$ 


非阻塞式等待结果:
[DELL@localhost lesson2]$ ./process 
waiting...
child PID: 7689 is begin
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
child is end
正常退出 PID:7689,退出码:0
进程终止:

进程的终止可以分为三种类型:
1)代码运行完毕,结果正确
2)代码运行完毕,结果不正确
3)代码异常终止
对于以上三种类型,常见的退出方法有以下两种:
正常终止(包括结果正常与不正常两种情况):
1、从main函数返回
2、调用exit函数
3、调用_exit函数
下面介绍_exit、exit函数:

#include <unistd.h>

void _exit(int status);
//_exit为系统调用,参数status定义了进程终止的状态,父进程可
以通过wait来获取该值。虽然status是int类型,但status仅有低八
位可以被父进程所用。


#include <stdlib.h>

void exit(int status);
//exit是库函数是_exit函数的封装,但在调用_exit函数之前还做>了以下工作:
//1)执行用户通过atexit/on_exit函数定义的清理函数
//2)关闭所有打开的流,所有缓存均被写入
//3)调用_exit函数

在命令行可以通过echo $?查看进程退出码。
return n <==> exit(n):因为调用main运行的函数会将main的返回>值当作exit的参数。
代码:

#include<stdio.h>

int main(){
  printf("hello world\n");

  return 0;
}

//输出
[DELL@localhost lesson2]$ ./process 
hello world
[DELL@localhost lesson2]$ echo $?
0
[DELL@localhost lesson2]$ 


#include<stdio.h>
#include<stdlib.h>

int main(){
  printf("hello world\n");
  exit(1);
}
//输出
[DELL@localhost lesson2]$ ./process 
hello world
[DELL@localhost lesson2]$ echo $?
1
[DELL@localhost lesson2]$ 


#include<stdio.h>
#include<unistd.h>

int main(){
  printf("hello world\n");
  _exit(2);
}

//输出
[DELL@localhost lesson2]$ ./process 
hello world
[DELL@localhost lesson2]$ echo $?
2
[DELL@localhost lesson2]$ 

vfork()后子进程return段错误详解:

我们先来看段代码,随后依照运行结果讲解vfork后子进程中return的问题。

<1>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main(){
  int ret = 200;
  pid_t id = vfork();
  if(id < 0){ 
    perror("vfork:");
    return -1; 
  }else if(id == 0){ 
    printf("child: ret = %d\n",ret);
    return 0;
    //exit(0);
  }else{
    printf("father;ret = %d\n",ret);
    return 0;
    //exit(0);
  }
}

结果:
[DELL@localhost lesson2]$ ./vfork 
child: ret = 200
father;ret = 32659
child: ret = 200
段错误
[DELL@localhost lesson2]$ 


<2>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main(){
  int ret = 200;
  pid_t id = vfork();
  if(id < 0){ 
    perror("vfork:");
    return -1; 
  }else if(id == 0){ 
    printf("child: ret = %d\n",ret);
    //return 0;
    exit(0);
  }else{
    printf("father;ret = %d\n",ret);
    //return 0;
    exit(0);
  }
}

结果:
[DELL@localhost lesson2]$ ./vfork 
child: ret = 200
father;ret = 200
[DELL@localhost lesson2]$ 


<3>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main(){
  int ret = 200;
  pid_t id = vfork();
  if(id < 0){ 
    perror("vfork:");
    return -1; 
  }else if(id == 0){ 
    printf("child: ret = %d\n",ret);
    return 0;
    //exit(0);
  }else{
    printf("father;ret = %d\n",ret);
    //return 0;
    exit(0);
  }
}

结果:
[DELL@localhost lesson2]$ ./vfork 
child: ret = 200
father;ret = 32710
[DELL@localhost lesson2]$


<4>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main(){
  int ret = 200;
  pid_t id = vfork();
  if(id < 0){ 
    perror("vfork:");
    return -1; 
  }else if(id == 0){ 
    printf("child: ret = %d\n",ret);
    //return 0;
    exit(0);
  }else{
    printf("father;ret = %d\n",ret);
    return 0;
    //exit(0);
  }
}

结果:
[DELL@localhost lesson2]$ ./vfork 
child: ret = 200
father;ret = 200
[DELL@localhost lesson2]$

调用return时,无论有几个进程共享这个内存空间均释放这块内存,而调用exit时,类似于引用计数,当调用该函数的进程为最后一个管理该块内存的进程时,才释放空间,其余情况,均结束掉自己的进程并不释放资源。所以,上面的四种现象就很好解释了:
<1> 当子进程调用return销毁空间,父进程再次打印ret时便会出现随机值,再次调用return销毁同一块已经释放过的空间时,就会出现段错误(地址越界)。
<2> 当父、子进程均调用exit时,子进程先调用exit时发现还有父进程管理着这块空间,于是,结束掉自己的进程就可以了,并不释放内存空间,所以父进程打印ret的值时还是200,随后父进程再次调用exit时发现只有它自己还管理着这块空间,所以释放掉该块空间。
<3> 当子进程先调用return销毁空间时,父进程再次打印ret时便会出现随机值,调用exit时发现该块空间已经释放时,只结束自己的进程就可以了。
<4> 当子进程先调用exit时,发现还有父进程管理着该块空间,便只结束掉自己的进程,所以,父进程打印ret时还是200,随后父进程调用return销毁空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值