PIPE
概述:
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的阻塞與否(甚至發生死鎖)