文章目录
- 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,不是读失败了,而是表示读取到了文件的末尾 - 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;
可以看到穿插现象 - Makefile
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=01pipe
all:$(BIN)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o $(BIN)