LINUX基础 [三] - 进程创建
目录
前言
进程创建的初次了解(创建进程的原理)
什么是fork函数?
初识fork函数
写时拷贝
fork函数存在的意义
fork调用失败的原因
进程终止
运行完毕结果不正确
main函数返回
库函数函数exit
系统调用接口_exit
进程异常终止
进程等待
进程等待是什么
进程等待为什么要进行
进程等待怎么做
阻塞和非阻塞轮询
前言
上节我们已经讲了进程的概念了,大家应该对进程有感悟同时也有更深入的思考,上节课介绍了那么多进程,但是进程该怎么创建呢,今天就来给大家讲解一下 进程的创建
进程创建的初次了解(创建进程的原理)
创建新进程在Linux的下是由父进程来完成的,创建完成的新进程是子进程。
新进程的地址空间有两种可能性:
子进程是父进程的复制品(除了PID和task_struct是子进程自己的,其余的都从父进程复制而来)
子进程装入另一个程序。
在Linux下的fork函数用于创建一个新的进程,使用fork函数来创建一个进程时,子进程只是完全复制父进程的资源。这样得到的子进程和父进程是独立的,具有良好的并发性。但是进程间通信需要专门的机制。
什么是fork函数?
之前我们在Linux下启动一个进程的时候利用的是./可执行程序,那是否有其他办法去启动一个进程呢?
初识fork函数
当然是有的,那就是使用fork()这个函数。在使用之前呢我们要先去查看一下这个函数该如何使用------ 使用man 手册查询一下 fork 函数的使用
man 2 fork
- 可以看到,这个函数的功能就是去创建一个子进程,其返回值为
pid_t - 注意:这里的 pid_t 类型 是无符号整数

函数说明:
-
通过复制调用进程创建一个新进程。
-
fork 有两个返回值。
-
父子进程代码共享,数据各自私有一份(采用写时拷贝)。
接下来,我们来测试一段代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // getpid, getppid, fork, sleep
#include <sys/types.h> // getpid, getppidint main()
{printf("before: I am a process\n");fork();printf("after: 创建一个新进程\n");return 0;
}
调用fork函数后,内核做了下面的工作:
1、创建了一个子进程的PCB结构体、并拷贝一份相同的进程地址空间和页表(PCB结构体中的一个指针指向该空间)
2、子进程和父进程起初共享代码和数据,并且页表中的虚拟地址和物理地址的映射关系是一样的,所以也指向相同的物理空间。
3、fork返回后将子进程添加到系统的进程列表中,由调度器调用(每个进程开始自己的旅程)
4、一旦其中任意一方尝试修改数据,那么就会发生写时拷贝,会开辟一块新的物理内存,然后改变页表的映射关系。
写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

