LINUX進程間通信:PIPE與FIFO


 

PIPE

http://ldl.wisplus.net/2010/10/01/linux%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1%EF%BC%9A%E7%AE%A1%E9%81%93/

 

概述:

int pipe(int pipefd[2]);
調用pipe函數在內核中開辟一塊緩沖區(稱為管道)用於單向通信,它有一個讀端一個寫端,然后通過filedes參數傳給用戶程序兩個文件描述符,filedes[0]指向PIPE的讀端,filedes[1]指向PIPE的寫端。所以在用戶程序看起來就像一個打開的文件,通過read(filedes[0]);
或者write(filedes[1]); 向這個文件讀寫數據其實是在讀寫內核緩沖區

創建PIPE的基本步驟:

• 父進程調用pipe 開辟PIPE,得到兩個文件描述符指向管道的兩端。
• 父進程調用fork 創建子進程,那么子進程也有兩個文件描述符指向同一管道。
• 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往PIPE里寫,子進程可以從PIPE里讀,PIPE是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
  
int main()
{
     char buf[20];
     pid_t pid;
     int fd[2];
     int n;
  
     pipe(fd); //創建管道
     if ((pid = fork()) < 0) //fork子進程
     {
         perror ( "fork error" );
         exit (-1);
     } else if (pid == 0) //子進程中
     {
         close(fd[1]); //關閉寫端
         n = read(fd[0],buf,20);
         write(STDOUT_FILENO,buf,n);
         close(fd[0]); //關閉讀端
         exit (0);
     } else //父進程中
     {
         close(fd[0]); //關閉讀端
         write(fd[1], "hello world" , strlen ( "hello world" ));
         close(fd[1]); //關閉寫端
         waitpid(pid,NULL,0);
         exit (0);
     }
}

popen函數與pclose函數

