Linux进程编程(exec族函数,system函数,popen函数)

目录

一、exec族函数

二、system函数

三、popen函数


一、exec族函数

        我们之前提到fork函数通常运用在一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

        除此之外还有另外的应用场景:一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec函数

函数原型:

#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[]);

一般来说第一个函数就适用于绝大多数场景了,其他函数用法稍有不同,但最终实现的功能都是一样的。
函数功能:
        
调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
返回值:
        exec函数族的函数执行成功后不会返回(如果exec族函数下还有代码则不会执行),调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的绝对路径
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束(经过我的试验,发现我传入可执行文件的绝对路径也可以,关于这个函数的参数解释我还不是很清除,希望大佬在评论区解答一下)
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件

execl代码示例:

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

int main()
{
	printf("before execl\n");
	if(execl("./echoarg","echoarg","file1",NULL)==-1)//返回值为-1表示执行失败
	{
		printf("execl failed\n");

		perror("why");//与exec族函数配合着用,会将错误原因打印出来
	}
    printf("after execl\n");//如果执行成功则不会执行这一句
	return 0;
}

echoarg.c函数:

#include <stdio.h>

int main(int argc, char **argv)
{
	int i=0;
	for(i=0;i<argc;i++)
	{
		printf("argv[%d]:%s\n",i,argv[i]);//将参数打印出来
	}
	return 0;
}

!!!关于main函数的参数及其用法,见Linux文件编程应用,文章开头讲如何实现cp命令时有讲,这里不再赘述。我们看一下运行结果:

解释:当我们执行 execl("./echoarg","echoarg","file1",NULL);这一句时,就相当于在终端输入了 ./echoarg file1 (这里要注意的是,虽然两者的打印结果不同,使用指令打印出来的第一个参数为 ./echoarg)但是都实现了echoarg可执行文件的功能,因为用指令的方式执行可执行文件时,必须要在前面加上./,就相当于传入的第一个参数为 ./echoarg。

至于exec族的其他函数,可以去看一下别人写得好的博客,我觉得这一个暂时就够用了,其他带p的涉及到了环境变量的知识,如果我以后学到了会进行补充。


二、system函数

函数原型:

#include <stdlib.h>

int system(const char *command);

想知道system函数实现什么功能之前,我们可以看一下system的源码:

int system(const char * cmdstring)
{
  pid_t pid;
  int status;

  if(cmdstring == NULL){
      
      return (1);
  }


  if((pid = fork())<0){

        status = -1;
  }
  else if(pid == 0){
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    -exit(127); //子进程正常执行则不会执行此语句
    }
  else{
        while(waitpid(pid, &status, 0) < 0){
          if(errno != EINTER){
            status = -1;
            break;
          }
        }
    }
    return status;
}

我们重点关心子进程那一段的代码,可以看到system函数调用了execl函数,文件路劲为bin目录下的sh文件,文件名为sh,参数为-c;说到这里,我们就要说一下 sh -c 是什么东西:

我们之前执行可执行文件都是用 ./+可执行文件名的方式,前面加上sh -c 则是另外一种方式,不过两者实现功能一样,这时候回头再看system的源码,execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);就是执行cmdstring(用户传入的字符串),只要将"./+可执行文件名"传入system函数即可

返回值:
system()函数的返回值如下:
·成功,则返回进程的状态值;
·当sh不能执行时,返回127;
·失败返回-1;

代码示例:
用system函数实现ps指令

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

int main()
{
	if(system("ps")==-1)//由于ps是指令,所以不需要加./前缀
	{
		printf("execl failed\n");//失败则打印大括号里的内容
		perror("why");
	}
	printf("after system\n");
	return 0;
}

运行结果:

可以看到实现了和ps一样的功能

注意:system与execl函数不同,system调用后会回到程序来执行剩下的代码(打印了after system)


三、popen函数

函数原型:

#include <stdio.h>

FILE *popen(const *char command, const char *type);

在说popen函数实现什么功能之前,我们不妨来思考以下,execl函数或者system函数也好,我们使用这两个函数来调用其他可执行文件时,打印的结果,或者说执行的结果最终都显示在终端上,如果我们想把这些数据放在字符串里,通过网络发出去等,总之想将运行结果保存起来,这时候就可以用到popen,而它相比system的好处就是:可以获取运行的输出结果

参数:
第一个参数和system函数的使用方法一样;
第二个参数type只能是读或者写中的一种,如果type是“r”,则文件指针连接到command的标准输出(终端),如果是“w”,连接到标准输入。(我们这里用“r”)

既然函数的返回值是FILE类型的指针,结合它的功能可知,运行结果会保存在用户定义的FILE类型的指针指向的内存空间里;我们可以用fread函数读取里面的结果。

fread函数原型:

#include <stdio.h>

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

代码示例:
用popen执行ps指令,将结果保存起来:

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

int main()
{
	char ret[1024]={0};
	FILE *fp;//存放popen的返回值

	fp=popen("ps","r");

	fread(ret,sizeof(char),1024,fp);
//	printf("ret=%s\n",ret);

	return 0;
}

我先把printf注释掉,看看运行结果:

可以看到什么都没有输出;

把注释取消后:

成功将ps的运行结果打印出来了。 


至此Linux进程编程的内容就更新完毕了,实际运用中,我们需要根据不同的场景判断哪个函数更合适,其实掌握上面三个函数就适用于大部分场景了,后续我会更新关于进程间通信的内容,有疑问的可以在评论区一起讨论一下!

相关推荐

  1. Linux exec 命令和Python exec 函数 区别

    2024-07-16 10:34:02       21 阅读
  2. scanf、printf、string函数

    2024-07-16 10:34:02       31 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-16 10:34:02       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-16 10:34:02       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-16 10:34:02       45 阅读
  4. Python语言-面向对象

    2024-07-16 10:34:02       55 阅读

热门阅读

  1. 实车部署 TARE 入门教程

    2024-07-16 10:34:02       24 阅读
  2. git环境编译升级

    2024-07-16 10:34:02       24 阅读
  3. Ubuntu系统和硬件问题

    2024-07-16 10:34:02       21 阅读
  4. 抽象工厂模式与工厂方法(简单工厂)的区别

    2024-07-16 10:34:02       23 阅读
  5. 力扣第八题——字符串转换整数

    2024-07-16 10:34:02       22 阅读
  6. OutOfMemoryError异常OOM排查

    2024-07-16 10:34:02       19 阅读