歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux系統編程之管道(二) 管道讀寫規則和Pipe Capacity、PIPE_BUF

linux系統編程之管道(二) 管道讀寫規則和Pipe Capacity、PIPE_BUF

日期:2017/3/3 16:24:11   编辑:關於Linux

一、當沒有數據可讀時

O_NONBLOCK disable:read調用阻塞,即進程暫停執行,一直等到有數據來到為止。

O_NONBLOCK enable:read調用返回-1,errno值為EAGAIN。

示例程序如下:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
ERR_EXIT("pipe error");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
sleep(3);
close(pipefd[0]);
write(pipefd[1], "hello", 5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10] = {0};
int flags = fcntl(pipefd[0], F_GETFL);
fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK); //enable fd的O_NONBLOCK
int ret = read(pipefd[0], buf, 10); //默認是disable fd的O_NONBLOCK
if (ret == -1) // 父進程不會阻塞,出錯返回
ERR_EXIT("read error");
printf("buf=%s\n", buf);
return 0;
}

特意在子進程中sleep了3s,讓父進程先被調度運行,而且讀端文件描述符標志設置為非阻塞,即立刻出錯返回,如下。

simba@ubuntu:~/Documents/code/linux_programming/APUE/pipe$ ./pipe_block

read error: Resource temporarily unavailable

二、當管道滿的時候

O_NONBLOCK disable: write調用阻塞,直到有進程讀走數據

O_NONBLOCK enable:調用返回-1,errno值為EAGAIN

管道是一塊內存緩沖區,可以寫個小程序測試一下管道的容量Pipe Capacity:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
ERR_EXIT("pipe error");
int ret;
int count = 0;
int flags = fcntl(pipefd[1], F_GETFL);
fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK); // 設置為非阻塞
while (1)
{
ret = write(pipefd[1], "A", 1);
if (ret == -1)
{
printf("err=%s\n", strerror(errno));
break;
}
count++;
}
printf("count=%d\n", count); //管道容量
return 0;
}

程序中將寫端文件描述符標志設置為非阻塞,當管道被寫滿時不會等待其他進程讀取數據,而是直接返回-1並置errno,輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/APUE/pipe$ ./pipe_capacity

err=Resource temporarily unavailable

count=65536

打印了錯誤碼,可以看到管道的容量是64kB,man 7 pipe中也有提到在2.6.11內核以前是4096,現在是65536。

三、如果所有管道讀端對應的文件描述符被關閉(管道讀端的引用計數等於0),則write操作會產生SIGPIPE信號,默認終止當前進程

示例代碼如下:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int sig)
{
printf("recv sig=%d\n", sig);
}
int main(int argc, char *argv[])
{
signal(SIGPIPE, handler);
int pipefd[2];
if (pipe(pipefd) == -1)
ERR_EXIT("pipe error");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
close(pipefd[0]);
exit(EXIT_SUCCESS);
}
close(pipefd[0]);
sleep(1);
int ret = write(pipefd[1], "hello", 5);
if (ret == -1)
{
printf("err=%s\n", strerror(errno));
}
return 0;
}

輸出測試:

simba@ubuntu:~/Documents/code/linux_programming/APUE/pipe$ ./close_fd_read

recv sig=13

err=Broken pipe

父進程睡眠1s確保所有讀端文件描述符都已經關閉,如果沒有安裝SIGPIPE信號的處理函數,則默認終止當前進程,即write函數不會返回,現在write錯誤返回-1,並置errno=EPIPE,對應的出錯信息是Broken pipe。

四、如果所有管道寫端對應的文件描述符被關閉(管道寫端的引用計數等於0),那麼管道中剩余的數據都被讀取後,再次read會返回0

示例程序如下:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int sig)
{
printf("recv sig=%d\n", sig);
}
int main(int argc, char *argv[])
{
signal(SIGPIPE, handler);
int pipefd[2];
if (pipe(pipefd) == -1)
ERR_EXIT("pipe error");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
sleep(1);
char buf[10] = {0};
int ret = read(pipefd[0], buf, 10);
printf("ret = %d\n", ret);
return 0;
}

輸出測試如下:

simba@ubuntu:~/Documents/code/linux_programming/APUE/pipe$ ./close_fd_write

ret = 0

