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

news2024/11/25 0:13:27

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)){ //宏1
        printf("异常终止:%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.coloradmin.cn/o/1274388.html

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

相关文章

传统算法: Pygame 实现深度优先搜索(DFS)

使用 Pygame 模块实现了深度优先搜索(DFS)的动画演示。首先,它通过邻接矩阵表示了一个图的结构,其中每个节点表示一个字符,每个字符的邻居表示与之相邻的节点。然后,通过深度优先搜索算法递归地访问所有节点,过程中通过动画效果可视化每一步的变化。每次访问一个节点,该…

数据爬虫(JSON格式)数据地图可视化(pyecharts)【步骤清晰,一看就懂】

一、前言 数据存储在网页上&#xff0c;需要爬取数据下来&#xff0c;数据存储格式是JSON&#xff0c;数据可视化在工作中也变得日益重要&#xff0c;接下来将数据爬虫与数据可视化结合起来&#xff0c;做个案例 注&#xff1a;当时数据是22年1月29日爬取数据 二、使用步骤 …

嵌入式数据传输及存储的C语言实现

各种类型的数据传输和存储就涉及到大小端的问题&#xff0c;首先要简单说下芯片的大小端问题&#xff0c;这里主要讨论Cortex-M内核。 M内核支持大端或者小端&#xff0c;实际应用中大部分内核都是小端。以STM32为例&#xff0c;全部都是小端&#xff0c;而且是芯片设计之初就固…

TR转发路由器测评—云企业网实现跨地域跨VPC的网络互通测评实战【阿里云产品测评】

文章目录 一.转发路由器 Transit Router 测评1.1 准备阶段1.2 本文测评收获1.3 什么是云企业网实例、转发路由器实例和云数据传输服务 二.使用云企业网实现跨地域跨VPC的网络互通2.2 **测试连通性**2.3 网络拓扑如下&#xff1a; 心得&#xff1a;总结&#xff1a; 声明&#x…

Vue+SpringBoot解决session跨域问题

做了一个前后端分离&#xff0c;因为前后端的 session id不一致&#xff0c;导致前端请求时&#xff0c;后端的session读取不到对应的值&#xff0c;造成登录问题。 解决方法&#xff1a; SpringBoot项目: 添加一个跨域配置 代码如下: 或者controller使用CrossOrigin Conf…

解决noauth authentication required异常

今天在使用redis数据库的时候&#xff0c;突然给报了个这个错误&#xff0c;上网一查才知道是因为 Redis 服务器需要密码进行身份验证&#xff0c;因此&#xff0c;我们需要通过auth password 进行身份验证。不过我这个密码还是试了很多次才想起来的&#xff0c;哦好像是听网课…

WSL2 docker GUI 界面

在 WSL2 docker 中运行GUI界面。 具体流程和远程显示Ubuntu界面类似&#xff0c;链接, 更简单一点&#xff0c; 少了 ssh 的部分。 安装好wsl2 和 docker wsl2 运行GUI程序&#xff0c;windows 会默认弹出窗口。 可以安装 gedit 测试一下 windows 下载并运行 Xlaunch. 运行 d…

【驱动】SPI驱动分析(二)-SPI驱动框架

SPI驱动框架 SPI驱动属于总线-设备-驱动模型的&#xff0c;与I2C总线设备驱动模型相比&#xff0c;大体框架是一样&#xff0c;他们都是实际的总线。总体框架如下图所示&#xff1a; 从上到下&#xff0c;分为三层&#xff0c;用户空间&#xff0c;内核空间&#xff0c;和硬件…

微信小程序真机调试的时候出现白屏解决办法

我使用uniapp开发微信小程序开发&#xff0c;使用真机调试的时候&#xff0c;出现白屏&#xff0c;页面里面啥都没有&#xff0c;也没有报错&#xff0c;这就让人很纳闷了&#xff1a; 解决办法&#xff1a; 打开真机调试&#xff0c;下面有&#xff1a;切换真机调试1.0&#…

RocketMQ-RocketMQ快速实战及集群原理

一、MQ简介 ​ MQ&#xff1a;MessageQueue&#xff0c;消息队列。是在互联网中使用非常广泛的一系列服务中间件。 这个词可以分两个部分来看&#xff0c;一是Message&#xff1a;消息。消息是在不同进程之间传递的数据。这些进程可以部署在同一台机器上&#xff0c;也可以分布…

虚拟机系列:Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置

Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置 Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置 在更新Oracle VM Virtua…

泛型和数据结构(树)

一&#xff1a;泛型的基本知识 泛型是用来约束操作的数据类型&#xff0c;并进行检查 基本格式&#xff1a;<数据类型> 注意&#xff1a;泛型只能支持引用数据类型&#xff0c;如果是基本数据类型&#xff0c;需要写他的包装类 先来看一下没有泛型约束的集合 我们发现此时…

【ArcGIS Pro二次开发】(78):批量合并GDB数据库

有些GDB数据库会按分幅或行政区划进行分开储存&#xff0c;尤其是一些地形测绘或国情地理数据。 如下图所示&#xff1a; 数据是完整的&#xff0c;但使用的时候要一个一个拖进地图中&#xff0c;进行分析的时候也需要将其合并后使用。 因此就做了这个合库工具。 一、要实现的…

万界星空科技仓库管理wms系统

企业在管理库存时&#xff0c;尤其是生产制造企业&#xff0c;使用传统方式比如纸笔、Excel 管理库存&#xff0c;由于工具和信息化存在局限&#xff0c;导致在管理库存时出现如下问题&#xff1a; 1、通过纸笔记录出入库申请&#xff0c;人为手动计算易出错&#xff0c;数据易…

[iOS学习笔记]浅谈RunLoop底层

RunLoop是什么&#xff1f; RunLoop是iOS开发中比较重要的知识点&#xff0c;它贯穿程序运行的整个过程。它是线程基础架构的一部分&#xff0c;是一种保障线程循环处理事件而不会退出的机制。同时也负责管理线程需要处理的事件&#xff0c;让线程有事儿时忙碌&#xff0c;没事…

键盘敲入一个字母,操作系统发生了什么?

一、设备控制器 我们的电脑设备可以接非常多的输入输出设备&#xff0c;比如鼠标键盘网卡硬盘打印机等&#xff0c;每个设备的用法和功能都不相同&#xff0c;那操作系统是如何把这些输入输出设备统一管理的呢&#xff1f; 为了屏蔽设备之间的差异&#xff0c;每个设备都有一…

rest_framework_django学习笔记一(序列化器)

rest_framework_django学习笔记一(序列化器) 一、引入Django Rest Framework 1、安装 pip install djangorestframework2、引入 INSTALLED_APPS [...rest_framework, ]3、原始RESTful接口写法 models.py from django.db import models 测试数据 仅供参考 INSERT INTO de…

我与开源的历程

我在2000年开始接触开源&#xff0c;当时在松下航空电子美国总部工作。我负责将 IFE 系统从 Win31 迁移到 Linux。作为一个完全不懂 Linux 的小白&#xff0c;我不得不找到一台笔记本电脑安装并自学 Redhat Linux 6.1。2003年回到新加坡后&#xff0c;我发现没有一个凝聚 Linux…

Java数据结构之《链式线性表的插入与删除》问题

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等偏下的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我…

Rust std fs 比 Python 慢!真的吗!?

作者&#xff1a;Xuanwo Databend Labs 成员&#xff0c;数据库研发工程师 https://github.com/xuanwo 我即将分享一个冗长的故事&#xff0c;从 OpenDAL 的 op.read()开始&#xff0c;以一个意想不到的转折结束。这个过程对我来说非常有启发性&#xff0c;我希望你也能感受到。…