当前位置: 首页 > article >正文

Linux:进程控制(一)

目录

一、写时拷贝

1.创建子进程

2.写时拷贝

 二、进程终止

1.函数返回值

2.错误码

3.异常退出

4.exit

5._exit

三、进程等待

1.进程等待的必要性

2.验证进程等待

3.获取具体退出信息

4.子进程退出和waitpid返回值


一、写时拷贝

        父子进程,代码共享,不作写入操作时,数据也是共享的,当任意一方试图写入,便通过写时拷贝的方式各自有一份副本。

1.创建子进程

        这是父进程的大致蓝图。

        创建子进程,分配内存块内核数据结构给子进程。

         将父进程的部分数据结构内容拷贝到子进程,即代码内容共享数据内容暂时共享

2.写时拷贝

  • 写实拷贝的大致蓝图

  • 为什么要作写时拷贝?

        子进程不一定会用到父进程的全部数据,故作写时拷贝用来完成特定的数据需求。

  • 页表

        页表并不是只有虚拟地址和物理地址两列,还有其他属性列,如“权限”、“是否存在于内存”。权限列有只读、可读可写等属性。

  • 写时拷贝的具体过程

        子进程复制父进程的部分内容后,操作系统在各自的页表中将数据段的属性设置为只读

        当发生写时拷贝时,由于页表项的属性是只读,故造成缺页中断,中断处理过程时,操作系统将页表项属性再设置为可读可写

 二、进程终止

1.函数返回值

        main函数返回值是当前进程的退出码,而普通函数的返回值仅仅是函数调用的结果。

        main函数返回值为0,即退出码为0,表示当前程序运行成功,非零则表示失败,而失败的原因由错误码表示。

2.错误码

        函数strerror用来打印错误码详情,Linux下错误码如下所示。

int main()
{
  for(int i = 0;i < 200 ;++i)
  {
    printf("%d : %s\n",i, strerror(i));
  }
  return 0;
}
0 : Success
1 : Operation not permitted
2 : No such file or directory
3 : No such process
4 : Interrupted system call
5 : Input/output error
6 : No such device or address
7 : Argument list too long
8 : Exec format error
9 : Bad file descriptor
10 : No child processes
11 : Resource temporarily unavailable
12 : Cannot allocate memory
13 : Permission denied
······

        错误码转换为错误描述,一般有两种情形,使用系统提供的或者自定义

  • 自定义错误码
enum 
{
  success=0,
  open_err,
  malloc_err
};
const char* errornumToDisc(int code)
{
  switch(code)
  {
    case success:
      return "Success!";
    case open_err:
      return "Open fail";
    case malloc_err:
      return "malloc error";
    default:
      return "unkonw errornum";
  }
}
int main()
{
  int code = open_err;
  printf("%d->%s\n",code,errornumToDisc(code));
  return code;
}
  •  系统提供的错误码errno

        当前程序退出后,默认errno保存着错误码。

        如果程序正常退出,errno的值为0

int main()
{
  printf("%d->%s\n",errno,strerror(errno));
}

        可以通过echo $?查看最近一次运行的退出码。 

[euto@VM-4-13-centos 241002]$ ./myprocess 
0->Success
[euto@VM-4-13-centos 241002]$ echo $?
0

        如果遇到错误退出,则可以通过errno打印错误信息。

int main()
{
  FILE* pf = fopen("a.txt","r");
  printf("%d->%s\n",errno,strerror(errno));
  return 0;
}
[euto@VM-4-13-centos 241002]$ ./myprocess 
2->No such file or directory

3.异常退出

        程序正常退出有三种方式。

  1. main函数的返回值
  2. 库函数exit
  3. 系统调用接口_exit

        进程退出的场景只有三种:

        1.正常退出:程序运行结束,运行成功,main函数返回0。

        2.正常退出:程序运行结束,运行失败,main函数返回非零值。

        3.异常退出:代码并没有被全部执行,一般异常退出是由于进程收到了异常信号,信号编码对应着发生异常的原因。

        执行kill -l这些都是可以让进程异常退出的信号。

