Linux内核设计的艺术-进程间通信-管道

本文深入解析Linux管道机制的实现原理,包括管道的创建过程、读写操作的具体流程及内核中的关键数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     管道操作分为两部分,一部分是创建管道,另一部分是管道的读写操作。代码如下:

#include <stdio.h>
#include <unistd.h>
int main()
{
	int n,fd[2];
	pid_t pid;
	int i,j;
	char str1[]="ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE
	ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE";

	char str2[512];
	if(pipe(fd)<0)
	{
		printf("pipe error\n");
		return -1;
	}
	
	if((pid = fork())<0)
	{
		printf("fork error\n");
		return -1;
	}else if(pid > 0)
	{
		close(fd[0]);//父进程
		for(i=0;i<10000;i++)
			write(fd[1],str1,strlen(str1));
	}
	else
	{
		close(fd[1]);//子进程
		for(j=0;j<20000;j++)
			read(fd[0],str2,strlen(str2));
	}
}
      父进程把str1中的数据写入管道,子进程从管道中读出数据,其中str1中字符长度为1024字节,即1KB。

      pipe会映射到sys_pipe执行,代码路径:fs/pipe.c

int sys_pipe(unsigned long * fildes)
{
	struct m_inode * inode;
	struct file * f[2];
	int fd[2];
	int i,j;

	j=0;
	for(i=0;j<2 && i<NR_FILE;i++)//准备在file_table[64]中申请两个空闲项
		if (!file_table[i].f_count)//找到空闲项
			(f[j++]=i+file_table)->f_count++;//每项引用计数为1
	if (j==1)
		f[0]->f_count=0;
	if (j<2)
		return -1;
	j=0;
	for(i=0;j<2 && i<NR_OPEN;i++)//准备在*filp[20]中申请两个空闲项
		if (!current->filp[i]) {//找到空闲项
			current->filp[ fd[j]=i ] = f[j];//分别与file_table[64]中申请的两个空闲项挂接
			j++;
		}
	if (j==1)
		current->filp[fd[0]]=NULL;
	if (j<2) {
		f[0]->f_count=f[1]->f_count=0;
		return -1;
	}
	if (!(inode=get_pipe_inode())) {//创建管道文件i节点
		current->filp[fd[0]] =
			current->filp[fd[1]] = NULL;
		f[0]->f_count = f[1]->f_count = 0;
		return -1;
	}
	f[0]->f_inode = f[1]->f_inode = inode;//i节点和表项挂接
	f[0]->f_pos = f[1]->f_pos = 0;//文件指针归0
	f[0]->f_mode = 1;		/* read */ //设置为读模式
	f[1]->f_mode = 2;		/* write */ //设置为写模式
	put_fs_long(fd[0],0+fildes); //将读管道文件句柄返回到用户空间(用户空间变量fd[0])
	put_fs_long(fd[1],1+fildes); //将写管道文件句柄返回到用户空间(用户空间变量fd[1])
	return 0;
}
       创建管道文件i节点,代码如下:

       代码路径:fs/inode.c

struct m_inode * get_pipe_inode(void)
{
	struct m_inode * inode;

	if (!(inode = get_empty_inode()))//申请空闲inode节点
		return NULL;
	if (!(inode->i_size=get_free_page())) {//承载的不再是文件大小,而是内存页面的起始地址
		inode->i_count = 0;
		return NULL;
	}
	inode->i_count = 2;	/* sum of readers/writers */ //引用计数设置为2
	PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;//PIPE_HEAD为写管道指针,PIPE_TAIL为读管道指针,都设置为0
	inode->i_pipe = 1;//设置管道文件属性
	return inode;
}
       代码路径:include/linux/fs.h

#define PIPE_HEAD(inode) ((inode).i_zone[0])
#define PIPE_TAIL(inode) ((inode).i_zone[1])
#define PIPE_SIZE(inode) ((PIPE_HEAD(inode)-PIPE_TAIL(inode))&(PAGE_SIZE-1))

       Linux0.11管道操作要实现的效果是,读管道进程执行时,如果管道中有未读数据,就读取数据,没有未读数据,就挂起,这样就不会读取垃圾数据;写管道进程时,如果管道中有剩余空间,就写入数据,没有剩余空间了,就挂起,这样就不会覆盖尚未读取的数据。另外,管道大小只有一个页面,所以写或读到页面尾端后,读写指针要能够回滚到页面首端以便继续操作。

    

      父进程创建完管道后,开始创建子进程,即读管道进程。创建完毕后,我们不妨假设此时系统中只有读管道和写管道两个进程处于就绪态,而且读管道进程先执行。read函数会映射到系统调用函数sys_read中执行,并最终执行到read_pipe函数中。

       代码路径:fs/read_write.c

