进程终止(exit && _exit) 进程等待(wait && waitpid) 进程替换(execv)

一.进程的终止

进程的创建:内核的相关管理数据结构(task_struct + mm_struct + 页表) + 代码和数据
那么进程创建的时候,先有数据结构还是代码和数据?
例子:高考完被学校录取,学校就已经有你的档案(虚拟地址空间),报道的时候你才真正的到学校(物理内存)
所以创建进程先构建数据结构(PCB),后写入代码和数据
在这里插入图片描述

进程终止是在做什么?

1.释放曾经的代码和数据所占的空间
2.释放内核数据结构 --> Z(僵尸状态)会被延迟处理

进程终止的3种情况

问题:main函数的为什么要有返回值呢?
实验:两段代码差异在return的值不一样

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 0;
}
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 100;
}

两段代码输出的结果都是一样的,且都是正常运行
在这里插入图片描述
区别就在与echo $?的值不一样
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行上面的return 100的代码,连续两次echo $? 为什么第一个 $?是100,第二个 $?是0呢?
因为 echo也是进程
在这里插入图片描述
退出码:0表示成功 !0表示失败
在这里插入图片描述
非零的退出码会被OS解释成一个错误码
查看错误码的函数strerror(报错信息)
在这里插入图片描述
查看错误码对于错误解释的代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    for(int errcode = 0; errcode <= 255; errcode++)  //错误码最多就有255个,后面会解释为什么
    {
        printf("%d:%s\n",errcode,strerror(errcode));
    }
    printf("I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 100;
}

在这里插入图片描述
随便写个命令,发现报错码是1,正好与上面的下标为2的错误信息相对
在这里插入图片描述
问题:父进程bash为什么要得到子进程的退出码呢?
答:要知道子进程的退出情况(成功、失败、失败的原因是什么),为用户负责

自定义退出码

引子:

#include <stdio.h>

int Div(int x,int y)
{
    if( 0 == y )
    {
        return -1;
    }
    else
        return x / y;
}

int main()
{
    int result = Div(10,0);
    printf("result:%d\n",result);
    return 0;
}

运行之后,输出是-1。
不知道是因为除0的原因导致结果为-1还是相除本身结果是-1
在这里插入图片描述
说明光靠打印结果(数字),是看不清涵义的。
所以自定义一下:

#include <stdio.h>

//自定义枚举常量
enum
{
    Success = 0,
    Div_Zero,
    Mod_Zero,
};

int exit_code = Success;

int Div(int x,int y)
{
    if( 0 == y )
    {
        exit_code = Div_Zero;
        return -1;
    }
    else
        return x / y;
}

int main()
{
    int result = Div(10,0);
    printf("result:%d\n",result);
    return exit_code;
}

这样就知道是因为什么退出了,退出码为1,说明除零了
在这里插入图片描述
我们不适合面对数字,更适合直观的语言,所以再写一个接口

#include <stdio.h>

//自定义枚举常量
enum
{
    Success = 0,
    Div_Zero,
    Mod_Zero,
};

int exit_code = Success;

const char* CodeToErrString(int code)
{
    switch(code)
    {
        case Success:
            return "Success";
        case Div_Zero:
            return "div zero!";
        case Mod_Zero:
            return "mod zero!";
        default:
            return "unknow error!";
    }
}

int Div(int x,int y)
{
    if( 0 == y )
    {
        exit_code = Div_Zero;
        return -1;
    }
    else
        return x / y;
}

int main()
{
    int result = Div(10,100);
    printf("result:%d[%s]\n",result,CodeToErrString(exit_code));
    result = Div(10,0);
    printf("result:%d[%s]\n",result,CodeToErrString(exit_code));
    return exit_code;
}

效果就很明显
在这里插入图片描述
综上所述:进程终止的2种情况
a.代码跑完,代码正确
b.代码跑完,代码不正确
由进程的退出码决定
第三种情况:程序能跑完吗?(生活中的事情一定要全做完吗?)
c.代码执行时,出现了异常,提前退出了
最经典的例子就是:VS 编译运行的时候,崩溃了(因为野指针) ------> 操作系统发现发现你的进程做了不该做的事情,OS杀掉了进程
故事:某学生考试作弊,考了90分,你还在乎他为什么考了10分嘛。
一旦出现异常,退出码就不重要了
为什么会出现了异常?原因是?
进程出现了异常,本质是因为进程收到了OS发给进程的信号!
这里证明OS发送给进程信号:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int* p = NULL;
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        *p = 100;
    }
    return 0;
}

