【Linux进程控制】进程创建|终止

目录

一、进程创建

fork函数

写时拷贝

二、进程终止

想明白:终止是在做什么?

进程退出场景

常见信号码及其含义

进程退出的常见方法

正常终止与异常终止

exit与_exit的区别


一、进程创建

fork函数

在Linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,原进程为父进程,其中返回值:子进程中返回0,父进程返回子进程id,出错返回-1;

测试 

#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h> 
int main()
{printf("before fork, pid = %d\n", getpid());pid_t id = fork();assert(id != -1);//进程创建失败(void)id;printf("after fork, pid = %d, fork return %d\n", getpid(), id);return 0;
}

上面代码执行路径如下图所示

进程调用fork,当控制转移到内核的fork代码后,内核做:

❍ 分配新的内存块和内核数据结构给子进程

❍ 将父进程部分数据结构内容拷贝至子进程

❍ 添加子进程到系统进程列表中

❍ fork返回,开始调度器调度

当父进程调用 fork() 时,会发生以下几件事情:

1.进程复制:操作系统会创建父进程的一个副本,这个副本就是子进程。子进程几乎与父进程完全相同,它们拥有相同的程序文本、数据段、堆栈、文件描述符等。

2.资源共享与复制:尽管子进程是父进程的一个副本,但是它们之间还是有所区别的,例如,它们有不同的进程ID(PID)、不同的父进程ID(PPID)以及一些独立的资源,如虚拟内存等。

3.执行流程fork() 调用之后,父进程和子进程都会从 fork() 函数调用后的下一条指令开始执行。

4.返回值fork() 在父进程中返回子进程的 PID,在子进程中返回 0,如果出错则返回 -1。

fork常规用法:

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求

  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因

  • 系统中有太多的进程

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

写时拷贝

写时拷贝(Copy-on-Write,简称COW)是一种计算机程序设计的优化策略。这种策略在多个进程试图写入同一块数据时,才会真正进行数据复制,而不是一开始就为每个进程分配独立的物理内存空间。

工作原理:

1.共享数据:当父进程通过 fork() 创建子进程时,并不立即为子进程分配一份父进程数据段的副本。相反,父子进程共享同一块物理内存页。

2.写操作检测:操作系统会标记这些共享的内存页为“写时拷贝”。这意味着如果任何一个进程试图写入这些页,操作系统会捕捉到这个写操作。

3.数据复制:当写操作发生时,操作系统会触发页错误(page fault)。操作系统随后会创建一个新的内存页,并将原页的内容复制到新页上,然后将写操作指向新的内存页。对于其他进程,原内存页保持不变。

4.页分离:这个过程称为页分离(page splitting)。之后,每个进程都会有自己的内存页副本,对其中一个进程的修改不会影响到另一个进程。

优点:

  • 效率提升:在 fork() 调用后,不需要立即复制父进程的所有资源,减少了不必要的内存消耗和复制时间。

  • 内存使用优化:只有在实际需要时才分配内存,这可以显著减少内存的使用。

  • 性能提升:减少了进程创建时的开销,提高了系统的整体性能。

缺点:

  • 写操作开销:第一次写操作时会有额外的开销,因为需要复制内存页。

  • 复杂性:实现写时拷贝会增加操作系统内核的复杂性。


 

二、进程终止

想明白:终止是在做什么?

操作系统要释放进程申请的相关内核数据结构和对应的数据和代码(本质就是释放系统资源)。

进程退出场景
  • 代码执行完毕,结果正确

#include <stdio.h>
int Add(int from, int to)
{int sum = 0;for(int i = from; i <= to; i++){sum += i;}return sum;
}
int main()
{printf("Add 1 to 100 is %d\n", Add(0, 100));return 0;
}
[wuxu@Nanyi lesson16]$ gcc -o test test1.c -std=c99
[wuxu@Nanyi lesson16]$ ./test
Add 1 to 100 is 5050
  • 代码运行完毕,结果不正确

#include <stdio.h>
int Add(int from, int to)
{int sum = 0;//此处应该是 i<=tofor(int i = from; i < to; i++){sum += i;}return sum;
}int main()
{printf("Add 1 to 100 is %d\n", Add(0, 100));return 0;
}
[wuxu@Nanyi lesson16]$ gcc -o test test1.c -std=c99
[wuxu@Nanyi lesson16]$ ./test
Add 1 to 100 is 4950
  • 代码异常终止。即代码没有跑完,程序崩溃

#include <stdio.h>int main()
{int* p = NULL;*p = 100;//空指针解引用--->野指针return 0;
}
[wuxu@Nanyi lesson16]$ gcc -o test test2.c -std=c99
[wuxu@Nanyi lesson16]$ ./test
Segmentation fault
  • 在程序执行结束时,我们会使用return语句返回一个数值作为main函数的返回值,这个返回值有什么用呢?

