UNIX環境高級編程——管道讀寫規則和pipe Capacity、PIPE_BUF

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

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

示例程序以下:編程

#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);
    }
//  sleep(3);
    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,讓父進程先被調度運行,並且讀端文件描述符標誌設置爲非阻塞,即馬上出錯返回,以下:

huangcheng@ubuntu:~$ ./a.out
read error: Resource temporarily unavailable

假設開啓35行,註釋29行,讓父進程先sleep,子進程先運行,則運行結果:ubuntu

huangcheng@ubuntu:~$ ./a.out
buf=hello


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

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

#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,輸出以下:

huangcheng@ubuntu:~$ ./a.out
err=Resource temporarily unavailable
count=65536
打印了錯誤碼,能夠看到管道的容量是64kB,man 7 pipe中也有提到在2.6.11內核之前是4096,如今是65536


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

示例代碼以下:函數

#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;
}
輸出測試:

huangcheng@ubuntu:~$ ./a.out
recv sig=13
err=Broken pipe

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


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

示例程序以下:spa

#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;
}
輸出測試以下:

huangcheng@ubuntu:~$ ./a.out
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)的狀況 :

#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;
}
輸出測試以下:

huangcheng@ubuntu:~$ ./a.out
n=01 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=02 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=03 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=04 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=05 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=06 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=07 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=08 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=09 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=10 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=11 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=12 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=13 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=14 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=15 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=16 pid=2598 read 4096 bytes from pipe buf[4095]=B
bpid=2600 write 69632 bytes to pipe
n=17 pid=2598 read 4096 bytes from pipe buf[4095]=B
n=18 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=19 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=20 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=21 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=22 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=23 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=24 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=25 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=26 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=27 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=28 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=29 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=30 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=31 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=32 pid=2598 read 4096 bytes from pipe buf[4095]=A
n=33 pid=2598 read 4096 bytes from pipe buf[4095]=A
apid=2599 write 69632 bytes to pipe
n=34 pid=2598 read 4096 bytes from pipe buf[4095]=A

     分析一下:如今的狀況是有兩個子進程在對管道進行阻塞寫入各68k,即每一個子進程徹底寫入68k才返回,而父進程對管道進行阻塞讀取,每次讀取4k,打印每4k中的最後一個字符,若是沒有數據到達就阻塞等待,若是管道剩餘數據不足4k,read 極可能返回 < 4k,但由於咱們寫入68k是4k整數倍,故不存在這種狀況。須要注意的是是邊寫邊讀,由於前面說過管道的容量只有64k,當管道被寫滿時子進程就阻塞等待父進程讀取後再寫入。由上面輸出能夠看出B進程先寫入64k的B,而後寫入剩下的4k的B,接着A進程先寫入64k的A以後接着寫完最後的4k的A,而後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只有4k,不能一次性寫入68k,若是此時管道是滿的(64k),則只能返回-1並置錯誤碼爲EAGAIN,且一個字符也不寫入,若不是滿的,則寫入的字節數是不肯定的,須要檢查write的返回值,並且這些字節極可能也與其餘進程寫入的數據穿插着。讀端也不能設置爲非阻塞,若是此時還沒有有數據寫入(管道爲空)則返回-1並置錯誤碼爲EAGAIN,若是有部分數據已經寫入,則讀取的數據字節數也是不肯定的,須要檢查read的返回值。總之測試4種不一樣情形下的狀況也應設置不一樣的條件。

詳細說明見:UNIX環境高級編程——管道和FIFO的額外屬性

       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 immediately

       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 non-atomic:  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 也具備管道的這些特性。
相關文章
相關標籤/搜索