linux系統程式設計之管道(二):管道讀寫規則

mickole發表於2013-07-15

一,管道讀寫規則

當沒有資料可讀時

  • O_NONBLOCK disable:read呼叫阻塞,即程式暫停執行,一直等到有資料來到為止。
  • O_NONBLOCK enable:read呼叫返回-1,errno值為EAGAIN。

當管道滿的時候

  • O_NONBLOCK disable: write呼叫阻塞,直到有程式讀走資料
  • O_NONBLOCK enable:呼叫返回-1,errno值為EAGAIN

如果所有管道寫端對應的檔案描述符被關閉,則read返回0

如果所有管道讀端對應的檔案描述符被關閉,則write操作會產生訊號SIGPIPE

當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。

當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。

二,驗證示例

示例一:O_NONBLOCK disable:read呼叫阻塞,即程式暫停執行,一直等到有資料來到為止。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        close(fds[0]);//子程式關閉讀端
        sleep(10);
        write(fds[1],"hello",5);
        exit(EXIT_SUCCESS);
    }

    close(fds[1]);//父程式關閉寫端
    char buf[10] = {0};
    read(fds[0],buf,10);
    printf("receive datas = %s\n",buf);
    return 0;
}

結果:

QQ截圖20130715221246

說明:管道建立時預設開啟了檔案描述符,且預設是阻塞(block)模式開啟

所以這裡,我們讓子程式先睡眠10s,父程式因為沒有資料從管道中讀出,被阻塞了,直到子程式睡眠結束,向管道中寫入資料後,父程式才讀到資料

示例二:O_NONBLOCK enable:read呼叫返回-1,errno值為EAGAIN。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        close(fds[0]);//子程式關閉讀端
        sleep(10);
        write(fds[1],"hello",5);
        exit(EXIT_SUCCESS);
    }

    close(fds[1]);//父程式關閉寫端
    char buf[10] = {0};
    int flags = fcntl(fds[0], F_GETFL);//先獲取原先的flags
    fcntl(fds[0],F_SETFL,flags | O_NONBLOCK);//設定fd為阻塞模式
    int ret;
    ret = read(fds[0],buf,10);
    if(ret == -1){

        perror("read error");
        exit(EXIT_FAILURE);
    }

    printf("receive datas = %s\n",buf);
    return 0;
}

結果:

QQ截圖20130715222752

示例三:如果所有管道寫端對應的檔案描述符被關閉,則read返回0

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        close(fds[1]);//子程式關閉寫端
        exit(EXIT_SUCCESS);
    }

    close(fds[1]);//父程式關閉寫端
    char buf[10] = {0};

    int ret;
    ret = read(fds[0],buf,10);
    printf("ret = %d\n", ret);

    return 0;
}

結果:

QQ截圖20130715223735

可知確實返回0,表示讀到了檔案末尾,並不表示出錯

示例四:如果所有管道讀端對應的檔案描述符被關閉,則write操作會產生訊號SIGPIPE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>

void sighandler(int signo);
int main(void)
{
    int fds[2];
    if(signal(SIGPIPE,sighandler) == SIG_ERR)
    {
        perror("signal error");
        exit(EXIT_FAILURE);
    }
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        close(fds[0]);//子程式關閉讀端
        exit(EXIT_SUCCESS);
    }

    close(fds[0]);//父程式關閉讀端
    sleep(1);//確保子程式也將讀端關閉
    int ret;
    ret = write(fds[1],"hello",5);
    if(ret == -1){
        printf("write error\n");
    }
    return 0;
}

void sighandler(int signo)
{
    printf("catch a SIGPIPE signal and signum = %d\n",signo);
}

結果:

QQ截圖20130715225323

可知當所有讀端都關閉時,write時確實產生SIGPIPE訊號

示例五:O_NONBLOCK disable: write呼叫阻塞,直到有程式讀走資料

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    int ret;
    int count = 0;
    while(1){
        ret = write(fds[1],"A",1);//fds[1]預設是阻塞模式
        if(ret == -1){
            perror("write error");
            break;
        }
        count++;
    }

    return 0;
}

