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。

cpp 复制代码
//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,作为变身的目标
cpp 复制代码
//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函数,新进程取代了子进程,但父进程依然存在:

cpp 复制代码
//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参数获得)

cpp 复制代码
//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()写个电子钟表,显示时分秒,每秒更新。

相关推荐
网络研究院3 天前
影响 Linux、Unix 系统的 CUPS 漏洞可导致 RCE
linux·运维·安全·unix·系统·漏洞
标标大人3 天前
unix中的exec族函数介绍
嵌入式硬件·unix
Jinterest10 天前
知乎:从零开始做自动驾驶定位; 注释详解(一)
人工智能·自动驾驶·unix
MonkeyKing_sunyuhua11 天前
dial unix /var/run/docker.sock: connect: permission denied
docker·eureka·unix
标标大人11 天前
unix中如何查询和修改进程的资源限制
服务器·unix
标标大人11 天前
什么是unix中的fork函数?
linux·运维·unix
Qian Xiaoo11 天前
UNIX体系结构
服务器·unix
web安全工具库14 天前
深入理解Python中的时间表示:Unix时间点、毫秒和微秒,以及time模块
开发语言·python·unix
YRr YRr14 天前
Unix-like 系统中的文件所有权管理:使用 sudo chown -R 命令的详解与实践应用
linux·服务器·unix
nfgo17 天前
Apollo自动驾驶项目(二:cyber框架分析)
人工智能·自动驾驶·unix