在这里插入图片描述
这个进程有没有野指针不重要,重要的是因为野指针了,触发了操作系统给该进程发信号
查看进程的信号:

kill -l

发的是11号信号,SIGSEGV全称为segmentation violation,就是段错误
在这里插入图片描述
把野指针删了运行:

#include <stdio.h>
#include <unistd.h>

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
    }
    return 0;
}

没有野指针,收到了信号,操作系统判断就是野指针。
在这里插入图片描述
综上所述:进程出现了异常时,我们可以看进程退出的时候,退出信号是多少,就可以判断我的进程为什么异常了
衡量一个进程退出,我们只需要两个数字:退出码 && 退出信号

退出码退出信号运行
00完全成功
!00代码运行正常,结果不对
0!0进程出异常了,退出码对不对无所谓
!0!0进程出异常了,退出码对不对无所谓

这两个数字一定要让父进程知道,要让父进程知道子进程做的怎么样!
但子进程如何传给父进程信息呢?
子进程PCB有sig_code(退出数字)和exit_code(退出信号)。
在这里插入图片描述
源码:
在这里插入图片描述

如何终止进程?

a. main函数return,表示进程终止
b. 代码调用exit函数

exit

在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        exit(123);
    }
    return 0;
}

运行之后退出码为123
在这里插入图片描述
如果把exit放入到其他函数当中,退出码依旧是exit中的值

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int ADD()
{
    exit(13);
}

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        ADD();
    }
    return 0;
}

在这里插入图片描述

_exit

c. _exit --> system call
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        _exit(29);
    }
    return 0;
}

退出码和代码一样是29
在这里插入图片描述
把他放入函数调用内部

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int Function()
{
    exit(29);
}

int main()
{
    while(1)
    {
        printf("I am a  process,%d\n",getpid());
        sleep(1);
        Function();
    }
    return 0;
}

退出码依旧是29
在这里插入图片描述
目前功能和exit差不多

exit 与 _exit的区别

区别:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("hello world");//注意这里没有\n
    sleep(2);
    exit(6);
}

printf已经执行了,存在在缓冲区,exit让我们看到了hello world,说明exit冲刷了缓冲区
在这里插入图片描述
同样的代码_exit()

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("hello world");
    sleep(2);
    _exit(6);
}

hello world并没有刷出来
在这里插入图片描述
说明区别在于exit会在进程结束的时候,冲刷缓冲区,_exit不会
注意这里说的缓冲区不是操作系统内核的缓冲区
在这里插入图片描述
在这里插入图片描述

二.进程等待

任何一个子进程,在退出的情况下,一般必须要被父进程等待。
如果子进程在退出的时候,父进程不管不顾。子进程会变成为Z(僵尸状态),PCB依旧会存在,引发内存泄漏(跟malloc后不delete性质相似)

为什么要进行进程等待?

1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源。(一定要考虑)
2.获取子进程的退出信息,知道子进程是什么原因退出的。(可选的功能)

进程如何等待?

有两个函数wait/waitpid

wait

在这里插入图片描述
演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(0);
    }
    //father
    sleep(10);//让子进程已经退了,父进程还在sleep,就能看见子进程的僵尸状态
    pid_t rid = wait(NULL);//看见回收僵尸进程
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);
    }
    sleep(3);
    printf("father quit ...\n");
    return 0;
}

查看进程方便:

while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep ; sleep 1; done

进程在时间轴上处于的状态:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

所以等待,可以解决子进程的僵尸问题的。
如果把父进程的sleep(10)去掉,父进程会一直等待子进程直到子进程退出。
在子进程没退的期间,父进程一直在阻塞等待。
子进程本身就是软件,父进程本质就是在等待某种软件的条件就绪,如何理解父进程堵塞等待子进程呢?

在这里插入图片描述

waitpid

在这里插入图片描述
第一个参数pid
pid=-1:等待任意一个子进程,与wait等效
pid>0 :等待进程pid与所写的pid相等的子进程
下面这两个的作用一模一样,表示等待任何一个子进程退出,哪个退了就返回哪个子进程的pid,就不再演示了

wait(NULL);
waitpid(-1,NULL,0);

把-1改成子进程的pid,表示等待指定的子进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(0);
    }
    //father
    sleep(10);//让子进程已经退了,父进程还在sleep,就能看见子进程的僵尸状态
    //pid_t rid = wait(NULL);//看见回收僵尸进程
    pid_t rid = waitpid(id,NULL,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);
    }
    sleep(3);
    printf("father quit ...\n");
    return 0;
}