当父进程形成子进程之后,子进程写入,发生写时拷贝,重新申请空间,进行拷贝,修改页表(OS)
但是,我们怎么知道发生了写时拷贝呢?写时拷贝的内容都是由操作系统来完成的
其实父进程创建子进程的时候首先将自己的读写权限改成只读,然后再创建子进程。
此时是操作系统在做,用户并不知道,而且用户可能会对某一数据进行写入,这时页表转换就会出现问题,操作系统就会介入,就触发了我们重新申请内存拷贝内容的策略机制
fork函数存在的意义
fork函数常规用法:
1、一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
2、一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。 (进程替换
fork调用失败的原因
系统中有太多的进程
实际用户的进程数超过了限制
进程终止
问题引入:为什么main函数要返回0?返回多少的意义是什么???
成功只有一种情况,但是失败可以有无数的原因和理由!! 所以main函数的本质是进程运行时是否是正确的结果,如果不是,可以用不同的数字表示不同的出错原因!
进程退出场景:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
运行完毕结果不正确
正常终止(可以通过 echo $? 查看进程退出码): $?->保存最后一次进程退出的退出码
1. 从main返回
2. 调用exit
3. _exit
main函数返回
进程中,谁会关心我的运行情况呢??——>父进程 !
我们之前写代码中,main函数只能return 0吗?
答案是肯定不是!
在多进程环境中,我们创建子进程的目的就是协助父进程办事,但是父进程怎么知道子进程把事情办得怎么样?所以父进程要知道子进程办的怎么样,就有了退出码,而main函数的返回值,就是进程的退出码!
其实main函数本质上也是一个被别人调用的函数,所以他return的结果其实是想告诉他的父进程自己的运行情况。
返回 0 就表示成功,其他数字就表示进程失败的原因,每个不同的数字代表不同的原因!
我们可以通过 strerror 函数来直接查看每个数字代表的意义

它可以返回描述错误码的字符串
#include<stdio.h>
#include<string.h>int main()
{for(int i = 0; i < 200; i++){printf("%d: %s\n", i, strerror(i));}return 0;
}
我们打印结果来看看

退出码 0 正好对应的是成功!

当我们134位置处时,发现已经没有错误信息了。
注意:错误码我们可以自己自定义!
main函数的退出码是可以被父进程获取的,用来判断子进程的运行结果
int main()
{return 31;
}
我们可以直接用 echo $? 指令查看进程的退出码:

我们可以发现指令:echo $? 返回的是上一个进程的错误码。当读取了一次之后,再读取就变成了0
库函数函数exit

exit和return的区别:return和exit在main函数里是等价的,因为exit表示退出进程,而main函数恰好执行完return也会退出进程,但是return在其他函数中代表的是函数返回。
系统调用接口_exit
#include void _exit(int status);参数:status 定义了进程的终止状态,父进程通过wait来获取该值

exit与_exit的区别
首先他们二者都可以让进程终止,并且使用方法也一样,那他们到底有什么区别呢?我们用代码来一探究竟!
//代码一:
int main()
{printf("Hello");exit(0);
}......
//代码二:
int main()
{printf("Hello");_exit(0);
}


为什么会出现这种情况呢?
printf打印如果不使用\n换行的话,数据会被存储到缓冲区里。exit函数会帮助我们刷新缓冲区的数据,然而_exit函数不会。因为exit函数在调用exit之前将所有缓存数据都写入了,所以在终止进程时,会将数据打印在屏幕上!

exit比_exit多做了一层最重要的工作就是刷新缓存,我们还可以得出另一个结论就是:缓冲区绝对不在内核区!!因为如果在内核区的话,系统调用的_exit在终止的时候也必然会把缓冲区刷新一下,因为现代操作系统不做任何浪费时间和空间的事情,所以肯定不是由内核维护缓存区,而是由用户区在维护!!(_exit压根看不到缓冲区,所以这个工作只能有exit去完成)
进程异常终止
用退出码可以告诉父进程自己的执行情况,那如果是异常中止了呢??那就连运行完毕这个条件都完成不了,更别谈结果是否正确了,所以我们可以知道异常必然是最先需要被知道的!因为一旦异常了,一般代码都没跑完,即使跑完了,错误码也不能让人相信,此时退出码就没有意义了!
举个例子:就好比我们平时考试一样,你考不好的时候大家会关心你为啥考不好,但如果你作弊了,性质就变了,即考得再好都让人觉得不可相信。
所以进程结束后应该优先判断该进程是否异常了,然后才能确定退出码能不能用!!
// 当我们在运行这样的代码时int a = 100;
a /= 0;
......
int *p = NULL;
*p = 100;
......
第一种情况: Floating point exception
第二种情况: Segmentation fault
当然不止这两个情况,但是它们都会让程序进程异常终止!
其实一旦程序出现了异常,操作系统就是通过 信号 的方式来杀掉这个进程!

而我们的前面两种情况正好对应了kill -8 和 kill -11
类似除0、野指针这样的错误,会触发一些硬件级别的错误,比如除0,cpu的状态寄存器会出现溢出的错误,而野指针,也就是们即将访问的虚拟地址在页表中找不到对应的映射,或者是建立的映射关系只有只读权限,反正最终会转化成一些硬件级别的信号来给操作系统。
所以,父进程需要关心子进程为什么异常,以及发生何种异常,系统会通过信号来告诉我们的进程发生了异常!!
while(1)
{printf("hello Linux, pid: %d\n", getpid());sleep(1);
}

所以我们最关键的是要看父进程是否收到了信号,如果没有收到就没有异常(具体如何收到,就涉及到进程等待的知识)
进程等待
进程等待是什么
首先在开始之前我们提个问题,到底什么是进程等待? - 是什么
进程等待的概念:
- 我们通常说的进程等待其实是通过wait/waitpid的方式,让父进程(一般)对子进程进行资源回收的等待过程,父进程必须等待这个子进程结束后,处理它的代码和数据!
进程等待为什么要进行
在了解完进程等待的概念后,新的问题出现了,我们为什么要进行进程等待?-为什么
- 在前面的文章中讲过,子进程退出,父进程如果不管不顾,就可能造成“僵尸进程”的问题,进而会造成内存泄露。
- 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的 kill -9 指令也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
进程等待怎么做
我们进行了进程等待分析,发现进程等待非常的有必要。那么进程等待具体是怎么做的? - 如何做
父进程通过调用wait/waitpid方法来解决僵尸进程回收问题,以及获取子进程退出情况
wait方法
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int* status);
-
返回值:成功,返回被等待进程的 pid,失败返回-1。
-
参数:输出型参数,获取子进程的退出状态,不关心则可以设置成为 NULL。
waitpid方法
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int* status, int options);
返回值:
- 当正常返回的时候 waitpid 返回等待到的子进程的进程 ID;
- 如果设置了选项 WNOHANG,而调用的过程中没有子进程退出,则返回0;
- 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在。
pid:
- pid = -1 表示等待任意一个子进程。与 wait 等效;
- pid > 0 表示等待进程 ID 与 pid 相等的子进程。
status:
- WIFEXITED(status):查看子进程是否正常退出。若为正常终止子进程返回的状态,则为真;WEXITSTATUS(status):查看进程的退出码。若非零,提取子进程的退出码。
options:
- 0:表示父进程以阻塞的方式等待子进程,即子进程如果处在其它状态,不处在僵尸状态(Z状态),父进程会变成 S 状态,操作系统会把父进程放到子进程 PCB 对象中维护的等待队列中,以阻塞的方式等待子进程变成僵尸状态,当子进程运行结束,操作系统会检测到,把父进程重新唤醒,然后回收子进程;
- WNOHANG:非阻塞轮询等待,若 pid 指定的子进程没有结束,处于其它状态,则 waitpid() 函数返回0,不予等待。若正常结束,则返回该子进程的 ID。
小Tips:wait 和 waitpid 都只能等待该进程的子进程,如果等待了其它的进程那么就会出错。
阻塞和非阻塞轮询
父进程只等待一个进程(阻塞式等待)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ pid_t id = fork(); if(id < 0) { perror("fork"); return 1; } else if(id == 0) { // child int cnt = 5; while(cnt) { printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--); sleep(1); } exit(0); } else { int cnt = 10; // parent while(cnt) { printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--); sleep(1); } int ret = wait(NULL); if(ret == id) { printf("wait success!\n"); } sleep(5); } return 0;
}
前五秒父子进程同时运行,紧接着子进程退出变成僵尸状态,五秒钟后父进程对子进程进行了等待,成功将子进程释放掉,最后再五秒钟后父进程也退出,整个程序执行结束。

