0
点赞
收藏
分享

微信扫一扫

(P24)管道:管道的读写规则


文章目录

  • ​​1.管道大的读写规则​​

1.管道大的读写规则

  • 当没有数据可读时
    (1)O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来为止
    (2)O_NONBLOCK enable:read调用返回01,errno值为EAGAIN
  • 管道写规则
    (1)当管道满的时候,O_NONBLOCK enable:write 调用非阻塞,fd为非阻塞模式,则返回错误,错误码是EAGAIN;
    (2)当管道满的时候,O_NONBLOCK disable:write 调用阻塞,fd为阻塞模式,write调用就会阻塞
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE
    SIGPIPE信号会将当前进程终止

-当要写入的数据量小于等于PIPE_BUF,Linux将保证写入的原子性
原子性:假设A进程和B进程都要向管道写入数据,A进程写入的数据量小于等于PIPE_BUF,则A进程写入的数据是连续的,中间并不会插入B进程写入的数据,man 7 pipe看PIPE_BUF;否则多个进程往管道写入数据,可能会出现数据穿插的问题,进程A的写入的数据就不是连续的了,可能会夹杂着B的数据

  • 当要写入的数据量大于PIPE_BUF,Linux将不再保证写入的原子性
  • PIPE_BUF=4K,在#include <linux/limits.h>
    管道的大小是65536,64K,在redhat 9上面是4K,所以管道的容量不一定等于PIPE_BUF
  • eg:P24pipe.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.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]);
ERR_EXIT(EXIT_SUCCESS);
}

close(pipefd[1]);
char buf[10] = {0};
read(pipefd[0], buf, 10);
close(pipefd[1]);
printf("buf = %s\n", buf);

return 0;
}

  • 测试:
    管道数据为空,读操作会阻塞
  • eg:P24pipe1.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.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]);
ERR_EXIT(EXIT_SUCCESS);
}

close(pipefd[1]);
char buf[10] = {0};
//将pipefd[0],设置为非阻塞模式
int flags = fcntl(pipefd[0], F_GETTFL);
fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
int ret = read(pipefd[0], buf, 10);
if (ret == -1)
ERR_EXIT("read error");

close(pipefd[1]);
printf("buf = %s\n", buf);

return 0;
}

  • 测试:
  • eg:P24pipe2.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.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)
{
//关闭子进程的写端
close(pipefd[1]);
ERR_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;
}

  • 测试:
    所有的写端(父,子进程)文件描述符都关闭了。任何(父进程,子进程)的读操作都是0,不是读失败了,而是表示读取到了文件的末尾
  • (P24)管道:管道的读写规则_#include

  • eg:P24pipe3.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.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 a 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]);
ERR_EXIT(EXIT_SUCCESS);
}

close(pipefd[0]);
sleep(1);
char buf[10] = {0};
int ret = write(pipefd[1], "hello", 10);
if (ret == -1)
printf("write error\n");

return 0;
}

  • 测试:
  • eg:P24pipe4.c:阻塞模式
    验证管道的内存缓冲区的大小

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.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;
while (1)
{
//管道模式是阻塞的
ret = write(pipefd[1], "A", 1);
if (ret == -1)
break;
count++;
}
return 0;
}

  • 测试:
    当管道满的时候,且fd是阻塞模式,ret操作就会阻塞
  • eg:P24pipe5.c:非阻塞模式
    验证管道的内存缓冲区的大小

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.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_NOBLOCK);
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;
}

  • 测试
    将其改成非阻塞模式,管道的容量是64K(man 7 pipe中的Pipe Capacity也可以看到),
  • eg:P24pipe6.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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


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

//PIPE_BUF是4KB,用大于>4K 就行了,但是不容易测出穿插情况
//所以用了68K
//68KB
#define TEST_SIZE 68*1024

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");

pid_t pid;
pid = fork();
//子进程a
if (pid == 0)
{
close(pipefd[0]);
ret = write(pipefd[1], a, sizeof(a));//往管道写入68K的数据
printf("apid = %d write %d bytes to pipe\n", getpid(), ret);
exit(0);
}
pid = fork();
//子进程b
if (pid == 0)
{
close(pipefd[0]);
ret = write(pipefd[1], b, sizeof(a));//往管道写入68K的数据
printf("bpid = %d write %d bytes to pipe\n", getpid(), ret);
exit(0);
}

//初始的父进程,接收进程a和进程b的数据
close(pipefd[1]);
sleep(1);
int fd = open("test.txt", O_WRPNLY | O_CREAT| O_TRUNC, 0644);
char buf[1024*4] = {0};
int n = 1;
while(1)
{
ret = read(pipefd[0], buf, sizeof(buf));
//当连个子进程写端没数据了,就会返回=0
if (ret == 0)
break;
//打印输出最后一个字符:buf[4095]
printf("n=%2d pid =%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[4095]);
write(fd, buf, ret);
}

return 0;
}

  • 测试:
    子进程a和子进程b都写入了68K数据;
    子进程写完68K数据才返回;
    子进程写完一部分数据,父进程就已经开始读了
    读取了16次,就是64K;
    可以看到穿插现象
  • (P24)管道:管道的读写规则_#include_02


  • (P24)管道:管道的读写规则_#include_03

  • Makefile

.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=01pipe
all:$(BIN)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o $(BIN)


举报

相关推荐

0 条评论