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)){ //宏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.mfbz.cn/a/206010.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TZOJ 1387 人见人爱A+B

答案&#xff1a; #include <stdio.h> void time(int ah, int am, int as, int bh, int bm, int bs, int* sum_h, int* sum_m, int* sum_s) //不需要返回值所以定义void函数&#xff0c;前面6个为输入&#xff0c;然后用指针存给后面三个 {*sum_s (as bs) % 60; …

达梦数据库安装(DM8)新版 windows11下安装及超详细使用教程

达梦数据库安装&#xff08;DM8&#xff09;新版 windows11下安装及超详细使用教程 新电脑安装重新写了一下 注意看一下踩坑部分 文章目录 1.DM 数据库安装1.1 windows11安装前准备1.1.0 安装环境要求1.1.1 检查系统信息1.1.2 检查系统内存1.1.3 检查存储空间 1.2 官网下载免…

11 款顶级的免费 iPhone 数据恢复软件

iPhone 拥有巨大的存储容量。您可以在 iPhone 设备上存储图像、文档和视频等数据。有时&#xff0c;您的 iPhone 会发生许多意外事件&#xff0c;例如意外删除&#xff0c;从而导致数据丢失。这里有 11 个最好的免费 iPhone 数据恢复软件&#xff0c;您可以免费下载&#xff0c…

Android Bitmap裁剪/压缩/缩放到限定的最大宽高值,Kotlin

Android Bitmap裁剪/压缩/缩放到限定的最大宽高值&#xff0c;Kotlin private fun cropImage(image: Bitmap): Bitmap {val maxWidth 1024 //假设宽度最大值1024val maxHeight 1024 //假设高度最大值1024val width image.widthval height image.heightif (width < maxWi…

架构的模式

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

一起学docker系列之十五深入了解 Docker Network:构建容器间通信的桥梁

目录 1 前言2 什么是 Docker Network3 Docker Network 的不同模式3.1 桥接模式&#xff08;Bridge&#xff09;3.2 Host 模式3.3 无网络模式&#xff08;None&#xff09;3.4 容器模式&#xff08;Container&#xff09; 4 Docker Network 命令及用法4.1 docker network ls4.2 …

开关电源基础而又硬核的知识

1.什么是Power Supply? Power Supply是一种提供电力能源的设备&#xff0c;它可以将一种电力能源形式转换成另外一种电力能源形式&#xff0c;并能对其进行控制和调节。 根据转换的形式分类&#xff1a;AC/DC、DC/DC、DC/AC、AC/AC 根据转换的方法分类&#xff1a;线性电源、…

Git分支批量清理利器:自定义命令行插件实战

说在前面 不知道大家平时工作的时候会不会需要经常新建git分支来开发新需求呢&#xff1f;在我这边工作的时候&#xff0c;需求都是以issue的形式来进行开发&#xff0c;每个issue新建一个关联的分支来进行开发&#xff0c;这样可以通过issue看到一个需求完整的开发记录&#x…

STM32F407-14.3.7-01PWM输入模式

PWM 输入模式 此模式是输入捕获模式的一个特例。其实现步骤与输入捕获模式基本相同&#xff0c;仅存在以下不同之处&#xff1a; 例如&#xff0c;可通过以下步骤对应用于 TI1① 的 PWM 的周期&#xff08;位于 TIMx_CCR1⑨ 寄存器中&#xff09;和占空 比&#xff08;位于 …

【投稿优惠|检索稳定】2023年信息系统和工程与数字化经济国际会议(ICISEDE 2023)

2024年信息系统和工程与数字化经济国际会议(ICISEDE 2024) 2024 International Conference on Information Systems and Engineering and the Digital Economy(ICISEDE 2024) [会议简介] 2024 年信息系统和工程与数字化经济国际会议(ICISEDE 2024)将于 2024 年 1 月 20 日在厦门…

⭐ Unity + ARKIT ARFace脸部追踪

相比之前的图像物体检测&#xff0c;这脸部检测实现起来会更加的简单。 &#xff08;1&#xff09;首先我们先在场景中的物体上添加一个AR Face Mananger组件&#xff1a; &#xff08;2&#xff09;以上組件的 Face Prefab所代表的就是脸部的模型也就是覆盖在脸部上面的投影模…

vue3+element-plus+vue-cropper实现裁剪图片上传

1.vue3element-plusvue-cropper实现裁剪图片 element-UI官网element-plus官网vue-croppervue3使用vue-cropper安装&#xff1a;npm install vue-croppernext 2.vue-cropper插件&#xff1a; <vue-cropper :img"option.img" /><script setup>import {reac…

numpy知识库:深入理解numpy.resize函数和数组的resize方法

前言 numpy中的resize函数顾名思义&#xff0c;可以用于调整数组的大小。但具体如何调整&#xff1f;数组形状变了&#xff0c;意味着数组中的元素个数发生了变化(增加或减少)&#xff0c;如何确定resize后的新数组中每个元素的数值呢&#xff1f;本次博文就来探讨并试图回答这…

二进制求和

这篇文章会收录到 : 算法通关村第十三关-白银挑战数字与数学高频问题-CSDN博客 二进制求和 描述 : 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 题目 : LeetCode 67.二进制求和 : 67. 二进制求和 分析 : 这个题也是用字符串来表示数据的…

Docker 使用心得

创建一个docker 镜像&#xff0c;相关运行代码&#xff0c;放在docker镜像文件同级&#xff0c; pm2 不能与 docker一起使用&#xff08;&#xff09; # node 服务docker FROM node:10.16.3LABEL author"sj"RUN mkdir -p /var/nodeCOPY ./node /var/nodeWORKDIR /va…

动手学深度学习(六)---权重衰退

文章目录 一、理论知识二、代码实现【相关总结】 主要解决过拟合 一、理论知识 1、使用均方范数作为硬性限制&#xff08;不常用&#xff09; 通过限制参数值的选择范围来控制模型容量 通常不限制偏移b 小的意味着更强的正则项 使用均方范数作为柔性限制 对于每个都可以找到使…

陈嘉庚慈善践行与卓顺发的大爱传承

陈嘉庚慈善践行&#xff0c;了解陈嘉庚后人与卓顺发的大爱传承。 2023年11月25日,卓顺发太平绅士以及陈家后人在分享他们对慈善领域见解的过程中,特别强调了慈善在促进社会和谐以及推动社会进步方面的关键作用。同时,他们深入探讨了如何在当今社会中继续传扬和实践家国情怀以及…

TCP解帧解码、并发送有效数据到FPGA

TCP解帧解码、并发送有效数据到FPGA 工程的功能&#xff1a;使用TCP协议接收到网络调试助手发来的指令&#xff0c;将指令进行解帧&#xff0c;提取出帧头、有限数据、帧尾&#xff1b;再将有效数据发送到FPGA端的BRAM上&#xff0c;实现信息传递。 参考&#xff1a;正点原子启…

使用canvas实现代码雨高级升阶版【附带源码和使用方法】

文章目录 前言基本绿色的彩色版本飘散雪花状后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端面试 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&…

ProgrammingError: nan can not be used with MySQL

该错误怎么发生的&#xff1f; 我们先在本地创建测试表&#xff1a; CREATE TABLE users_test (id int NOT NULL AUTO_INCREMENT COMMENT 主键,trade_account varchar(50) DEFAULT NULL COMMENT 交易账号,username varchar(50) DEFAULT NULL,email varchar(100) DEFAULT NULL…
最新文章