waipid也能体现出回收僵尸进程
在这里插入图片描述
是有可能等待失败的,故意填一个错误的id,失败的话会返回-1

pid_t rid = waitpid(id+1,NULL,0):

在当前的系统当中,只要id不填错,基本不会等待失败
创建子进程不就是未来让子进程帮我们完成任务吗!
完成的怎样我怎么知道?
就是第二个参数 status–> 输出型参数(需要定义一块空间,把空间的地址传进来,未来在操作系统等待的时候,可以把数据通过指针让用户看到)
在这里插入图片描述
典型的输出型参数就是

int a;
scanf("%d",&a);

status 表示的子进程退出信息

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(1);
    }
    //father
    sleep(7);
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);
    }
    else
    {
        printf("wait failed!\n");
    }
    sleep(3);
    printf("father quit,status:%d\n",status);
    return 0;
}

发现退出信息status == 256
在这里插入图片描述
退出信息:退出码 && 退出信号
问题:整俩全局变量分别表示退出码和退出信号不好吗?
答:父进程看不到子进程的数据,如果子进程修改了父进程的数据(比如全局变量)就会发生写实拷贝。
一个数是如何拿到两个数字?
不能把他当成一个整数
status是int类型,有32个比特位,只考虑低16位
在这里插入图片描述
用位操作符就能看到子进程的退出信息:

printf("father quit,status:%d,child quit code:%d,child quit signal:%d\n",status,(status>>8)&0xFF,status & 0x7F);

运行没问题,256 == 2^8 --> 1 0000 0000
在这里插入图片描述
为了方便测试,把exit(1)改成exit(123)
在这里插入图片描述
杀掉子进程
在这里插入图片描述
如果不想使用位操作符,推荐两个宏,这两个宏的本质就是进行位操作

WIFEXITED(status)//W:wait IF:if EXITED:退出

若为正常终止子进程的状态,则真(查看进程是否正常退出,就是查signal位)

WEXITSTATUS(status):

若WIFEXITED真(非零),提取子进程退出码。(查看进程的退出码)
应用:WIFEXITED(status) && WEXITATUS(status)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(1);
    }
    //father
    sleep(7);
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            printf("child success,child exit code:%d\n",WEXITATUS(status));
        }
        else
        {
            printf("child quit unnormal!\n");
        }
        printf("wait success,rid:%d\n",rid);
    }
    else
    {
        printf("wait failed!\n");
    }
    sleep(3);
    return 0;
}

子进程正常退出时:退出码为1
在这里插入图片描述
给子进程一个野指针,异常
在这里插入图片描述

阻塞等待 && 非阻塞等待

如果子进程没有退出
而父进程在进行执行waitpid等待,阻塞等待。
在这里插入图片描述
进程阻塞的时候,父进程其他事情什么都没干,浪费了时间。
于是就有了非阻塞等待,也就是第三个参数optinos
讲个小故事理解非阻塞等待:
在这里插入图片描述

在这里插入图片描述

张三在等待李四这种过程当中,可以做其他的事情,就称为非阻塞等待。
张三同学每隔几分钟就给李四打电话,拨电话的过程就叫做函数调用。
说话的过程就叫做函数传参。
李四跟张三说我还没好,叫函数返回值。
每一次函数调用的本质就是再检测子进程(李四)的状态。
在这里插入图片描述
李四那头没说好,张三就一直等待。叫做阻塞等待。
打电话类似函数调用,不就绪就不返回。
waitpid检测状态的变化(也就是打电话)

pid_t waitpid(pid_t pid,int* status,int options);//options == 0 则为阻塞等待
                                                 //options == WNOHANG 则为非阻塞等待

WNOHANG 本质就是一个宏。(若云服务器卡住了,一般把这种情况叫做HANG住了)
阻塞等待:
pid_t > 0 : 等待成功了,子进程退出了,并且父进程回收成功
pid_t < 0 : 等待失败了
非阻塞等待:
pid_t == 0 : 检测时成功的,只不过子进程还没退出。需要下一次进行重复等待
非阻塞等待的时候 + 循环 = 非阻塞轮询(允许父进程做一些其他的事情(比如张三在下面打王者))
应用非阻塞轮询代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
        sleep(2);  //为了观察现象
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(66);
    }
    //father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,WNOHANG);
        if(rid == 0)  //非阻塞轮询
        {
            sleep(1);  //为了观察现象
            printf("child is running,father check next time!\n");
            //DootherThing();  干一些其他事情
        }
        else if(rid > 0)  //运行成功
        {
            if(WIFEXITED(status))  //正常退出
            {
                printf("child quit sucess,child exit code : %d\n",WEXITSTATUS(status));
            }
            else  //错误退出
            {
                printf("child quit unnormal!\n");
            }
            break;  //成功了直接break
        }
        else  //等待失败
        {
            printf("waitpid failrd!\n");
            break;  //失败了直接break
        }
    }
    return 0;
}

