【linux】进程控制

进程的独立性

进程:内核相关管理数据结构(pcb+地址空间+页表)+代码+数据

当fork函数创建一个子进程时,如何保证进程具有独立性?

父进程的数据以及代码来自磁盘,而子进程的代码是共享父进程的,至于数据,如果子进程不修改数据的话,同样也是和父进程共享的,而如果子进程要修改数据,就会发生写时拷贝,保证数据的独立性,子进程的退出也不会对父进程做干扰.父进程退出也不会对子进程做干扰.

frok函数的返回值

子进程给父进程返回自己的pid,父进程给子进程返回0,这样是为了对子进程进行标识,进而进行管理.这个在之前的文章中有讲过

进程的终止

进程终止做了什么

1.释放曾经的代码和数据所占的空间。

2.释放内核的数据结构(pcb+地址空间+页表)

当时当子进程退出后,父进程还没有获取到子进程的状态时,子进程的代码和数据已经被释放,而内核中的数据结构还没有被释放。因为子进程的内核数据结构pcb中保存了子进程的退出码和退出信号,需要等待父进程获取.

进程终止的3种情况

1.代码跑完,结果正确

2.代码跑完,结果错误

3.代码执行时,出现了异常,提前退出了


下面看一段程序,这段程序代码会跑完,结果也正确

这段程序中的return 0表示什么呀?

其实这个进程相当于bash的子进程,而main函数return的是该进程的退出码,该子进程会将该退出码返回给bash,因为bash要知道子进程退出的情况,给用户提供失败的原因是什么,对用户负责.

这个退出码 可以通过指令

echo $?

0就是这个程序的退出码,一般success表示成功

strerror

针对上面的错误码的话,对计算机而言,他知道退出码0,1,2,3对应着什么,我们可以通过strerror函数查看对应的退出码对应的是什么错误

退出码:0成功

!0失败

不同的非0值一方面表示失败,另一方面表示失败的原因,而对于计算机可以将退出码和错误做对应,如果我们想知道退出码对应的错误,就可以通过strerror函数来确定,对应的退出码都有错误描述string


退出码

如果我们将退出码设置为1的话

为什么第一次获取到的退出码是1,然后使用pwd进程后,在获取退出码的时候就变成0了呢?

其实父进程bash获取到的,是最近一个子进程退出的退出码,也就是说最后的0的退出码是pwd进程执行成功给bash的退出码,需要告诉父进程,子进程把任务完成的怎么样了


退出码可以使用默认,也可以自己定义

枚举类型:0表示成功,1表示除0,2表示模0,exit_code为退出码,刚开始让他=0表示成功.

然后Div函数里面如果分母为0了,就说明除0了,就把他的退出码修改成1;

打印退出码和退出码对应的信息,这里已经打印,就不用通过bash查看退出码了


退出信号

针对进程终止的前两种情况。我们可以通过系统或者自定义的退出码来确定。而针对于第三种,程序出现异常,在vs中,编译运行的时候,就会崩溃,原因是操作系统发现你做了不该做的事情,操作系统杀死了进程,一旦出现异常,退出码就没有任何意义了

下面展示代码演示代码出现异常

当对空指针解引用并赋值100时,会出现段错误,出现野指针问题.

而段错误在信号类型属于11号信号

进程出现异常本质是因为进程收到了os发给进程的信号

所以进程的终止,我们只需要两个信息,一个是退出码,一个是退出信号,

先确定是否异常,如果不是异常,就是代码跑完了,看退出码就可以了

退出码 退出信号 程序
0 0 程序跑完,结果正确
!0 0 程序跑完,结果不正确
!0 !0 异常
0 !0 异常

进程如何终止

1.main函数中的return 表示进程终止(非main函数,return表示函数结束)

2.crrl+c (异常终止)

3.exit函数,_exit函数(正常终止)

exit函数

c 复制代码
#include<stdlib.h>
void exit(int status);

exit函数中的参数status是退出码,exit函数不管在程序的任何一个位置,只要到了exit的话,就表示进程要终止了,而return只有在main函数里面的表示进程要结束了,其他调用函数里面return只能说明函数结束.

_exit函数也是退出进程,基本上和exit的功能一样

c 复制代码
#include <unistd.h>
void _exit(int status);

exit函数和_exit函数的区别和联系

区别 联系
exit是库函数,而_exit是系统调用,exit会冲刷缓冲区,_exit不会冲刷缓冲器 两者都会结束进程

这里的缓冲区不是内核缓冲区,因为操作系统的内核缓冲区在系统调用底层,如果是内核缓冲区的话,_exit也会冲刷缓冲区,作为库函数exit会调用_exit

验证exit会冲刷缓冲区,而_exit不会:

helloworld会立刻打印出来是因为exit会冲刷缓冲区

helloworld没有打印出来,是因为helloworld此时在缓冲区

进程等待

概念

任何子进程,在退出的情况下,一般必须要被父进程进行等待,进程在退出的时候,如果父进程不管不顾,退出进程,子进程会出现僵尸状态,导致内存泄露.

