管道读写规则及 PIPE_BUF 涉及的写原子性问题

结论:
一、当管道内没有数据可读时
O_NONBLOCK disable:read 调用阻塞,直到有可读数据
O_NONBLOCK enable: read 调用返回 -1,errno 值为 EAGAIN

二、当管道已满或者剩余空间不够时
O_NONBLOCK disable:write 调用阻塞,直到有进程读走数据,管道中有足够大的可用空间
O_NONBLOCK enable: write 调用返回 -1,errno 值为 EAGAIN

匿名管道默认是阻塞模式,可通过以下示例修改为非阻塞模式

int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

命名管道的阻塞模式和非阻塞模式设定参见以下示例

open("fifo", flags | O_NONBLOCK);  //非阻塞
open("fifo", flags);  //阻塞

管道容量

三、
如果所有管道写端对应的文件描述符被关闭,则 read 调用返回 0
如果所有管道读端对应的文件描述符被关闭,则 write 调用会产生信号 SIGPIPE,进而可能导致写进程退出

四、man 7 pipe
1、当要写入的数据量 n <= PIPE_BUF 时,linux 将保证写入的原子性
O_NONBLOCK disable:如果管道中有足够大的可用空间便写入;否则 write 调用阻塞,直到管道中有足够大的可用空间
O_NONBLOCK enable: 如果管道中有足够大的可用空间便写入;否则 write 调用返回 -1,errno 值为 EAGAIN
2、当要写入的数据量 n > PIPE_BUF 时,linux 将不再保证写入的原子性
O_NONBLOCK disable:write 直到将所有数据写入管道后返回,期间可能有其他进程穿插写入
O_NONBLOCK enable: 如果管道已满,write 调用返回 -1,errno 值为 EAGAIN;否则实际写入的数据量为 1 ~ n,即部分写入,期间可能有其他进程穿插写入
3、读数据不会因为读取数据量 n 是否大于 PIPE_BUF 失去原子性

五、综合以上一、二和四1、3,内核会对管道(匿名/命名管道)进行同步与互斥

在这里插入图片描述

验证如下:
1、O_NONBLOCK disable:read 调用阻塞

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

int main(void)
{
  int    fds[2];
  pid_t  pid;
  time_t t;
  char   buf[10] = {0};

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }

  if (0 == pid)
  {
    close(fds[0]);  //关闭读端
    sleep(10);
    write(fds[1], "hello", 5);
    exit(EXIT_SUCCESS);
  }

  close(fds[1]);  //关闭写端
  t = time(NULL);
  read(fds[0], buf, sizeof(buf));
  t = time(NULL) - t;
  printf("receive data: %s, interval time: %ds\n", buf, t);

  waitpid(pid, NULL, 0);

  return 0;
}
/*
 * receive data: hello, interval time: 10s
 */

2、O_NONBLOCK enable:read 调用返回 -1,errno 值为 EAGAIN(11)

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

int main(void)
{
  int    fds[2], flags;
  pid_t  pid;
  char   buf[10] = {0};

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }

  if (0 == pid)
  {
    close(fds[0]);
    sleep(10);
    write(fds[1], "hello", 5);
    exit(EXIT_SUCCESS);
  }

  close(fds[1]);
  flags = fcntl(fds[0], F_GETFL);
  fcntl(fds[0], F_SETFL, flags | O_NONBLOCK);
  if (-1 == read(fds[0], buf, sizeof(buf)))
  {
    perror("read error");
    printf("errno: %d\n", errno);
    waitpid(pid, NULL, 0);
    exit(EXIT_FAILURE);
  }
  printf("receive data: %s\n", buf);

  waitpid(pid, NULL, 0);

  return 0;
}
/*
 * read error: Resource temporarily unavailable
 * errno: 11
 */

3、O_NONBLOCK disable:write 调用阻塞

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

int main(void)
{
  int  fds[2];
  char buf[4096];
  int  count = 0;

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  memset(buf, 'A', sizeof(buf));
  while(1)
  {
    count += write(fds[1], buf, sizeof(buf));
    printf("writed data length: %d\n", count);
  }

  return 0;
}
/*
  writed data length: 4096
  writed data length: 8192
  writed data length: 12288
  writed data length: 16384
  writed data length: 20480
  writed data length: 24576
  writed data length: 28672
  writed data length: 32768
  writed data length: 36864
  writed data length: 40960
  writed data length: 45056
  writed data length: 49152
  writed data length: 53248
  writed data length: 57344
  writed data length: 61440
  writed data length: 65536
  阻塞卡住
 */

