Linux学习记录——십사 进程控制(1)
文章目录
- 1、进程创建
- 1、fork函数
- 2、进程终止
- 1、情况分类
- 2、如何理解进程终止
- 3、进程终止的方式
- 3、进程等待
1、进程创建
1、fork函数
fork函数从已存在进程中创建一个新进程,新进程为子进程,原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1。fork会有两个返回值,这个上一篇已经写了原因。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程(不会完全一样)
添加子进程到系统进程列表当中
fork返回,开始调度器调度
两个进程都没有修改数据的时候,都指向同一块物理内存空间,只有一方尝试修改数据的时候,才会再开一个空间。
写时拷贝
通常,父子代码共享,父子在不写入时,数据也是共享的,父进程按照自己的模板给子进程创建了虚拟内存,创建了页表,然后指向物理内存中同样的数据,当子进程修改数据时,系统就会拷贝一下子进程的数据,进行修改,并改变页表的映射关系,最后就指向了一个新的物理内存空间。
存在写时拷贝的意义在于,系统不允许不高效的程序出现,父进程中子进程不需要的数据子进程也不会去读取,当子进程要用到另外的空间时,写时拷贝才会出现,本质上这是一种资源筛选。
fork常规用法:
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
fork调用失败的原因:
系统中有太多进程
实际用户的进程数超过了限制
2、进程终止
1、情况分类
程序正常退出,可以有结果正确或者不正确地退出。
程序崩溃,本质是进程因为某些原因,收到了操作系统给的信号,比如之前的kill -9。
对于结果是否正确,可以看程序退出码。之前我们会习惯地写,int main() return 0;如果结果正确就返回0,如果不正确,就会返回非0,这个不正确的信息会展现给用户,来判断程序的错误。
1 mytest:mytest.c2 gcc -o $@ $^ 3 .PHONY:clean4 clean:5 rm -f mytest
$@表示目标文件mytest, $^表示依赖文件mytest.c
1 #include <stdio.h>2 3 int add_to_top(int top)4 {5 int sum = 0;6 for(int i = 0; i < top; ++i)7 {8 sum += i;9 }10 return sum;11 }12 13 int main()14 {15 int result = add_to_top(100);16 if(result == 5050) return 0;17 else return 1; 18 }
一段简短的代码。然后变成可执行程序。因为没写输出,所以我们看不到结果,但是可以通过echo $?命令来查看程序退出码
但是多次使用后就没有用了,因为这个命令只保留最近一次的退出码。因为上一个echo $?执行成功,所以返回0。
操作系统对于不同的错误都有对应的退出码,但给到用户的不能是一个数字,而是给错误信息。看一下具体的退出码。
1 #include <stdio.h>2 #include <string.h> 3 4 int add_to_top(int top)5 {6 int sum = 0;7 for(int i = 0; i < top; i++)8 {9 sum += i;10 }11 return sum;12 }13 14 int main()15 {16 for(int i = 0; i <= 200; i++)17 {18 printf("%d: %s\n", i, strerror(i));19 }20 //int result = add_to_top(100);21 // if(result == 5050) return 0;22 //else return 1;23 }
总共提供了133个错误代码。
但并不是退出码和错误信息一定会对应。
2、如何理解进程终止
进程退出时,系统就需要释放对应的内核数据结构 + 代码和数据
3、进程终止的方式
除了main函数return结束,我们也可以用exit函数结束。
可以直接退出,exit里面的数字就是退出码。并且即使exit在调用的函数里面,也会直接退出,不再执行下面的代码。所以exit在代码的任何地方都可以退出进程。需要加上头文件stdlib.h。
另外一个
貌似和exit一样。但从内部来讲,exit是进行完缓冲区的数据,进行完操作后才退出,而_exit是不管不顾,直接找系统干掉这个进程,不等缓冲区刷新。库函数实现的代码里,exit是封装了_exit。
3、进程等待
子进程退出时,如果父进程不去回收,就会变成僵尸进程,会造成内存泄漏。僵尸状态的进程是无法被杀死的。
一个进程的执行是要结果的,子进程结束后用户得需要知道它的状态,代码正常跑完可以通过退出码来知道,运行异常可以通过抛出的信号来知道,所以要想知道子进程执行的结果,就要知道退出码和是否抛出异常。
所以进程等待就是通过系统调用,获取子进程退出码或者退出信号的方式,顺百年释放内存问题。
进程等待有两种方式。wait和waitpid。wait会等待所有父进程创建的子进程,如果不传参,传NULL,那么就不管结果,只回收。可以写这样一段代码来展现等待过程。
30 pid_t id = fork();31 if(id == 0)32 {33 //子进程34 int cnt = 0;35 while(cnt)36 {37 printf("我是子进程, 我还活着, 我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());38 sleep(1);39 }40 exit(0);41 }42 sleep(10);43 //父进程44 pid_t ret_id = wait(NULL);45 printf("我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d\n", getpid(), getppid(), ret_id);46 sleep(5);47 return 0;
子进程退出时,父进程在等待,所以就可以回收子进程。
如果要获取退出结果,就要用到waitpid。
参数里,pid > 0,表示等待指定的进程;pid = -1,等待任一个子进程,与wait等效。如果等待成功,就会把等待的这个进程的pid返回,失败就返回-1.
第二个参数status是输出型参数,用来获取子进程的退出状态,它的退出状态就是上面3个结果,代码正常结束和抛出异常,所以这个参数就是来接收这些的。关于退出时返回的信号,可以用kill -l查看,总共64个信号,前面一半是常用的,不过没有0号信号,所以信号也是数字。乍一看status是要接收2个整数,但实际上不应以整数角度看待它,要以位图结构来看待。位图结构简而言之就是看二进制位,status是4个字节,32个比特位。
改一下之前的代码
上面cnt是5。exit括号里是10,但是status的结果并不是10.这时候出现数字的把它写成二进制数,左面16不看,后面16个,左面的8个是退出状态,也就是高地址的8位,这里的就是return的或者exit括号里的数字,最低的7个位如果是0,就是正常退出,然后再看退出码来判断结果是否正确。最后还剩一位是core dump标志。那我们获取这两个数。
这样code就是结果是否正确,signal就是正常退出。如果在子进程那里有一些异常,程序打印一次就退出了,父进程就回收它了,比如野指针等问题。把while条件改成while(1),也可以用kill -9杀死程序,父进程就回收它了。
父进程是如何获取子进程信息的?一个进程有自己的pcb,地址空间等等,进程的pcb,也就是task_struct结构体,在结构体里有两个变量,对应的就是返回值和退出信号。当进程结束时,系统会把pcb维护起来。waitpid是系统调用接口,能够访问到这个进程的pcb,然后把数据拿到,再返回给用户即可。
子进程在没有退出前,父进程会一直等待子进程死亡,这也就是一种阻塞等待。此时父进程不在运行状态,所以父进程没有运行,它在阻塞队列中待着。等到子进程结束后,pcb中某一个指向父进程的指针会去找父进程,父进程因此用阻塞状态变成运行状态,来到运行队列中,然后调用waitpid回收子进程。
用wait的时候默认是阻塞式调用,而waitpid则是在等待过程中让父进程去做其他事,保持运行状态,这是非阻塞轮询。非阻塞时调用时有三个状态,一个正常结束,一个出错,一个正在运行。如果成功,就返回子进程pid;如果第三个参数被设置,那就是非阻塞式调用,那么进程存在且正在运行,就返回0,出错返回-1.
改一下代码,变成非阻塞
为了让父进程做其他事情,还可以有其他办法,先放下现在的代码:
1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <unistd.h>5 #include <sys/types.h>6 #include <sys/wait.h>
19 int main()20 {21 /*for(int i = 0; i <= 200; i++)22 {23 printf("%d: %s\n", i, strettot(i));24 exit(123);25 }*/26 //int result = add_to_top(100);27 //if(result == 5050) return 0;28 //else return 1;29 pid_t id = fork();30 if(id == 0)31 {32 //子进程33 int cnt = 5;34 while(cnt)35 {36 printf("我是子进程, 我还活着, 我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());37 sleep(1);38 }39 if(1 == 1) exit(0);40 exit(10);41 }42 //父进程43 while(1)44 {45 int status = 0;46 pid_t ret_id = waitpid(id, &status, WNOHANG);47 if(ret_id < 0)48 {49 printf("waitpid error!\n"); 50 exit(1);51 }52 else if(ret_id == 0)53 {54 printf("子进程还没有退出,我在做做其他事情\n");55 sleep(1);56 continue;57 }58 else59 {60 printf("我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n", getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);61 break;62 }63 64 }65 }
人为放一些任务函数让父进程去调用, 并且写一些别的可调用的函数让父进程去做点事
19 #define TASK_NUM 1020 //预设一些任务21 void sync_disk()22 {23 printf("这是一个刷新数据的任务\n");24 }25 26 void sync_log()27 {28 printf("这是一个同步日志的任务\n");29 }30 31 void net_send()32 {33 printf("这是一个网络发送的任务\n");34 }35 36 //要保存的任务相关的37 typedef void(*func_t)();38 func_t other_task[TASK_NUM] = {NULL};39 40 41 int LoadTask(func_t func)42 {43 int i = 0;44 for(; i < TASK_NUM; i++)45 {46 if(other_task[i] == NULL) break;47 }48 if(i == TASK_NUM) return -1;49 else other_task[i] = func;50 return 0;51 }52 53 void InitTask()54 {55 for(int i = 0; i < TASK_NUM; i++)56 {57 other_task[i] = NULL;58 }59 LoadTask(sync_disk);60 LoadTask(sync_log);61 LoadTask(net_send);62 }63 64 void RunTask()65 {66 for(int i = 0; i < TASK_NUM; i++)67 {
W> 68 if(other_task[i] == NULL) continue;69 other_task[i]();70 }71 }
在//父进程之后我们这样写
96 InitTask();97 //父进程98 while(1)99 {100 int status = 0;101 pid_t ret_id = waitpid(id, &status, WNOHANG);102 if(ret_id < 0)103 {104 printf("waitpid error!\n");105 exit(1);106 }107 else if(ret_id == 0)108 {109 printf("子进程还没有退出,我在做做其他事情\n");110 RunTask();111 sleep(1);112 continue;113 }114 else115 {116 printf("我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n", getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);117 break;118 }119 }
之前获取信号和返回码的时候,我们是自己的写的代码,也可以用给的宏来获取。
下一篇继续写进程相关的知识。
结束。
相关文章:

Linux学习记录——십사 进程控制(1)
文章目录1、进程创建1、fork函数2、进程终止1、情况分类2、如何理解进程终止3、进程终止的方式3、进程等待1、进程创建 1、fork函数 fork函数从已存在进程中创建一个新进程,新进程为子进程,原进程为父进程。 #include <unistd.h> pid_t fork(vo…...

使用 create-react-app 脚手架搭建React项目
❀官网 1、安装脚手架:npm install -g create-react-app 2、查看版本:create-react-app -V !!!注意 Node版本必须是14以上,不然会报以下错误。 3、创建react项目(项目名不能包含大写字母&…...

inquirerjs
inquirerjs inquirerjs是一个用来实现命令行交互界面的工具集合。它帮助我们实现与用户的交互交流,比如给用户一个提醒,用户给我们一个答案,我们根据用户的答案来做一些事情,典型应用如plop等生成器工具。 npm install inquirer…...
[数据库]内置函数
●🧑个人主页:你帅你先说. ●📃欢迎点赞👍关注💡收藏💖 ●📖既选择了远方,便只顾风雨兼程。 ●🤟欢迎大家有问题随时私信我! ●🧐版权:本文由[你帅…...

shell基本知识
为什么学习和使用Shell编程 什么是Shell shell的起源 shell的功能 shell的分类 如何查看当前系统支持的shell? 如何查看当前系统默认shell? 驼峰语句 shell脚本的基本元素 shell脚本编写规范 shell脚本的执行方式 shell脚本的退出状态 …...
Http长连接和短连接
http1.0以前,默认使用的是短连接,客户端与服务器之间每进行一次http操作,就会建立一次连接,例如,打开一个网页,包括html文件,js,css,每获取一次资源,就需要进…...

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作
[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作 什么是数据库的表以及表空间 在MySQL中,一个数据库可以包含多个表,每个表是由若干个列(column)和行(row)组成的。表是存储数据的基本…...

Git版本控制工具(详解)
Git版本控制工具 Git常见命令速查表 集中式版本控制 cvs和svn都是属于集中式版本控制系统 他们的主要特点是单一的集中管理服务器 保存所有文件的修订版本协同开发人员通过客户端连接到这台服务器 取出最新的文件或者提交更新 优点每个人都可以在一定程度上看到项目中的其他…...
408考研计算机之计算机组成与设计——知识点及其做题经验篇目2:指令系统
今天我们来讲一讲指令系统里面的知识点以及做题技巧 1、定义 考点1:指令定义 指令是指示计算机执行某种操作的命令,一台计算机的所有指令的集合构成该机的指令系统,也称为指令集。指令系统是指令集体系结构ISA中最核心的部分,ISA…...

Java语法中的方法引用::是个什么鬼?
1.函数式接口 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法(通俗来说就是只有一个方法要去被实现,因此我们也能通过这个去动态推断参数类型),但是可以拥有多个非抽象方法的接口。函数式接…...

【使用vue init和vue create的区别以及搭建vue项目的教程】
vue init 是vue-cli2.x的初始化方式,可以使用github上面的一些模板来初始化项目 webpack是官方推荐的标准模板名 使用方式:vue init webpack 项目名称 例如使用github上面electron-vue的模板使用方式:vue init electron-vue 项目名称教程目…...

二、HTTP协议02
文章目录一、HTTP状态管理Cookie和Session二、HTTP协议之身份认证三、HTTP长连接与短连接四、HTTP中介之代理五、HTTP中介之网关六、HTTP之内容协商七、断点续传和多线程下载一、HTTP状态管理Cookie和Session HTTP的缺陷无状态。Cookie和Session就用来弥补这个缺陷的。 Cooki…...

免费Api接口汇总(亲测可用,可写项目)
免费Api接口汇总(亲测可用)1. 聚合数据2. 用友API3. 天行数据4. Free Api5. 购物商城6. 网易云音乐API7. 疫情API8. 免费Api合集1. 聚合数据 https://www.juhe.cn/ 2. 用友API http://iwenwiki.com/wapicovid19/ 3. 天行数据 https://www.tianapi.com…...

12.并发编程
1.并发并发:逻辑流在时间时重叠构造并发程序:进程:每个逻辑控制流是一个进程,由内核调度和维护进程有独立的虚拟地址空间,想要通信,控制流必须使用某种显式的进程间通信机制(IPC)I/O多路复用:程…...

C/C++指针与数组(一)
预备知识 1、数据的存储 2、基本内建类型 1)类型的大小 C offers a flexible standard with some guaranteed minimum sizes, which it takes from C: A short integer is at least 16 bits wide.An int integer is at least as big as short.A long integer is a…...

Android使用移动智能终端补充设备标识获取OAID
官网http://www.msa-alliance.cn/col.jsp?id120首先到官网注册账号,申请下载相关sdk和授权证书2.把 oaid_sdk_x.x.x.aar 拷贝到项目的 libs 目录,并设置依赖,其中x.x.x 代表版本号3.supplierconfig.json 拷贝到项目 assets 目录下࿰…...
极目智能与锐算科技达成战略合作,4D毫米波成像雷达助力智能驾驶落地
近日,智能驾驶方案提供商武汉极目智能技术有限公司(以下简称“极目智能”)宣布与毫米波成像雷达公司锐算(上海)科技有限公司(以下简称“锐算科技”)达成战略合作,双方将合作开发基于…...

OpenCV基础(一)
1.认识图像(彩色图中每一个像素点都包含三个颜色通道RGB,数值范围为0~255,0代表黑色,255代表白色) import cv2 #opencv 读取的格式为BGRimg cv2.imread(cat.png) #读取图像 cv2.imshow(cat, img) #显示图像img&#x…...
pinia 的使用(笔记)
文章目录1. Pinia 与 Vuex 的区别2. pinia 安装与搭建3. pinia 的使用3.1 基本使用3.2 订阅状态3.3 订阅 actions1. Pinia 与 Vuex 的区别 Pinia 是 Vue 的状态管理库,相当于 Vuex 取消了 mutations,取消了 Module 模块化命名空间现在的 pinia 采用的是…...

DolphinDB 机器学习在物联网行业的应用:实时数据异常率预警
数据异常率预警在工业安全生产中是一项重要工作,对于监控生产过程的稳定性,保障生产数据的有效性,维护生产设备的可靠性具有重要意义。随着大数据技术在生产领域的深入应用,基于机器学习的智能预警已经成为各大生产企业进行生产数…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...