管道(二)——管道读写规则、管道容量Pipe Capacity、原子性PIPE_BUF——linux系统编程


本人的《APUE》《UNPv12》到啦!希望博文质量能有所提高!

4种情况下的管道读写规则

我们用代码简单试验在一般情况下(O_NONBLOCK disable,默认阻塞)的4种情况:
1.当没有数据可读
2.当管道满的时候
3.所有管道读端对应的文件描述符被关闭
4.写端对应的文件描述符被关闭
但其实存在两种情况:
1.一般情况下(O_NONBLOCK disable,默认阻塞)
2.非阻塞情况下(设置O_NONBLOCK enable)

另一种情况代码可以见前辈大佬的博文:
linux系统编程之管道(二):管道读写规则
linux系统编程之管道(二):管道读写规则和Pipe Capacity、PIPE_BUF

特别指出pipe和FIFO设定O_NONBLOCK 的方法:

  • 对于pipe,由于其没有open调用且在pipe调用中也无法指定O_NONBLOCK标志,故必须使用fcntl:fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK); //enable fd的O_NONBLOCK
    其中或等于的目的是避免在设置所需标志的同时清除了其他可能存在的文件标志
  • 对于FIFO(在下一篇)可以在open时指定:writefd=open(FIFO1,O_WRONLY|O_NONBLOCK);

当没有数据可读(若父进程不写而子进程读)

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

int main(int argc, char *argv[])
{
	int pipe_fd[2];
	int r_size = 0;
	
	pid_t child_pid;
	if (pipe(pipe_fd) == -1)
	{
		perror("pipe error:");
		return -1;
	}

	
	//那么这里pipe就会打开2个文件描述符pipe_fd[0]表示读端
	child_pid = fork();
	if (child_pid == 0)//读
	{
		//子进程也继承了2个文件描述符
		close(pipe_fd[1]);//子进程读端我们会关闭写端
		char recv_buffer[50];
		memset(recv_buffer, 0, sizeof(recv_buffer));
		cout << "child ready read..." << endl;
		read(pipe_fd[0], recv_buffer, sizeof(recv_buffer));//希望读到buffer的所有字节
		cout << "child recv buffer=" << recv_buffer << endl;
		
	
		while (1)
		{
			sleep(1);//不让子进程结束
		}
	}
	else if (child_pid > 0)//写
	{		
		
		close(pipe_fd[0]);//父进程写端我们会关闭读端
		char buffer[50] = { 0 };
		strcpy(buffer, "hello rabbit!");
		//如果发送的是字符串那么可以使用strlen函数
		//r_size = write(pipe_fd[1], buffer, strlen(buffer));
		
		while (1)
		{
			sleep(1);//不让父进程结束
		}
	}
	return 0;
}

结果如下:
在这里插入图片描述
也就是read端没有往下走,也就是说:
若写端没有往管道中写数据(管道为空)那么读端将堵塞,直到管道有数据写入,read才会解堵塞(解堵塞可以通过sleep一段时间之后write来得到验证)

当管道满(若父进程写而子进程不读)

O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

我们猜想过去一定也会堵塞,所以我们需要测试究竟写多少才会堵塞,也就是测试一下管道容量,所以我们将write放到死循环里面,一个一个字节不断往里面写:

int main(int argc, char *argv[])
{
	int pipe_fd[2];
	int r_size = 0;
	
	int write_sum = 0;
	
	
	pid_t child_pid;
	if (pipe(pipe_fd) == -1)
	{
		perror("pipe error:");
		return -1;
	}

	
	//那么这里pipe就会打开2个文件描述符pipe_fd[0]表示读端
	child_pid = fork();
	if (child_pid == 0)//读
	{
		//子进程也继承了2个文件描述符
		close(pipe_fd[1]);//子进程读端我们会关闭写端
		char recv_buffer[50];
		memset(recv_buffer, 0, sizeof(recv_buffer));
		//cout << "child ready read..." << endl;
		//read(pipe_fd[0], recv_buffer, sizeof(recv_buffer));//希望读到buffer的所有字节
		//cout << "child recv buffer=" << recv_buffer << endl;
		
	
		while (1)
		{
			sleep(1);//不让子进程结束
		}
	}
	else if (child_pid > 0)//写
	{		
		
		close(pipe_fd[0]);//父进程写端我们会关闭读端
		char buffer[50] = { 0 };
		strcpy(buffer, "hello rabbit!");
		//如果发送的是字符串那么可以使用strlen函数
		
		while (1)
		{
			r_size = write(pipe_fd[1], buffer, 1);
			write_sum += r_size;//write_sum记录一共写了多少数据
			cout << "write_sum" << write_sum << endl;
		}
	}
	return 0;
}

打印结果如下
在这里插入图片描述

管道容量Pipe Capacity

