【Linux】进程控制:从fork到exec

news2024/11/30 13:36:54

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录

  • 一:🔥 进程创建
    • 🥝 fork函数初识
    • 🥝 写时拷贝
    • 🥝 fork常规用法
    • 🥝 fork调用失败的原因
  • 二:🔥 进程终止
    • 🥝 进程常见退出方法
    • 🥝 _exit 函数和 exit 函数
    • 🥝 return退出
  • 三:🔥 进程等待
    • 🥝 进程等待必要性
    • 🥝 进程等待的方法
      • 📚 wait方法
      • 📚 waitpid方法
    • 🥝 获取子进程status
      • 📚 从status中获取退出码和退出信号
  • 四:🔥 进程程序替换
    • 🥝 「替换原理」
    • 🥝 「替换函数」
    • 🥝 「函数解释」
  • 五:🔥 共勉

一:🔥 进程创建

🥝 fork函数初识

🐲 linuxfork 函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程

#include <unistd.h>
pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程id,出错返回-1

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

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
    在这里插入图片描述
    🦁 当一个进程调用 fork 之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序:
int main(void)
{
	pid_t pid;
	printf("Before: pid is %d\n", getpid());
	if ((pid = fork()) == -1)perror("fork()"), exit(1);
	printf("After:pid is %d, fork return %d\n", getpid(), pid);
	sleep(1);
	return 0;
}

运行结果:
[root@localhost linux]# . / a.out
Before : pid is 43676
After : pid is 43676, fork return 43677
After : pid is 43677, fork return 0

💦 这里看到了三行输出,一行 before,两行 after。进程 43676 先打印 before 消息,然后它有打印 after。另一个 after。消息由 43677 打印的。注意到进程 43677 没有打印 before,为什么呢?如下图所示:

在这里插入图片描述

  • 所以,fork 之前父进程独立执行,fork 之后,父子两个执行流分别执行。注意,fork 之后,谁先执行完全由调度器决定。

🥝 写时拷贝

🦁 通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以 写时拷贝 的方式各自一份副本。具体见下图:

在这里插入图片描述

🥝 fork常规用法

  • 💦 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 💦 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

🥝 fork调用失败的原因

  • 系统中有太多的进程。
  • 实际用户的进程数超过了限制。

二:🔥 进程终止

🥝 进程常见退出方法

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

  1. 从 main 返回
  2. 调用 exit
  3. _exit

💢 异常退出:

  • ctrl + c,信号终止

🥝 _exit 函数和 exit 函数

_exit 函数

#include <unistd.h>
void _exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值

🦁 说明:虽然 statusint,但是仅有 低8位 可以被父进程所用。所以 _exit(-1) 时,在终端执行发现返回值是 255

exit函数

#include <unistd.h>
void exit(int status);

🍊 exit 最后也会调用 exit, 但在调用 exit 之前,还做了其他工作:

  1. 执行用户通过 atexit 或 on_exit 定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用 _exit。

在这里插入图片描述

实例:

int main()
{
	printf("hello");
	exit(0);
}

运行结果(冲刷缓冲区):
[root@localhost linux]# ./a.out
hello[root@localhost linux]#


int main()
{
	printf("hello");
	_exit(0);
}

运行结果(直接退出):
[root@localhost linux]# ./a.out
[root@localhost linux]#

🥝 return退出

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

三:🔥 进程等待

🥝 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成 僵尸进程 的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,我们需要知道父进程派给子进程的任务完成的如何。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

🥝 进程等待的方法

📚 wait方法

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int*status);

返回值:
	成功返回被等待进程pid,失败返回-1。
参数:
	输出型参数,获取子进程退出状态,不关心则可以设置成为 NULL

📚 waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
	当正常返回的时候 waitpid 返回收集到的子进程的进程ID;
	如果设置了选项 WNOHANG ,而调用中 waitpid 发现没有已退出的子进程可收集,则返回0;
	如果调用中出错,则返回 -1,这时 errno 会被设置成相应的值以指示错误所在;
