目录

一.进程创建

1.fork创建子进程,操作系统做了什么?

2.fork的用法以及失败的原因

二.进程终止

1.三种退出情况

2.main函数为什么一般情况下都要return 0

3.进程的退出码

1).0与非0

2).linux用指令查看一个进程结束后的退出码

3).程序崩溃时退出码无意义

4).被信号杀掉的进程

4.exit与_exit

1).exit

2).exit与_exit的区别

3).exit与return的区别

三.进程等待

1.进程等待的经典场景

2.wait

3.waitpid

4.wait/waitpid的本质


一.进程创建

1.fork创建子进程,操作系统做了什么?

本质就是:操作系统新创建了一个进程

(进程 = 内核数据结构 + 进程的代码和数据)

1.创建子进程,操作系统需要为子进程分配新的内核数据结构和内存块

2.拷贝父进程的虚拟地址空间,但映射到物理内存时采用写时拷贝

3.共享父进程fork之后的代码,而数据分离(代码是只读的,代码共享并不影响进程的独立性)

4.添加子进程到系统进程列表中

5.fork返回,开始由调度器调度

2.fork的用法以及失败的原因

fork的用法

父进程通过复制自己来同时执行不同的代码段,

如:父进程等待用户请求,生成子进程来完成这个请求

或一个进程要执行不同的程序

fork失败的原因

系统中的进程过多

实际用户拥有的进程数超过了限制

二.进程终止

1.三种退出情况

正常退出且结果正确

正常退出且结果不正确

非正常退出(程序崩溃)

2.main函数为什么一般情况下都要return 0

在我们之前写程序时,int main{ return 0; }就像是固定模板一样,但是,为什么一般都要这么写呢?

为什么要return 0呢?return 1可不可以?

我们以上面这个程序为例,当main函数内return 1时好像也不会报错


实际上,return 0就代表的是: 程序结束,且正常退出

这便是程序的退出码


我们正式引出程序的退出码这一概念!

3.进程的退出码

1).0与非0

0: 代表程序正常退出且结果正确

非0: 代表程序正常退出且结果有误

这里的非零, 可以是1/2/3/4/ ... n(具体n是多少呢?), 且每一个数字都有自己的退出时的含义(结果为什么有误)

解释问题一: 具体n是多少呢,一共有多少退出码??

这里用到strerror函数

#include<stdio.h>
#include<string.h>int main()
{for(int i = 0; i < 150; i++){printf("[%d]: %s\n", i, strerror(i));}return 0;
}

2).linux用指令查看一个进程结束后的退出码

指令: echo $?

本质: $?其实就是bash中的一个变量, bash做为main函数的父进程, main函数退出后返回的退出码给了bash中的$?变量

用一个ls指令来显示一个不存在的文件,会提示No such file or directory, 这就是刚刚用strerror打印出来的下标为2对应的结果

这个进程的退出码的函数是可以自己定义的,但是推荐使用strerror函数中定义的

3).程序崩溃时退出码无意义

当程序崩溃,通常情况下都是没有运行到return的,这个时候将不再关心退出码

//当程序崩溃时退出码将无意义,此时观察一下程序退出码是什么
int main()
{printf("开始实验\n");int *p = NULL;*p = 10;//在这里程序崩溃(野指针)return 0;
}

上述程序在访问野指针的时候崩溃了,此时还没有执行到return语句,可以看到指令框弹出了一条Segmentation fault(段错误)

此时程序崩溃,退出码已经无效,那么Segmentation fault又是谁给弹出来的呢?

其实是信号,操作系统给该进程发送了一个信号,并且强制结束掉了该进程(信号部分先暂时不详细说明)

4).被信号杀掉的进程

被信号杀掉的进程的退出码是无意义的, 因为这本质上与进程崩溃属于同一类, 并没有执行到return语句

4.exit与_exit

1).exit

exit直接结束掉进程, 这并不属于程序崩溃, exit(), 括号中是一个int类型, 表示程序的退出码

//观察exit的退出码
int main()
{printf("传参为5, 观察exit函数结束掉的进程退出码是否为exit传入的参数");exit(5);return 0;
}

2).exit与_exit的区别

exit要包含头文件stdlib.h

_exit要包含头文件unistd.h

