uc_09_创建新进程 exec() system()

1  什么是创建新进程(夺舍)

        在前面文章中,我们学习了fork()函数用来创建子进程

        子进程是父进程的副本,复制父进程除代码段以外的其他数据,代码段数据和父进程共享。

        子进程的PID与父进程不同:

        

        而创建新进程则不同。

        与fork()不同,exec函数族不是创建调用进程的子进程,而是创建一个新的进程去掉调用进程自身。

        新进程会用自己的全部地址空间,覆盖调用进程的地址空间。

        新进程的PID与调用进程相同(子进程变身后,父子关系不变):

        

2  创建新进程(夺舍)

        exec不是一个函数,而是一堆函数(6个),称为exec函数族。它们的功能是相同的,用法也相近,只是参数的形式和数量略有不同。建议只熟练用第1个即可。

        #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[]);

                功能:让新进程取代原本的旧进程

                path:可执行文件的路径

                arg:命令行参数

                ...:不定长参数(可变长参数),就像printf()

                envp:旧进程为新进程指定的环境变量,不指定则从调用进程复制。

                            变相地在向新进程传递数据!

                l:list,新进程的命令行参数以字符指针列表(const char* arg, ...)的形式传入,列表以

                     指针结束,别忘了写NULL

               v:vector,新进程的命令行参数以字符指针数组(char* const argv[])的形式传入,数组以

                     空指针结束。

                p:path,若第一个参数中不包含"/"完整路径,则将其视为文件名,并根据PATH环境变

                      量搜索该文件。

                e:environment,新进程的环境变量以字符指针数组(char* const envp[])的形式传入,

                      数组以空指针结束,不指定环境变量则从调用进程复制。

        其实6个exec函数只有execve是真正的系统调用,其它5个是对evecve的简单包装:

        

        调用exec函数不仅改变调用进程的地址空间和进程映像,调用进程的一些属性也发生了变化(归零、默认、失效):

        -任何处于阻塞状态的信号都会丢失

        -被设置为捕获的信号会还原为默认操作

        -有关线程属性的设置会还原为缺省值

        -有关进程的统计信息会复位

        -与进程内存相关的任何数据都会丢失,包括内存映射文件(局部变量等)

        -标准库在用户空间维护的一切数据结构(如通过atexit或on_exit函数注册的退出处理函数)

          都会丢失

        但有些属性会被新进程继承下来,如PID,PPID,实际用户ID,实际组ID,优先级,文件描述符等。

        注意,如果新进程创建成功,exec函数是不会返回的,因为成功的exec调用会以跳转到新进程的入口地址作为结束,而刚刚运行的代码是不会存在于新进程的地址空间中的(旧进程已死,没得返回;新进程没调用,也就返不给新进程)。但如果进程创建失败,exec函数会返回-1

//new.c  变身的目标
#include<stdio.h>
#include<unistd.h>int main(int argc,char* argv[],char* envp[]){printf("PID : %d\n",getpid());printf("命令行参数:\n");for(char** pp = argv;*pp;pp++){printf("%s\n",*pp);}printf("环境变量:\n");for(char** pp = envp;*pp;pp++){printf("%s\n",*pp);}printf("---------------------\n");return 0;
}
//编译执行为new,作为变身的目标
//exec.c  创建新进程(bash的子进程exec变身成new进程)
#include<stdio.h>
#include<unistd.h>int main(void){printf("%d进程:我要变身了\n",getpid());/*if(execl("./new","new","hello","123",NULL) == -1){ //第一个参数已定位,故第二perror("execl");                                 //个new无需再./return -1;}*//*if(execl("/bin/ls","ls","-i","-a","-l",NULL) == -1){ //变身成ls命令perror("execl");                                //命令的本质就是可执行程序return -1; }*//*if(execlp("lsSSSS","ls","-a","-i",NULL) == -1){  //报错perror("execlp");return -1;}*///演示execve(),定义2个char* []//指定新进程的环境变量,变相在新旧进程间传递数据!char* envp[] = {"NAME=laozhang","AGE=18","FOOD=guobaorou",NULL};/*if(execle("./new","new","hello","123",NULL,envp) == -1){perror("execle");return -1;}*/char* argv[] = {"new","hello","123",NULL};if(execve("./new",argv,envp) == -1){perror("execve");return -1;}printf("%d进程:变身完成了\n",getpid());//不会执行,因为前面已经进入new进程了return 0;                             //本进程已被抛弃,代码自然不被执行
}//编译执行

        调用exec函数固然可以创建出新的进程,但是新进程会取代原来的进程。如果既想创建新的进程,同时又希望原来的进程继续存在, 则可以考虑fork() + exec()模式,即在fork产生的子进程里调用exec函数,新进程取代了子进程,但父进程依然存在:

        

