Linux Shell中管道的原理及C實現框架

在shell中我們經常用到管道,有沒考慮過Shell是怎麼實現管道的呢?

cat minicom.log | grep "error"

標準輸入、標準輸出與管道

我們知道,每一個進程都有3個標準的輸入輸出文件描述符

描述符編號 簡介 作用
0 標準輸入 通用於獲取輸入的文件描述符
1 標準輸出 通用輸出普通信息的文件描述符
2 標準錯誤 通用輸出錯誤信息的文件描述符

我們還知道,系統調用pipe可以創建無名管道

int pipe(int pipefd[2]);

pipe的作用是創建無名管道,並創建兩個文件描述符

文件描述符 作用
pipefd[0] 管道數據出口
pipefd[1] 管道數據入口

Shell實現管道的原理

在上文的基礎上,我們再看看Shell如何實現管道的。

Shell中通過fork+exec創建子進程來執行命令。如果是含管道的Shell命令,則管道前後的命令分別由不同的進程執行,然後通過管道把兩個進程的標準輸入輸出連接起來,就實現了管道。

例如

grep "error" minicom.log | awk '{print $1}'

這句命名的作用非常簡單,

  1. 通過grep命令在minicom.log中檢索含有error關鍵字的行
  2. 通過awk命令打印grep的輸出結果中每一行的第一個字段

在Shell中要實現這樣的效果,有4個步驟:

  1. 創建pipe
  2. fork兩個子進程執行grep和awk命令
  3. 把grep子進程的標準輸出、標準錯誤重定向到管道數據入口
  4. 把awk子進程的標準輸入重定向到管道數據出口

這樣就實現了Shell管道:grep把結果輸出到管道,awk從管道獲取數據

Shell如何用C實現管道

我沒研究過Shell的代碼,但不妨礙我們從功能倒推實現,如果是我,我會怎麼做呢?

int main(int argc, char **argv)
{
    while(1) {
        int pfds[2];
        pid_t cmd1, cmd2;
        if ((cmd1 = fork()) < 0) {
            ...
        } else if (cmd1 == 0) { /* child */
            /*
             * dup2 把 pfds[1](管道數據入口描述符) 複製到 文件描述符1&2
             * 實現把cmd1的標準輸出和標準錯誤 輸送到管道
             */
            dup2(pfds[1], STDOUT_FILENO);
            dup2(pfds[1], STDERR_FILENO);
            close(pfds[0]);
            close(pfds[1]);
            exec(cmd1...);
            __exit(127);
        }

        if ((cmd2 = fork()) < 0) {
            ...
        } else if (cmd2 == 0) { /* child */
            /*
             * dup2 把 pfds[0](管道數據出口描述符) 複製到 文件描述符0
             * 實現cmd2從管道中讀取(cmd1的輸出)數據
             */
            dup2(pfds[0], STDIN_FILENO);
            close(pfds[0]);
            close(pfds[1]);
            exec(cmd2...);
            __exit(127);
        }
        close(pfds[0]);
        close(pfds[1]);
        wait(...); 
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章