int sys_read(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;

	if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
		return -EINVAL;
	...
	inode = file->f_inode;
	if (inode->i_pipe)
		return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;
	...
}
      代码路径:fs/pipe.c

int read_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, read = 0;

	while (count>0) {
		while (!(size=PIPE_SIZE(*inode))) {//此时size为0,进入里面执行
			wake_up(&inode->i_wait);//唤醒写进程
			if (inode->i_count != 2) /* are there any writers? */
				return read;
			sleep_on(&inode->i_wait);//将读进程挂起,切换到写进程
		}
		chars = PAGE_SIZE-PIPE_TAIL(*inode);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;
		count -= chars;
		read += chars;
		size = PIPE_TAIL(*inode);
		PIPE_TAIL(*inode) += chars;
		PIPE_TAIL(*inode) &= (PAGE_SIZE-1);
		while (chars-->0)
			put_fs_byte(((char *)inode->i_size)[size++],buf++);
	}
	wake_up(&inode->i_wait);
	return read;
}
       由于此时管道中没有任何数据,所以wake_up唤醒了写进程(state为就绪态),sleep_on挂起读进程(state为不可中断等待状态),然后切换到写进程执行。


       写管道进程write最后映射到sys_write中,最终执行了write_pipe函数,如下:

       代码路径:fs/read_write.c

int sys_write(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;
	
	if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))
		return -EINVAL;
	...
	inode=file->f_inode;
	if (inode->i_pipe)
		return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;
	...
}
      代码路径:fs/pipe.c

for(i=0;i<10000;i++)
	write(fd[1],str1,strlen(str1));
      第一次循环,执行的流程如下:

int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

	while (count>0) {//1024
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//size为0xFFF,4095
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);
		}
		chars = PAGE_SIZE-PIPE_HEAD(*inode);//chars为4096,0x1000
		if (chars > count)//4096>1024
			chars = count;//chars为1024
		if (chars > size)//1024<4095
			chars = size;
		count -= chars;//count为0
		written += chars;//written为1024
		size = PIPE_HEAD(*inode);//size为0
		PIPE_HEAD(*inode) += chars;//head为1024
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);//head为1024
		while (chars-->0)//从0位置写入1024个字节
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
	wake_up(&inode->i_wait);
	return written;
}
       第二次循环,执行的流程如下:

int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

	while (count>0) {//1024
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//size为4095-1024=3071
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);
		}
		chars = PAGE_SIZE-PIPE_HEAD(*inode);//char为4096-1024=3072
		if (chars > count)//3072>1024
			chars = count;//chars为1024
		if (chars > size)//1024<3071
			chars = size;
		count -= chars;//count为0
		written += chars;//written为1024
		size = PIPE_HEAD(*inode);//size为1024
		PIPE_HEAD(*inode) += chars;//head为2048
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);//head为2048
		while (chars-->0)//从1024地址往后写入1024个字节
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
	wake_up(&inode->i_wait);
	return written;
}
         第三次循环,如下:

int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

	while (count>0) {//count为1024
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//size为4095-2048=2047
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);
		}
		chars = PAGE_SIZE-PIPE_HEAD(*inode);//chars为4096-2048=2048
		if (chars > count)//2048>1024
			chars = count;//chars为1024
		if (chars > size)//1024<2047
			chars = size;
		count -= chars;//count为0
		written += chars;//written为1024
		size = PIPE_HEAD(*inode);//size为2048
		PIPE_HEAD(*inode) += chars;//head为2048+1024=3072
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);//head为3072
		while (chars-->0)//从2048地址写入1024个字节
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
	wake_up(&inode->i_wait);
	return written;
}
         第四次循环,如下:

int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

	while (count>0) {//count为1024
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//size为4095-3072=1023
			wake_up(&inode->i_wait);//唤醒读进程
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);//写进程挂起,切换到读进程
		}
		chars = PAGE_SIZE-PIPE_HEAD(*inode);//chars为4096-3072=1024
		if (chars > count)//1024不大于1024
			chars = count;
		if (chars > size)//1024>1023
			chars = size;//chars为1023
		count -= chars;//count为1
		written += chars;//written为1023
		size = PIPE_HEAD(*inode);//size为3072
		PIPE_HEAD(*inode) += chars;//head为3072+1023=4095
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);//head为4095
		while (chars-->0)//从3072地址开始写入1023个字节,只剩下最后一个字节没有写入了
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
	wake_up(&inode->i_wait);
	return written;
}
       继续执行本while循环,size为4095-4095为0,一次最多能写入4095个字节,但不代表最后一个字节不能写入,所以wake_up唤醒了读进程(state为就绪态),sleep_on挂起写进程(state为不可中断等待状态),然后切换到读进程执行。继续从刚才切换进程的位置开始执行。
      代码路径:fs/pipe.c