//forkexec.c  fork() + exec()模式
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>int main(void){//创建子进程pid_t pid = fork();if(pid == -1){perror("fork");return -1;}//子进程代码,exec变身if(pid == 0){if(execl("./new","new","hello","123",NULL) == -1){perror("execl");return -1;}//return 0; //可以注释掉,因为已变身成new进程了,这里压根不执行}               //即使变身失败,也return -1; 了//父进程代码,收尸int s;//用来输出子进程的终止状态if(waitpid(-1,&s,0) == -1){ //-1任意PID,0阻塞perror("waitpid");return -1;}if(WIFEXITED(s)){printf("正常终止:%d\n",WEXITSTATUS(s));}else{printf("异常终止:%d\n",WTERMSIG(s));}//创建第二个子进程pid = fork();if(pid == -1){perror("fork");return -1;}//子进程代码if(pid == 0){if(execl("/bin/ls","ls","-i","-l",NULL) == -1){perror("execl");return -1;}//return 0;}//父进程代码if(waitpid(-1,&s,0) == -1){perror("waitpid");return -1;}if(WIFEXITED(s)){ //宏,判断进程死因printf("正常终止:%d\n",WEXITSTATUS(s));}else{printf("异常终止:%d\n",WTERMSIG(s));}return 0;
}
//编译执行

3  system() 最优

        system()   ==   fork()   +   exec ()   +   waitpid()

        使用system()函数而不用vfork() + exec ()的好处是,system函数针对各种错误和信号都做了必要的处理,而且system是标准库函数,可跨平台使用,各种报错措施也完备。

        #include <stdlib.h>

        int system(const char* command);

                功能:执行shell命令

                command:shell命令行字符串

                返回值:成功返回command进程的终止状态,失败返回-1 

        system()函数执行command参数所表示的命令行,并返回命令进程的终止状态。

        若command参数取NULL,返回非0表示shell可用,返回0表示shell不可用。

       

        在system()函数内部调用了vfork()   exec ()   和waitpid()等函数:

        -如果调用vfork()或waitpid()函数出错,则返回-1 

        -如果调用exec()函数出错,则在子进程中执行exit(127)

        -如果都成功,则返回command进程的终止状态(由waitpid()的status参数获得)

//system.c  system()函数演示
#include<stdio.h>
#include<stdlib.h> //system()是标准库函数
#include<sys/wait.h>int main(void){int s = system("./new hello 123"); //就像在命令行输入if(s == -1){ //system()失败perror("system");return -1;}if(WIFSIGNALED(s)){ //宏1printf("异常终止:%d\n",WTERMSIG(s)); //sytem()成功,./new失败}else{printf("正常终止:%d\n",WEXITSTATUS(s));}//创建第2个新进程s = system("ls -i -l --color=auto"); //试试不加--if(s == -1){                     //.bashrc中alias ls='ls --color=autu'后,perror("system");            //只有bash终端自带效果,但程序中要手敲return -1;}if(WIFEXITED(s)){ //宏2,等效滴printf("正常终止:%d\n",WEXITSTATUS(s));}else{printf("异常终止:%d\n",WTERMSIG(s));}return 0;
}
//编译执行

       vfork()生成的子进程,不会复制数据,而是共用父进程的数据,省时省力,至今用于system()底层。运行时子进程优先用,父进程阻塞;子进程结束后,父进程才继续运行。

        近来,写时复制的发明,vfork()用得少了,可不深究,更多用fork()即可。

3.1  电子表?

      尝试用system()写个电子钟表,显示时分秒,每秒更新。

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

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

相关文章

docker-compose;私有镜像仓库harbor搭建;镜像推送到私有仓库harbor

docker-compose&#xff1b;私有镜像仓库harbor搭建&#xff1b;镜像推送到私有仓库harbor 文章目录 docker-compose&#xff1b;私有镜像仓库harbor搭建&#xff1b;镜像推送到私有仓库harbordocker-compose私有镜像仓库harbor搭建镜像推送到私有仓库harbor docker-compose D…

论坛自动多播放源采集源码

论坛自动多播放源采集源码是一种用于自动抓取论坛中的多个视频播放源的程序源代码。它可以自动搜索并采集论坛中的多个视频播放源&#xff0c;帮助用户快速找到所需的视频资源。该源码可以帮助用户节省时间和精力&#xff0c;提高视频资源的获取效率。 演示 地 址&#xff1a;…

2023年小美赛认证杯A题太阳黑子预测(Sunspot Forecasting)思路模型代码解析

2023年小美赛认证杯A题&#xff1a;太阳黑子预测&#xff08;Sunspot Forecasting&#xff09; 【请电脑打开本文链接&#xff0c;扫描下方名片中二维码&#xff0c;获取更多资料】 一、问题重述 太阳黑子是太阳光球上的现象&#xff0c;呈暂时性斑点&#xff0c;比周围区域…

2023年中国金融科技研究报告

第一章 行业概况 1.1 定义 金融科技&#xff08;FinTech, Financial Technology&#xff09;代表了金融和技术的交汇。这一领域虽然处于发展的初期阶段&#xff0c;但已经展现出深远的影响力。金融科技的业务模式多样&#xff0c;涵盖了从传统金融服务的数字化转型到新兴技术…

python中的序列类型

文章目录 字符串列表元组由元组构成的列表 字符串 字符串是编程语言中的一种基本数据类型&#xff0c;用于表示一串字符序列。在Python中&#xff0c;字符串是不可变的&#xff0c;也就是说一旦字符串被创建&#xff0c;就无法修改其中的字符。 Python中的字符串可以用单引号…

索尼mxf覆盖部分恢复案例(索尼PMW-580)

索尼mxf覆盖部分恢复案例(索尼PMW-580) 索尼的摄像机型号是比较繁多的&#xff0c;高端系列基本上是以mxf文件为主&#xff0c;这一类案例之前处理不少&#xff0c;今天我们看一个索尼pmw-580摄像机删除后又覆盖的恢复案例。 故障存储:64G SD卡/Exfat文件系统 故障现象: 拍…

【开发规范】前端开发中引用文件的方式

1. 介绍 在前端开发中&#xff0c;使用别名引用文件和使用相对路径引用文件是两种不同的方式&#xff0c;它们通常用于引用模块、组件或资源文件。这两种方式的区别主要在于它们的含义和用途。 2. 使用别名引用文件 符号通常是一种别名&#xff0c;表示项目的根路径或者某个特…

Kettle 浅入浅出

前言 最近又要迭代客户定制化的数据处理系统了。提到数据处理&#xff0c;不禁想到了以前使用过的 ETL 处理工具 Kettle。本文将对 Kettle 做一些简单的介绍。 Kettle 介绍 在介绍 Kettle 前先了解下什么是 ETL&#xff0c;ETL 是 Extract-Transform-Load 的缩写&#xff0c…

Django回顾1

目录 1.c/s架构 2.b/s架构 3.基于socket写一个web应用 main.py index.html 4.手写web框架 main.py login.html time.html user_list.html user_list_new 二.HTTP协议 1.什么是HTTP协议 2.HTTP协议的作用 3.HTTP版本及区别 4.HTTP协议的特点 5.HTTP请求协议 常…

腹泻的原因,种类,风险因素,如何预防

谷禾健康 腹泻是常见的健康问题&#xff0c;相信绝大多数人在生活中都曾遭受过腹泻的困扰。 根据2016年柳叶刀期刊统计&#xff0c;慢性腹泻影响全世界 3%-20% 的成年人。全球每年有17亿儿童腹泻病例&#xff0c;腹泻是五岁以下儿童死亡的第五大原因&#xff0c;每年约有52.5万…

