Linux進程間通信---管道(pipe)
概述
管道又稱無名管道、匿名管道,是被所有UNIX like系統支持的古老通信方式。
管道是單向字節流,在Linux中管道是通過指向同一個臨時的VFS inode的兩個file數據結構來實現的,此VFS inode指向內存中的同一個物理頁面。這就隱藏了讀寫管道和讀寫普通文件的差別。管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,單獨構成一種文件系統,並且只存在與內存中。數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。
管道在應用程序中體現爲2個打開的文件描述符:
+--------------------+ +------------------------------+ +--------------------+
| | | | | |
| | | +-----------------+ | | |
| | | | | | | |
| fd[0]<------------+ Pipe +<--------------+fd[1] |
| | | +-----------------+ | | |
| | | | | |
+--------------------+ +------------------------------+ +--------------------+
User Application Kernel User Application
特點
- 只有具有親緣關係的進程纔可以通過管道進行通信;
- 半雙工,同一時刻,數據只能往一個方向流動;
- 寫入管道的順序遵循先入先出(FIFO)原則;
- 從管道讀取數據是一次性操作,數據一旦讀取,則從管道中刪除;
+-----------+ +-----------+ +---------------------+ +-----------+ +-------------+
| | | | | | | | | |
| | | | | +------+ | | | | |
| | w | |Y | | | | R | | Y | |
| Write +---> Writable? +--> Write | Pipe | Read +---> Readable? +---> Write |
|Application| |(not full) | | End | | End | |(not empty)| | Application |
| | | | | +------+ | | | | |
| | | | | | | | | |
+-----------+ +------+----+ +---------------------+ +-----+-----+ +-------------+
| |
N| | N
| |
+------v----+ +-----v-----+
| | | |
| Sleep | | Sleep |
| | | |
+-----------+ +-----------+
相關API
#include <unistd.h>
/* 創建無名管道
* @filedes: 爲 int 型數組的首地址,其存放了管道的文件描述符 filedes[0]、filedes[1]。
* 當一個管道建立時,它會創建兩個文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用於讀管道,而 fd[1] 固定用於寫管道。
* 一般文件 I/O 的函數都可以用來操作管道( lseek() 除外)。
* @return 成功:0,失敗:-1 */
int pipe(int filedes[2]);
舉例
一般情況: 子進程寫、父進程讀
一般情況下,在父進程中創建管道,並fork出子進程,這樣在父子進程中分別用fd[0]和fd[1]進行讀寫,例如,父進程中創建fd_pipe[2], 並fork子進程, 子進程向fd[1]中寫入“Hello, Father"字符串,父進程中通過fd_pipe[0]讀取該字符串並打印。(如果需要同時讀寫,爲避免混亂,應使用2個管道)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
if( pipe(fd_pipe) < 0 ){// 創建無名管道
perror("pipe");
}
pid = fork(); // 創建進程
if( pid < 0 ){ // 出錯
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子進程
char str[] = "Hello, father";
write(fd_pipe[1], str, strlen(str));
printf("[child] write done. \n");
_exit(0);
}else if( pid > 0){// 父進程
wait(NULL); // 等待子進程結束,回收其資源
char str[50] = {0};
printf("[father] before read\n");
read(fd_pipe[0], str, sizeof(str));
printf("[father] after read\n");
printf("[father] str=[%s]\n", str); // 打印數據
}
return 0;
}
管道空: read會阻塞,直到有數據
管道滿 : write會阻塞,直到可寫
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#define debug(fmt, args...) do { now(); printf(fmt, ##args); } while(0)
char* now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
char str[1024] = "";
memset(str, 'a', sizeof(str));
if( pipe(fd_pipe) < 0 ){// 創建無名管道
perror("pipe");
}
pid = fork(); // 創建進程
if( pid < 0 ){ // 出錯
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子進程
int i = 0;
int len = 0;
while(1) {
i++;
len = write(fd_pipe[1], str, sizeof(str)); // 寫滿後會阻塞
debug("[child] i ===%d. write %d byte\n", i, len);
}
_exit(0);
}else if( pid > 0){// 父進程
char str[2048] = {0};
int len = 0;
while(1) {
sleep(2);
len = read(fd_pipe[0], str, sizeof(str));
debug("[father] read %d byte data\n", len);
}
}
return 0;
}
輸出結果:
[Tue Mar 12 17:14:22 2019] [child] i ===1. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===2. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===3. 1024
... ...
[Tue Mar 12 17:14:22 2019] [child] i ===62. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===63. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===64. 1024 // 此處寫完後,需要等待3s之後,父進程將數據讀出來之後纔可以繼續寫入)
[Tue Mar 12 17:14:24 2019] [father] read 2048
[Tue Mar 12 17:14:26 2019] [father] read 2048
[Tue Mar 12 17:14:26 2019] [child] i ===65. 1024
[Tue Mar 12 17:14:26 2019] [child] i ===66. 1024
[Tue Mar 12 17:14:26 2019] [child] i ===67. 1024
[Tue Mar 12 17:14:26 2019] [child] i ===68. 1024
[Tue Mar 12 17:14:28 2019] [father] read 2048
讀端關閉,導致SIGPIPE
當管道的所有讀端均關閉時,如果再往裏寫入數據時會拋出SIGPIPE信號,其默認處理動作是中斷當前進程。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <signal.h>
#define debug(fmt, args...) do { now(); printf(fmt, ##args); } while(0)
char* now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
static void sig_handler(int signo)
{
debug("[father] get signal %d\n", signo);
}
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
char str[16] = "";
struct sigaction psa;
psa.sa_handler = sig_handler;
sigaction(SIGPIPE, &psa, NULL);
memset(str, 'a', sizeof(str));
if( pipe(fd_pipe) < 0 ){// 創建無名管道
perror("pipe");
}
pid = fork(); // 創建進程
if( pid < 0 ){ // 出錯
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子進程
sleep(3);
close(fd_pipe[0]); // 主動關閉讀端
sleep(100);
_exit(0);
} else if ( pid > 0){// 父進程
int len = 0;
close(fd_pipe[0]);
while(1) {
len = write(fd_pipe[1], str, sizeof(str));
// 當所有讀端都關閉後,纔會拋出SIGPIPE信號
sleep(1);
debug("[father] write %d byte data\n", len);
}
}
return 0;
}
輸出結果:3秒後,子進程關閉讀端,寫入錯誤,拋出SIGPIPE信號。
[Tue Mar 12 18:29:27 2019] [father] write 16 byte data
[Tue Mar 12 18:29:28 2019] [father] write 16 byte data
[Tue Mar 12 18:29:29 2019] [father] write 16 byte data
[Tue Mar 12 18:29:29 2019] [father] get signal 13
[Tue Mar 12 18:29:30 2019] [father] write -1 byte data
[Tue Mar 12 18:29:30 2019] [father] get signal 13
[Tue Mar 12 18:29:31 2019] [father] write -1 byte data
[Tue Mar 12 18:29:31 2019] [father] get signal 13
非阻塞方式使用PIPE
在非阻塞模式下,write/read函數會直接返回-1, 而不是阻塞等待。
fcntl(fd, F_SETFL, 0); // 設置爲阻塞模式, 默認情況
fcntl(fd, F_SETFL, O_NONBLOCK); // 設置爲非阻塞模式
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <fcntl.h>
#define debug(fmt, args...) do { now(); printf(fmt, ##args); } while(0)
char* now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
char str[1024] = "";
memset(str, 'a', sizeof(str));
if( pipe(fd_pipe) < 0 ){// 創建無名管道
perror("pipe");
}
// 設置讀寫均爲非阻塞
fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK);
fcntl(fd_pipe[1], F_SETFL, O_NONBLOCK);
pid = fork(); // 創建進程
if( pid < 0 ){ // 出錯
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子進程
int i = 0;
int len = 0;
while(1) {
i++;
len = write(fd_pipe[1], str, sizeof(str));
debug("[child] i ===%d. write %d byte\n", i, len);
sleep(3); // 確保寫入速度低
}
_exit(0);
}else if( pid > 0){// 父進程
char str[2048] = {0};
int len = 0;
while(1) {
// 非阻塞,len爲-1
len = read(fd_pipe[0], str, sizeof(str));
debug("[father] read %d byte data\n", len);
sleep(1);
}
}
return 0;
}
輸出結果:
每隔3s,才能讀出1024字節數據,其他時候返回-1, 並不會阻塞。
[Tue Mar 12 18:38:46 2019] [father] read -1 byte data
[Tue Mar 12 18:38:46 2019] [child] i ===1. write 1024 byte
[Tue Mar 12 18:38:47 2019] [father] read 1024 byte data
[Tue Mar 12 18:38:48 2019] [father] read -1 byte data
[Tue Mar 12 18:38:49 2019] [child] i ===2. write 1024 byte
[Tue Mar 12 18:38:49 2019] [father] read 1024 byte data
[Tue Mar 12 18:38:50 2019] [father] read -1 byte data
[Tue Mar 12 18:38:51 2019] [father] read -1 byte data
[Tue Mar 12 18:38:52 2019] [child] i ===3. write 1024 byte