標准IO函數庫提供了popen函數,它創建一個管道並啟動另外一個進程,該進程從該PIPE讀出標准輸入或將標准輸出寫入該PIPE。
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen函數:先執行fork,然后調用exec(sh)以執行command,並且返回一個標准I/O文件指針。(錯誤返回NULL)
如果type是”r”,則文件指針連接到command的標准輸出,(該進程為讀段,command所指進程為寫端),參數”w”同理.
This command is passed to /bin/sh using the -c flag;
pclose函數:關閉由popen創建的標准I/O流,等待命令執行結束,然后返回shell的終止狀態(錯誤返回-1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
  
#define PAGER "${PAGER:-more}" //如果shell變量PAGER已經定義而且非空,則使用其值,否則使用字符串more
int main()
{
     char line[100];
     FILE *fpin,*fpout;
     if ((fpin = fopen ( "A.txt" , "r" )) == NULL)
     {
        perror ( "can't open A.txt" );
        exit (-1);
     }
     if ((fpout = popen(PAGER, "w" )) == NULL)
     {
         perror ( "popen error" );
         exit (-1);
     }
     while ( fgets (line,100,fpin) != NULL)
     {
         if ( fputs (line,fpout) == EOF)
         {
             perror ( "fputs error to pipe" );
             exit (-1);
         }
     }
     if (pclose(fpout) == -1)
         perror ( "pclose error" );
     exit (0);
}

FIFO

FIFO即是命名PIPE,文件系統中有個路徑名與之關聯。PIPE只能由有親緣關系的進程使用,它們共同的祖先進程創建了管道。但是,通過FIFO,不相關的進程也能交換數據

創建FIFO

int mkfifo(const char *pathname, mode_t mode);
mode為存取許可權(需結合進程的umask).一般的文件I/O函數都可以用於FIFO
mkfifo函數已經隱含指定O_CREAT | O_EXCL,也就是說,要么創建一個新的FIFO,要么返回EEXIST錯誤(文件已經存在)

刪除FIFO

int unlink(const char *pathname);
不同於PIPE,FIFO只有通過unlink才能從文件系統中刪除

打開FIFO

int open(const char *pathname, int flags);
使用open函數打開FIFO,默認情況下沒有指定O_NONBLOCK標志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//fifo_write.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h>
#define FIFO_FILE "/tmp/myfifo"
  
int main()
{
     int fd = 0;
     int n;
     char buf[100];
  
     if ((fd = open(FIFO_FILE,O_WRONLY | O_NONBLOCK)) < 0) //非阻塞方式打開
     {
         perror ( "open error" );
         exit (-1);
     }
     while (1)
     {
         fgets (buf,100,stdin);
         n = strlen (buf);
         if ((n = write(fd,buf,n)) < 0)
         {
             if ( errno == EAGAIN)
                 printf ( "The FIFO has not been read yet.Please try later\n" );
         }
     }
     return 0;
}
  
//fifo_read.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
  
#define FIFO_FILE "/tmp/myfifo"
  
int main()
{
     char buf[100];
     int n = 0;
     int fd;
     if ((mkfifo(FIFO_FILE,S_IRWXU) < 0) && ( errno != EEXIST)) //如果該fifo文件不存在,創建之
     {
         perror ( "mkfifo error" );
         exit (-1);
     }
     if ((fd = open(FIFO_FILE,O_RDONLY | O_NONBLOCK)) < 0) //非阻塞方式打開
     {
         perror ( "open error" );
         exit (-1);
     }
     while (1)
     {
         if ((n = read(fd,buf,100)) < 0)
         {
             if ( errno == EAGAIN)
             {
                 printf ( "No data yet\n" );
             }
         } else write(STDOUT_FILENO,buf,n);
         sleep(1); //sleep
     }
     unlink(FIFO_FILE);
     return 0;
}

FIFO與PIPE的讀寫:

(1)對於PIPE或FIFO的write總是往末尾添加數據,對他們的read則總是從開頭返回數據。如果對PIPE或FIFO調用lseek,那就返回ESPIPE錯誤
(2)一個文件描述符能以2中方式設置成非阻塞:(默認為阻塞)
• 調用open是可指定O_NONBLOCK標志
• 如果文件描述符已經打開,那么可以調用fcntl設置O_NONBLOCK標志(PIPE只能采用這種方式)
(3)讀寫規則:
阻塞(缺省設置):
只讀open
• FIFO已經被只寫打開:成功返回
• FIFO沒有被只寫打開:阻塞到FIFO被打開來寫
只寫open
• FIFO已經被只讀打開:成功返回
• FIFO沒有被只讀打開:阻塞到FIFO被打開來讀
從空PIPE或空FIFO中read
• FIFO或PIPE已經被只寫打開:阻塞到PIPE或FIFO中有數據或者不再為寫打開着
• FIFO或PIPE沒有被只寫打開:返回0(文件結束符)
write
• FIFO或PIPE已經被只讀打開:
寫入數據量不大於PIPE_BUF(保證原子性):有足夠空間存放則一次性全部寫入,沒有則進入睡眠,直到當緩沖區中有能夠容納要寫入的全部字節數時,才開始進行一次性寫操作
寫入數據量大於PIPE_BUF(不保證原子性):緩沖區一有空閑區域,進程就會試圖寫入數據,函數在寫完全部數據后返回
• FIFO或PIPE沒有被只讀打開:給線程產生SIGPIPE(默認終止進程)

O_NONBLOCK設置:
只讀open
• FIFO已經被只寫打開:成功返回
• FIFO沒有被只寫打開:成功返回
只寫open
• FIFO已經被只讀打開:成功返回
• FIFO沒有被只讀打開:返回ENXIO錯誤
從空PIPE或空FIFO中read
• FIFO或PIPE已經被只寫打開:返回EAGAIN錯誤
• FIFO或PIPE沒有被只寫打開:返回0(文件結束符)
write
• FIFO或PIPE已經被只讀打開:
寫入數據量不大於PIPE_BUF(保證原子性):有足夠空間存放則一次性全部寫入,沒有則返回EAGAIN錯誤(不會部分寫入)
寫入數據量大於PIPE_BUF(不保證原子性):有足夠空間存放則全部寫入,沒有則部分寫入,函數立即返回
• FIFO或PIPE沒有被只讀打開:給線程產生SIGPIPE(默認終止進程)

PIPE或FIFO若干額外的規則:
• 如果請求讀取的數據量多余當前可用的數據量,那么返回這些可用的數據
• 如果請求寫入的數據字節數小於或等於PIPE_BUF,那么write操作保證是原子的(O_NONBLOCK標志的設置對原子性沒有影響)
• 當對PIPE或FIFO最后一個關閉時,仍在該PIPE或FIFO上的數據將被丟棄

FIFO與PIPE的限制:

• 它們是半雙工的(單向性),即數據只能在一個方向上流動。由進程A流向進程B或由進程B流向進程A。
• PIPE的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那里繼承PIPE文件描述符。FIFO可以實現無關進程間的通信。
• 一個進程在任意時刻打開的最大文件描述符個數OPEN_MAX(通過調用sysconf(_SC_OPEN_MAX)獲得)
• 可原子地寫往PIPE或FIFO的最大數據量PIPE_BUF(通常定義在limits.h)

小結:

• PIPE普遍用於SHELL中,不過也可以從程序中使用,往往是從子程序向父程序回傳信息。使用PIPE時涉及的某些代碼(pipe、fork、close、exec和waitpid)可通過使用popen和pclose來避免,由它們處理具體細節並激活一個shell
• FIFO與管道類似,但他們是用mkfifo創建的,之后需要用open打開。打開管道時必須小心,因為有許多規則制約着open的阻塞與否(甚至發生死鎖)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM