zoukankan      html  css  js  c++  java
  • linux系统编程之管道(二):管道读写规则

    一,管道读写规则

    当没有数据可读时

    • 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
    ",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
    ",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
    ", 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
    ");
        }
        return 0;
    }
    
    void sighandler(int signo)
    {
        printf("catch a SIGPIPE signal and signum = %d
    ",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
    ",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
    ", 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
    ", 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
    ", 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++, getpid(), ret, buf[4095]);
            write(fd, buf, ret);
    
        }
        return 0;    
    }

    结果:

    QQ截图20130715235256

    QQ截图20130715235322

    可见各子进程间出现穿插写入,并没保证原子性写入,且父进程在子进程编写时边读。

  • 相关阅读:
    使用ParseExact方法将字符串转换为日期格式
    Windows 备用数据流(ADS)的妙用___转载
    ms17_010利用复现(32位)
    将手机号设置为空号
    PowerShell批量创建文件夹
    让程序显示运行时间
    使用Sleep方法延迟时间
    使用TimeSpan对象获取时间间隔
    DateTime小综合
    DDMS介绍
  • 原文地址:https://www.cnblogs.com/mickole/p/3192461.html
Copyright © 2011-2022 走看看