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

13.进程控制_2

文章目录

  • 一、非阻塞式等待
    • 1.1 首先介绍status值的简单获取
    • 1.2 非阻塞式
    • 1.3 非阻塞等待+函数指针调度
    • 1.4 非阻塞等待的意义
      • 1. 保持父进程活跃
      • 2. 支持并发任务调度
      • 3. 提高程序响应性
      • 4. 更灵活的控制流程
  • 二、进程程序替换
    • 2.1 程序替换如何执行
    • 2.2 exec 系列函数一览(共 6 个)
    • 2.3 示例:执行 `ls -a -l` 命令
      • 1. `execl`
      • 2. `execlp`
      • 3. `execle`
      • 4. `execv`
      • 5. `execvp`
      • 6. `execve`
    • 2.4 程序替换调用别的语言(C 调用 Python 脚本)
    • 示例代码:C 调用 Python 脚本 `hello.py`
      • 🔹 Python 脚本(hello.py)
      • 🔹 C 程序(call_python.c)
    • 🧪 编译并运行
    • 2.5 `execve`真正的系统调用
      • 为什么说 `execve` 是“真正的系统调用”?
      • 示例:使用 `execve` 执行 `ls -l`
      • exec 系列函数调用关系图(简化)
  • 三、Makefile

一、非阻塞式等待

1.1 首先介绍status值的简单获取

printf("success:%d, sig number:%d, child exit code:%d\n",ret, (status & 0x7F),       // &上信号位低七位数据,来查看是否是正常终止 (status >> 8) & 0xFF); // 在正常终止的情况下,再去查看退出状态我想说的是这里的>>并不会改变status,并没有赋值右移获取次低八位数据
  • 查看进程是否正常退出(status & 0x7F) —> WIFEXITED(status)
  • 查看正常退出情况下进程退出码(status >> 8) & 0xFF) —> WEXITSTATUS(status)