//观察exit与_exit的区别
int main()
{printf("传参为5, 观察exit函数结束掉的进程退出码是否为exit传入的参数");_exit(5);return 0;
}

我们可以看到退出码仍然还是我们传入的参数5,这一点与exit一样

但是,有一个地方明显有问题,printf打印的那句话去哪了?

为什么exit就有printf打印的那句话,而_exit却没有?

下面画一张图,来解释以上的问题

exit是C语言的库函数, _exit是系统调用

其实exit的底层就是调用了_exit, exit是_exit的一层封装, 多加入了一些功能

(所以这同时也进一步揭示了缓冲区实际是在库中维护的)

3).exit与return的区别

1.return只有在main函数中, 才可视为进程结束且返回退出码

2.exit在任意位置, 可以直接结束进程, 并且以exit函数参数做为进程结束的退出码

3.return n等同于exit(n), 因为调用main函数且main函数return n后

调用main函数的函数会将main函数的返回值n当作exit的参数, 传给exit

三.进程等待

1.进程等待的经典场景

当父进程有某项任务要交给子进程去做时, 父进程是一定要需要知道子进程完成的情况的

这也就是父进程是一定要接收子进程的退出状态, 并且检验然后才可完由其父进程回收子进程

父进程以进程等待的方式, 等待接收子进程的退出信息, 并且回收子进程

以下介绍两个函数wait与waitpid

2.wait

wait是系统调用, 阻塞式的等待回收子进程,

