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

Linux是怎样工作的--第三章

第三章:进程管理

文章目录

目录

前言

一、创建进程

二、fork()函数

三、execve()函数

1. const char *pathname

2. char *const argv[]

3. char *const envp[]

四、结束进程

总结



前言

本章将介绍内核提供的创建与删除进程的功能,但是目前我们还不了解第五章关于虚拟内存的内容,据无法理解linux创建与删除进程的机制,因此本章抛弃了虚拟内存,单纯讲述进程的创建与删除,第五章就详细介绍完整的运行机制


提示:以下是本篇文章正文内容,下面案例可供参考

一、创建进程

在Linux中,创建进程有如下两个目的:

  • 将同一个程序分成多个进程进行运行(例如,使用Web服务器接收多个请求)
  • 创建另一个程序(例如,从bash启动一个新的程序)

为了达成目的,Linux提供了fork()(底层为clone()的系统调用)与execve()函数(底层为execve()的系统调用)

二、fork()函数

要将同一个程序分成多个进程进行处理,只需要使用fork()函数即可,调用fork()函数即可,在嗲用fork()函数,基于发起调用的进程,创建一个新的进程的那个进程称为父进程,被创建的哪个进程为子进程

创建新进程的流程如下:

        1.为子进程申请内存空间,并复制父进程的内存到子进程的内存空间

        2.父进程与子进程分裂成两个进程,以实现不同的代码,这一点的实现依赖于fork函数分别返回不同的值给父进程与子进程

接下来我们实现以下程序对fork函数一探究竟

#include<unistd.h>//提供 fork()(创建进程)和 getpid()(获取当前进程 ID)等系统调用函数。
#include<stdio.h>//提供 printf() 函数(用于输出信息)。
#include<stdlib.h>//提供 exit() 函数(终止进程)和 EXIT_SUCCESS/EXIT_FAILURE 等状态宏(表示进程退出状态)。
#include<err.h>static void child()//static内部函数的内部工具,不对外提供接口
{printf("I'm child!my pid is %d.\n",getpid());exit(EXIT_SUCCESS);
}
static void parent(pid_t pid_c)
{printf("I'm parent!my pid is %d and the pid of my child is %d.\n",getpid(),pid_c);exit(EXIT_SUCCESS);
}int main()
{pid_t ret;ret=fork();if(ret==-1)//创建子进程失败(如系统资源不足)。{err(EXIT_FAILURE,"fork() failure");}if(ret==0)//表示当前执行的是子进程{child();}else{parent(ret);//正整数:表示当前执行的是父进程}err(EXIT_FAILURE,"shouldn't reach here");
}

当父进程和子进程从fork()函数中恢复时,会获得fork的反沪指,fork对父进程返回子进程的ID,对与子进程返回0,编译并运行上述代码

cc -o fork fork.c
./fork

三、execve()函数

在打算启动另一个程序时,需要调用execve()函数,我们先看一下内核在运行时的流程

  1. 读取可执行文件,并读取创进程的内存映像所需的信息
  2. 用新进程的数据覆盖当前进程的内存
  3. 从最初命令开始运行新进程

也就是说,在启动另一个程序时,并非新增一个进程,而是替换了当前进程

接下来我们详细的说明这一流程,首先,读取可执行文件,以及创建进程的内存映像所需的信息,可执行文件中不仅能包含进程在运行中使用的代码与数据,还包含开始运行程序时所需的数据

  • 代码的代码段在文件中的偏移量,大小,以及内存映像的地址
  • 代码以外的变量等数据的数据段在文件中的偏移量,大小以及内存映像的起始地址
  • 程序执行的第一条指令的内存地址(第一段)

与高级编程语言编写的代码不同,在CPU上执行机器语言必须提供需要的操作的内存地址,因此在代码段和数据段中必须包含内存映像的起始地址,比如使用伪代码编写下一段源代码

c=a+b

在机器语言层面,这段源代码将转变成下面直接对内存地址进行操作的指令

load m100 r0   将内存地址100(变量a)的值读取到名为r0的寄存器中
load m200 r1   将内存地址200(变量b)的值读取到名为r1的寄存器中
add r0 r1 r2   将r0与r1相加,并将结果存在r2的寄存器中
store r2 m300  将r2的值纯存在内存m300中

最后从入口执行程序

然而,Linux可执行文件的结构遵循名字为ELF(excutable and linkable format,可执行与可连接结构),ELF的相关信息可通过readelf命令来获取

西面我们可以尝试获取/bin/sleep的ELF信息

通过附件-h选项,可以获取起始地址

通过附加-S选项,可以获取代码段与数据段在文件中的偏移量,大小和起始地址

在程序运行时创建的进程的内存映像信息,可以从/proc/pid/maps这一文件中找到,sleep命令的相关信息如下

再打算新建一个别的进程时,通常采用称为fork and exec的方式,既有父进程调用fork()创建子进程,再由子进程调用exec(),下图所示为bash进程创建echo进程的流程

