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

进程控制(Linux)

fork函数初始

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

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

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{printf("begin\n");fork();printf("after\n");return 0;
}

fork之前,父进程运行;fork之后,创建子进程,调度器决定父子进程谁先被调用。

写时拷贝细节

当fork之后,父子进程共享代码,但是对数据任意一方进行修改,都会触发写时拷贝。可是,是如何触发的呢?操作系统又是如何得知的呢?这和页表的权限位有关,读写和可执行。操作系统在创建子进程的时候,会把数据的写权限取消掉,保留读的权限;当你要对这部分数据进行写入的时候,操作系统就会感知到,由于这部分是数据,操作系统不会异常结束进程,而是会再给你开辟一块空间,写入数据,这就是写时拷贝。

虚拟内存,地址空间,进程地址空间,程序地址空间这些名字实际上都是一个东西,那就是进程地址空间,这也是最准确的叫法。

进程终止

进程退出场景:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法:

  • return结束
  • exit
  • _exit

查询最近一次退出码:

echo $?

exit

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{printf("Linxu");exit(11);return 0;
}

_exit

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{printf("Linxu");_exit(12);return 0;
}

return 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{printf("Linxu");return 0;
}

return 语句会在进程结束后,返回一个数字,叫退出码。告诉父进程,子进程的运行情况。C语言也会使用errno全局变量记录最近一次调用函数的情况。默认认为0是成功运行,其他值则是有异常。exit和_exit会设置退出码,发送信号,结束进程。

野指针,在操作系统上看就是,访问到了非写权限的地址,操作系统做出回应,终止进程

因此,我们也可以使用指令kill搭配选项终止进程。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{while(1){printf("pid : %d\n",getpid());sleep(1);}return 0;
}

使用kill -11 终止上面进程,即使该进程代码没问题,我也能通过发送信号,告诉操作系统该进程不正常,从而异常结束。

所以,进程是否能正常退出,除了代码的语法和逻辑有关,还和进程的维护有直接关系。总而言之,如果你的进程被黑了,权限被改了,那么你的代码就形同虚设了。

exit和_exit函数都是设置退出码,结束进程。那两者有什么区别呢?exit是库函数,而_exit是系统调用接口。exit在清理掉缓冲流等之后,接着调用_exit函数结束进程;而_exit直接就结束进程。

进程等待