P1 什么是链表 C语言简单易懂

目录 前言 01 什么是链表 02 数组的特点 03 数组的缺点 3.1 删除数组其中一个元素 3.2 数组增加某个节点 04 链表 前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《 C 》✨✨✨ &#x1f525; 推荐专栏2: 《 Linux C应用编程&#xff08;概念…

如何在Ubuntu上清理缓存和垃圾文件

随着时间的推移&#xff0c;Ubuntu系统上可能会积累大量的缓存和垃圾文件&#xff0c;占据宝贵的磁盘空间&#xff0c;同时也可能影响系统性能。为了确保系统保持高效运行并释放磁盘空间&#xff0c;我们可以定期执行清理操作。在本文中&#xff0c;我们将介绍一些常见的清理步…

Leetcode98 验证二叉搜索树

题意理解&#xff1a; 首先明确二叉树的定义&#xff0c;对于所有节点&#xff0c;根节点的值大于左子树所有节点的值&#xff0c;小于右子树所有节点的值。 注意一个误区&#xff1a; 根节点简单和左孩子&#xff0c;右孩子比大小是不够的&#xff0c;要和子树比&#xff0c;…

2024年天津天狮学院专升本专业课考试报名通知

天津天狮学院2024年高职升本科专业课报名时间考试时间通知 1.报名条件 报名条件和具体要求按照天津市招生委员会的文件规定执行。考生必须完成文化课报名环节&#xff0c;且填报天津天狮学院志愿&#xff0c;方可报考我校专业课考试。考生提供的各种证件应真实有效&#xff0…

数据结构和算法-树和二叉树的定义和基本术语和性质

文章目录 树的基本概念和相关术语相关的应用节点间的关系描述节点&#xff0c;树的属性描述有序树vs无序树树vs森林小结 树的相关性质考点1考点2考点3考点4考点5考点6小结 二叉树的相关概念和基本术语重要 &#xff08;五种状态&#xff09;特殊二叉树小结 二叉树的相关性质二叉…

css深度选择器>>>、/deep/ 、::v-deep 、:deep()

很多vue的组件库 , 如vant&#xff0c;elementUI&#xff0c; iview等都可能自定义样式。 如项目中用到了 elementui&#xff0c;如果使用预处理器 scss, sass,less , 修改样式可能修改不掉, 而且这种需求出现的频率非常高。但如果去掉scoped话又会影响全局样式。当然我们知道可…

设计模式之美学习笔记-理论篇1-面向对象的特性

一、设计模式前言 面向对象 主流的编程范式或者是编程风格有三种&#xff0c;它们分别是面向过程、面向对象和函数式编程。面向对象这种编程风格又是这其中最主流的。现在比较流行的编程语言大部分都是面向对象编程语言。大部分项目也都是基于面向对象编程风格开发的。面向对…

中职组网络安全-FTPServer20221010.img(环境+解析)

任务环境说明&#xff1a; √服务器场景&#xff1a;FTPServer20221010.img √服务器操作系统&#xff1a;未知&#xff08;关闭链接&#xff09; √FTP用户名&#xff1a;attack817 密码&#xff1a;attack817 1.分析attack.pcapng数据包文件&#xff0c;通过分析数据包attack…

elasticsearch聚合、自动补全、数据同步

目录 一、数据聚合1.1 聚合的种类1.2 DSL实现聚合1.2.1 Bucket聚合语法1.2.2 聚合结果排序1.2.3 限定聚合范围1.2.4 Metric聚合语法 1.3 RestAPI实现聚合 二、自动补全2.1 拼音分词器2.2 自定义分词器2.3 自动补全查询2.4 RestAPI实现自动补全 三、数据同步3.1 思路分析3.1.1 同…

哈希表Leetcode 1657. 确定两个字符串是否接近

如果可以使用以下操作从一个字符串得到另一个字符串&#xff0c;则认为两个字符串 接近 &#xff1a; 操作 1&#xff1a;交换任意两个 现有 字符。 例如&#xff0c;abcde -> aecdb操作 2&#xff1a;将一个 现有 字符的每次出现转换为另一个 现有 字符&#xff0c;并对另…