4、O_NONBLOCK enable: write 调用返回 -1,errno 值为 EAGAIN

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main(void)
{
  int  fds[2], flags;
  char buf[4096];
  int  ret, count = 0;

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  flags = fcntl(fds[1], F_GETFL);
  fcntl(fds[1], F_SETFL, flags | O_NONBLOCK);

  memset(buf, 'A', sizeof(buf));
  while(1)
  {
    ret = write(fds[1], buf, sizeof(buf));
    if (-1 == ret)
    {
      perror("write error");
      printf("errno: %d\n", errno);
      exit(EXIT_FAILURE);
    }

    count += ret;
    printf("writed data length: %d\n", count);
  }

  return 0;
}
/*
  writed data length: 4096
  writed data length: 8192
  writed data length: 12288
  writed data length: 16384
  writed data length: 20480
  writed data length: 24576
  writed data length: 28672
  writed data length: 32768
  writed data length: 36864
  writed data length: 40960
  writed data length: 45056
  writed data length: 49152
  writed data length: 53248
  writed data length: 57344
  writed data length: 61440
  writed data length: 65536
  write error: Resource temporarily unavailable
  errno: 11
 */

5、所有管道写端对应的文件描述符被关闭,read 调用返回 0

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

int main(void)
{
  int    fds[2];
  char   buf[10] = {0};
  int    ret;

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  close(fds[1]);  //此处用单进程验证

  ret = read(fds[0], buf, sizeof(buf));
  printf("receive data len: %d\n", ret);

  return 0;
}
/*
 * receive data len: 0
  */

6、所有管道读端对应的文件描述符被关闭,write 调用会产生信号 SIGPIPE(13,kill -l)

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

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

