Linux进程(阶段三exec族函数,system及popen)

10.exec族函数

可以看精彩博文:https://blog.csdn.net/u014530704/article/details/73848573
exec族函数函数的作用:
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
exec族函数定义:
  可以通过这个网站查询:linux函数查询
功能:
  在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
函数族:
  exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
 函数原型:

#include <unistd.h>
extern char **environ;

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 execvpe(const char *file, char *const argv[],char *const envp[]);//用的少
1

返回值:
  exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

以execl函数为例子来说明:

//文件echoarg.c
#include <stdio.h>

int main(int argc,char *argv[])//argv二级指针
{
    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]); 
    }
    return 0;
}
//文件execl.c(demo18.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execl("./echoarg","echoarg","abc",NULL) == -1)//调用当前目录下的echoarg,结尾以NULL结尾。
    {
        printf("execl failed!\n");  
        perror("why");//这里用perror打印出错误原因 
    }
    printf("after execl\n");
    return 0;
}

调用echoarg程序的结果如下:
在这里插入图片描述
以下是使用execl函数调用ls这个进程代码(demo19.c);

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execl("/bin/ls","ls",NULL) == -1)//
    {
        printf("execl failed!\n");

        perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果等同于执行了一个ls,如下图:
在这里插入图片描述
调用ls -l把文件详细清单打出来,只需要在execl里传参就好,代码如下:

int main(void)
{
    printf("before execl\n");
    if(execl("/bin/ls","ls","-l",NULL) == -1)//传-l就好了
    
   

运行结果如下图:
在这里插入图片描述
execlp函数的使用代码(demo22.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("this pro get systerm date:\n");
    if(execlp("ps","ps",NULL) == -1)//不用加绝对路径去找可执行问民间,因为在环境变量直接找
    {
        printf("execl failed!\n");

        perror("why");
    }
    printf("after execl\n");
    return 0;
}
~          

上面的exaclp函数带p,通过环境变量PATH(作用是:只要在环境变量下的可执行文件,不管在任何目录都可以运行而且不用./pro,直接pro就好,像ls那些指令一样,不然像其他普通的那些可执行文件还要在自己当前的目录下执行才可以)查找到可执行文件ps;下图是运行的结果:
在这里插入图片描述
execvp函数的使用代码(demo23.c)

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

int main(void)
{
    printf("this pro get systerm date:\n");

    char *argv[] = {"ps",NULL,NULL};//把后面的参数写在字符串指针数组这(二级指针)

    if(execvp("ps",argv) == -1)//然后使用的时候用二级指针代替
    {
        printf("execl failed!\n");

        perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果图
在这里插入图片描述
execvp函数的使用代码(demo24.c)

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

int main(void)
{
    printf("this pro get systerm date:\n");

    char *argv[] = {"ps",NULL,NULL};

    if(execv("/bin/ps",argv) == -1)//每带p的exec族函数就是要加绝对路径
    {
        printf("execl failed!\n");

        perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果图
在这里插入图片描述
小结:exec族函数的运行调用指令的功能都是一样的,就是使用的方式不一样。

11.exec族函数配合fork使用

exec族函数作用:
在这里插入图片描述
exec族函数配合fork使用:(下图是需求)
在这里插入图片描述
这里就是创建子进程,让子进程干活(把之前文件操作的配置文件的demo14.c代码运行起来)这里的代码是直接把demo14.c的代码拷贝至创建的子进程中,让其修改配置cofig.txt文件,代码为(demo25.c)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
        pid_t pid;
        int data;

        printf("father pid = %d\n",getpid());

        while(1){
                printf("please input a number\n");
                scanf("%d",&data);
                if(data == 1){
                        pid = fork();
                        if(pid > 0){
                                wait(NULL);
                        }

                        if(pid == 0){
                                int fdSrc;

                                char *readBuf = NULL;

                                fdSrc = open("./config.txt",O_RDWR);
                                int size = lseek(fdSrc,0,SEEK_END);
                                lseek(fdSrc,0,SEEK_SET);

                                readBuf = (char *)malloc(sizeof(char)*size + 8);

                                int n_read = read(fdSrc,readBuf,size);
                                lseek(fdSrc,0,SEEK_SET);

                                char *p = strstr(readBuf,"LENG=");
                                if(p==NULL){
                                        printf("not found\n");
                                        exit(-1);
                                }
                                p = p+strlen("LENG=");
                                *p = '5';

                                int n_write = write(fdSrc,readBuf,strlen(readBuf));

                                close(fdSrc);
								exit(0);
                        }

                }
                else{
                        printf("wait do nothing\n");
                }

        }

        return 0;
}

运行结果如下:config.txt这个文件成功被配置了。
在这里插入图片描述
现在用另外一种方式(上面的方式感觉很繁杂):把文件操作修改配置文件的代码demo14.c先gcc demo14.c -o changeData,先编译成可执行的代码文件,然后将其拷贝至当前jinchen目录,然后修改子进程的代码(demo26.c)。

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

int main()
{
        pid_t pid;
        int data;

        printf("father pid = %d\n",getpid());

        while(1){
                printf("please input a number\n");
                scanf("%d",&data);
                if(data == 1){
                        pid = fork();

                        if(pid > 0){
                                wait(NULL);
                        }

                        if(pid == 0){
                                execl("./changeData","changeData","config.txt",NULL);//子进程直接调用可执行程序就好了,看上去很简洁;此刻execl调用这个changeData进程去取代这个子进程(changeData程序里面也有return 0;正常退出),所以exit没什么用了,可以直接注释。

                              // exit(0);
                        }

                }
                else{
                        printf("wait do nothing\n");
                }

        }

        return 0;
}

代码运行结果如图:cat了config.txt的参数变化
在这里插入图片描述

12.system函数

函数介绍如下图:
在这里插入图片描述
博文详细介绍:https://www.cnblogs.com/leijiangtao/p/4051387.html
system就是封装后的execl。(system函数部分源代码)

 else if(pid == 0){
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    -exit(127); //子进程正常执行则不会执行此语句
    }

system函数返回值: 如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值。如果 system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。
以下代码(demo27.c)是使用system函数(system的参数直接传指令在界面运行什么样就怎么样传就可以了)调用修改配置文件代码

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

int main()
{
        pid_t pid;
        int data;

        while(1){

                printf("please input a number\n");
                scanf("%d",&data);
                if(data == 1){
                        pid = fork();

                        if(pid > 0){
                                wait(NULL);
                        }

                        if(pid == 0){
                        //      execl("./changeData","changeData","config.txt",NULL);
                                system("./changeData config.txt");
                        }//直接./changeData config.txt传就可以了

                }
                else{
                        printf("wait do nothing\n");
                }

        }

        return 0;
}

下面是运行截图:
在这里插入图片描述
下面是exec族函数和system函数的区别代码(demo28.c)

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

int main(void)
{
    printf("this pro get systerm date:\n");

    if(system("ps") == -1)//调用直接、简单
    {
        printf("execl failed!\n");

        perror("why");
    }
    printf("after execl\n");
    return 0;
}

从运行结果图可以看出exec族函数和system的区别,system函数调用进程后还会返回原先的程序继续执行后面代码,如图:
在这里插入图片描述
当执行system调用参数(可执行程序名)错误时候代码:

int main(void)
{
    printf("this pro get systerm date:\n");

    if(system("pswqw") == -1)
    {
        printf("execl failed!\n");

        perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行后system会直接在界面打出错误信息,如图
在这里插入图片描述

13.popen函数

popen函数介绍如图:
在这里插入图片描述
详细看博文:https://blog.csdn.net/libinbin_1014/article/details/51490568

使用popen(“ps”,“r”)这样的函数时这么回事的,执行ps程序,然后把程序运行的结果定位到管道中,把数据往里面扔,然后数据流出,可以调用fread的读到缓冲区来(场景类似下图)
在这里插入图片描述

这里先用system函数调用ps进程看看结果:

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

int main(void)
{
        char ret[1024] = {0};

        system("ps");
        printf("ret=%s\n",ret);

        return 0;
}

运行结果如图:(结果没有存任何东西)
在这里插入图片描述
下面是popen的调用代码(demo29.c),popen的ps执行结果会流到fp管道当中(也叫流),然后用读取流中的数据的函数fread读到ret缓冲区去。

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

//  size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

int main(void)
{
        char ret[1024] = {0};
        FILE *fp;//流

        fp = popen("ps","r");//调用popen函数,把结果捕获到流去
        int nread = fread(ret,1,1024,fp);//读流的数据,存到ret缓冲区

//      printf("read ret %d byte,ret=%s\n",nread,ret);当printf被注释时就是运行下图的蓝色标记结果,没有注释时时红色标记的结果。

        return 0;
}

下图是popen有printf和没有printf的运行结果;从中可以看出ps运行的结果被捕获到流中去了。
在这里插入图片描述

14.进程总结

进程学习的知识点小总结:
1.进程的5个概念(五问)
2.fork的使用
3.创建进程发生什么事情
4.创建进程的应用场景(模拟socket客户端接入)
5.vfork的创建
6.进程的退出(包括了父进程等待子进程退出<宏的应用,及解析退出码等>)
7.exec族函数system及popen函数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值