[euto@VM-4-13-centos 241002]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

        任何进程的退出信息都可以用两个数字来表示,进程退出码异常信号

  • 进程收到异常信号,则异常信号不为0,于是进程的退出码没有意义。
  • 进程的异常信号值为0,则讨论进程退出码的值。 

4.exit

EXIT(3)                                                             Linux Programmer's Manual                                                             EXIT(3)

NAME
       exit - cause normal process termination

SYNOPSIS
       #include <stdlib.h>

       void exit(int status);

        exit函数是由C语言库提供,用来直接退出当前进程,参数的值即为退出码,如果运行成功,则执行exit(0)退出。

5._exit

_EXIT(2)                                                            Linux Programmer's Manual                                                            _EXIT(2)

NAME
       _exit, _Exit - terminate the calling process

SYNOPSIS
       #include <unistd.h>

       void _exit(int status);

       #include <stdlib.h>

       void _Exit(int status);

        _exit是Linux系统调用接口,由Linux操作系统提供,总体上的用法和exit没有区别。差异在于exit函数在退出进程的时候会刷新缓冲区,而_exit函数则不会刷新缓冲区,不会刷新缓冲区也说明这个缓冲区不在操作系统内部。
        分别执行下面两段代码,观察有何不同。

int main()
{
 printf("hello");
 exit(0);
}
int main()
{
 printf("hello");
 _exit(0);
}

        总结,推荐使用exit,原因不是因为它会刷新缓冲区,而是因为它是由C语言库提供,内部其实封装了_exit,进程的结束肯定是由操作系统调用系统调用接口来结束,exit只是封装Linux系统调用接口_eixt罢了,同时也封装了Windows平台的系统调用接口,这样一来,使用exit的程序具备可移植性,在Linux平台调用Linux平台的系统调用接口_exit,在Windows平台调用Windows平台的系统调用接口。

三、进程等待

1.进程等待的必要性

        父子进程的联系中,父进程为了实现某一目的而创建子进程,子进程在彻底消杀之前,会进入僵尸状态,僵尸状态只保留PCB,PCB内包含进程退出时的控制信息,比如进程退出码异常退出信号

        子进程进入僵尸状态后,执行kill -9命令是无法消杀子进程的。

        父进程要获取子进程完成目标的情况,就要获取子进程PCB中的退出信息。

  • 于是,父进程通过进程等待的方式,获取退出信息:

        1.父进程通过wait方式,回收子进程的资源。(必然发生)

        2.父进程通过wait方式,获取子进程的退出信息。(可能发生)

  • 值得一提的是,父进程必须在子进程退出后退出,否则子进程退出时,父进程已经被消杀,无法回收子进程资源,造成内存泄露。

2.验证进程等待

        系统调用接口如下。

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

pid_t wait(int *status);//父进程进入阻塞等待,等待任意一个子进程。
//参数:status是输出参数,如不需要则传参NULL
//返回值:返回值大于0,则为子进程的pid,返回值小于0,则等待失败。

pid_t waitpid(pid_t pid, int *status, int options);
//参数:
//pid传参为-1,则等待任意一个子进程,等效wait。pid>0,则等待对应id的子进程。
//status用来接收退出信息,options是等待方式,为0表示阻塞等待。
//返回值:
//正常情况则返回子进程的PID

        验证第1点,父进程通过等待回收子进程的资源。

int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    int i = 5;
    while(i)
    {
      printf("Child is running,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
      --i;
    }
    printf("子进程准备退出,子进程为僵尸进程\n");
    exit(0);
  }

  pid_t rid = wait(NULL);
  if(rid > 0)
  {
    printf("wait success,rid:%d\n",rid);
  }

}

        程序运行结果中rid的值子进程的pid相同。