int read_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, read = 0;

	while (count>0) {//count为512
		while (!(size=PIPE_SIZE(*inode))) {//此时size为0,进入里面执行
			wake_up(&inode->i_wait);//唤醒写进程
			if (inode->i_count != 2) /* are there any writers? */
				return read;
			sleep_on(&inode->i_wait);//将读进程挂起,切换到写进程
		}
		chars = PAGE_SIZE-PIPE_TAIL(*inode);//从这里开始执行,chars为4096-0=4096
		if (chars > count)//4096>512
			chars = count;//chars为512
		if (chars > size)//512<4096
			chars = size;
		count -= chars;//count为0
		read += chars;//read为512
		size = PIPE_TAIL(*inode);//size为0
		PIPE_TAIL(*inode) += chars;//tail为512
		PIPE_TAIL(*inode) &= (PAGE_SIZE-1);//tail为512
		while (chars-->0)//从0开始读入512个字节到buf
			put_fs_byte(((char *)inode->i_size)[size++],buf++);
	}
	wake_up(&inode->i_wait);
	return read;
}
     
for(j=0;j<20000;j++)
     read(fd[0],str2,strlen(str2));
       第二次循环,如下:

int read_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, read = 0;

	while (count>0) {//count为512
		while (!(size=PIPE_SIZE(*inode))) {//size为4095-512=3585,剩余能够读的空间
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) /* are there any writers? */
				return read;
			sleep_on(&inode->i_wait);
		}
		chars = PAGE_SIZE-PIPE_TAIL(*inode);//4096-512=3586
		if (chars > count)//3586>512
			chars = count;//chars为512
		if (chars > size)//512<3585
			chars = size;
		count -= chars;//count为0
		read += chars;//read为512
		size = PIPE_TAIL(*inode);//size为512
		PIPE_TAIL(*inode) += chars;//tail为512+512=1024
		PIPE_TAIL(*inode) &= (PAGE_SIZE-1);//tail为1024
		while (chars-->0)//从512位置开始读入512个字节
			put_fs_byte(((char *)inode->i_size)[size++],buf++);
	}
	wake_up(&inode->i_wait);
	return read;
}
      继续循环,直到读进程时间片用完,切换到写进程,写进程执行如下:

int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

	while (count>0) {//count为1
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//size为0
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);//刚才执行到这里
		}
		chars = PAGE_SIZE-PIPE_HEAD(*inode);//从这里开始执行,chars为4096-4095=1
		if (chars > count)//1不大于1
			chars = count;
		if (chars > size)//1<0
			chars = size;
		count -= chars;//count为0
		written += chars;//written为1
		size = PIPE_HEAD(*inode);//size为4095
		PIPE_HEAD(*inode) += chars;//head为4096,0x1000
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);//head为0x000
		while (chars-->0)//从4095位置开始写入一个字节
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
	wake_up(&inode->i_wait);
	return written;
}
       写进程继续执行循环,如下:

int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

	while (count>0) {//count为1024
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//应该为剩余能够写的空间
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);
		}
		chars = PAGE_SIZE-PIPE_HEAD(*inode);//chars为4096
		if (chars > count)//4096>1024
			chars = count;//chars为1024
		if (chars > size)//4096<剩余的空间
			chars = size;
		count -= chars;//count为0
		written += chars;//written为1024
		size = PIPE_HEAD(*inode);//size为0
		PIPE_HEAD(*inode) += chars;//head为1024
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);//head为1024
		while (chars-->0)//从0位置开始写入1024个字节
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
	wake_up(&inode->i_wait);
	return written;
}

       以后循环,以此类推,直到再次没有剩余空间为止,写进程挂起(state为不可中断状态),唤醒读进程(state为就绪态),然后进程调度,Linux重新分配时间片的规则是当所有处于就绪态的进程时间片均为0时,分配时间片。此时读进程是唯一处于就绪态的进程,并且时间片已经用完了。重新分配读进程和写进程时间片,并且切换到读进程继续执行。以后的流程大体一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值