【例子1】张三参加一场考试,回家后给老爹汇报成绩

如果小明考了100分(满分100)那么他的老爹并不会关心他为什么考了100分;但当小明考了1分,他的老爹则会问他为什么考1分。因此做出如下约定,每个数字标识不同的原因:

状态码描述
1考试过程中生病了导致没考好
2没有好好学习导致没考好
......

在操作系统中,对于程序正常终止我们并不关心(正常程序终止返回状态码0),但程序一旦出现错误(返回码非0),我们就需要知道程序出错的原因。操作系统对于不同的状态码给了不同的错误描述信息,我们可以使用errno.h 下的 errno 变量获取错误码,使用 strerror(errno)获取错误码的错误描述

#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),父进程可以继续执行下一步操作;否则,它可能会停止执行或执行不同的代码路径。

状态报告:父进程可能需要向用户或其他进程报告子进程的执行结果。退出码是传递这种状态信息的简单方式。

在Linux中,可以使用echo $?来查看最近一次执行的子进程的退出码

我们在回到刚刚野指针的例子上,重新执行一下程序:

[wuxu@Nanyi lesson16]$ gcc -o test test2.c -std=c99
[wuxu@Nanyi lesson16]$ ./test
Segmentation fault
[wuxu@Nanyi lesson16]$ echo $?
127
[wuxu@Nanyi lesson16]$ echo $?
0

通过观察我们发现同一个程序,为什么两次的退出码不一样?

其实第一个127是./test的执行码,表示这个程序出现了Segmentation fault错误,第二个执行码表示echo这个命令执行成功,返回0

一旦程序出现异常,退出码就没有意义了

为什么出现了异常?--> 我们可以看进程退出的时候,退出信号是多少,就可以判断进程为什么异常了。

进程出异常本质是因为进程收到了OS发给进程的信号

【示例】我们写一个除0的程序,看会出现什么错误

#include <stdio.h>int main()
{int a = 1 / 0;return 0;
}
[wuxu@Nanyi lesson16]$ ./error
Floating point exception

在该程序发生错误时,操作系统给该程序的进程发送了8号信号SIGFPE。我们可以通过 kill -l 查看所有的信号码以及对应信号名

我们来验证一下,上面的程序时接收到8号信号才终止的

#include <stdio.h>int main()
{while(1){}return 0;
}

 

常见信号码及其含义
信号码信号名称含义
1SIGHUP挂起,通常在终端关闭或控制进程结束时发送给子进程。
2SIGINT中断,通常在用户按下Ctrl+C时发送。
3SIGQUIT退出,用户按下Ctrl+\时发送,通常会导致进程终止并生成核心转储。
4SIGILL非法指令,执行了非法的机器语言指令。
5SIGTRAP跟踪陷阱,由调试器使用。
6SIGABRT中止,调用abort()函数时发送。
7SIGBUS总线错误,涉及硬件错误。
8SIGFPE浮点异常,如除以零。
9SIGKILL杀死,无法捕获、阻塞或忽略,总是终止进程。
10SIGUSR1用户定义的信号1,可用于应用程序。
11SIGSEGV段违例,访问非法内存地址。
12SIGUSR2用户定义的信号2,可用于应用程序。
13SIGPIPE管道破裂,写入无读者的管道时发生。
14SIGALRM报警,由alarm()函数设置的时间到期时发送。
15SIGTERM终止,请求进程终止。
信号码信号名称含义
16SIGSTKFLT栈溢出(Linux特有,在一些系统中不存在)
17SIGCHLD子进程结束,子进程处于停止状态或被终止时发送给父进程。
18SIGCONT继续执行,如果进程已停止,则使其继续运行。
19SIGSTOP停止进程的执行,无法被捕获或忽略。
20SIGTSTP停止进程的执行,可以被捕获,通常在用户按下Ctrl+Z时发送。
21SIGTTIN后台进程组尝试读取控制终端时发送。
22SIGTTOU后台进程组尝试写入控制终端时发送。
23SIGURGI/O紧急情况,套接字有紧急数据可读。
24SIGXCPU超过CPU时间限制(CPU时间限制超时)。
25SIGXFSZ超过文件大小限制。
26SIGVTALRM虚拟定时器警报(类似于SIGALRM,但是计算的是进程的虚拟时间)。
27SIGPROF性能计数器超时(类似于SIGALRM,但是包括了处理器时间和时钟时间)。
28SIGWINCH窗口大小改变,通常在终端窗口大小改变时发送。
29SIGIOI/O可执行(Solaris系统中为SIGPOLL)。
30SIGPWR电源故障(系统关机)。
31SIGSYS系统调用异常(无效的系统调用)。

