进程返回linux系统编程之管道(二):管道读写规则和Pipe Capacity、PIPE_BUF

题记:写这篇博客要主是加深自己对进程返回的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。

    一、

    当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,始终等到有数据离开为止。

    O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

    示例程序如下:

    

    

 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: [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

    

    

 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: [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信号,默认终止以后进程

    示例代码如下:

    

    

 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: [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

    示例程序如下:

    

    

 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: [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)的情况 :

    

    

 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: [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 也具有管道的这些特性。

文章结束给大家分享下程序员的一些笑话语录: IBM和波音777
  波音777是有史以来第一架完全在电脑虚拟现实中设计制造的飞机,所用的设备完全由IBM公司所提供。试飞前,波音公司的总裁非常热情的邀请IBM的技术主管去参加试飞,可那位主管却说道:“啊,非常荣幸,可惜那天是我妻子的生日,So..”..
  波音公司的总载一听就生气了:“胆小鬼,我还没告诉你试飞的日期呢!”

你可能感兴趣的:(linux)