【linux】进程控制——进程创建,进程退出,进程等待 ...

海哥  金牌会员 | 2024-7-10 22:52:18 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 534|帖子 534|积分 1602

个人主页:东洛的克莱斯韦克-CSDN博客
  祝福语:愿你拥抱自由的风
  相干文章
  【Linux】进程地址空间-CSDN博客
  【linux】详解linux根本指令-CSDN博客
  

目录

进程控制概述
创建子进程
fork函数
父子进程执行流
原理刨析
常见用法
出错原因
进程退出
概述
场景分析
返回退出状态的函数
exit和_exit
进程等待
概述
进程等待方法
wait
waitpid
详谈status


进程控制概述

一个进程创建了另一个进程,创建者为父进程,被创建者为子进程。
父进程可以创建多个子进程。
操纵系统是一号进程,所有进程构成一颗多叉树布局。

每个父进程只对直系的子进程负责。
父进程创建子进程的一整套流程为:创建子进程——>子进程完成任务——>父进程回收子进程
创建子进程

fork函数

   功能:   创建一个子进程
  
     头文件:   #include <unistd.h>          函数原型:   pid_t fork(void);          返回值:   子进程返回   0   ,父进程返回子进程   id   ,出错返回-1       fork之后的父进程和子进程谁先被执行是由调度器决定     代码示例   
  1. int main( void )
  2. {
  3. pid_t pid;
  4. printf("Before: pid is %d\n", getpid());
  5. if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
  6. printf("After:pid is %d, fork return %d\n", getpid(), pid);
  7. sleep(1);
  8. return 0;
  9. }
复制代码
父子进程执行流

父进程和子进程共享代码,可以用判定语句让父进程和子进程执行差别代码块实现分流。
  1. int main()
  2. {
  3. pid_t t = fork();
  4. if (-1 == t)
  5. {
  6. //创建进程失败
  7. }
  8. else if (0 == t)
  9. {
  10. //子进程代码块
  11. }
  12. else
  13. {
  14. //父进程代码块
  15. }
  16. return 0;
  17. }
复制代码
上述代码中如果在父子进程分别写一个死循环就会有个神奇的现象——在一个main函数中能同时运行两个死循环。
   系统层面理解
  这在语言层面是无法理解的。在系统层面,是由两个进程运行了该代码。
fork()也是一个函数,在fork函数内部实现在创建一个进程的方法,在执行fork的main函数的return的时候就已经有了两个进程,子进程的fork返回 0 ,父进程的fork 返回子进程的pid(进程唯一标识符)。
出了fork函数,父进程和子进程分别执行自己的代码块,也就有了上述一个在main函数中运行了两个死循环的现象。
   在fork函数中,linux内核做了如下工作
  Linux内核为子进程创建内核资源,包罗task_struct(管理进程),mm_struct(管理进程地址空间),页表等,并为该子进程分配唯一标识符PID。子进程的内核资源继续于父进程。
Linux内核复制父进程的上下文,包罗堆栈指针、步伐计数器(PC)以及通用寄存器的状态。
Linux内核为子进程分配独立的进程地址空间
处理返回值,子进程返回 0 ,父进程返回 子进程的PID。
原理刨析

原理分析设计进程地址空间,可参考【Linux】进程地址空间-CSDN博客
子进程的页表也是继续于父进程,这意味着子进程的地址空间在物理内存的映射是重叠的。当子进程要对父进程的数据进行写入时,会发生写时拷贝。
通过写时拷贝实现代码共享,数据隔离。
上述策略可以有用节省物理内存

常见用法

   一个父进程希望复制自己,使父子进程同时执行差别的代码段。比方,父进程等待客户端请求,天生子进程来处理请求     一个进程要执行一个差别的步伐。比方子进程从fork返回后,调用exec函数(进程步伐替换)  出错原因

系统中进程太多
用户进程的数量超过了限定
进程退出

概述

父进程先退,子进程未退。此时子进程的父亲进程被改为1号进程(操纵系统),那么该子进程被称为孤儿进程
父进程未退,子进程先退。但父进程没有读取到子进程的退出(进程等待),该子进程的状态被改为 Z 状态,Z 状态的进程被成为僵尸进程。
操纵系统杀不掉僵尸进程,以是僵尸进程会造成内存泄漏
如下是维持30秒僵尸进程的代码示例
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. pid_t id = fork();
  6. if(id < 0){
  7. perror("fork");
  8. return 1;
  9. }
  10. else if(id > 0){ //parent
  11. printf("parent[%d] is sleeping...\n", getpid());
  12. sleep(30);
  13. }else{
  14. printf("child[%d] is begin Z...\n", getpid());
  15. sleep(5);
  16. exit(EXIT_SUCCESS);
  17. }
  18. return 0;
  19. }
复制代码
场景分析

   进程异常退出
  异常退出的本质是因为进程出发了硬件级别的错误,操纵系统给进程发送信号终止进程。
被信号杀掉的进程由于自身任务没有完成绩挂掉了,以是是异常退出。
每一种信号都有自己的编号,可用下述命令查看
  1. kill -l