进程退出的常见方法

正常终止与异常终止

正常终止(可以通过 echo $? 查看进程退出码)

  • 从main函数返回

  • 调用exit

  • _exit

异常终止

  • ctrl + c 信号终止

exit与_exit的区别

  • 终止处理程序和I/O缓冲区exit()会执行终止处理程序和I/O缓冲区的清理,而_exit()则不会。

  • 头文件exit()stdlib.h中定义,而_exit()unistd.h中定义。

  • 用途:由于_exit()不会进行清理工作,它通常用于不需要这些清理步骤的底层系统编程。

【例子】

#include <stdio.h>
#include <unistd.h>int main()
{printf("1 + 1 = %d", 1 + 1);_exit(1);return 0;
}
[wuxu@Nanyi lesson16]$ vim test5.c
[wuxu@Nanyi lesson16]$ gcc -o test test5.c -std=c99
[wuxu@Nanyi lesson16]$ ./test
[wuxu@Nanyi lesson16]$ echo $?
1

通过结果我们发现,并没有打印1+1=2这个结果,也就是_exit不会刷新缓冲区,故最后并没有打印。

如果换成exit

[wuxu@Nanyi lesson16]$ gcc -o test test5.c -std=c99
[wuxu@Nanyi lesson16]$ ./test
1 + 1 = 2 [wuxu@Nanyi lesson16]$ echo $?
1

我们会发现它打印出最终结果,顺便提醒一下 exit与_exit 头文件不一样哦

exit最后也会调用_exit,但在exit除了调用_exit,还做了其他工作:

❍ 执行用户通过atexit或on_exit定义的清理函数 ​

❍ 关闭所有打开的流,所有的缓存数据均被写入(即刷新缓冲区)

​ ❍ 再调用_exit

return退出

return是一种更常见的退出进程方法。执行return n 等同于执行exit(n),因为调用main的运行时函数会将main的返回值当作exit的参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/142085.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Python电商项目汇报总结】**采集10万+淘宝商品详情数据注意事项总结汇报**

大家好&#xff0c;今天我想和大家聊聊我们在采集10万淘宝商品详情数据时需要注意的一些关键问题。这不仅仅是一个技术活&#xff0c;更是一场细心与合规的较量。下面&#xff0c;我就用咱们都听得懂的话&#xff0c;一一给大家说道说道。 **一、明确目标&#xff0c;有的放矢…

Autosar BswM配置-手动建立Swc Port实现自定义模式切换

文章目录 前言Mode配置Interface配置Data type mappingBswM配置BswMModeRequestPort配置BswMModeCondition配置BswMLogicalExpression配置BswMDataTypeMappingSetsSWC接口配置RTE接口map代码实现总结前言 客户需求中需要在指定电压范围内允许通信,而目前项目中通信主要由PNC控…

C++从入门到起飞之——继承下篇(万字详解) 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、派⽣类的默认成员函数 1.1 四个常⻅默认成员函数 1.2 实现⼀个不能被继承的类 ​编辑 2. 继承与友…

SpringBoot 消息队列RabbitMQ 交换机模式 Fanout广播 Direct定向 Topic话题

介绍 作用是接收生产者发送的消息&#xff0c;并根据某种规则将这些消息路由到一个或多个队列。交换机根据绑定规则和路由键来决定如何将消息分发到队列。简而言之&#xff0c;交换机是消息路由的核心组件&#xff0c;它负责将消息从生产者引导到适当的队列&#xff0c;以便消…

防火墙--NAT技术,基于源NAT,NAT服务器,双向NAT

文章目录 防火墙--NAT技术一、基于源NAT**方式**&#xff1a;NAT No-PATNAPT出接口地址方式Smart NAT三元组 NAT 二、基于服务器的NAT多出口场景下的NAT Server 三、双向NAT 防火墙–NAT技术 基于源NAT&#xff1a;用于将内部网络的私有IP地址转换为公共IP地址&#xff0c;以便…

[Meachines] [Easy] Sauna DC域+AS-REP+TGT票证窃取+AutoLogon凭据+DCSync攻击

信息收集 IP AddressOpening Ports10.10.10.175TCP:53,80,88,135,139,389,445,464,593,3268,3269,5985 $ nmap -p- 10.10.10.175 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 53/tcp open domain? | fingerprint-strings: | DNSVersionBindReqTCP…

如何处理模型API速率限制

引言 当我们访问大模型相关的API服务时&#xff0c;通常会遇到速率限制(即限流)&#xff0c;它用于防止用户向某个API发送大量请求&#xff0c;防止请求过载&#xff0c;确保每个人都能公平地访问API。 速率限制的方式 速率限制通常有以下几种形式&#xff1a; RPM(request…

详解HTTP/HTTPS协议

