Linux下的系统编程——进程的执行与回收(八)
前言:
前面我们对进程已经有了一个初步的了解与认识,现在让我们学习一下进程中一些函数的具体使用,比如exec可以执行一些指定的程序,wait / waitpid可以回收子进程,什么是孤儿进程,什么是僵尸进程,下面让我们一起对这些进行中的操作进行学习吧
目录
一、exec函数族
1.execlp:
2.execl:
3.execvp
4.exec函数族的一般规律:
二、回收子进程
1.孤儿进程:
2 .僵尸进程:
编辑
3.wait:
4.waitpid
5.waitpid回收多个子进程
一、exec函数族
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核丕换壳。

1.execlp:
int execlp(const char *file, const char *arg, ...); 借助 PATH 环境变量找寻待执行程序
参1: 程序名
参2: argv0
参3: argv1
...: argvN
哨兵:NULL
该函数通常用来调用系统程序。如: ls、date、cp、cat等命令。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{int i;pid_t pid; //创建子进程if(pid == -1){perror("fork error");exit(1);}else if(pid == 0){ //子进程//execlp("ls","-l","-d","-h",NULL);//错误写法/************************************/execlp("ls","ls","-l","-h",NULL); /************************************/perror("exec error");exit(1);}else if(pid > 0){ //父进程sleep(1);printf("I'm parent : %d\n",getpid());}return 0;
}

date命令的实现:
execlp("date","date",NULL);

2.execl:
int execl(const char *path, const char *arg, ...); 自己指定待执行程序路径。(路径+程序名)
#include <stdio.h>int main(int argc, char **argv)
{printf("Hello, %s!\n", argv[1]);printf("Hello, world!\n");return 0;
}

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{int i;pid_t pid; //创建子进程if(pid == -1){perror("fork error");exit(1);}else if(pid == 0){ //子进程//execlp("ls","-l","-d","-h",NULL);//execlp("date","date",NULL);/************************************/execl("./a.out","./a.out","linux",NULL);/************************************/perror("exec error");exit(1);}else if(pid > 0){ //父进程sleep(1);printf("I'm parent : %d\n",getpid());}return 0;
}