#include<unistd.h>//提供系统调用接口,如fork()(创建子进程)、execve()(执行新程序)、getpid()(获取当前进程 PID)、fflush()(刷新缓冲区)。
#include<stdio.h>//提供标准输入输出函数,如printf()。
#include<stdlib.h>//提供程序退出状态定义,如EXIT_SUCCESS(成功退出,值为 0)、EXIT_FAILURE(失败退出,值为非 0)。
#include<err.h>//提供err()函数,用于简化错误处理(打印错误信息并退出程序)。
static void child() {char* args[] = {"/bin/echo", "hello", NULL};  // 传给新程序的参数列表printf("I'm child! my pid is %d.\n", getpid());  // 打印子进程PIDfflush(stdout);  // 强制刷新输出缓冲区execve("/bin/echo", args, NULL);  // 执行新程序/bin/echoerr(EXIT_FAILURE, "exec() failed");  // 如果execve失败,打印错误并退出
}
static void parent(pid_t pid_c) {printf("I'm parent! my pid is %d and the pid of my child is %d.\n", getpid(), pid_c);exit(EXIT_SUCCESS);  // 父进程正常退出
}
int main() {pid_t ret;ret = fork();  // 创建子进程if (ret == -1) {  // fork失败的情况err(EXIT_FAILURE, "fork() failure");}if (ret == 0) {  // 当前是子进程child();} else {  // 当前是父进程(ret为子进程PID)parent(ret);}err(EXIT_FAILURE, "shouldn't reach here");  // 理论上不会执行
}
  1. 程序启动,main()函数调用fork()创建子进程。
  2. fork()失败(ret == -1),调用err()打印错误并退出。
  3. ret == 0(当前是子进程):
    • 调用child()函数,打印子进程 PID。
    • 执行execve("/bin/echo", ...),子进程被替换为/bin/echo程序,输出 "hello" 后退出。
  4. ret > 0(当前是父进程,ret为子进程 PID):
    • 调用parent(ret)函数,打印父进程 PID 和子进程 PID。
    • 调用exit(EXIT_SUCCESS)正常退出。

execve 是 Unix/Linux 系统中一个核心的系统调用,用于在当前进程中执行一个新的程序。它的作用是将当前进程的代码、数据、堆栈等全部替换为新程序的内容(进程 ID 保持不变),因此也被称为 “进程替换”

#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
1. const char *pathname
  • 作用:指定要执行的新程序的路径(可以是绝对路径或相对路径)。
  • 要求:必须是一个可执行文件(二进制文件或脚本文件,脚本文件需在首行指定解释器,如 #!/bin/bash,且当前进程对该文件有可执行权限x 权限)。
  • 示例"/bin/echo"(绝对路径)、"./myprogram"(相对路径,当前目录下的可执行文件)。
2. char *const argv[]
  • 作用:传递给新程序的命令行参数数组
  • 格式要求
    • 数组中的每个元素是一个字符串(参数),按顺序对应新程序的命令行参数。
    • 惯例上,argv[0] 是新程序的名称(通常与 pathname 的文件名一致,也可自定义)。
    • 数组必须以 NULL 结尾(标记参数列表结束)。
  • 示例在之前的代码中,char* args[] = {"/bin/echo", "hello", NULL}; 表示给 /bin/echo 程序传递参数 hello,等价于命令行执行 echo hello
3. char *const envp[]
  • 作用:传递给新程序的环境变量数组
  • 格式要求
    • 数组中的每个元素是一个 “键 = 值” 格式的字符串(如 "PATH=/usr/bin""USER=root")。
    • 数组必须以 NULL 结尾。
  • 特殊情况
    • 如果 envp 为 NULL,新程序会继承当前进程的环境变量(通过全局变量 environ 获取)。
    • 若需自定义环境变量,需显式构造该数组(例如 char* env[] = {"LANG=en_US", "DEBUG=1", NULL};)。

编译上述代码

四、结束进程

可以调用_exit()函数(底层发起exit_group()系统调用),来结束进程,进程结束后,所有分配给进程的内存将被回收

但是我们很少会使用_exit()函数,而是通过调用C标准库的exit()函数来结束进程的运行,在这种情况下C标准库会在调用自身的种植处理后调用_exit()函数


总结

本文主要时学习了进程的创建以及结束

http://www.dtcms.com/a/512548.html

相关文章:

  • 网站开发外包报价单网站建设延期通知单
  • 莱芜做网站做网站时给网页增加提醒
  • 怎么做自助购物网站网络销售渠道
  • 【YOLOv4核心技术详解】从损失函数到网络架构
  • XMW技术:颠覆未来的创新引擎
  • 练习python题目小记(四)
  • 网站是先解析后备案吗快手做任务网站
  • C++输入输出模式(ACM模式)笔记(个人)(第十六天)
  • 杭州高端网站开发检查色盲效果网站
  • 面试(五)——Java 集合体系
  • k8s java应用pod内存占用过高问题排查
  • Android8.0+Camera2编译烧录源码研习
  • 液压产品做哪个网站好网站关键字排名怎么做
  • 做网站小程序内蒙古银税互动平台
  • 从虚拟甲板到未来战场!数字孪生重构海战航母战斗群
  • 宁波住房与城乡建设部网站软件技术适合女生学吗大专
  • 从零实现 vLLM (1.2):如何实现张量并行
  • 设计系统掉电保持参数参考
  • 机器学习:基于大数据的基金数据分析可视化系统 股票数据 金融数据 股价 Django框架 大数据技术(源码) ✅
  • 网站留言板样式洛阳青峰网络公司做网站
  • 基因数据库网站开发价格导航门户网站怎么做
  • Java Web登录系统实现(不使用开发工具)
  • 安徽省建设安全质量协会网站百度新闻官网
  • 数据结构——最短路径算法
  • SBC在企业中的应用场景
  • ai痕迹记录
  • 中建八局第一建设公司网站网站建设丨找王科杰专业
  • 网站建设的目标是什么制作简单门户网站步骤
  • C++11----新引入的默认成员函数
  • 广州商城型网站建设佛山网站建设有哪些