管道讀寫規則和Pipe Capacity、PIPE_BUF

1、當沒有數據可讀時
O_NONBLOCK disable:read調用阻塞,即進程暫停執行,一直等到有數據來到爲止。
linux

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

示例程序以下:小程序

 

 C++ Code 
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
 
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > 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,讓父進程先被調度運行,並且讀端文件狀態標誌設置爲非阻塞,即馬上出錯返回,以下。api

simba@ubuntu:~/Documents/code/linux_programming/APUE/pipe$ ./pipe_block 
read error: Resource temporarily unavailable
socket


2、當管道滿的時候
O_NONBLOCK disable: write調用阻塞,直到有進程讀走數據
O_NONBLOCK enable:調用返回-1,errno值爲EAGAIN
函數

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

 

 C++ Code 
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
 
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > 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
atom

打印了錯誤碼,能夠看到管道的容量是64kB,man 7 pipe中也有提到在2.6.11內核之前是4096,如今是65536。spa


3、若是全部管道讀端對應的文件描述符被關閉(管道讀端的引用計數等於0),則write操做會產生SIGPIPE信號,默認終止當前進程code

示例代碼以下:

 

 C++ Code 
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
 
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > 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。


4、若是全部管道寫端對應的文件描述符被關閉(管道寫端的引用計數等於0),那麼管道中剩餘的數據都被讀取後,再次read會返回0

示例程序以下:

 

 C++ Code 
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
 
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > 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。


5、當要寫入的數據量不大於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)的狀況 :

 

 C++ Code 
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
74
75
76
 
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > 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中的最後一個字符,若是沒有數據到達就阻塞等待,若是管道剩餘數據不足4k,read 極可能返回 < 4k,但由於咱們寫入68k是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 Capacity 只有64k,不能一次性寫入68k,若是此時管道是滿的(64k),則只能返回-1並置錯誤碼爲EAGAIN,且一個字符也不寫入,若不是滿的,則寫入的字節數是不肯定的,須要檢查write的返回值,並且這些字節極可能也與其餘進程寫入的數據穿插着。讀端也不能設置爲非阻塞,若是此時還沒有有數據寫入(管道爲空)則返回-1並置錯誤碼爲EAGAIN,若是有部分數據已經寫入,則讀取的數據字節數也是不肯定的,須要檢查read的返回值。總之測試4種不一樣情形下的狀況也應設置不一樣的條件。

 O_NONBLOCK disabled, n <= PIPE_BUF
              All n bytes are written atomically; write(2) may block if there is not room for n bytes to be written imme‐
              diately


       O_NONBLOCK enabled, n <= PIPE_BUF
              If  there  is  room  to write n bytes to the pipe, then write(2) succeeds immediately, writing all n bytes;
              otherwise write(2) fails, with errno set to EAGAIN.


       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.


       O_NONBLOCK enabled, n > PIPE_BUF
              If  the  pipe  is full, then write(2) fails, with errno set to EAGAIN.  Otherwise, from 1 to n bytes may be
              written (i.e., a "partial write" may occur; the caller should check the return value from write(2)  to  see
              how many bytes were actually written), and these bytes may be interleaved with writes by other processes.


管道的前4種讀寫規則具備廣泛意義,Tcp socket 也具備管道的這些特性。


參考:《APUE》

相關文章
相關標籤/搜索