3.execvp
加载一个进程,使用自定义环境变量env
int execvp(const char*file, const char *argv[]);
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{int i;pid_t pid; 创建子进程if(pid == -1){perror("fork error");exit(1);}else if(pid == 0){ //子进程//execlp("ls","-l","-d","-h",NULL);//execlp("date","date",NULL);//execl("./a.out","./a.out","linux",NULL);/************************************/char *argv[] = {"date",NULL};execvp("date",argv);/************************************/perror("exec error");exit(1);}else if(pid > 0){ //父进程sleep(1);printf("I'm parent : %d\n",getpid());}return 0;
}

4.exec函数族的一般规律:
l:命令行参数列表
p:使用PATH环境变量
v:使用命令行参数数组
exec函数一旦调试成功即执行新的程序,不返回。只要失败才返回,错误值-1。所以通常我们直接在exec函数调用后调用 perror()和exit()。无需if判断。·
二、回收子进程
1.孤儿进程:
父进程死亡子进程进孤儿院
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
模拟孤儿进程:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{pid_t pid;pid = fork();if (pid == 0) {while (1) {printf("I am child, my parent pid = %d\n", getppid());sleep(1);}} else if (pid > 0) {printf("I am parent, my pid is = %d\n", getpid());sleep(9);printf("------------parent going to die------------\n");} else {perror("fork");return 1;}return 0;
}
查看进程状态:ps ajx

进程孤儿院:
1 2035 2035 2035 ? -1 Ss 1001 0:00 /lib/systemd/systemd --user
解决方法:
杀死子进程: kill -9 4871

2 .僵尸进程:
子进程死亡,父进程一直不管
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(zombie)进程。(死亡以后没有回收)
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
模拟僵尸进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{pid_t pid;pid = fork();if (pid == 0) {printf("---child, my parent= %d, going to sleep 10s\n", getppid());sleep(10);printf("-------------child die--------------\n");} else if (pid > 0) {while (1) {printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);sleep(1);}} else {perror("fork");return 1;}return 0;
}
查看进程状态:ps ajx
解决方法:
杀死父进程: kill -9 4770
![]()
*3.wait:
wait函数: 回收子进程退出资源, 阻塞回收任意一个。
pid_t wait(int *status)
参数:(传出) 回收进程的状态。
返回值:成功: 回收进程的pid
失败: -1, errno
函数作用1: 阻塞等待子进程退出
函数作用2: 清理子进程残留在内核的 pcb 资源
函数作用3: 通过传出参数,得到子进程结束状态
获取子进程正常终止值:
WIFEXITED(status) --》 为真 --》调用 WEXITSTATUS(status) --》 得到 子进程 退出值。
获取导致子进程异常终止信号:
WIFSIGNALED(status) --》 为真 --》调用 WTERMSIG(status) --》 得到 导致子进程异常终止的信号编号。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{pid_t pid, wpid;int status;pid = fork();if (pid == 0) {printf("---child, my id= %d, going to sleep 10s\n", getpid());sleep(10);printf("-------------child die--------------\n");return 73;} else if (pid > 0) {//wpid = wait(NULL); // 不关心子进程结束原因wpid = wait(&status); // 如果子进程未终止,父进程阻塞在这个函数上if (wpid == -1) {perror("wait error");exit(1);}if (WIFEXITED(status)) { //为真,说明子进程正常终止. printf("child exit with %d\n", WEXITSTATUS(status));}if (WIFSIGNALED(status)) { //为真,说明子进程是被信号终止.printf("child kill with signal %d\n", WTERMSIG(status));}printf("------------parent wait finish: %d\n", wpid);} else {perror("fork");return 1;}return 0;
}
正常终止:

被信号终止:

*4.waitpid
waitpid函数: 指定某一个进程进行回收。可以设置非阻塞。
waitpid(-1, &status, 0) == wait(&status);
pid_t waitpid(pid_t pid, int *status, int options)
参数:
pid:指定回收某一个子进程pid
> 0: 待回收的子进程pid
-1:任意子进程
0:同组的子进程。
status:(传出) 回收进程的状态。
options:WNOHANG 指定回收方式为,非阻塞。
返回值:
> 0 : 表成功回收的子进程 pid
0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
-1: 失败。errno
回收任意子进程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>int main(int argc,char *argv[])
{int i;pid_t pid,wpid;for(i = 0;i < 5;i++){if(fork()==0) //循环期间,子进程不forkbreak;}if(i == 5){ //父进程//wait(NULL);//一次wait/waitpid函数调用,只能回收一个子进程/*****************************************/wpid = waitpid(-1,NULL,WNOHANG);//回收任意子进程,没有结束的子进程,父进程直接返回0/****************************************/if(wpid == -1){perror("waitpid error");exit(1);}printf("I'm parent ,wait a child finish :%d\n",wpid);}else{ //子进程,从break跳出sleep(i);printf("I'm %dth child\n",i+1);}return 0;
}

回收指定进程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>int main(int argc,char *argv[])
{int i;pid_t pid,wpid,tmpid;for(i = 0;i < 5;i++){pid = fork();if(pid == 0){ //循环期间,子进程不forkbreak;}if(i == 2){tmpid = pid;printf("*************pid= %d***************\n",pid);}}if(i == 5){ //父进程,从表达式2跳出sleep(5); //设置睡眠,等所有子进程结束后再回收//wait(NULL); //一次wait/waitpid函数调用,只能回收一个子进程//wpid = waitpid(-1,NULL,WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0printf("I am parent , before waitpid , pid = %d\n",tmpid);/********将前面sleep(5)屏蔽***************///wpid = waitpid(tmpid,NULL,0); //指定一个进程回收,阻塞回收/****************************************//*****************************************/wpid = waitpid(tmpid,NULL,WNOHANG); //指定一个进程回收,不阻塞/****************************************/if(wpid == -1){perror("waitpid error");exit(1);}printf("I'm parent ,wait a child finish :%d\n",wpid); //wpid回收的是真正的子进程id}else{ //子进程,从break跳出sleep(i);printf("I'm %dth child,pid = %d\n",i+1,getpid());}return 0;
}

注意:
一次wait/waitpid调用只能回收一个子进程,无法回收他孙子辈的进程,多次清理需要while
5.waitpid回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>int main(int argc,char *argv[])
{int i;pid_t pid,wpid;for(i = 0;i < 5;i++){pid = fork();if(pid == 0){ //循环期间,子进程不forkbreak;}}if(i == 5){ //父进程/**********使用阻塞回收子进程********/while((wpid = waitpid(-1,NULL,0))){printf("wait child %d\n",wpid);}/***********************************/}else{ //子进程sleep(i);printf("I'm %dth child ,pid =%d\n",i+1,getpid());}return 0;
}
结束一个回收一个

之后返回-1,表示没有失败了

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>int main(int argc,char *argv[])
{int i;pid_t pid,wpid;for(i = 0;i < 5;i++){pid = fork();if(pid == 0){ //循环期间,子进程不forkbreak;}}if(i == 5){/*********使用阻塞回收子进程***********//*while((wpid = waitpid(-1,NULL,0))){printf("wait child %d\n",wpid);}*//***********************************//*******使用非阻塞方式回收子进程******/while((wpid = waitpid(-1,NULL,WNOHANG)) != -1){if(wpid > 0){ printf("wait child %d\n",wpid);}else if(wpid == 0){sleep(1);continue;}/************************************/}}else{sleep(i);printf("I'm %dth child ,pid =%d\n",i+1,getpid());}return 0;
}

相关文章:
Linux下的系统编程——进程的执行与回收(八)
前言: 前面我们对进程已经有了一个初步的了解与认识,现在让我们学习一下进程中一些函数的具体使用,比如exec可以执行一些指定的程序,wait / waitpid可以回收子进程,什么是孤儿进程,什么是僵尸进程…...
第十九章 ObjectScript - 执行例程
文章目录 第十九章 ObjectScript - 执行例程执行例程New 命令 第十九章 ObjectScript - 执行例程 执行例程 执行例程时,使用DO命令,如下所示: do ^routinename要执行一个过程、函数或子程序(不访问其返回值),可以使用以下命令: do label^ro…...
Podman安装与使用
1.Podman简介 Podman是一个无守护进程的容器引擎,用于在Linux系统上开发、管理和运行OCI容器。 Podman的主要功能包括: 创建和管理容器:Podman可以创建、启动、停止和删除容器,以及管理容器的生命周期。容器镜像管理࿱…...
C++ 嵌套循环
一个循环内可以嵌套另一个循环。C 允许至少 256 个嵌套层次。 语法 C 中 嵌套 for 循环 语句的语法: for ( init; condition; increment ) {for ( init; condition; increment ){statement(s);}statement(s); // 可以放置更多的语句 }C 中 嵌套 while 循环 语句的…...
锁( ReentrantLock,Synchronized)
1.lock和synchronized 语法层面 synchronized 是关键字,源码在 jvm 中,用 c 语言实现; Lock 是接口,源码由 jdk 提供,用 java 语言实现; 使用 synchronized 时,退出同步代码块锁会自动释放&…...
主频计算-架构真题(二十三)
某文件系统采用多级索引结构,若磁块大小为4K字节,每个块号需占4个字节,那么采用二级索引结构时的文件最大长度可占用()个物理块。 1、1024 2、1024*1024 3、2048*2048 4、4096*4096 答案:B 解析&…...
docker安装redis实操记录
1.Docker拉取镜像 docker pull redis2.Docker挂载配置文件 创建挂载文件夹 mkdir -p /home/redis/data下载默认配置文件 redis.conf 3.启动redis 容器 docker run --restartalways --log-opt max-size100m --log-opt max-file2 -p 6379:6379 --name redis -v /home/redi…...
MobaXterm 突破14个session限制
通常情况下:随着工作时间的增长,我们会保存许许多多的linux到本地的mobastream,然后当超过14个,就会被被限制,这个会让人很头疼。 1. 安装python,配置好环境变量 测试安装成功: 2. 基于项目进行…...
使用Redisson实现高并发抢红包
一、概述 1、简介 在传统的抢红包场景中,如果面临高并发请求,通常需要考虑加锁来保证数据的一致性。而在分布式环境下,为了解决分布式锁的问题,我们可以使用Redisson这样的分布式Java对象和服务框架来实现。 本篇博客将演示如何…...
【网络编程】TCP/IP协议(互联网的基石)
(꒪ꇴ꒪ ),Hello我是祐言QAQ我的博客主页:C/C语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍快上🚘,一起学习,让我们成为一个强大的攻城狮࿰…...
【VS Code插件开发】自定义侧边栏、视图(六)
🐱 个人主页:不叫猫先生,公众号:前端舵手 🙋♂️ 作者简介:前端领域优质作者、阿里云专家博主,共同学习共同进步,一起加油呀! 📢 资料领取:前端…...
lv3 嵌入式开发-8 linux shell脚本函数
目录 1 函数的定义 2 函数的调用 3 变量的作用域 4 练习 1 函数的定义 基本语法: function name() {statements[return value] }function是 Shell 中的关键字,专门用来定义函数; name是函数名; statements是函数要执行…...
国际版阿里云腾讯云免费开户:服务器怎样转移
阿里云服务器怎样转移 阿里云服务器作为云核算范畴的领军企业之一,为用户供应了高性能、可靠、安全的云服务器服务。随着业务的发展和需求的改动,或许会有需求将阿里云服务器进行转移的情况。本文将介绍阿里云服务器转移的步骤和注意事项,帮…...
区块链实验室(20) - FISCO控制台连接到指定的节点
在FISCO技术文档中,控制台默认采用config.toml作为配置文件,并指定了连接的节点地址和商品,如下所示。 [network] peers["127.0.0.1:20200", "127.0.0.1:20201"] # The peer list to connect在该案例中,控…...
网络渗透day10-工具和技术
以下为网络渗透的工具和技术。 让我更详细地描述网络渗透测试的各个阶段以及使用的工具。 1. 信息收集阶段: 目标识别: 在这一阶段,渗透测试人员确定测试的目标,例如特定的服务器、应用程序或网络。 开放源情报(OSIN…...
SSE 和 WebSocket 应用
SSE 和 WebSocket 应用 一.SSE 和 WebSocket 对比二.SSE 和 WebSocket 调试SpringBoot 下 SSE 应用1.依赖2.启动类3.接口类4.Html 测试5.测试结果 SpringBoot 下 WebSocket 应用1.依赖2.启动类3.WS 切点配置4.WS连接类配置5.WS Html 测试6.测试结果 一.SSE 和 WebSocket 对比 …...
mac帧 arp
1.分片 2.MSS max segment size 3.跨网络的本质 就是经历很多的子网或者局域网 4.将数据从A主机跨网络送到B主机的能力 IP和mac IP解决的是路径选择的问题 5.数据链路层 用于两个设备(同一种数据链路节点)之间进行传递 6.以太网ether 7.局域网通…...
java面试题-Redis相关面试题
Redis相关面试题 面试官:什么是缓存穿透 ? 怎么解决 ? 候选人: 嗯~~,我想一下 缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询&…...
你用过 Maven Shade 插件吗?
文章首发地址 Maven Shade插件是Maven构建工具的一个插件,用于构建可执行的、可独立运行的JAR包。它解决了依赖冲突的问题,将项目及其所有依赖(包括传递依赖)合并到一个JAR文件中。 下面是对Maven Shade插件的一些详解ÿ…...
Android 后台启动Activity适配
在Android 9及以下版本,后台启动Activity相对自由,但是如果在Activity上下文之外启动Activity会有限制。 Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag所以此时需要给intent添加flag&#x…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