复制代码

用户可以输入如下指令,向进程发送信号来终止进程
kill     -9     进程PID
Ctrl    c 
   进程正常退出,结果不精确
  进程退出码:进程正常竣事后给外部返回一个值,来代表自己任务完成情况。
如下指令可以查看近来一次进程的退出码
  1. echo $?
复制代码
? 中储存的是近来一次进程的退出码的值
退出码的值可以自己定,退出码的寄义可以根据代码的需求给定
可用如下步伐体现linux的退出码的寄义
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<errno.h>
  4. #include<string.h>
  5. int main()
  6. {
  7.   int i = 0;
  8.   for(i;i<134;i++)
  9.   {
  10.     printf("%d:,%s\n",i,strerror(i));
  11.   }
  12.   return 0;
  13. }
复制代码
  进程正常退出,结果精确
  一样寻常情况下无需关心
返回退出状态的函数

   return  是一种常见的退出进程方法。执行  return n  等同于执行  exit(n),  因为调用  main  的运行时函数会将  main  的返回值当做 exit  的参数     但如果该函数不是main函数,就必要exit函数竣事进程,并返回退出状态        #include <unistd.h>          void _exit(int status);          参数:   status    界说了进程的终止状态,父进程通过   wait   来获取该值               #include <unistd.h>              void exit(int status);              
       参数:    status     界说了进程的终止状态,父进程通过    wait    来获取该值          exit和_exit

   exit   末了也会调用 _  exit ,   但在调用 _  exit   之前,还做了其他工作:      1.   执行用户通过   atexit  或  on_exit  界说的清理函数。      2.   关闭所有打开的流,所有的缓存数据均被写入      3.   调用  _exit      如下体现图   
  代码示例
  1. int main()
  2. {
  3. printf("hello");
  4. exit(0);
  5. }
复制代码
运行结果
   [root@localhost linux]# ./a.out     hello[root@localhost linux]#   
  1. int main()
  2. {
  3. printf("hello");
  4. _exit(0);
  5. }
复制代码
运行结果
     [root@localhost linux]# ./a.out        [root@localhost linux]#     进程等待

概述

父进程只对直系的子进程负责,父进程创建子进程的目的是为了让子进程为自己完成一些事变。以是在子进程变成 X 状态(死亡状态)之前,有一个 Z 状态即僵尸状态。
僵尸状态是为了能让父进程读取子进程的退出信息。
退出信息:进程是否触发硬件级别的错误而被信号所杀(异常竣事),进程如果没有异常竣事退出码是否符合预期(结果运行的结果是否精确)
进程等待有如了局景
1.父进程等待子进程,但子进程并未退出,此时父进程可以阻塞等待,非阻塞等待,非阻塞轮询等待。
2.子进程变成僵尸进程,父进程等待子进程并读取子进程退出信息,子进程被设为 X 状态。
子进程的退出信息只能被父进程拿到——父进程只对直系子进程负责。
进程等待和 Z 状态本质是为了应对父子进程退出的各种场景,让父进程拿到子进程的退出信息
进程等待方法

wait

      头文件:   #include<sys/types.h>          头文件:   #include<sys/wait.h>          函数原型:   pid_t wait(int*status)         功能:等待子进程         返回值:  乐成返回被等待进程pid   ,失败返回   -1         参数:  输出型参数,获取子进程退出状态,   不关心则可以设置成为   NULL        wait函数的特性是:如果子进程为僵尸状态,直接获取子进程的退出信息,如果子进程并未退出,父进程的状态被改为 S 状态(就寝状态,阻塞状态)——父进程一直等待子进程     参数:我们可以界说一个int类型的变量,wait用指针接收,wait去子进程中拿到退出信息就可以通过  status通报给父进程     代码示例   
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4.        #include <unistd.h>
  5. #include<stdlib.h>
  6. #include <sys/wait.h>
  7. int main()
  8. {
  9.   pid_t i = fork();
  10. if (-1  == i)//创建子进程失败
  11. {
  12. printf("创建子进程失败\n");
  13. return 2;
  14. }
  15. else if (0 == i)//子进程
  16. {
  17. int cat = 2;
  18. while (cat)
  19. {
  20. //eep(1);
  21. printf("我是一个子进程 %d  我的父亲是 %d\n", getpid(), getppid());  
  22. cat--;
  23. }
  24. exit(21);
  25. //return 12;      
  26. }
  27. else//父进程
  28. {
  29. int  status = 0;
  30.   pid_t t = wait(&status);
  31. sleep(1);
  32. printf("我是一个父进程%d,子进程退出状态为%d,子进程的退出码是%d\n", getpid(), WIFEXITED(status),WEXITSTATUS(status));
  33. //printf("我是一个父进程%d,子进程退出状态为%p\n", getpid(),status);
  34.   return 99;
  35. }
  36. }