int main(void)
{
  int    fds[2];
  char   buf[10] = {0};
  int    ret;

  if(SIG_ERR == signal(SIGPIPE, sighandler))
  {
    perror("signal error");
    exit(EXIT_FAILURE);
  }

  if(0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  close(fds[0]);

  ret = write(fds[1], "hello", 5);
  printf("write ret val: %d\n", ret);

  return 0;
}
/*
 * catch a signal, signum: 13
 * write ret val: -1
 */

7、当要写入的数据量 n > PIPE_BUF 时,linux 将不再保证写入的原子性
O_NONBLOCK disable:write 直到将所有数据写入管道后返回,期间可能有其他进程穿插写入

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

#define PIPE_SIZE  4096
#if 0
// error
#define WRITE_SIZE PIPE_SIZE + 1
#else
// ok
#define WRITE_SIZE PIPE_SIZE
#endif
#define READ_SIZE  PIPE_SIZE + 1

int main(void)
{
  int   fds[2], ofd;
  pid_t pid;
  char  obuf[WRITE_SIZE], c;
  int   len;
  char  ibuf[READ_SIZE] = {0};

  if (0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }
  else if (0 == pid)
  {
    close(fds[0]);
    for (c = 'A'; c <= 'M'; c++)
    {
      memset(obuf, c, WRITE_SIZE);
      len = write(fds[1], obuf, WRITE_SIZE);
      printf("child1 write %d bytes to pipe\n", len);
    }
    exit(0);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }
  else if (0 == pid)
  {
    close(fds[0]);
    for (c = 'N'; c <= 'Z'; c++)
    {
      memset(obuf, c, WRITE_SIZE);
      len = write(fds[1], obuf, WRITE_SIZE);
      printf("child2 write %d bytes to pipe\n", len);
    }
    exit(0);
  }

  close(fds[1]);
  ofd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
  ibuf[4096] = '\n';

  while (1)
  {
    sleep(1);

    if (0 == (len = read(fds[0], ibuf, PIPE_SIZE)))
      break;

    printf("father read %d bytes from pipe\n", len);

    if (PIPE_SIZE == len)
      len = READ_SIZE;

    if (len != write(ofd, ibuf, len))
    {
      printf("write error, quit\n");
      exit(EXIT_FAILURE);
    }

    printf("father write %d bytes to file 'test.txt'\n", len);
  }

  return 0;
}

异常结果:
在这里插入图片描述
8、读数据不会因为读取数据量 n 是否大于 PIPE_BUF 失去原子性

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

#define PIPE_SIZE  4096
#define WRITE_SIZE PIPE_SIZE
#if 0
#define READ_SIZE  PIPE_SIZE
#else
#define READ_SIZE  PIPE_SIZE + 1
#endif

int main(void)
{
  int   fds[2], ofd;
  char  obuf[WRITE_SIZE], c;
  int   len = 0;
  pid_t pid;
  char  ibuf[READ_SIZE + 1] = {0};

  if (0 != pipe(fds))
  {
    perror("pipe error");
    exit(EXIT_FAILURE);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }
  else if (0 == pid)
  {
    close(fds[1]);

    ofd = open("child1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    ibuf[READ_SIZE] = '\n';

    while (1)
    {
      if (0 == (len = read(fds[0], ibuf, READ_SIZE)))
        break;

      printf("child1 read %d bytes from pipe\n", len);

      if (READ_SIZE == len)
        len += 1;

      if (len != write(ofd, ibuf, len))
      {
        printf("write error, quit\n");
        exit(EXIT_FAILURE);
      }

      printf("child1 write %d bytes to file 'child1.txt'\n", len);
    }
    exit(0);
  }

  pid = fork();
  if (-1 == pid)
  {
    perror("fork error");
    exit(EXIT_FAILURE);
  }
  else if (0 == pid)
  {
    close(fds[1]);

    ofd = open("child2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    ibuf[READ_SIZE] = '\n';

    while (1)
    {
      if (0 == (len = read(fds[0], ibuf, READ_SIZE)))
        break;

      printf("child2 read %d bytes from pipe\n", len);

      if (READ_SIZE == len)
        len += 1;

      if (len != write(ofd, ibuf, len))
      {
        printf("write error, quit\n");
        exit(EXIT_FAILURE);
      }

      printf("child2 write %d bytes to file 'child2.txt'\n", len);
    }
    exit(0);
  }

  for (c = 'A'; c <= 'Z'; c++)
  {
    memset(obuf, c, WRITE_SIZE);
    len += write(fds[1], obuf, WRITE_SIZE);
    printf("father write %d bytes to pipe\n", len);
  }

  close(fds[1]);

  waitpid(-1, NULL, 0);
  waitpid(-1, NULL, 0);

  return 0;
}
  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码存在以下问题: 1. 在生产者函数中,每次入数据时,入的是一个字符串,但缓冲区只有4个字节大小,会导致溢出。 2. 在消费者函数中,每次取数据时,取的是4个字节大小的缓冲区,但实际上入的数据可能小于4个字节,导致取到的数据不正确。 3. 在主函数中,等待子进程退出的循环条件不正确,应该循环4次而不是循环3次。 4. 在主函数中,需要关闭管道端和端,否则会有某个进程永远等待。 修改后的代码如下: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> char r_buf[1024]; // 缓冲 char w_buf[1024]; // 缓冲 int pipe_fd[2]; pid_t pid1, pid2, pid3, pid4; int producer(int id); int consumer(int id); int main(int argc, char **argv) { if (pipe(pipe_fd) < 0) { printf("pipe create error\n"); exit(-1); } else { printf("pipe is created successfully!\n"); if ((pid1 = fork()) == 0) producer(1); if ((pid2 = fork()) == 0) producer(2); if ((pid3 = fork()) == 0) consumer(1); if ((pid4 = fork()) == 0) consumer(2); } close(pipe_fd[0]); close(pipe_fd[1]); int i, pid, status; for (i = 0; i < 4; i++) pid = wait(&status); exit(0); } int producer(int id) { printf("producer %d is running!\n", id); close(pipe_fd[0]); int i = 0; for (i = 1; i < 10; i++) { sleep(3); if (id == 1) // 生产者1 strcpy(w_buf, "aaa"); else // 生产者2 strcpy(w_buf, "bbb"); if (write(pipe_fd[1], w_buf, strlen(w_buf) + 1) == -1) printf("write to pipe error\n"); } close(pipe_fd[1]); printf("producer %d is over!\n", id); exit(id); } int consumer(int id) { close(pipe_fd[1]); printf("consumer %d is running!\n", id); if (id == 1) // 消费者1 strcpy(w_buf, "ccc"); else // 消费者2 strcpy(w_buf, "ddd"); while (1) { sleep(1); memset(r_buf, 0, sizeof(r_buf)); int n = read(pipe_fd[0], r_buf, sizeof(r_buf)); if (n <= 0) break; printf("consumer %d get %s, while the w_buf is %s\n", id, r_buf, w_buf); } close(pipe_fd[0]); printf("consumer %d is over!\n", id); exit(id); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值