每次写入一个字节,在65536停止,说明这种情况下,写端将一直写数据,知道管道满为止,实验可知写入65536字节=64kb时候管道满,停止写入
说明管道容量是64KB
我们也可以在man 7 pipe中得到验证:

   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.  Since Linux 2.6.35, the default pipe
   capacity is 65536 bytes, but the capacity can be queried and set  using
   the  fcntl(2)  F_GETPIPE_SZ  and F_SETPIPE_SZ operations.  See fcntl(2)
   for more information.

若(父进程)写端突然关闭

如果所有管道写端对应的文件描述符被关闭(管道写端的引用计数等于0),那么管道中剩余的数据都被读取后,再次read会返回0。

我们让写端写10个字节,然后突然关闭,观察结果:

int main(int argc, char *argv[])
{
	int pipe_fd[2];
	int r_size = 0;
	int w_size = 0;
	int write_sum = 0;
	
	
	pid_t child_pid;
	if (pipe(pipe_fd) == -1)
	{
		perror("pipe error:");
		return -1;
	}

	//那么这里pipe就会打开2个文件描述符pipe_fd[0]表示读端
	child_pid = fork();
	if (child_pid == 0)//读
	{
		//子进程也继承了2个文件描述符
		close(pipe_fd[1]);//子进程读端我们会关闭写端
		char recv_buffer[50];
		memset(recv_buffer, 0, sizeof(recv_buffer));
		cout << "child ready read..." << endl;
		r_size=read(pipe_fd[0], recv_buffer, sizeof(recv_buffer));//希望读到buffer的所有字节
		cout << "r_size=" << r_size<<endl;
		cout << "child recv buffer=" << recv_buffer << endl;
		
		while (1)
		{
			sleep(1);
		}
	}
	else if (child_pid > 0)//写
	{		
		
		close(pipe_fd[0]);//父进程写端我们会关闭读端
		char buffer[50] = { 0 };
		strcpy(buffer, "hello rabbit!");
		//如果发送的是字符串那么可以使用strlen函数


		w_size = write(pipe_fd[1], buffer, 10);
		cout << "w_size =" << w_size << endl;

		exit(0);//写端关闭
	}
	return 0;
}

结果打印如下:
由于exit程序会退出调试
在这里插入图片描述
写端突然关闭在父进程为写端情况下,会打印出目前写入的所有数据后结束
看起来比较正常,不如到过来子进程写,突然结束,父进程读试试?
我们只需更换一下父子进程代码位置,然后测试结果:
在这里插入图片描述
这种情况也无事发生。因为代码的情况是写了一部分之后才关闭的。
而如果写端(对应文件描述符)从代码一开始就被关闭(注释),则read会返回0(文件结束符)

若(子进程)读端突然关闭

如果所有管道读端对应的文件描述符被关闭(管道读端的引用计数等于0),则write操作会产生SIGPIPE信号,默认终止当前进程

也就是子进程结束,或者子进程调用close(pipe_fd[0])
为了保证关闭的时候父进程还在往里面写,我们使得子进程在0.1秒之后关闭,并仍然把write放在死循环里面一个一个字节写入。打印r_size观察写入结果,为1确保是还在写的状态下子进程结束。

int main(int argc, char *argv[])
{
	int pipe_fd[2];
	int r_size = 0;
	
	int write_sum = 0;
	
	
	pid_t child_pid;
	if (pipe(pipe_fd) == -1)
	{
		perror("pipe error:");
		return -1;
	}

	//那么这里pipe就会打开2个文件描述符pipe_fd[0]表示读端
	child_pid = fork();
	if (child_pid == 0)//读
	{
		//子进程也继承了2个文件描述符
		close(pipe_fd[1]);//子进程读端我们会关闭写端
		char recv_buffer[50];
		memset(recv_buffer, 0, sizeof(recv_buffer));
		cout << "child ready read..." << endl;
		read(pipe_fd[0], recv_buffer, sizeof(recv_buffer));//希望读到buffer的所有字节
		cout << "child recv buffer=" << recv_buffer << endl;
		
	
		sleep(0.1);//0.1s后子进程结束
		exit(0);
	}
	else if (child_pid > 0)//写
	{		
		
		close(pipe_fd[0]);//父进程写端我们会关闭读端
		char buffer[50] = { 0 };
		strcpy(buffer, "hello rabbit!");
	
		while (1)
		{		
			r_size = write(pipe_fd[1], buffer, 1);
			cout << "r_size=" << r_size << endl;
			write_sum += r_size;//write_sum记录一共写了多少数据
			cout << "write_sum" << write_sum << endl;	
		}
	}
	return 0;
}

结果会收到信号:SIGPIPE对应13信号
在这里插入图片描述
也就是说,如果读端突然关闭,写端将收到系统返回的管道损坏信号,编号13,即给线程产生SIGPIPE

linux写入原子性问题

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性;当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
在man 7 pipe中,其详细列出了4种情况

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
当写入数据大于PIPE_BUF时,各子进程间出现穿插写入,并没保证原子性写入,且父进程在子进程编写时边读。

内容参考:
《APUE》《UNPv2》
linux系统编程之管道(二):管道读写规则
linux系统编程之管道(二):管道读写规则和Pipe Capacity、PIPE_BUF

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页