在这里插入图片描述
这里举例父进程可以做的DootherThing:下载资源等
task.h

#pragma once

#include <stdio.h>

void PrintLog();
void Download();
void MysqlDataSync();

task.c

#include "task.h"

void PrintLog()
{
    printf("begin PrintLog...\n");
}

void Download()
{
    printf("begin Download...\n");
}

void MysqlDataSync()
{
    printf("begin MySQLDataSync...\n");
}

myprocess.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "task.h"

typedef void(*func_t)();

#define N 3  //三个任务
func_t tasks[N] = {NULL};

void LoadTask()  //要下载的任务
{
    tasks[0] = PrintLog;
    tasks[1] = Download;
    tasks[2] = MysqlDataSync;
}

void HandlerTask()  //处理任务
{
    int i = 0;
    for(i = 0;i < N; i++ )
    {
        tasks[i]();  //回调方式
    }
}

void DootherThing()
{
    HandlerTask();
}

void ChildRun()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
        sleep(2);
        cnt--;
    }
}

int main()
{
    printf("I am father,pid:%d.ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id == 0)
    {
        //child
        ChildRun();
        printf("child quit ...\n");
        exit(66);
    }
    LoadTask();
    //father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,WNOHANG);
        if(rid == 0)  //非阻塞轮询
        {
            sleep(1);
            printf("child is running,father check next time!\n");
            DootherThing();
        }
        else if(rid > 0)  //运行成功
        {
            if(WIFEXITED(status))  //正常退出
            {
                printf("child quit sucess,child exit code : %d\n",WEXITSTATUS(status));
            }
            else  //错误退出
            {
                printf("child quit unnormal!\n");
            }
            break;  //成功了直接break
        }
        else  //等待失败
        {
            printf("waitpid failrd!\n");
            break;  //失败了直接break
        }
    }
    return 0;
}

在这里插入图片描述

三.进程的程序替换

关于程序替换的函数一共有7个
在这里插入图片描述
在这里插入图片描述

先看代码 && 现象

execl

在这里插入图片描述
先看现象:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("testtexec ... begin!\n");
    execl("/usr/bin/ls","ls","-l","-a",NULL);  //最后要以NULL结尾 跟argv[]最后以NULL结尾相似
    printf("testtexec ... end!\n");
    return 0;
}

发现输出的是ls命令,ls本身就是C语言写的。exec*函数就是执行新的程序(进程)
在这里插入图片描述

原理

进程 = 内核数据结构 + 代码和数据
用ls代码和数据覆盖testexec的物理内存中的数据和代码 (覆盖的同时堆与栈也会刷新)
在这里插入图片描述
进程替换的本质:用新进程的代码和数据覆盖老进程的代码和数据。
问题:在替换的时候有没有创建新的进程?
答:没有,执行新程序是拿老程序的壳子 (pid不变)
在这里插入图片描述
回到代码:为什么前面的printf输出了,后面的printf没有输出?
在这里插入图片描述
在这里插入图片描述
execl函数的返回值可以不关心了
只要替换成功,就不会向后继续运行
只要继续运行了,一定就是替换失败了
让其失败一次:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("testtexec ... begin!\n");
    execl("/usr/bin/lslslslss","ls","-l","-a",NULL);
    printf("testtexec ... end!\n");
    return 0;
}

在这里插入图片描述

多进程替换

想要进程替换,还不想影响程序本身,可以fork创建子进程,让子进程去替换,父进程wait等待即可
先看现象:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
    	sleep(2);
        //child
        execl("/usr/bin/ls","ls","-l","-a",NULL);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

子进程执行ls,父进程等待成功,退出码为0
在这里插入图片描述
创建子进程,让子进程完成任务:
1.让子进程执行父进程代码的一部分
2.让子进程执行一个全新的程序
平常创建子进程进行修改的时候,只有数据被更改
替换是连着数据和代码一起更改,发生写实拷贝
在这里插入图片描述

使用部分的替换方法,并认识函数参数的含义

在这里插入图片描述

execv

把命令一起打包传参
在这里插入图片描述
代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
        sleep(2);
        char* const argv[] = 
        {
            (char*)"ls",  //编译器检查严格,强转类型避免Warning
            (char*)"-l",
            (char*)"-a",
            (char*)"--color",
            NULL
        };
        //child
        //execl("/usr/bin/ls","ls","-l","-a",NULL);
        execv("/usr/bin/ls",argv);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

在这里插入图片描述

execvp

p表示:用户可以不传要执行的文件路径(但要传文件名),直接告诉exec*,我要执行谁即可
系统会自动在环境变量PATH中进行查找
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
        sleep(2);
        char* const argv[] = 
        {
            (char*)"ls",
            (char*)"-l",
            (char*)"-a",
            (char*)"--color",
            NULL
        };
        //child
        //execl("/usr/bin/ls","ls","-l","-a",NULL);
        //execv("/usr/bin/ls",argv);
        execvp("ls",argv);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

execlp

同样是传文件名,与execvp是一对。
在这里插入图片描述

execlp("ls","ls","-l","-a",NULL);
execlp("top","top",NULL);

用自己的程序替换

上面的程序替换,我们替换的都是系统命令,可不可以替换我们自己写的程序?
先用C语言替换C++
mypragma.cc

#include <iostream>

using namespace std;

int main()
{
    cout << "hello C++,I am a C++ pragma!" << endl;
    cout << "hello C++,I am a C++ pragma!" << endl;
    cout << "hello C++,I am a C++ pragma!" << endl;
    return 0;
}

编译后形成的可执行程序为mypragma
在这里插入图片描述
用testexec替换myprogma程序

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
    	sleep(2);
        execl("./mypragma","mypragma",NULL);  //已经找到,所以可以直接写mypragma(./mypragma也行)
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

在这里插入图片描述
并且改一下代码,验证进程并没有被替换
mypragma.cc

#include <iostream>
#include <unistd.h>

using namespace std;

int main()
{
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    return 0;
}

testexec.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
    	printf("child pid:%d\n",getpid());
        execl("./mypragma","mypragma",NULL);  //已经找到,所以可以直接写mypragma(./mypragma也行)
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

发现新的子进程pid与老的进程pid一样
在这里插入图片描述
不仅仅是C语言替换C++。Python,JAVA,Shell脚本都可以
任何脚本文件都有解释器,解释器都是由C/C++写的,解释器相当于可执行程序。在Linux下跑都会变成进程,只要是进程就可以被替换
比如Shell与Python:

execl("/usr/bin/bash","bash","test.sh",NULL);
execl("/usr/bin/python3","python3","test.py",NULL);

execvpe

e:environment:环境变量;envp不传参时是NULL
在这里插入图片描述
argv 是不是很像main函数的参数 ; envp 是环境变量表
mypragma.cc

#include <iostream>
#include <unistd.h>

using namespace std;

int main(int argc,char* argv[],char* env[])
{
    int i = 0;
    for(;argv[i];i++)  //打印命令行参数
    {
        printf("argv[%d] : %s\n",i,argv[i]);
    }

    printf("-------------------------------------\n");
    for(int i = 0;env[i];i++)  //打印环境变量
    {
        printf("env[%d] : %s\n",i,env[i]);
    }
    printf("-------------------------------------\n");

    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    cout << "hello C++,I am a C++ pragma!" << getpid() << endl;
    return 0;
}

testexec.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("testtexec ... begin!\n");

    pid_t id = fork();
    if(id == 0)
    {
        char* const argv[] = 
        {
            (char*)"mypragma",
            NULL
        };
        char* const envp[] = 
        {
            (char*)"HAHA=111111",
            (char*)"hehe=222222",
            NULL
        };
        printf("child pid:%d\n",getpid());
        execvpe("./mypragma",argv,envp);
        exit(1);  //如果替换失败,则退出
    }

    //  father
    int status = 0;
    pid_t rid = waitpid(id ,&status,0);
    if(rid > 0)
    {
        printf("father wait success,child exit coed:%d\n",WEXITSTATUS(status));
    }
    printf("testtexec ... end!\n");
    return 0;
}

环境变量也传给子进程mypragma.cc了
在这里插入图片描述
当然也可以直接传选项:

char* const argv[] = 
        {
            (char*)"mypragma",
            (char*)"-a",
            (char*)"-b",
            NULL
        };

选项也就有了
在这里插入图片描述
上面的函数都出自三号手册,都是C语言层做了简单的封装
系统封装只有execve

execve

在这里插入图片描述
有这么多函数就是为了支持不同的应用场景
在这里插入图片描述

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅碎时光807

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值