参数:
	pid:
		Pid = -1, 等待任一个子进程。与wait等效。
		Pid > 0. 等待其进程ID与pid相等的子进程。
	status:
		WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
		WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
	options:
		WNOHANG: 若pid指定的子进程没有结束,则 waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

🦁 waitpid() 函数的作用是:等待指定的一个子进程或者任意一个进程。(这个可以有options参数控制)

  • status:输出型参数,获取子进程的退出信息,如果不需要进程退出的退出信息,可设置为NULL
  • options:当 options 设置为0的时候,叫做阻塞等待。当 options 设置为 WNOWAIT 的时候,叫做非阻塞等待。(后面会有阻塞等待和非阻塞等待的例子)
    在这里插入图片描述

🥝 获取子进程status

📚 从status中获取退出码和退出信号

  • waitwaitpid,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递 NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status 不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

在这里插入图片描述
🦁 有两种方法我们可以获取 status 中的退出信息。

方法一:位运算

既然我们已经知道了status中的比特位组成部分,我们就可以通过位运算的操作直接获取退出信息。

int exit_code = (status >> 8) & 0xff;    // 获取退出码
int exit_signal = status & 0x7f;        // 获取退出信号

方法二:使用宏

在系统中,提供了两个宏来获取提出码和退出信号。

WIFEXITED(status);          // 用于查看进程是否正常退出,其实就是查看是否有退出信号
WEXITSTATUS(status);        // 用于获取进程的退出码

再次提醒:如果一个进程被信号杀死,则退出码没有意义。

  • 下面分别对阻塞等待非阻塞等待举出一个例子:

🐮 在子进程运行的时候,父进程在干什么呢?如果父进程就在那里等待子进程完成任务,接收子进程的退出信息的话,这种方式就是阻塞等待。就好像父进程被阻塞住不能前进一样。。如果父进程在子进程运行的时候,自己可以感自己的事情,这种方式就叫做非阻塞等待。

所以想要判断是否为阻塞或者非阻塞等待,就只要判断父进程在子进程运行的时候,可不可以自己运行自己的代码即可。

四:🔥 进程程序替换

🥝 「替换原理」

  • 🦁 fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时, 该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。

🐮 要注意两个问题:

  1. 当进程被另一个进程替换时,并没有创建一个新的进程 而只是在原来的进程的基础上,在进程的物理内存中代码和数据被另一个进程的代码和数据段所替换而已。其余的数据结构类似 PCBmm_struct页表 等等结构并没有改变。

  2. 在子进程进行程序替换之后,父进程中的代码段和数据段并没有受到任何的影响。 这是因为当子进程在进行进程替换时,需要对进程的数据和代码段进程修改,这时进程会发生写时拷贝,而在写时拷贝之后,父子进程的代码和数据独立了,所以相互之间的数据和代码不会受到影响。

🥝 「替换函数」

进程替换函数是 exec 系列函数,而这一系列的函数一共有7个函数。

#include <unistd.h>
int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char * const envp[]);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execve(const char* path, char* const argv[], char* const envp[]);
int execvpe(const char* file, char* const argv[], char* const envp[]);
  • 其中 l 和 v 的区别在于程序运行参数的赋予方式不同,l 是通过函数参数逐个给与,最终以NULL结尾,而 v 是通过字符指针数组一次性给与。
  • 其中有没有 p 的区别在于程序是否需要带路径,也就是是否会默认到 path 环境变量指定的路径下寻找程序,没有 p 的需要指定路径,有p的会默认到 path 环境变量指定路径下寻找。
  • 其中有没有 e 的区别在于程序是否需要自定义环境变量,没有 e 则默认使用父进程环境变量,有e则自定义环境变量。

🐮 exec 调用举例如下:

#include <unistd.h>