同樣地父進程睡眠1s確保所有的寫端文件描述符都已經關閉,read返回0。

五、當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性;當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。

On Linux, PIPE_BUF is 4096 bytes。

The precise semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be written。即由文件描述符的標志,是否有多個進程向管道寫入以及寫入的字節數所決定准確的語義,總共分4種情況,具體可man一下。

下面的程序演示O_NONBLOCK disabled ,size > PIPE_BUF(4K)的情況 :

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
#define TEST_SIZE 68*1024 // 68KB
/* 默認O_NONBLOCK disabled ,這裡驗證 size > PIPE_BUF(4K)的情況 */
int main(int argc, char *argv[])
{
char a[TEST_SIZE];
char b[TEST_SIZE];
memset(a, 'A', sizeof(a));
memset(b, 'B', sizeof(b));
int pipefd[2];
int ret = pipe(pipefd);
if (ret == -1)
ERR_EXIT("pipe error");
int pid = fork();
if (pid == 0)
{
close(pipefd[0]);
ret = write(pipefd[1], a, sizeof(a)); // 全部寫完才返回
printf("apid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
}
pid = fork();
if (pid == 0)
{
close(pipefd[0]);
ret = write(pipefd[1], b, sizeof(b));
printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
exit(0);
}
close(pipefd[1]);
sleep(1);
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
char buf[1024 * 4] = {0};
int n = 1;
while (1)
{
ret = read(pipefd[0], buf, sizeof(buf)); //當管道被寫入數據,就已經可以開始讀了,每次讀取4k
if (ret == 0) // 管道寫端全部關閉,即讀到了結尾
break;
printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n",
n++, getpid(), ret, buf[4095]);
write(fd, buf, ret);
}
return 0;
}

輸出測試如下:

simba@ubuntu:~/Documents/code/linux_programming/APUE/pipe$ ./pipe_buf

n=01 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=02 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=03 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=04 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=05 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=06 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=07 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=08 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=09 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=10 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=11 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=12 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=13 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=14 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=15 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=16 pid=7137 read 4096 bytes from pipe buf[4095]=B

n=17 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=18 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=19 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=20 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=21 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=22 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=23 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=24 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=25 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=26 pid=7137 read 4096 bytes from pipe buf[4095]=A

apid=7138 write 69632 bytes to pipe

n=27 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=28 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=29 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=30 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=31 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=32 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=33 pid=7137 read 4096 bytes from pipe buf[4095]=A

n=34 pid=7137 read 4096 bytes from pipe buf[4095]=B

bpid=7139 write 69632 bytes to pipe

分析一下:現在的情況是有兩個子進程在對管道進行阻塞寫入各68k,即每個子進程完全寫入68k才返回,而父進程對管道進行阻塞讀取,每次讀取4k,打印每4k中的最後一個字符。需要注意的是是邊寫邊讀,因為前面說過管道的容量只有64k,當管道被寫滿時子進程就阻塞等待父進程讀取後再寫入。由上面輸出可以看出B進程先寫入64k的B,然後A進程寫入68k的A之後B進程接著寫完最後4K的B,然後write返回。由A進程write完畢輸出的提示可知此時A進程已經寫完成了,但父進程還沒讀取A完畢,當兩個子進程全部寫完退出時關閉寫端文件描述符,則父進程read就會返回0,退出while循環。可以得出結論:當多個進程對管道進行寫入,且一次性寫入數據量大於PIPE_BUF時,則不能保證寫入的原子性,即可能數據是穿插著的。man 手冊的解釋如下:

O_NONBLOCK disabled, n > PIPE_BUF

The write is nonatomic: the data given to write(2) may be interleaved with write(2)s by other process; the write(2) blocks until n bytes have been written.

注意我們這裡設定了size=68k,則寫端不能設置成非阻塞,因為PIPE_BUF只有64k,不能一次性寫入68k,故只能返回-1,且一個字符也不寫入,讀端也不能設置為非阻塞,因為有可能此時管道還沒被寫入數據或者不夠一次性read的數據量的時候去讀,則直接返回-1,不會阻塞等待足夠數據量後進行讀取。總之測試4種不同情形下的情況也應設置不同的條件。

管道的前4種讀寫規則具有普遍意義,Tcp socket 也具有管道的這些特性。

Copyright © Linux教程網 All Rights Reserved