1. system()

程序可以通过调用 system() 函数来执行任意的shell命令。同样这个Shell命令执行的输出会打印在终端上。\

#include <stdlib.h>
int system(const char* command);

system()库函数通过fork出一个子进程,并且让子进程通过execl()程序替换来执行对应的 shell 命令。问题是替换成什么程序可以执行shell命令呢?答案是 /bin 目录下的sh程序。sh -c command即可执行指定的命令。sh 其实就是一个shell程序,在我的系统中 sh 是一个指向 dash 的软链接,所以system()的真正执行步骤应该是:调用system(),函数内部创建了一个子进程并替换成 shell 程序,而 shell 程序执行一条命令的步骤同样是创建一个子进程,并让子进程替换成需要执行的 command,所以可以看到system()运行期间总共存在三个进程。下图展现了用system()执行sleep命令的步骤:

system()当命令执行完毕才会返回,也就是阻塞函数。

在命令执行期间,SIGCHLD信号将被阻塞,SIGINT和SIGQUIT信号将被忽略。

如果 command 为NULL,则system()返回一个表示当前系统是否有可用shell的状态。

2. 基本的 system() 实现

先按照文档的描述来实现一个简单的system()函数,这其实就是对Linux编程的基本知识如创建子进程、进程等待、进程替换的一个综合运用。

int my_system(const char* command) {
    pid_t pid = fork();
    int status;

    switch(pid) {
    case -1: 
        return -1; 
    case 0:  // child
        execl("/bin/sh", "sh", "-c", command, NULL);
        exit(-1);  // execl error
    default:  // parent
        waitpid(pid, &status, 0); 
        return status;
    }   
}

3. popen() : 通过管道与Shell命令进行通信

popen()system()的功能类似,同样是执行一条Shell命令。但popen()使用了管道来读取Shell命令的输出或者是通过管道来给Shell命令传递输入。

#include <stdio.h>
FILE* popen(const char* command, const char* mode);
int pclose(FILE* stream);

popen()函数创建了一个管道,然后创建一个子进程来执行Shell,而Shell同样需要创建一个子进程来执行指定的Shell命令。这个过程类似于system()。mode 参数是一个字符串,它确定函数的调用者是从管道中读取 Shell 命令的输出(mode为 r ),还是通过管道给Shell命令传递输入(mode为 w )。(由于单个管道只支持单向信息传输,所以必须在创建管道之前就确定数据传输方向)。下图展示了两种mode取值的不同之处:

popen()返回了该管道的操作句柄,即一个文件流指针,函数调用者可以通过这个操作句柄来和Shell命令进行通信。这也点出了popen()system()的不同之处,即system()是一个阻塞函数,Shell命令执行完成之前不会返回,用户通过终端与Shell命令交互。而popen()是一个非阻塞函数,它在建立一个子进程并执行程序替换sh -c command之后会立刻返回,而不等待Shell程序的终止。用户通过主程序与Shell命令进行通信。即可以总结为popen()的调用进程与Shell命令进程是并行执行的。

由于使用的是管道,所以与 pipe 创建出的管道有相同的读写特性,即管道写端关闭后尝试读会返回0,管道读端关闭后尝试写会受到 SIGPIPE 信号而被杀死。

一旦 I/O 结束后可以使用pclose()函数关闭管道,并且该函数会等待Shell程序终止才返回。而pclose()的返回值为Shell进程的终止状态,即 wait 所得到的状态。

4. 基本的popen() 实现

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

FILE* my_popen(const char* command, const char* type) {
    FILE* fp; 

    // if type is invalid
    if((type[0] != 'w' && type[0] != 'r') || type[1] != '\0') {
        return NULL;
    }   

    int pipefd[2];
    pipe(pipefd);

    pid_t pid = fork();
    switch(pid) {
        case -1: 
            perror("fork error");
            exit(0);
        case 0:  // child
            if(type[0] == 'w') {
                close(pipefd[1]);
                dup2(pipefd[0], STDIN_FILENO);
                close(pipefd[0]);  // close extra read end
            }   
            else if(type[0] == 'r') {
                close(pipefd[0]);
                dup2(pipefd[1], STDOUT_FILENO);
                close(pipefd[1]);  // close extra write end
            }   

            execl("/bin/sh", "sh", "-c", command, NULL);
            exit(-1);
        default:  // parent
            if(type[0] == 'r') {
                close(pipefd[1]);
                fp = fdopen(pipefd[0], type);
            }
            else {
                close(pipefd[0]);
                fp = fdopen(pipefd[1], type);
            }

            return fp;
    }
}
Last modification:November 10th, 2019 at 08:10 am