如果回收子进程失败wait会返回-1, 如果回收子进程成功wait会返回子进程的pid, wait的返回值是pid_t类型

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>//目标,让父进程通过使用wait系统调用阻塞式的等待回收子进程
int main()
{pid_t id = fork();if(id == 0){//子进程int count = 5;while(count){printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());count--;sleep(1);}}else{//父进程printf("I am father, 我开始等待回收子进程\n"); pid_t res = wait(NULL);if(res > 0){printf("I am father, 等待子进程(pid: %d)成功\n", res);}else if(res == -1){printf("I am father, 等待子进程失败\n");}}return 0;
}

wait的参数只有一个, 是一个输出型参数, 当调用wait回收子进程时, 会将子进程的退出状态填入这个输出型参数中

这个输出型参数status是一个32位的数字, 低七位存放信号部分(第8位先不考虑), 次低八位存放退出码

当程序正常退出, 则信号部分将不被关心

当程序异常崩溃, 则退出码部分将不被关系(异常崩溃的本质就是操作系统给这个异常进程发送了信号并且强制终止)

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>//目标,让父进程通过使用wait系统调用阻塞式的等待回收子进程
int main()
{pid_t id = fork();if(id == 0){//子进程int count = 5;while(count){printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());count--;sleep(1);}exit(55);//子进程结束, 退出码为55}else{//父进程int status = 0;printf("I am father, 我开始等待回收子进程\n"); pid_t res = wait(&status);if(res > 0){printf("I am father, 等待子进程(pid: %d)成功\n", res);printf("wait中输出型参数status:%d\n", status);printf("status中的次低八位(退出码): %d\n", (status >> 8) & 0xFF);printf("status中的低七位(信号): %d\n", status & 0x7F);}else if(res == -1){printf("I am father, 我回收子进程失败\n");}}return 0;
}

以上子进程正常退出的情况,  信号不被关心, 默认为0, 如果是被父进程kill掉呢?

进程被信号所杀, 退出码不再关心默认为0

3.waitpid

参数:

第一个参数pid:

pid > 0, 回收为对应pid的子进程

pid == -1, 回收任意一个子进程

第二个参数status:

与wait的参数是同一个, 都是输出型参数

第三个参数options:

options传参为0: 阻塞式的等待回收子进程

options传参为WNOHANG: 非阻塞式的等待回收子进程

(WNOHANG是一个宏, #define WNOHANG 1)

返回值:

回收子进程成功, 返回子进程的pid

回收子进程过程中出错, 返回-1

如果使用非阻塞等待(WNOHANG)式的调用, 子进程还未结束, 返回0

两个宏函数

WIFEXITED(status): 若正常终止子进程返回状态, 则为真 (判断子进程是否是正常退出的)

WEXITSTATUS(status): 若子进程是正常退出的, 则表示正常退出的退出码

重点讲解options, 阻塞/非阻塞等待

阻塞等待:

父进程不在继续向后执行, 而是一直在waitpid调用处阻塞式的等待, 直到回收掉子进程, 父进程才可以执行接下来的逻辑

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>//目标,让父进程通过使用wait系统调用阻塞式的等待回收子进程
int main()
{pid_t id = fork();if(id == 0){//子进程int count = 5;while(count){printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());count--;sleep(1);}//观察进程崩溃(异常退出)//int *p = NULL;//*p = 10;exit(55);}else{//父进程int status = 0;printf("I am father, 我开始等待回收子进程\n"); //阻塞式等待pid_t res = waitpid(id, &status, 0);if(res > 0)//子进程回收成功{if(WIFEXITED(status))//子进程正常退出{printf("I am father, 等待子进程(pid: %d)成功\n", res);printf("子进程正常退出且退出码为: %d\n", WEXITSTATUS(status));}else//子进程异常退出{printf("I am father, 等待子进程(pid: %d)成功\n", res);printf("子进程异常退出且信号为: %d\n", status & 0x7F);}}else if(res == -1)//子进程回收失败{printf("子进程回收失败\n");}}return 0;
}

非阻塞等待:

父进程在等待回收子进程的同时, 仍可以执行自己的逻辑(非阻塞等待采用轮询检测方案)

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<vector>typedef void (*handler_t)(); //函数指针类型void fun1()
{printf("正在执行任务一\n");
}
void fun2()
{printf("正在执行任务二\n");
}
void Load(std::vector<handler_t>& handlers)
{printf("正在装载任务...\n");handlers.push_back(fun1);handlers.push_back(fun2);printf("装载完成!\n");
}//目标: 使用waitpid的非阻塞式调用 并且使用宏函数WIFEXITED WEXITSTATUS
int main()
{std::vector<handler_t> handlers;//定义了一个函数指针数组pid_t id = fork();if(id == 0){//子进程int count = 5;while(count){printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());count--;sleep(1);}//观察进程崩溃(异常退出)//int *p = NULL;//*p = 10;exit(55);}else{//父进程int status = 0;printf("I am father, 我开始等待回收子进程\n"); //非阻塞式等待int quit = 0;//子进程退出状态 --- 0:未退出 1:已退出while(!quit){pid_t res = waitpid(id, &status, WNOHANG);if(res > 0)//子进程回收成功(等待子进程成功且子进程已经结束){if(WIFEXITED(status))//子进程正常退出{printf("I am father, 等待子进程(pid: %d)成功\n", res);printf("子进程正常退出且退出码为: %d\n", WEXITSTATUS(status));quit = 1;}else//子进程异常退出{printf("I am father, 等待子进程(pid: %d)成功\n", res);printf("子进程异常退出且信号为: %d\n", status & 0x7F);quit = 1;}}else if(res == 0)//等待子进程成功但子进程并未结束{//to do somethingprintf("已经等待到子进程, 但子进程仍在执行任务还未结束, 我是父进程, 我将继续执行我的任务, 稍后在回收子进程\n"); if(handlers.empty()){Load(handlers);//装载任务}for(auto elem : handlers){elem();}sleep(1);}else//子进程回收失败{printf("子进程回收失败\n");quit = 1;}} } return 0;
}

一段关于waitpid内部的伪代码

(便于理解阻塞与非阻塞的区别)

4.wait/waitpid的本质

wait/waitpid都是系统调用, 本质就是去子进程的PCB(task_struct)中读取两个东西(即子进程的退出码)

int exit_code/int exit_signal

当进程结束时, main函数最终return一个数, 之后会调用exit 将return的这个数 做为exit的参数, 而exit的底层又是调用的_exit

所以最终会由系统调用将进程的退出状态(即退出码与信号写入到进程的PCB中, 即int exit_code与int exit_signal)

然后由父进程调用wait/waitpid系统调用去子进程的PCB中读取这两个值, 然后再以某种形式写入到status中

【Linux】进程控制 —— 进程创建/终止/等待相关推荐

  1. linux下多线程的创建与等待详解 【转载】

    linux下多线程的创建与等待详解 http://blog.chinaunix.net/uid-23842323-id-2656572.html 所有线程都有一个线程号,也就是Thread ID.其类 ...

  2. Linux——进程控制:创建、终止、等待、替换

    进程创建 fork #include <unistd.h> pid_t fork(void); 操作系统做了什么? 调用fork之后,内核的工作: 分配新的内存块和内核数据结构给子进程 将 ...

  3. 进程创建-终止-等待-替换

    文章目录 进程创建 进程终止 进程等待 进程替换 进程创建 意义: 进程运行时常会出现崩溃,为了避免父进程出现奔溃,则会创建子进程去代替父进程处理事务,即使崩溃并不会影响到父进程的正常运行,再创建一个 ...

  4. linux c控制进程并发量,浅谈Linux环境下并发编程中C语言fork()函数的使用

    由fork创建的新进程被称为子进程(child process).fork函数被调用一次,但返回两次.子进程的返回值是0,而父进程的返回值则是新进程的进程ID.将子进程ID返回给父进程的理由是:因为一 ...

  5. 进程控制——进程等待,程序替换

    目录 1.进程等待 1.1进程等待必要性 1.2进程等待的方法 1.3获取子进程status ​2.程序替换 2.1替换原理 2.2替换函数 2.3execl使用实例: 2.4 minishell实现 ...

  6. Linux中httpd353错误,linux - 由于控制进程退出并显示错误代码,因此httpd.service的作业失败 - 堆栈内存溢出...

    更改00-nova-placement-api.conf ,我正在虚拟机中研究00-nova-placement-api.conf , 我想重启httpd : systemctl restart ht ...

  7. Linux - 进程控制(进程替换)

    0.引入 创建子进程的目的是什么? 就是为了让子进程帮我执行特定的任务 让子进程执行父进程的一部分代码 如果子进程想执行一个全新的程序代码呢? 那么就要使用进程的程序替换 为什么要有程序替换? 也就是 ...

  8. 操作系统学习笔记02【进程控制——进程互斥的硬件实现方法】【自用】

    1.进程控制 什么是进程控制? 如何实现进程控制: 为了避免把某一进程pbc从一个队列转移到另一个队列,但是并没有把pbc内的状态标志改为新的对应状态,从而导致pcb的状态标志与实际所处队列不一样这一 ...

  9. 【Linux】Linux进程控制 --- 进程创建、终止、等待、替换、shell派生子进程的理解…

    柴犬: 你好啊,屏幕前的大帅哥or大美女,和我一起享受美好的今天叭

最新文章

  1. python字符串写入excel-python 操作 Excel 之写入
  2. Linux学习笔记:Linux分区
  3. 数据中心基础运维人员的职业规划
  4. 这个浮躁的年代,时刻提醒自己
  5. 科技核心期刊目录_中医学2019年版中国科技核心期刊目录(附影响因子)
  6. 如何修改服务器上的端口号,如何修改远程服务器端口号
  7. 圆环和环形是一样的吗_EXCEL圆环图与柱形图的组合
  8. Oracle 数据库连接工具
  9. 升余弦滤波器与无码间串扰(二)
  10. matlab中pwm占空比计算代码,如何计算pwm波占空比
  11. 均匀分布的期望和方差
  12. 计算机表格斜杠怎么打,如何在excel表格中绘制斜线并上下打字
  13. Spring Security(14)——权限鉴定基础
  14. 亚马逊qa是什么意思_“亚马逊成就”是什么意思?
  15. 关于mac重启/home目录丢失解决方案
  16. 壁纸 - 精选海量高清图片与桌面背景
  17. Intel汇编-无符号整数除法
  18. LeetCode hot-100 简单and中等难度,61-70.
  19. 【华为思科】访问web服务器
  20. 概述 | 全景图像拼接技术全解析

热门文章

  1. 树状结构 | 北邮OJ | 109. 中序遍历树
  2. java基于springboot+vue+elementui的饭店点菜外卖平台 前后端分离
  3. 目前比较好的外汇交易平台有哪些
  4. vue太阳系模型,vue solar-system
  5. SAP Field Service Management 和微信集成的案例分享和实现介绍
  6. 软件和资源收集(一)
  7. linux系统自动校时设置,linux系统自动校时
  8. AddressBook 地址簿 (电话簿) 访问与修改-IOS开发
  9. iTOP-IMX6Q开发板QtE4.7例程源码-音频和视频
  10. 深度学习中用到的numpy命令数组运算部分命令汇总