1.2 非阻塞式

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {pid_t id = fork();if (id == 0) {// 子进程int cnt = 5;while (cnt--) {printf("我是子进程: %d, 父进程: %d, 计数: %d\n", getpid(), getppid(), cnt + 1);sleep(1);}exit(10); // 子进程退出码}// 父进程:非阻塞等待子进程int status = 0;while (1) {pid_t ret = waitpid(id, &status, WNOHANG);if (ret == 0) {// 子进程还在运行printf("父进程轮询中...\n");sleep(1);} else if (ret > 0) {// 子进程退出if (WIFEXITED(status)) {printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));}break;} else {perror("waitpid 出错");break;}}return 0;
}

这里对于前面的阻塞式等待做了哪些修改呢?

  1. 阻塞式
waitpid(id, &status, 0); //其中 0 表示父进程会阻塞等待子进程结束。
  1. 非阻塞式
waitpid(id, &status, WNOHANG); // 使用WNOHANG这个宏,这样父进程就会立即返回,不会等待子进程结束

1.3 非阻塞等待+函数指针调度

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define Num 10// 定义函数指针类型
typedef void (*func_t)();// 函数指针数组
func_t handlerTask[Num];// 示例任务函数
void task1() {printf("执行任务1\n");
}void task2() {printf("执行任务2\n");
}// 加载任务到函数指针数组
void loadTask() {memset(handlerTask, 0, sizeof(handlerTask));handlerTask[0] = task1;handlerTask[1] = task2;
}int main() {loadTask(); // 加载任务pid_t id = fork();if (id == 0) {// 子进程int cnt = 5;while (cnt--) {printf("我是子进程: %d, 父进程: %d, 计数: %d\n", getpid(), getppid(), cnt + 1);sleep(1);}exit(10); // 子进程退出码}// 父进程:非阻塞等待子进程int status = 0;while (1) {pid_t ret = waitpid(id, &status, WNOHANG);if (ret == 0) {// 子进程还在运行printf("父进程轮询中...\n");sleep(1);} else if (ret > 0) {// 子进程退出if (WIFEXITED(status)) {printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));}break;} else {perror("waitpid 出错");break;}}// 父进程执行任务printf("父进程开始执行任务...\n");for (int i = 0; i < Num; ++i) {if (handlerTask[i]) {handlerTask[i](); // 执行任务函数}}return 0;
}

1.4 非阻塞等待的意义

1. 保持父进程活跃

父进程不会因为等待子进程而停滞,可以继续执行其他任务,比如:

  • 处理用户输入
  • 执行后台逻辑
  • 维护网络连接

2. 支持并发任务调度

可以同时管理多个子进程,轮询它们的状态,而不是一个个阻塞等待。

for (int i = 0; i < N; ++i) {waitpid(child[i], &status[i], WNOHANG);
}

3. 提高程序响应性

在图形界面、服务器、嵌入式系统中,阻塞会导致界面卡顿或服务中断。非阻塞等待能让程序持续响应外部事件。

4. 更灵活的控制流程

你可以根据子进程状态决定是否继续等待、重启任务、或执行清理操作,而不是被动等待。


二、进程程序替换

2.1 程序替换如何执行

#include <stdio.h>
#include <unistd.h>int main()
{printf("process is running\n");// 使用 execlp 自动查找路径并执行 ls 命令execlp("ls", "ls", "-a", "-l", NULL);// 如果 execlp 执行失败,打印错误信息perror("execlp");// 这行代码不会执行,除非 execlp 失败printf("process done\n");return 0;
}

程序输出结果
在这里插入图片描述
在这之前我们再来介绍一下创建子进程的两个目的

  1. 子进程执行父进程代码的一部分,也就是执行对应的磁盘代码的一部分
  2. 想让子进程执行一个全新的程序,子进程加载磁盘上指定的程序,执行新的程序代码和数据,这也就是今天这一节的主题也就是进程程序替换

这里我们观察输出结果,发现在进程程序替换之后,printf("process done");这一句代码并没有执行,也就是说明,进程程序替换,是做了一个覆盖。

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

2.2 exec 系列函数一览(共 6 个)

函数名参数形式是否查找 PATH说明
execl列表❌ 不查找 PATH参数一个个列出来
execlp列表✅ 查找 PATH自动查找命令路径
execle列表 + 环境❌ 不查找 PATH可指定环境变量
execv数组❌ 不查找 PATH参数用数组传递
execvp数组✅ 查找 PATH自动查找命令路径
execve数组 + 环境❌ 不查找 PATH最底层,需指定环境变量

不过相比于上面的表格,下面这张我认为更加方便大家记忆

字母含义说明
llist(列表)参数一个个列出来,如 "ls", "-l", NULL
vvector(数组)参数用数组传递,如 char* args[] = {"ls", "-l", NULL}
ppath(自动查找路径)会根据环境变量 PATH 自动查找程序位置
eenv(自定义环境变量)允许你传入自己的环境变量数组 envp[]

2.3 示例:执行 ls -a -l 命令

1. execl

execl("/usr/bin/ls", "ls", "-a", "-l", NULL);

2. execlp

execlp("ls", "ls", "-a", "-l", NULL);

3. execle

/* 对于自己构建的环境变量 */
char*const envp_[] = {(char*)"MYENV=111111", NULL // 这里不进行强转的话他又不认};
execle("/usr/bin/ls", "ls", "-a", "-l", NULL, envp_);/* 对于系统环境变量的传入 */
extern char** environ; //  声明这个二级指针,否则头文件又不知道了
execle("/usr/bin/ls", "ls", "-a", "-l", NULL, environ);/* 那如果我这两种都想要呢?怎么办? 这个时候就要用到 putenv */
// putenv:向当前进程添加或修改环境变量
putenv("MYENV=22222");
execle("/usr/bin/ls", "ls", "-a", "-l", NULL, environ);
// 这个时候咱们自己的环境变量就被添加到这张系统环境变量表当中了

4. execv

char* args_[] = { "ls", "-a", "-l", NULL };
execv("/usr/bin/ls", args_);

5. execvp

char* args[] = { "ls", "-a", "-l", NULL };
execvp("ls", args);

6. execve

char* args[] = { "ls", "-a", "-l", NULL };
char* envp[] = { "PATH=/bin", NULL };
execve("/usr/bin/ls", args, envp);

2.4 程序替换调用别的语言(C 调用 Python 脚本)


示例代码:C 调用 Python 脚本 hello.py

🔹 Python 脚本(hello.py)

# hello.py
print("Hello from Python!")

🔹 C 程序(call_python.c)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {printf("Calling Python script...\n");// 使用 execlp 调用 python3 并执行 hello.pyexeclp("python3", "python3", "hello.py", NULL);// 如果 exec 调用失败perror("execlp failed");return 1;
}

🧪 编译并运行

gcc call_python.c -o call_python
./call_python

输出:

Calling Python script...
Hello from Python!

2.5 execve真正的系统调用

使用man指令查看手册会发现这六个函数
在这里插入图片描述
我们继续深入理解 execve ——真正的系统调用。


为什么说 execve 是“真正的系统调用”?

因为它是 Linux 内核提供的原始接口,其他所有 exec 系列函数(如 execl, execvp, execle 等)都是对它的封装和简化。它直接与内核交互,完成进程替换的核心操作。


示例:使用 execve 执行 ls -l

#include <unistd.h>
#include <stdio.h>int main() {char *args[] = { "ls", "-l", NULL };char *env[] = { "PATH=/bin", NULL };execve("/bin/ls", args, env);// 如果 execve 失败,打印错误信息perror("execve failed");return 1;
}

exec 系列函数调用关系图(简化)

execl   ┐
execle  ├───► 封装参数 + 环境变量 → 调用 execve
execlp  ┘execv   ┐
execvp  ├───► 封装参数数组 → 调用 execve
execve  ◄─── 最终调用的系统接口

这个底层调用在2号手册之中,而这一章就是系统调用的手册页!

在这里插入图片描述

三、Makefile

.PHONY: all           # 声明伪目标,避免与文件名冲突
all: mybin processsub # 默认目标,构建 mybin 和 processsub 两个可执行文件mybin: mybin.c        # 构建 mybin,依赖 mybin.cgcc -o $@ $^ -std=c99  # 编译命令:$@ 表示目标名,$^ 表示所有依赖processsub: processsub.c # 构建 processsub,依赖 processsub.cgcc -o $@ $^ -std=c99  # 编译命令,同上.PHONY: clean         # 声明 clean 为伪目标
clean:                # 清理目标rm -f processsub mybin # 删除生成的可执行文件

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

相关文章:

  • 网站收录免费咨询wordpress 当前分类id
  • 选择TVS管的方法
  • 网站开发制作案例为什么百度搜索不到我的网站
  • 爬虫插件 js chrome插件 简单方案 优势在于不用做爬虫里面困难的解密 反爬之类的。针对小数据量的是可以的。
  • C2000芯片的lib库制作遇到问题记录
  • 重庆做网站哪家好joomla适合做什么网站
  • 网站建设运营知乎网站备案 价格
  • 从点云到模型,徕卡RTC360如何搞定铝单板测量?
  • js 网站头部固定国内网站放国外服务器
  • 网站验证:技术、策略与重要性
  • 怎样做金融理财网站响水县住房建设局网站
  • Flutter---Text
  • 怎样在外管局网站做延期付款做网站的可行性分析
  • Android 通过广播监听home键和任务键
  • 注册公司的网站开发做网站公司
  • 发票识别技术:结合OCR与AI技术,实现纸质票据高效数字化,推动企业智能化转型
  • 哈尔滨flash网站网页设计全网营销型网站 新闻
  • 从零开始,一步一步地搭建录屏类自动发布工作3:Phase 4 全功能录制功能实现
  • 阜新网站推广个人主页网站设计代码
  • 面试知识点重现
  • 网络原理:网络通信基础概念全面整理
  • CCF-GESP 等级考试 2025年9月认证C++三级真题 - 编程题 解析
  • 景山网站建设公司内蒙古网站建设云聚
  • 网站怎么做app个人网站取什么域名好
  • 剪映制作一个音乐滚动流程
  • 网站模板预览wordpress ftp没有权限
  • **编程基础学习(二)-C语言变量、控制结构和函数
  • 02117 信息组织【第七章】
  • FFRT的核心并发范式与样例概览
  • 用jsp做一网站的流程佛山app开发公司排名