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

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函数从已存在进程中创建一个新进程&#xff0c;新进程为子进程&#xff0c;原进程为父进程。 #include <unistd.h> pid_t fork(vo…...

使用 create-react-app 脚手架搭建React项目

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

inquirerjs

inquirerjs inquirerjs是一个用来实现命令行交互界面的工具集合。它帮助我们实现与用户的交互交流&#xff0c;比如给用户一个提醒&#xff0c;用户给我们一个答案&#xff0c;我们根据用户的答案来做一些事情&#xff0c;典型应用如plop等生成器工具。 npm install inquirer…...

[数据库]内置函数

●&#x1f9d1;个人主页:你帅你先说. ●&#x1f4c3;欢迎点赞&#x1f44d;关注&#x1f4a1;收藏&#x1f496; ●&#x1f4d6;既选择了远方&#xff0c;便只顾风雨兼程。 ●&#x1f91f;欢迎大家有问题随时私信我&#xff01; ●&#x1f9d0;版权&#xff1a;本文由[你帅…...

shell基本知识

为什么学习和使用Shell编程 什么是Shell shell的起源 shell的功能 shell的分类 如何查看当前系统支持的shell&#xff1f; 如何查看当前系统默认shell&#xff1f; 驼峰语句 shell脚本的基本元素 shell脚本编写规范 shell脚本的执行方式 shell脚本的退出状态 &#xf…...

Http长连接和短连接

http1.0以前&#xff0c;默认使用的是短连接&#xff0c;客户端与服务器之间每进行一次http操作&#xff0c;就会建立一次连接&#xff0c;例如&#xff0c;打开一个网页&#xff0c;包括html文件&#xff0c;js&#xff0c;css&#xff0c;每获取一次资源&#xff0c;就需要进…...

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作

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

Git版本控制工具(详解)

Git版本控制工具 Git常见命令速查表 集中式版本控制 cvs和svn都是属于集中式版本控制系统 他们的主要特点是单一的集中管理服务器 保存所有文件的修订版本协同开发人员通过客户端连接到这台服务器 取出最新的文件或者提交更新 优点每个人都可以在一定程度上看到项目中的其他…...

408考研计算机之计算机组成与设计——知识点及其做题经验篇目2:指令系统

今天我们来讲一讲指令系统里面的知识点以及做题技巧 1、定义 考点1&#xff1a;指令定义 指令是指示计算机执行某种操作的命令&#xff0c;一台计算机的所有指令的集合构成该机的指令系统&#xff0c;也称为指令集。指令系统是指令集体系结构ISA中最核心的部分&#xff0c;ISA…...

Java语法中的方法引用::是个什么鬼?

1.函数式接口 函数式接口&#xff08;Functional Interface&#xff09;就是一个有且仅有一个抽象方法&#xff08;通俗来说就是只有一个方法要去被实现&#xff0c;因此我们也能通过这个去动态推断参数类型&#xff09;&#xff0c;但是可以拥有多个非抽象方法的接口。函数式接…...

【使用vue init和vue create的区别以及搭建vue项目的教程】

vue init 是vue-cli2.x的初始化方式&#xff0c;可以使用github上面的一些模板来初始化项目 webpack是官方推荐的标准模板名 使用方式&#xff1a;vue init webpack 项目名称 例如使用github上面electron-vue的模板使用方式&#xff1a;vue init electron-vue 项目名称教程目…...

二、HTTP协议02

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

免费Api接口汇总(亲测可用,可写项目)

免费Api接口汇总&#xff08;亲测可用&#xff09;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.并发并发&#xff1a;逻辑流在时间时重叠构造并发程序&#xff1a;进程&#xff1a;每个逻辑控制流是一个进程&#xff0c;由内核调度和维护进程有独立的虚拟地址空间&#xff0c;想要通信&#xff0c;控制流必须使用某种显式的进程间通信机制(IPC)I/O多路复用&#xff1a;程…...

C/C++指针与数组(一)

预备知识 1、数据的存储 2、基本内建类型 1&#xff09;类型的大小 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首先到官网注册账号&#xff0c;申请下载相关sdk和授权证书2.把 oaid_sdk_x.x.x.aar 拷贝到项目的 libs 目录&#xff0c;并设置依赖&#xff0c;其中x.x.x 代表版本号3.supplierconfig.json 拷贝到项目 assets 目录下&#xff0…...

极目智能与锐算科技达成战略合作,4D毫米波成像雷达助力智能驾驶落地

近日&#xff0c;智能驾驶方案提供商武汉极目智能技术有限公司&#xff08;以下简称“极目智能”&#xff09;宣布与毫米波成像雷达公司锐算&#xff08;上海&#xff09;科技有限公司&#xff08;以下简称“锐算科技”&#xff09;达成战略合作&#xff0c;双方将合作开发基于…...

OpenCV基础(一)

1.认识图像&#xff08;彩色图中每一个像素点都包含三个颜色通道RGB&#xff0c;数值范围为0~255&#xff0c;0代表黑色&#xff0c;255代表白色&#xff09; 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 的状态管理库&#xff0c;相当于 Vuex 取消了 mutations&#xff0c;取消了 Module 模块化命名空间现在的 pinia 采用的是…...

DolphinDB 机器学习在物联网行业的应用:实时数据异常率预警

数据异常率预警在工业安全生产中是一项重要工作&#xff0c;对于监控生产过程的稳定性&#xff0c;保障生产数据的有效性&#xff0c;维护生产设备的可靠性具有重要意义。随着大数据技术在生产领域的深入应用&#xff0c;基于机器学习的智能预警已经成为各大生产企业进行生产数…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...