父进程等待多个子进程(阻塞式等待)
一个 wait 只能等待任意一个子进程,因此父进程如果要等待多个子进程可以通过循环来多次调用 wait 实现等待多个子进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> #define N 5
// 父进程等待多个子进程
void RunChild()
{ int cnt = 5; while(cnt--) { printf("I am child, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } return;
}
int main()
{ for(int i = 0; i < N; i++) { pid_t id = fork();// 创建一批子进程 if(id == 0) { // 子进程 RunChild(); exit(0); } // 父进程 printf("Creat process sucess:%d\n", id); } sleep(10); for(int i = 0; i < N; i++) { pid_t id = wait(NULL); if(id > 0) { printf("Wait process:%d, success!\n", id); } } sleep(5); return 0;
}

如果子进程不退出,父进程在执行 wait 系统调用的时候也不返回(默认情况),默认叫做阻塞状态。由此可以看出,一个进程不仅可以等待硬件资源,也可以等待软件资源,这里的子进程就是软件。
获取子进程的退出信息(阻塞式等待)
相关文章:
LINUX基础 [三] - 进程创建
目录 前言 进程创建的初次了解(创建进程的原理) 什么是fork函数? 初识fork函数 写时拷贝 fork函数存在的意义 fork调用失败的原因 进程终止 运行完毕结果不正确 main函数返回 库函数函数exit 系统调用接口_exit 进程异常终止 进…...
【day1】数据结构刷题 链表
一 反转链表 206. 反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head [1,2] 输出:[2,1]…...
鼠标在客户区内按下左键和双击右键
书籍:《Visual C 2017从入门到精通》的2.6鼠标 环境:visual studio 2022 内容:【例2.44】鼠标在客户区内按下左键和双击右键 1.创建一个单文档程序 一个简单的单文档程序-CSDN博客https://blog.csdn.net/qq_20725221/article/details/1463…...
c++ map和vector模板类
在这一章中C语法之模板函数和模板类-CSDN博客 我们学习了怎样写模板函数和模板类,接下来我们来学习系统给我们写好的两个模板类:map和vector。 我相信有了上文的基础,能帮助我们更好的理解这些模板类。 map和vector 是C STL(标准模板库) 中的一部分&a…...
hn航空app hnairSign unidbg 整合Springboot
声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 逆向分析 学习unidbg补环境。先弄一个…...
Arm Linux ceres库编译
由于工作需要,需在国产化系统上编译ceres库,手上有一块树莓派,就在树莓派上面进行测试编译ceres库,总体来说比较顺利。只出现了一点小问题 参考链接: Ceres中文教程-安装 Ceres官方网站(英文) …...
c++中的四种cast转换
文章目录 前言一、dynamic_cast二、static_cast三、const_cast四、reinterpret_cast总结 前言 C继承并扩展C语言的传统类型转换方式,提供了功能更加强大的转型机制(检查与风险) 转换类型典型用途安全性static_cast相关类型转换(…...
矩阵补充,最近邻查找
矩阵补充,最近邻查找 矩阵补充是向量召回最简单的一种方法,现在不常用,学习矩阵补充是为了更好的理解后面学到的双塔模型 下图,输入用户ID和物品ID后从Eebedding层拿到对应的向量做内积,内积的结果就是矩阵补充 模型…...
gradio调用多个CSS的HTML页
很多博客介绍的gradio读取html和css比较简单,如果要做很细致的前端页面优化,比如丰富的响应式的cssjs,至少要有html多个css,是暂不能实现的。bootstrap、font-awesome、jquery等 方案一当然是直接更换htmlcss为主的部署方式&#…...
NVIDIA NeMo 全面教程:从入门到精通
NVIDIA NeMo 全面教程:从入门到精通 文章目录 NVIDIA NeMo 全面教程:从入门到精通目录框架介绍NeMo的核心特点NeMo的架构NeMo与其他框架的比较NeMo的模型集合NeMo的工作流程NeMo 2.0的新特性 安装指南系统要求使用Docker容器安装步骤1:安装Do…...
Go 语言封装邮件发送功能
Go 语言封装邮件发送功能 🏆 目标📦 依赖包🌟 项目结构🚀 代码实现🛠️ 主要方法说明🧪 单元测试🌈 使用示例🏆 代码亮点🌟 改进方向🚀 总结 在现代 Web 开发…...
加新题了,MySQL 8.0 OCP 认证考试 题库更新
MySQL 8.0 OCP 认证考试 题库更新 MySQL 8.0 Database Administrator 考试科目:1Z0-908 近期发现,MySQL OCP认证考试题库发生变化,出现了很多新题,对此,CUUG专门收集整理了最新版本的MySQL考试原题,并会给…...
Thales靶机攻略
1.下载导入VBox,并启动靶机 靶机地址:https://download.vulnhub.com/thales/Thales.zip 解压后,在VBox中导入虚拟电脑。包含所有网卡的MAC地址。 导入完成,设置网卡模式为仅主机网络。开启靶机。 kali网卡更改为桥接模式。点击工…...
尝试使用Tauri2+Django+React项目(2)
前言 尝试使用tauri2DjangoReact的项目-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146403103在前面笔者不知道怎么做,搞了半天 笔者看到官网,原来可以使用二进制文件,好好好 嵌入外部二进制文件 | Taurihttps://v2.taur…...
6.1 模拟专题:LeetCode 1576. 替换所有的问号
1. 题目链接 LeetCode 1576. 替换所有的问号 2. 题目描述 给定一个仅包含小写字母和问号 ? 的字符串 s,要求将所有 ? 替换为任意小写字母,使得替换后的字符串中 没有相邻的两个字符相同。 示例: 输入:s "?zs" →…...
Linux安装go环境
安装一个lazydocker,根据文档需要先安装go环境 https://github.com/jesseduffield/lazydocker 官方文档解析 https://go.dev/doc/install 文档内容如下,一共三步 1.删除先前安装的go,解压下载的go压缩包到/usr/local目录 2.添加环境变量&…...
卡特兰数在数据结构上面的运用
原理 Catalan数是一个数列,其第n项表示n个不同结点可以构成的二叉排序树的数量。Catalan数的第n项公式为:  其中,是组合数,表示从2n个元素中选择n个元素的组合数。 Catalan数的原理可以通过以下方式理解&…...
Unity知识点快速回顾系列
Unity知识点快速回顾系列导航 主要想用于快速回顾unity相关知识点,基本只讲解知识点,只有简单的示例,目前还在整理中。 一、C#知识点入门、基础、核心、进阶 二、Unity 知识点入门、基础、核心、进阶 三、Unity 数据持久化 四、Unity 知识点快…...
悟空crm v12安装好后出现 网络错误问题(已解决)
请求网址: http://wwww.aaaa.com/gateway/adminUser/queryUserNumInfo 请求方法: POST 状态代码: 502 Bad Gateway 远程地址: 101.37.79.226:9807 引荐来源网址政策: strict-origin-when-cross-origin...
便携版:随时随地,高效处理 PDF 文件
PDF-XChange Editor Plus 便携版是一款功能强大且极其实用的 PDF 阅读与编辑工具。它不仅支持快速浏览 PDF 文件,还提供了丰富的编辑功能,让用户可以轻松处理 PDF 文档。经过大神优化处理,这款软件已经变得十分轻便,非常适合需要随…...
【Golang】补充:占位符、转义字符、错误处理
🔥 个人主页:星云爱编程 🔥 所属专栏:Golang 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 1、占位符 1.1通用占位符 %v :默认格式的值。适…...
文件上传绕过的小点总结(4)
9.末尾点删除处理缺陷 给出源码: $file_name trim($_FILES[upload_file][name]); $file_name deldot($file_name);//删除文件名末尾的点 $file_ext strrchr($file_name, .); $file_ext strtolower($file_ext); //转换为小写 $file_ext str_ireplace(::$DATA,…...
AI比人脑更强,因为被植入思维模型【23】损失规避思维模型
我觉得这是一个很有趣的思维模型。 我们学习一个思维模型,不光是指导自己的思维,其实也可以预测或者思考别人的思维模型,也就是别人会怎么想,怎么做? 定义 三层解释思维模型是一种深入剖析事物本质的思考框架&#x…...
如何用Spring AI构建MCP Client-Server架构
现代 Web 应用正加速与大语言模型(LLMs)深度融合,构建超越传统问答场景的智能解决方案。为突破模型知识边界,增强上下文理解能力,开发者普遍采用多源数据集成策略,将 LLM 与搜索引擎、数据库、文件系统等外部资源互联。然而,异构数据源的协议差异与格式壁垒,往往导致集…...
如何让WordPress不同的页面、栏目显示不同的小工具侧边栏
WooSidebars 是一款用于 WordPress 的插件,主要功能是允许用户根据不同的上下文条件(如特定页面、博客文章、分类目录或搜索结果页面等)来更改侧边栏中显示的小工具。 自定义小工具区域:用户可以轻松创建自定义的小工具区域,并将其设置为在多种条件下显示,只需点击几次即…...
智慧座椅的节能效果如何?
嘿呀,你知道不,咱这叁仟智慧座椅的节能效果,那可是像个神秘小宇宙,根据不同的技术和应用场景,会展现出超有趣的变化哦,下面就给你唠唠常见的几种情况哈! 能源回收大变身:有些叁仟智…...
Matlab:二维绘图篇——不同坐标系下的绘图命令
目录 1.极坐标系下绘图:polar命令 实例——极坐标图形 实例——直角坐标与极坐标系图形 2.半对数坐标系下绘图:semilogx和semilogy 实例——半对数坐标系图形 3.双对数坐标系下绘图:loglog 实例——双对数坐标系绘图 4.双y轴坐标&…...
HTTP 协议中请求与响应的详细解析
前言:HTTP(Hypertext Transfer Protocol,超文本传输协议)是用于在互联网上传输超文本的协议 --由一个请求和响应组成,一个完整的 HTTP 请求由请求行(Request Line)、请求头(Headers&…...
对三维物体模型的阈值操作
对三维物体模型的阈值操作 1. 使用point_coord_x、point_coord_y、point_coord_z阈值分割麻辣兔头2. point_normal_x、point_normal_y、point_normal_z有什么区别?3. 去除离群点 1. 使用point_coord_x、point_coord_y、point_coord_z阈值分割麻辣兔头 dev_open_win…...
prometheus 添加alertmanager添加dingtalk机器人告警
1、dingtalk创建机器人,目前我们采用加白名单的方式校验 2、定位到如下图 test结果如下...