Child is running,pid:24262,ppid:24261
Child is running,pid:24262,ppid:24261
Child is running,pid:24262,ppid:24261
Child is running,pid:24262,ppid:24261
Child is running,pid:24262,ppid:24261
子进程准备退出,子进程为僵尸进程
wait success,rid:24262

        验证第2点,父进程通过等待获取子进程的退出信息。

int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    int i = 5;
    while(i)
    {
      printf("Child is running,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
      --i;
    }
    printf("子进程准备退出,子进程为僵尸进程\n");
    exit(1);
  }
  int tmp = 0;
  pid_t rid = waitpid(id,&tmp,0);
  if(rid > 0)
  {
    printf("wait success,rid:%d,退出信息:%d\n",rid,tmp);
  }
}

        打印结果如下。

Child is running,pid:28256,ppid:28255
Child is running,pid:28256,ppid:28255
Child is running,pid:28256,ppid:28255
Child is running,pid:28256,ppid:28255
Child is running,pid:28256,ppid:28255
子进程准备退出,子进程为僵尸进程
wait success,rid:28256,退出信息:256

        退出信息为什么是256呢?

  • 接受退出信息的是一个整型变量status,共32个比特位。
  • 退出信息一般是有两个值,分别是异常信号编号进程退出码
  • status的32个比特位中,高16位没有意义,低16位中,高8位用来表示进程退出时的退出码,低7位用来表示异常信号编号,剩下一位有特殊意义,是core dump标志

        上述例子中,由于没有收到异常信号编号,则异常信号编号为0,进程退出码为1。

3.获取具体退出信息

  • 为什么要用系统调用接口获取退出信息?

        子进程的退出信息存储在PCB中,属于操作系统内核数据结构,而获取内核数据结构,必须用系统调用接口。

  • 通过对status变量作运算获取信号编号进程退出码
  int tmp = 0;
  pid_t rid = waitpid(id,&tmp,0);
  if(rid > 0)
  {
    printf("wait success,rid:%d,退出信息:%d,异常信号编号:%d,
进程退出码;%d\n",rid,tmp,tmp&0x7f,(tmp>>8)&0xff);
  }
  •  通过宏判断
pid_t waitpid(pid_t pid, int *status, int options);
//参数:
//status用来接收退出信息
//宏操作WIFEXITED(status),若为正常终止子进程,则返回真。
//在正常终止子进程的情况下,宏操作WEXITSTATUS(status)返回子进程的退出码。

4.子进程退出和waitpid返回值

  • 子进程正常退出,则WIFEXITED返回真,可以获取到子进程的退出码,waitpid返回子进程的PID。

  • 子进程异常退出,则WIFEXITED返回假,waitpid返回子进程的PID,同时异常信号编号写入status中。

  • 子进程正在运行,waitpid返回0,父进程可能阻塞。

  • 子进程不存在,调用发生错误,waitpid返回负数。


http://www.kler.cn/news/340270.html

相关文章:

  • python操作.docx、.pptx文件
  • C++-再探构造函数(进阶)
  • 用户在网页上输入一个网址,它整个页面响应的流程是什么?
  • Koa2项目实战2(路由管理、项目结构优化)
  • 柯桥外语培训韩语学习考级韩语中TOPIK常用语法表达
  • Python Kivy应用程序中的中文乱码问题
  • 『网络游戏』自适应制作登录UI【01】
  • Chapter 2 - 3. Understanding Congestion in Fibre Channel Fabrics
  • 基于springboot vue 学生就业信息管理系统设计与实现
  • SpringBoot实战:设计与实现明星周边电子商务平台
  • vulnhub-Web Developer 1靶机
  • 向量数据库|第1期|从零开始学习
  • Python-Learning
  • 红帽7—Mysql路由部署
  • nginx问题解决-nginx代理数据库端口
  • 力扣34.在排序数组中查找元素的第一个和最后一个位置(二分查找)
  • GAMES104:16 游戏引擎的玩法系统:基础AI-学习笔记
  • 探索Python中的装饰器模式
  • LeetCode 189. Rotate Array 解题思路和python代码
  • 程序传入单片机的过程,以Avrdude为例分析