为什么要进程等待

1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(必须)

2.获取子进程的退出信息,知道子进程是因为什么原因退出的(选择)

怎么实现进程等待(通过wait/waitpid)

wait方法

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int*status);

返回值:

成功返回被等待进程pid,失败返回-1。

参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

代码演示

c 复制代码
#include<stdio.h>
   #include<stdlib.h>
   #include <unistd.h>
   #include<sys/types.h>
   #include<sys/wait.h>
   void childrun()//5秒子进程退出
   {
   int cnt=5;
   while(cnt)
  {
  printf("i am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
  sleep(1);
  cnt--;
  }
  }
  int main()
  {
  printf("i am father, pid:%d,ppid:%d\n",getpid(),getppid());
  size_t id=fork();
  if(id==0)
  {childrun();                                                                
    printf("child quit ...\n");//提示子进程退出
    exit(0);//子进程退出
  }
  sleep(10);//等待10秒是为了让查看状态看到子进程退出后处于的僵尸状态
  int  rd=wait(NULL);//rd就是等待的子进程的pid
  if(rd>0)
  {
  printf("wait success,rd:%d\n",rd);
  }
  }

rd是子进程的pid,然后子进程的资源被回收,就不会引起内存泄漏.

如果子进程没有退出,父进程其实一直在进行阻塞等待,子进程本身就是软件,父进程本质上在等待某种软件条件就绪,父进程阻塞等待子进程,就会将父进程的pcb连接到子进程的运行队列里面去,直到子进程退出,父进程会将获取子进程pcb里退出号,以及退出信号,然后父进程会回收子进程pcb资源.

waitpid方法

#include<sys/types.h>

#include<sys/wait.h>

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

当正常返回的时候waitpid返回收集到的子进程的进程id;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

pid=-1,等待任一个子进程。与wait等效。

pid>0.等待其进程id与pid相等的子进程。

options设置为0

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回。

options设置为WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

c 复制代码
(status>>8)&0xFF //取次低8位退出号
c 复制代码
status&0x7F //取低7位退出状态
c 复制代码
#include<stdio.h>
   #include<stdlib.h>
   #include <unistd.h>
   #include<sys/types.h>
   #include<sys/wait.h>
   void childrun()//5秒子进程退出
   {
   int cnt=5;
   while(cnt)
  {
  printf("i am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
  sleep(1);
  cnt--;
  }
  }
  int main()
  {
  printf("i am father, pid:%d,ppid:%d\n",getpid(),getppid());
  size_t id=fork();
  if(id==0)
  {childrun();                                                                
    printf("child quit ...\n");//提示子进程退出
    exit(0);//子进程退出
  }
  sleep(10);//等待10秒是为了让查看状态看到子进程退出后处于的僵尸状态
  int  rd=waitpid(-1,NULL,0);//rd就是等待的子进程的pid
  if(rd>0)
  {
  printf("wait success,rd:%d\n",rd);
  }
  }

情况1正常退出

c 复制代码
#include<stdio.h>
   #include<stdlib.h>
   #include <unistd.h>
   #include<sys/types.h>
   #include<sys/wait.h>
   void childrun()//5秒子进程退出
   {
   int cnt=5;
   while(cnt)
  {
  printf("i am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
  sleep(1);
  cnt--;
  }
  }
  int main()
  {
  printf("i am father, pid:%d,ppid:%d\n",getpid(),getppid());
  size_t id=fork();
  if(id==0)
  {childrun();                                                                
    printf("child quit ...\n");//提示子进程退出
    exit(0);//子进程退出
  }
  sleep(10);//等待10秒是为了让查看状态看到子进程退出后处于的僵尸状态
  int status=0;
  int  rd=waitpid(id,&status,0);//rd就是等待的子进程的pid
  if(rd>0)
  {
  printf("wait success,rd:%d exit_code:%d exit_singal:%d\n",rd(status>>8)&0xFF,status&0x7f); 
  }
  }

情况2 9号信号验证

将上述代码子进程改成死循环

情况3 11号信号验证

将子进程中的循环改成cnt,然后将之前的段错误的代码加到子进程中去

相关推荐
宁zz20 小时前
乌班图安装jenkins
运维·jenkins
大丈夫立于天地间20 小时前
ISIS协议中的数据库同步
运维·网络·信息与通信
cg501720 小时前
Spring Boot 的配置文件
java·linux·spring boot
暮云星影20 小时前
三、FFmpeg学习笔记
linux·ffmpeg
rainFFrain21 小时前
单例模式与线程安全
linux·运维·服务器·vscode·单例模式
GalaxyPokemon21 小时前
Muduo网络库实现 [九] - EventLoopThread模块
linux·服务器·c++
mingqian_chu21 小时前
ubuntu中使用安卓模拟器
android·linux·ubuntu
@郭小茶1 天前
docker-compose方式部署docker项目
运维·docker·容器
GalaxyPokemon1 天前
Muduo网络库实现 [十] - EventLoopThreadPool模块
linux·服务器·网络·c++