复制代码

  waitpid

   函数原型
    pid_ t waitpid(pid_t pid, int *status, int options);         返回值       当正常返回的时候  waitpid  返回网络到的子进程的进程  ID  ;      如果设置了选项  WNOHANG,  而调用中  waitpid  发现没有已退出的子进程可网络  ,  则返回  0  ;      如果调用中出错  ,  则返回  -1,  这时  errno  会被设置成相应的值以指示错误所在;         参数       pid  :      Pid=-1,  等待任一个子进程。与  wait  等效。     Pid>0.  等待其进程  ID  与  pid  相等的子进程。       status:       输出型参数,获取子进程退出状态,  不关心则可以设置成为  NULL      options:     WNOHANG:   若  pid  指定的子进程没有竣事,则  waitpid()  函数返回  0  ,不予以等待。若正常竣事,则返回该子进程的ID  。    如果为0:进入阻塞等待,与wait等价      如下是差别场景的代码示例        父进程只等待一次,子进程变成孤儿进程      
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4.        #include <unistd.h>
  5. #include<stdlib.h>
  6. #include <sys/wait.h>
  7. int main()
  8. {
  9.   pid_t i = fork();
  10. if (-1  == i)//创建子进程失败
  11. {
  12. printf("创建子进程失败\n");
  13. return 2;
  14. }
  15. else if (0 == i)//子进程
  16. {
  17. int cat = 2;
  18. while (1)
  19. {
  20. sleep(1);
  21. printf("我是一个子进程 %d  我的父亲是 %d\n", getpid(), getppid());  
  22. cat--;
  23. }
  24. exit(21);
  25. //return 12;      
  26. }
  27. else//父进程
  28. {
  29. int  status = 0;
  30.   pid_t t = waitpid(-1, &status, WNOHANG);
  31. sleep(1);
  32. printf("我是一个父进程%d,子进程退出状态为%d,子进程的退出码是%d\n", getpid(), WIFEXITED(status),WEXITSTATUS(status));
  33. //printf("我是一个父进程%d,子进程退出状态为%p\n", getpid(),status);
  34.   return 99;
  35. }
  36. }
复制代码
    非阻塞轮询等待
    父进程死循环等待,options的参数是WNOHANG,这种等待称为非阻塞轮询等待,等待期间父进程可以做自己的事变
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4.        #include <unistd.h>
  5. #include<stdlib.h>
  6. #include <sys/wait.h>
  7. int main()
  8. {
  9.   pid_t i = fork();
  10. if (-1  == i)//创建子进程失败
  11. {
  12. printf("创建子进程失败\n");
  13. return 2;
  14. }
  15. else if (0 == i)//子进程
  16. {
  17. int cat = 2;
  18. while (1)
  19. {
  20. sleep(1);
  21. printf("我是一个子进程 %d  我的父亲是 %d\n", getpid(), getppid());  
  22. cat--;
  23. }
  24. exit(21);
  25. //return 12;      
  26. }
  27. else//父进程
  28. {
  29. int  status = 0;
  30. while(1)
  31. {
  32.   pid_t t = waitpid(-1, &status, WNOHANG);
  33. sleep(1);
  34. //printf("我是一个父进程%d,子进程退出状态为%d,子进程的退出码是%d\n", getpid(), WIFEXITED(status),WEXITSTATUS(status));
  35. if (-1 == t)//等待失败
  36. {
  37. return -1;
  38. }
  39. else if (0 == t)
  40. {
  41. sleep(1);
  42.   printf("子进程还没完事\n");
  43. }
  44. else{//等待成功
  45.   break;
  46. }
  47. //printf("我是一个父进程%d,子进程退出状态为%p\n", getpid(),status);
  48. }
  49. return 99;
  50. }
  51. }
复制代码

     为什么要用上述两个函数来完成父进程对子进程退出信息的获取呢
       wait和waitpid是系统提供的接口,获取子进程的退出信息是由操纵系统做的。为什么父进程不能直接获取,而要让操纵系统介入呢?     子进程退出后,其相干数据被释放掉,退出信息被保存在内核数据布局中,一个进程在上层代表的是用户,如果一个进程可以直接拿到子进程的退出信息,那么阐明该进程可以直接访问内核数据布局,换句话说用户可以通过代码访问内核数据布局,那么操纵系统的安全性怎么得到包管呢?        父进程和子进程都代码共享了,不能在代码层面拿到退出信息吗      进程是系统层面的概念,如果不用系统提供的接口,子进程的退出信息是不会反馈给父进程的代码上的。   详谈status

   wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操纵系统填充。      如果通报NULL,体现不关心子进程的退出状态信息。      否则,操纵系统会根据该参数,将子进程的退出信息反馈给父进程。      status不能简单的当作整形来看待,可以当作位图来看待,详细细节如下图(只研究status低16比特位)     比特位 0~6 对应了继承到的信号,注意这里还通过 127 0x7f 界说了 STOP 状态
  比特位 7 用来标示是否有天生 core 文件
  比特位 8~15 通过 exit() 接口退出进程,也就是意味着错误码最大为 255 ,如果是 256 那么实际上是 0 ;
     
     头文件中提供了宏,无需进行位运算
     WIFEXITED(status):    若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)          WEXITSTATUS(status):    若   WIFEXITED   非零,提取子进程退出码。(查看进程的退出码)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

海哥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表