HTTP HTTP协议全名为超文本传输协议。HTTP协议是应用层协议&#xff0c;其传输层协议采用TCP协议。 请求—响应模型 HTTP协议采用请求-响应模型&#xff0c;通常由客户端发起请求由服务端完成响应。资源存储在服务端&#xff0c;客户端通过请求服务端获取资源。 认识URL 当…

Linux 系统盘空间不足,想要将 Docker 镜像和容器数据迁移到数据盘

摘要&#xff1a;大家在Linux上用Docker部署项目的时候&#xff0c;有时候会部署多个项目&#xff0c;系统盘空间不足&#xff0c;数据盘又挂载有很多空间&#xff0c;这时候就会想要将 Docker 镜像和容器数据迁移到数据盘&#xff0c;本文主要讲解迁移步骤和迁移过程中遇到的一…

vue2的diff算法

Vue2 的虚拟 DOM diff 算法是一种高效的算法&#xff0c;用于比较新旧两个虚拟 DOM 树&#xff0c;找出差异并更新到真实 DOM 上。这个算法的核心在于尽量减少不必要的 DOM 操作&#xff0c;提高性能。 虚拟dom&#xff1a;把DOM数据化&#xff0c;先通过不断地操作数据&#…

数据集 CULane 车道线检测 >> DataBall

数据集 CULane 车道线检测 自动驾驶 无人驾驶目标检测 CULane是用于行车道检测学术研究的大规模具有挑战性的数据集。它由安装在六辆由北京不同驾驶员驾驶的不同车辆上的摄像机收集。收集了超过55小时的视频&#xff0c;并提取了133,235帧。数据示例如上所示。我们将数据集分为…

【C++算法】前缀和

前缀和 题目链接 前缀和https://www.nowcoder.com/practice/acead2f4c28c401889915da98ecdc6bf?tpId230&tqId2021480&ru/exam/oj&qru/ta/dynamic-programming/question-ranking&sourceUrl%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%2…

传神论文中心|第25期人工智能领域论文推荐

在人工智能领域的快速发展中&#xff0c;我们不断看到令人振奋的技术进步和创新。近期&#xff0c;开放传神&#xff08;OpenCSG&#xff09;传神社区发现了一些值得关注的成就。传神社区本周也为对AI和大模型感兴趣的读者们提供了一些值得一读的研究工作的简要概述以及它们各自…

Java-idea小锤子图标

这一版的idea小锤子图标其实就在这里 点进去就找到了~

mtk7628 网口灯问题

板子上电插入网线到网口&#xff0c;只有wan口灯会亮&#xff0c;插入lan口灯不会亮。对比了ok的代码&#xff0c;先对比设备树&#xff0c;未看到网口相关的GPIO。 mt7628an_WMD-7688A-12816.dts mt7628an_hilink_hlk-7628n.dts 继续查看网口相关代码&#xff0c;加打印&…

在实际LabVIEW开发中,哪些算法是常用的?

在LabVIEW的实际开发中&#xff0c;常用的算法主要集中在数据处理、控制系统、信号处理、图像处理等领域。以下是一些常用算法的介绍&#xff1a; 1. PID控制算法 PID&#xff08;比例-积分-微分&#xff09;控制是LabVIEW中常用的算法之一&#xff0c;广泛应用于工业自动化和…

Leetcode—1184. 公交站间的距离【简单】

2024每日刷题&#xff08;161&#xff09; Leetcode—1184. 公交站间的距离 实现代码 class Solution { public:int distanceBetweenBusStops(vector<int>& distance, int start, int destination) {int clockwise 0;int counterclockwise 0;if(start > desti…

华为防火墙智能选路篇之链路权重(带宽)负载分担

基于链路的权重负载分担&#xff08;真机演示&#xff09; 这里博主采用真机演示&#xff0c;模拟器只能配置没办法模拟出效果&#xff0c;真机能够真实的体验出效果&#xff0c;更好的去理解&#xff0c;所以这边采用真机配置了。环境简化了&#xff0c;防火墙内网接了一台测试…

Zookeeper工作机制和特点

1. Zookeeper工作机制 Zookeeper从设计模式角度来理解&#xff1a; 是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心的数据&#xff0c;然后接受观察者的 注册&#xff0c;一旦这些数据的状态发生变化&#xff0c;Zookeeper就将负责通知…

2-3.Android 存储之存储空间(私有空间、公共空间)

一、内部存储与外部存储 内部存储指位于设备的内部存储空间 外部存储指位于设备的外部存储介质&#xff0c;例如&#xff0c;SD 卡 简单理解&#xff0c;内部存储就是存储在手机自身&#xff0c;外部存储就是存储在手机可以外接的东西&#xff0c;好比电脑的硬盘和 U 盘 二、…