int main()
{
	char *const argv[] = {"ps", "-ef", NULL};
	char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
	
	execl("/bin/ps", "ps", "-ef", NULL);
	
	// 带p的,可以使用环境变量PATH,无需写全路径
	execlp("ps", "ps", "-ef", NULL);
	
	// 带e的,需要自己组装环境变量
	execle("ps", "ps", "-ef", NULL, envp);
	
	execv("/bin/ps", argv);
	
	// 带p的,可以使用环境变量PATH,无需写全路径
	execvp("ps", argv);
	
	// 带e、p的,需要自己组装环境变量,无需写全路径
	execvpe("ps", argv, envp);
	
	// 带e的,需要自己组装环境变量
	execve("/bin/ps", argv, envp);
	
	exit(0);
}

🦁 事实上:其实 execve 才是真正的系统调用,其他的几个函数只不过对于 execve 进行了封装。以满足不同的调用需求。

🥝 「函数解释」

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行, 不再返回。
  • 如果调用出错则返回 -1。
  • 所以 exec 函数只有出错的返回值而没有成功的返回值。

五:🔥 共勉

以上就是我对 【Linux】进程控制 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

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

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

相关文章

群晖使用Docker搭建NASTool自动化观影工具并实现在线远程管理

文章目录 前言1. 本地搭建Nastool2. nastool基础设置3. 群晖NAS安装内网穿透工具4. 配置公网地址5. 配置固定公网地址 前言 本文主要分享一下如何在群晖NAS中本地部署Nastool&#xff0c;并结合cpolar内网穿透工具&#xff0c;轻松实现公网环境远程管理与访问本地NAS中储存的影…

惠普电脑怎么开启vt_惠普电脑开启vt虚拟化图文教程(支持新旧bios开启方法)

最近使用惠普电脑的小伙伴们问我&#xff0c;惠普电脑怎么开启vt虚拟。大多数可以在Bios中开启vt虚拟化技术&#xff0c;当CPU支持VT-x虚拟化技术&#xff0c;有些电脑会自动开启VT-x虚拟化技术功能。而大部分的电脑则需要在Bios Setup界面中&#xff0c;手动进行设置&#xff…

数字媒体产业园区:创新资源集聚,助力企业成长

在当今数字化浪潮汹涌的时代&#xff0c;数字媒体产业园区作为创意与技术的交汇点&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;成为助力企业成长的重要平台。其中&#xff0c;“数字媒体产业园区”以其创新资源的集聚效应&#xff0c;为入驻企业提供了广阔的发展空间…

双十一有什么好物推荐?双十一必买清单大汇总

双十一的钟声即将敲响&#xff0c;数码好礼的选购热潮已然兴起。在这个信息爆炸的时代&#xff0c;我们被各种数码产品的广告和推荐所包围。如何从中筛选出真正适合自己的数码礼物呢&#xff1f;本文将以专业的视角、客观的评价&#xff0c;为你梳理数码产品的优缺点&#xff0…

将 QT 应用程序打包成如意玲珑软件包

在上一篇文章《国产系统之如意玲珑》中&#xff0c;我为大家介绍了一款创新的国产软件包管理工具——如意玲珑&#xff08;Linyaps&#xff09;。该工具集致力于解决 Linux 系统下传统软件包格式带来的复杂性和依赖问题&#xff0c;提供了一种更独立、更简洁的打包和管理方式。…

python爬虫,爬取网页壁纸图片

python爬虫实战&#xff0c;爬取网页壁纸图片 使用python爬取壁纸图片&#xff0c;保存到本地。 爬取彼岸图网&#xff0c;网站地址https://pic.netbian.com/ 本人小白&#xff0c;记录一下学习过程。 开始前的准备 安装python环境&#xff0c;略。 python编辑器pycharm2…

Linux:防火墙相关命令使用(Ubuntu)

1.安装防火墙工具 虚拟机安装好系统后&#xff0c;默认是没有管理工具的。如果已经安装可以跳过。 # 安装ufw&#xff08;Uncomplicated Firewall&#xff09;&#xff0c;这是Ubuntu上管理防火墙的一个简单工具 sudo apt-get install ufw2.开启和关闭防火墙 # 开启防火墙 sud…

RuoYi-Vue若依框架-后端设置不登陆访问(白名单)