結果:

QQ截圖20130715231009

說明:fd開啟時預設是阻塞模式,當pipe緩衝區滿時,write操作確實阻塞了,等待其他程式將資料從管道中取走

示例六:O_NONBLOCK enable:呼叫返回-1,errno值為EAGAIN

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    int ret;
    int count = 0;
    int flags = fcntl(fds[1],F_GETFL);
    fcntl(fds[1],F_SETFL,flags|O_NONBLOCK);
    while(1){
        ret = write(fds[1],"A",1);//fds[1]預設是阻塞模式
        if(ret == -1){
            perror("write error");
            break;
        }
        count++;
    }
    printf("the pipe capcity is = %d\n",count);

    return 0;
}

結果:

QQ截圖20130715231520

可知也出現EGIN錯誤,管道容量是65536位元組

man 7 pipe說明:

Pipe capacity
       A pipe has a limited capacity.  If the pipe is full, then a write(2)
       will block or fail, depending on whether the O_NONBLOCK flag is set
       (see below).  Different implementations have different limits for the
       pipe capacity.  Applications should not rely on a particular
       capacity: an application should be designed so that a reading process
       consumes data as soon as it is available, so that a writing process
       does not remain blocked.

       In Linux versions before 2.6.11, the capacity of a pipe was the same
       as the system page size (e.g., 4096 bytes on i386).  Since Linux
       2.6.11, the pipe capacity is 65536 bytes.

三,管道寫與PIPE_BUF關係

man幫助說明:
PIPE_BUF
       POSIX.1-2001 says that write(2)s of less than PIPE_BUF bytes must be
       atomic: the output data is written to the pipe as a contiguous
       sequence.  Writes of more than PIPE_BUF bytes may be nonatomic: the
       kernel may interleave the data with data written by other processes.
       POSIX.1-2001 requires PIPE_BUF to be at least 512 bytes.  (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:

       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
       阻塞模式時且n<PIPE_BUF:寫入具有原子性,如果沒有足夠的空間供n個位元組全部寫入,則阻塞直到有足夠空間將n個位元組全部寫入管道       
       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.
      非阻塞模式時且n<PIPE_BUF:寫入具有原子性,立即全部成功寫入,否則一個都不寫入,返回錯誤
       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.
      阻塞模式時且n>PIPE_BUF:不具有原子性,可能中間有其他程式穿插寫入,直到將n位元組全部寫入才返回,否則阻塞等待寫入
       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.
   非阻塞模式時且N>PIPE_BUF:如果管道滿的,則立即失敗,一個都不寫入,返回錯誤,如果不滿,則返回寫入的位元組數為1~n,即部分寫入,寫入時可能有其他程式穿插寫入
  • 當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。
  • 當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。

注:管道容量不一定等於PIPE_BUF

示例:當寫入資料大於PIPE_BUF時

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>


#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

#define TEST_SIZE 68*1024

int main(void)
{
    char a[TEST_SIZE];
    char b[TEST_SIZE];
    char c[TEST_SIZE];

    memset(a, 'A', sizeof(a));
    memset(b, 'B', sizeof(b));
    memset(c, 'C', sizeof(c));

    int pipefd[2];

    int ret = pipe(pipefd);
    if (ret == -1)
        ERR_EXIT("pipe error");

    pid_t pid;
    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);
    }

    pid = fork();

    
    if (pid == 0)//第三個子程式
    {
        close(pipefd[0]);
        ret = write(pipefd[1], c, sizeof(c));
        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, 0644);
    char buf[1024*4] = {0};
    int n = 1;
    while (1)
    {
        ret = read(pipefd[0], buf, sizeof(buf));
        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;    
}

結果:

QQ截圖20130715235256

QQ截圖20130715235322

可見各子程式間出現穿插寫入,並沒保證原子性寫入,且父程式在子程式編寫時邊讀。

相關文章