僵尸进程情况:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){int cnt = 5;while(cnt){printf("i am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);}else{while(1){printf("i am father, pid:%d, ppid:%d\n",getpid(),getppid());sleep(1);}}return 0;
}

查询指令:

while :; do ps ajx | head -1 && ps ajx | grep testwait | grep -v grep;sleep 1;echo "------------";done

wait结束进程

包含的头文件

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

创建10个进程,使用wait回收进程

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define N 10
void runChild()
{int cnt = 5;while(cnt){printf("i am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}
}
int main()
{for(int i = 0; i < N;i++){pid_t id = fork();if(id == 0){runChild();exit(0);}printf("create child process: %d success\n",id);}for(int i = 0; i < N; i++){pid_t id = wait(NULL);if(id > 0){printf("wait: %d success\n",id);}}sleep(5);return 0;
}

进程替换

execl代码 执行可执行程序

单进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{printf("begin: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());//这类方法的标准写法execl("/usr/bin/ls","ls","-a","-l",NULL);printf("after: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());return 0;
}

原理:旧程序的代码和数据被切换成新程序的代码和数据,不替换环境变量

多进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if(id == 0){ printf("begin: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());//这类方法的标准写法execl("/usr/bin/ls","ls","-a","-l",NULL);printf("after: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());//exit(0);}//fatherelse{pid_t ret = waitpid(id,NULL,0);if(ret > 0){printf("wait success, father pid:%d, ret:%d\n",getpid(),ret);}}return 0;
}

程序替换接口

在Linux中一共有7个程序替换接口,6个库函数接口,1个系统接口execve;

7个接口没有本质区别,都是直接操作进程。不仅c和c++可执行程序可以被调用,像脚本语言,python,java等等语言都可以被调用,因为这些语言在操作系统看来就是一个个进程,没有区别。python也有类似execl接口的函数,只不过使用python语言进行包装,底层还是调用execl接口。

以ls指令为例,如下图解释:

这里参数的作用就是确定一个程序的路径,其次是执行方法,再次就是其他参数,如命令行参数

这6个库函数,带p的函数,指定环境变量PATH;带v的函数,使用字符数组传递后面的参数,不使用可变参数;带e的参数,可以传递环境变量;

上面我执行的ls指令是内部程序,外部程序也可以执行,如下面的代码:

otherExe可执行程序:

#include <iostream>
using namespace std;
int main()
{cout <<"helo Linux C++" <<endl;return 0;
}

mycommand可执行程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//#include <sys/types.h>
int main()
{pid_t id = fork();if(id == 0){ printf("begin: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());//这类方法的标准写法execl("./otherExe","otherExe",NULL);printf("after: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());exit(0);}//fatherelse{pid_t ret = waitpid(id,NULL,0);if(ret > 0){printf("wait success, father pid:%d, ret:%d\n",getpid(),ret);}}return 0;
}

运行结果:

在mycommand程序中调用execl函数,进行程序替换,运行otherExe函数;注意这里是多进程,子进程和父进程互不影响,并发运行。环境变量不会被替换,环境变量具有全局属性,进程在被创建时,就存在了。虽然环境变量也属于数据,但是进程替换并不会替换环境变量,否则父子关系无法保证,该进程无法回收。

vim工具一次编译多个文件

.PHONY:all
all:otherExe mycommand
mycommand:mycommand.cgcc -o $@ $^
otherExe:otherExe.ccg++ -o $@ $^ -std=c++11 
.PHONY:clean
clean:rm -rf mycommand otherExe

上面execl替换外部程序的代码,我们可以使用简便的指令,实现一次编译多个文件,方法如上图的vim代码。

Linux形成的可执行程序是有格式,ELF,有表头,会存储进程入口地址,execl就是依靠这个地址实现进程替换。

如何上传环境变量:

  • 新增环境变量:使用putenv,把环境变量上传到调用进程的上下文中。
  • 覆盖环境变量:使用execl式的接口,要求末尾带e,如execle

覆盖环境变量

#include <iostream>
using namespace std;
int main(int argc,char* argv[],char* env[])
{cout <<"helo Linux C++" <<endl;int i = 0;cout <<"命令行参数" << endl;for(;argv[i]; i++){cout << i << " " << argv[i] << endl;}i = 0;for(; env[i]; i++){cout <<i << " " << env[i] <<endl;}return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{//putenv("MYENV=123456");pid_t id = fork();if(id == 0){ printf("begin: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());char* const myenv[] = {"MYVAL=123","MYPATH=/usr/bin/XXX",NULL};execle("./otherExe", "otherExe", "-a", "-b", NULL, myenv);printf("after: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());exit(0);}//fatherelse{pid_t ret = waitpid(id,NULL,0);if(ret > 0){printf("wait success, father pid:%d, ret:%d\n",getpid(),ret);}}return 0;
}

新增环境变量

#include <iostream>
using namespace std;
int main(int argc,char* argv[],char* env[])
{cout <<"helo Linux C++" <<endl;int i = 0;cout <<"命令行参数" << endl;for(;argv[i]; i++){cout << i << " " << argv[i] << endl;}i = 0;for(; env[i]; i++){cout <<i << " " << env[i] <<endl;}return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{putenv("MYENV=123456");pid_t id = fork();if(id == 0){ printf("begin: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());char* const myenv[] = {"MYVAL=123","MYPATH=/usr/bin/XXX",NULL};execl("./otherExe", "otherExe", "-a", "-b", NULL);printf("after: i am a process  pid:%d, ppid:%d\n",getpid(),getppid());exit(0);}//fatherelse{pid_t ret = waitpid(id,NULL,0);if(ret > 0){printf("wait success, father pid:%d, ret:%d\n",getpid(),ret);}}return 0;
}

总结:

exec系列函数

  • l代表列表,参数使用列表一个个传参
  • v代表vector,参数使用数组传参
  • p代表PATH环境变量
  • e代表env环境变量

6个库函数只在参数上有区别,本质一样,底层最终都会调用一个系统调用接口execve

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

相关文章:

  • 【C++】——string类的使用(详细讲解)
  • 借助 Amazon ECS 全新的内置蓝绿部署功能,加速安全的软件发布进程
  • 【脑电分析系列】第24篇:运动想象BCI系统构建:CSP+LDA/SVM与深度学习方法的对比研究
  • 【论文速递】2025年第22周(May-25-31)(Robotics/Embodied AI/LLM)
  • MySQL 5.7 多实例部署完整指南(基于二进制包)
  • Git的使用——Git命令、密钥/私钥、文件推送/提交、分支增删改查、文件回滚、.gitignore文件忽略
  • [已更新]2025华为杯D题数学建模研赛D题研究生数学建模思路代码文章成品:低空湍流监测及最优航路规划
  • [C++类的默认成员函数——lesson5.构造函数析构函数]
  • 第二十七章 ESP32S3 INFRARED_TRANSMISSION 实验
  • ✅ Python车牌识别计费系统 PyQt5界面 YOLOv5+CRNN 深度学习 MySQL可视化 车牌检测(建议收藏)
  • 盛水最多的容器_优选算法(C++)双指针
  • QT-串口,完结!
  • Git常用命令合集
  • Qt(模态对话框的切换)
  • QT-模型视图结构
  • C语言 C语句
  • 《理解Reactor网络编程模型》
  • Mirror Maze 镜面反射
  • 一个案例弄懂nfs
  • 在飞牛NAS使用Lucky做动态解析到域名?
  • 多实例 MySQL 部署
  • 使用批处理脚本快速切换 Claude API 实现多平台环境配置
  • SkyDiffusion:用 BEV 视角打开街景→航拍图像合成新范式
  • 免费下载适用于 Windows PC 的 Pixologic Zbrush 2026
  • 10.eNSP下载及安装教程(2025年9月21日)
  • Qt 实战 之 打包部署
  • QT-文件
  • 2001-2022年全国地级市高铁开通数据
  • TenstoRT加速YOLOv11——C++端加速
  • 面试技巧第三篇:嵌入式操作系统基础考点:任务、调度和中断