https://m.toutiao.com/is/8j9aeeh/?=linux 0,、前言我們經(jīng)??吹竭@樣一種說法『在 linux 世界中,一切皆文件』,。對于沒有深入研究過 linux 的同學,,可能比較困惑,該怎么理解呢,?當啟動一個 linux 進程時,,程序默認打開三個 I/O 設備文件:標準輸入文件 stdin,,標準輸出文件 stdout,標準錯誤輸出文件 stderr,,而每打開一個文件,,就有一個代表該打開文件的文件描述符(比較小的整數(shù)),。而一般情況下,,stdin、stdout 和 stderr 對應的文件描述符就是 0,、1,、2。 接下來,,我將以一個進程間通信的例子來深入介紹下 linux 中文件的概念,。 1、管道linux 進程間通信方式主要有:
管道通信(pipe)的原理是:首先調用系統(tǒng)函數(shù) pipe,,它會在內核中開辟出一塊緩沖區(qū)用來進行進程間通信,,這塊緩沖區(qū)稱為管道,它有一個讀端和一個寫端,。 #include <uinstd.h> int pipe(int _pipefd[2]); // 調用成功返回 0,,否則返回 -1 pipe 函數(shù)入?yún)⑹情L度為 2 的整型數(shù)組,如果調用成功,,會通過 _pipefd 數(shù)組傳出給用戶程序兩個文件描述符,,其中 _pipefd[0] 指向管道的讀端, _pipefd[1] 指向管道的寫端,這個管道對用戶進程來說就可以認為是一個虛擬文件,,可以通過 read(_pipefd[0]),、或者 write(_pipefd[1]) 進行操作。具體通信流程如下: (1)父進程(主進程)創(chuàng)建管道,,得到兩個文件描述符指向管道的兩端 (2)調用 fork 函數(shù)創(chuàng)建子進程,,則子進程也得到兩個文件描述符指向同一管道 (3)在子進程中,關閉讀端(_pipfd[0]),,往管道中寫數(shù)據(jù) (4)在父進程中,,關閉寫端(_pipefd[1]),從管道中讀數(shù)據(jù) fork 函數(shù)的原理: fork 函數(shù)很有意思,,它用于創(chuàng)建子進程,,和一般的函數(shù)有一些區(qū)別,雖然只被調用一次,,卻返回兩次(不是兩個返回值哦,,是返回兩次,每次返回一個結果),,它返回的結果是子進程的 PID 和0,,其中調用進程返回了子進程的PID,,而子進程則返回了0. 父進程和子進程的執(zhí)行順序是不定的。fork() 函數(shù)調用完成以后父進程的虛擬存儲空間被拷貝給了子進程的虛擬存儲空間,,因此也就實現(xiàn)了共享文件等操作,。但是 virtual memory(虛擬的存儲空間)映射到 physical memory(物理存儲空間)的過程中采用了寫時拷貝技術(具體的操作大小是按著頁控制的),該技術主要是將多進程中同樣的對象(數(shù)據(jù))在物理存儲其中只有一個物理存儲空間,,而當其中的某一個進程試圖對該區(qū)域進行寫操作時,,內核就會在物理存儲中開辟一個新的物理頁面,將需要寫的區(qū)域內容復制到新的物理頁面中,,然后對新的物理頁面進行寫操作,。這時就是實現(xiàn)了對不同進程的操作而不會產(chǎn)生影響其他的進程,同時也節(jié)省了很多的物理存儲,。 2,、管道通信的例子下面的例子中有 1 個數(shù)據(jù)文件(file),一個 python 文件(reader.py),,一個 c++ 文件(main.cc),,主程序的執(zhí)行流程如下: (1)創(chuàng)建子進程 (2)子進程中 shell 執(zhí)行命令:cat file | python reader.py
(3)子進程中把 stdout 重定向到管道 (4)主進程從管道讀文件 file this is file data reader.py import sysfor line in sys.stdin: str = '' str += line str += '\n' str += 'this is python data' sys.stdout.write(str) main.cc #include <stdio.h>#include <unistd.h>#include <string.h>#include <iostream>#include <sys/wait.h>#include <sys/types.h>#include <stdlib.h> int main(){ int _pipefd[2]; int ret = pipe(_pipefd); if (ret < 0) { perror('pipe\n'); } pid_t id = fork(); if (id < 0) { perror('fork\n'); } else if (id == 0) { close(_pipefd[0]); dup2(_pipefd[1], STDOUT_FILENO); // 重定向 stdout 到 _pipefd[1] const char* cmd = 'cat file | python reader.py'; execl('/bin/bash', 'bash', '-c', cmd); } else { close(_pipefd[1]); char buf[1024]; memset(buf, '\0', sizeof(buf)); read(_pipefd[0], buf, sizeof(buf)); // 從管道讀數(shù)據(jù) std::string result(buf); result += '\n'; result += 'this is cpp data'; std::cout << result << '\n'; } return 0;} 程序運行結果: 3,、系統(tǒng)函數(shù) popen上述例子中的功能其實可以用系統(tǒng)函數(shù) popen 代替,它的原型為: FILE * popen(const char *cmdstring, const char *type) popen 函數(shù)的實現(xiàn)包括以下幾步: (1)調用 pipe() 建立管道 (2)調用 fork() 創(chuàng)建子進程 (3)在子進程中調用 exec 族函數(shù)執(zhí)行命令,,通過管道將結果傳送至父進程 (4)在主進程中等待子進程執(zhí)行,,子進程執(zhí)行完成后將接收其結果,返回結果的文件指針 /* * popen.c Written by W. Richard Stevens */ #include <sys/wait.h> #include <errno.h> #include <fcntl.h> #include 'ourhdr.h' static pid_t *childpid = NULL; /* ptr to array allocated at run-time */ static int maxfd; /* from our open_max(), {Prog openmax} */ #define SHELL '/bin/sh' FILE * popen(const char *cmdstring, const char *type) { int i, pfd[2]; pid_t pid; FILE *fp; /* only allow 'r' or 'w' */ if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) { errno = EINVAL; /* required by POSIX.2 */ return(NULL); } if (childpid == NULL) { /* first time through */ /* allocate zeroed out array for child pids */ maxfd = open_max(); if ( (childpid = calloc(maxfd, sizeof(pid_t))) == NULL) return(NULL); } if (pipe(pfd) < 0) return(NULL); /* errno set by pipe() */ if ( (pid = fork()) < 0) return(NULL); /* errno set by fork() */ else if (pid == 0) { /* child */ if (*type == 'r') { close(pfd[0]); if (pfd[1] != STDOUT_FILENO) { dup2(pfd[1], STDOUT_FILENO); close(pfd[1]); } } else { close(pfd[1]); if (pfd[0] != STDIN_FILENO) { dup2(pfd[0], STDIN_FILENO); close(pfd[0]); } } /* close all descriptors in childpid[] */ for (i = 0; i < maxfd; i++) if (childpid[ i ] > 0) close(i); execl(SHELL, 'sh', '-c', cmdstring, (char *) 0); _exit(127); } /* parent */ if (*type == 'r') { close(pfd[1]); if ( (fp = fdopen(pfd[0], type)) == NULL) return(NULL); } else { close(pfd[0]); if ( (fp = fdopen(pfd[1], type)) == NULL) return(NULL); } childpid[fileno(fp)] = pid; /* remember child pid for this fd */ return(fp); } int pclose(FILE *fp) { int fd, stat; pid_t pid; if (childpid == NULL) return(-1); /* popen() has never been called */ fd = fileno(fp); if ( (pid = childpid[fd]) == 0) return(-1); /* fp wasn't opened by popen() */ childpid[fd] = 0; if (fclose(fp) == EOF) return(-1); while (waitpid(pid, &stat, 0) < 0) // 等待子進程執(zhí)行結 if (errno != EINTR) return(-1); /* error other than EINTR from waitpid() */ return(stat); /* return child's termination status */ } popen 還有個重載函數(shù): int open(const char * pathname, int flags, mode_t mode); mode_t 有如下類型: 4,、總結linux 一切皆文件的思想本質上就是為了操作的一致性,,即使是硬件,也可以利用系統(tǒng)調用進行讀寫操作,。 |
|