找到SecurityConfig类 确认自己的需求 /*** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可…

请速度收藏,Python爬虫必备的8大技巧!

想要快速学习爬虫&#xff0c;最值得学习的语言一定是Python&#xff0c;Python应用场景比较多&#xff0c;比如&#xff1a;Web快速开发、爬虫、自动化运维等等&#xff0c;可以做简单网站、自动发帖脚本、收发邮件脚本、简单验证码识别脚本。 爬虫在开发过程中也有很多复用的…

输电线路语义分割图像数据集,图片总共1200张左右,包含分割标签,json标签

输电线路语义分割图像数据集&#xff0c;图片总共1200张左右&#xff0c;包含分割标签&#xff0c;json标签 输电线路语义分割图像数据集介绍 数据集概述 名称&#xff1a;输电线路语义分割图像数据集图片数量&#xff1a;约1200张标注格式&#xff1a;JSON (包含像素级分割标…

中小型医院网站:Spring Boot开发策略

2 相关技术简介 2.1 Java技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xff0c;…

迅速入门Pytorch深度学习框架

一、引言 随着人工智能技术的飞速发展&#xff0c;深度学习已成为当今科技领域的热门话题。PyTorch作为一款功能强大且易于使用的深度学习框架&#xff0c;受到了越来越多开发者和研究者的青睐。本文旨在帮助新手快速入门PyTorch&#xff0c;掌握其基本概念、核心功能以及实际…

【汇编语言】寄存器(内存访问)(三)—— 字的传送

文章目录 前言1. 字的传送2. 问题一3. 问题一的分析与解答4. 问题二5. 问题二的分析与解答结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程的角度出发就太片面了&#xff0c;其实学习汇编语言…

【LeetCode每日一题】——1588.所有奇数长度子数组的和

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【题目进阶】八【解题思路】九【时间频度】十【代码实现】十一【提交结果】 一【题目类别】 前缀和 二【题目难度】 简单 三【题目编号】 1588.所有奇数长度子数组的和 …

【AI系统】AI 学习方法与算法现状

在人工智能&#xff08;AI&#xff09;的漫长历史中&#xff0c;我们见证了从早期的规则驱动系统到现代的机器学习模型的转变。AI的学习方法是其进步的核心&#xff0c;而算法现状则反映了当前技术的高度和未来的发展方向。 Ⅰ.AI 学习方法 AI的工作原理基于深度神经网络&…

源码编译llama.cpp for windows on arm

源码编译llama.cpp for windows on arm 这里有编译好的&#xff0c;直接下载使用 https://github.com/turingevo/llama.cpp-build/releases 1 先编译openblas for windows on arm 查看我的文章 《源码编译 openblas for windows on arm》 2 启用OpenBlas加速 上一步openb…

A0002.主机访问虚拟机中windows系统时,ping不通问题解决

问题再现 解决方法 查看主机的IP地址及DNS 在虚拟机中设置IP地址和DNS

leetcode计数排序

计数排序&#xff08;counting sort&#xff09;通过统计元素数量来实现排序&#xff0c;通常应用于整数数组。 给定一个长度为 的数组 nums &#xff0c;其中的元素都是“非负整数” def counting_sort(nums: list[int]):"""计数排序"""# 完整实…

设置 Notepad++ 制表符(Tab 缩进)宽度为2个空格大小

Notepad 默认的制表符宽度是 4 个空格的大小&#xff0c;一个规模比较大的代码段或者 xml 等文件&#xff0c;小屏幕打开时看到的情景真的和让人着急&#xff0c;拖来拖去&#xff01;有两种方案可以解决这种情况。 修改缩进为空格 这种我们不太推荐&#xff0c;但是有些公司…

【Vue】Vue3.0(十一)Vue 3.0 中 computed 计算属性概念、使用及示例

上篇文章&#xff1a;【Vue】Vue3.0&#xff08;十&#xff09;toRefs()和toRef()的区别及使用示例 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年10月15日10点23分 文章目录 Vue 3.0中…