linux0.11源码分析-管道

265 阅读6分钟

sys_pipe

管道用于进程之间的通信,一个用读一个写,一般是父进程fork一个子进程,因为管道是父进程创建的,子进程也会共享父进程的文件表。

sys_pipe用于创建一个匿名管道,所谓的管道就是内核的一个缓冲区,为什么我们可以像操作文件一样使用open和read来操作管道?其实进程中保存了2个文件描述符,一个只能读一个只能写,通过文件描述符可以找到file,file中就有这个管道的地址。

fs/pipe.c

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

    // 首先从系统文件表中取两个空闲项(引用计数字段为0的项),并分别设置引用计数为1。
    // 若只有1个空闲项,则释放该项(引用计数复位).若没有找到两个空闲项,则返回-1.
	j=0;
	for(i=0;j<2 && i<NR_FILE;i++)
		if (!file_table[i].f_count)
			(f[j++]=i+file_table)->f_count++;
	if (j==1)
		f[0]->f_count=0;
	if (j<2)
		return -1;
        
        
    // 针对上面取得的两个文件表结构项,分别分配一文件句柄号,并使用进程文件结构指针
    // 数组的两项分别指向这两个文件结构。而文件句柄即是该数组的索引号。类似的,如果
    // 只有一个空闲文件句柄,则释放该句柄(置空相应数组项)。如果没有找到两个空闲句柄,
    // 则释放上面获取的两个文件结构项(复位引用计数值),并返回-1.
	j=0;
	for(i=0;j<2 && i<NR_OPEN;i++)
		if (!current->filp[i]) {
			current->filp[ fd[j]=i ] = f[j];
			j++;
		}
	if (j==1)
		current->filp[fd[0]]=NULL;
	if (j<2) {
		f[0]->f_count=f[1]->f_count=0;
		return -1;
	}
    
    // 然后利用函数get_pipe_inode()申请一个管道使用的i节点,并为管道分配一页内存作为
    // 缓冲区。如果不成功,则相应释放两个文件句柄和文件结构项,并返回-1.
	if (!(inode=get_pipe_inode())) {
		current->filp[fd[0]] =
			current->filp[fd[1]] = NULL;
		f[0]->f_count = f[1]->f_count = 0;
		return -1;
	}
    
    // 如果管道i节点申请成功,则对两个文件结构进行初始化操作,让他们都指向同一个管道
    // i节点,并把读写指针都置零。第1个文件结构的文件模式置为读,第2个文件结构的文件
    // 模式置为写。最后将文件句柄数组复制到对应的用户空间数组中,成功返回0,退出。
	f[0]->f_inode = f[1]->f_inode = inode;
	f[0]->f_pos = f[1]->f_pos = 0;
	f[0]->f_mode = 1;		/* read */
	f[1]->f_mode = 2;		/* write */
	put_fs_long(fd[0],0+fildes);
	put_fs_long(fd[1],1+fildes);
	return 0;
}

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))
#define PIPE_EMPTY(inode) (PIPE_HEAD(inode)==PIPE_TAIL(inode))
#define PIPE_FULL(inode) (PIPE_SIZE(inode)==(PAGE_SIZE-1))

get_pipe_inode

fs/inode.c

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

    // 首先从内存i节点表中取得一个空闲i节点。如果找不到空闲i节点则返回NULL。然后为
    // 该i节点申请一页内存,并让节点的i_size字段指向该页面。如果已没有空闲内存,
    // 则释放该i节点,并返回NULL
	if (!(inode = get_empty_inode()))
		return NULL;
	if (!(inode->i_size=get_free_page())) {
		inode->i_count = 0;
		return NULL;
	}
    // 然后设置该i节点的引用计数为2,并复位管道头尾指针。i节点逻辑块号数组i_zone[]
    // 的i_zone[0]和i_zone[1]中分别用来存放管道头和管道尾指针。最后设置i节点是管
    // 道i节点标志并返回该i节点号。
	inode->i_count = 2;	/* sum of readers/writers */
	PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;
	inode->i_pipe = 1;
	return inode;
}

write_pipe

把用户空间的数据写进管道中(缓冲区)

// 参数inode是管道对应的i节点,buf是数据缓冲区指针,count是将写入管道的字节数。
int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;
	while (count>0) {
    
    		// 计算现在管道的空闲空间,如果size = 0表示管道满了,则唤醒等待该节点的进程
                //通常唤醒的是读管道进程。如果已没有读管道者,即i节点引用计数值小于2,则
                //向当前进程发送SIGPIPE信号,否则管道满了,写进程进入睡眠
		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);
		}
        
        // 程序执行到这里表示管道缓冲区中有可写空间size.于是我们管道头指针到缓冲区
        // 末端空间字节数chars。写管道操作是从管道头指针处开始写的。如果chars大于还
        // 需要写入的字节数count,则令其等于count。如果chars大于当前管道中空闲空间
        // 长度size,则令其等于size,然后把需要写入字节数count减去此次可写入的字节数
        // chars,并把写入字节数累驾到witten中。
        
		chars = PAGE_SIZE-PIPE_HEAD(*inode); //表示可以向管道中写进去多少字节
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;
		count -= chars;
		written += chars;
        
        // 再令size指向管道数据头指针处,并调整当前管道数据头部指针(前移chars字节)。
        // 若头指针超过管道末端则绕回。然后从用户缓冲区复制chars个字节到管道头指针
        // 开始处。对于管道i节点,其i_size字段中是管道缓冲块指针。
		size = PIPE_HEAD(*inode); //头部指针
		PIPE_HEAD(*inode) += chars;
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1); // 若头指针超过管道末端则绕回
		while (chars-->0)
			((char *)inode->i_size)[size++]=get_fs_byte(buf++); //把用户空间中的buf复制到缓冲区
	}
    // 当此次写管道操作结束,则唤醒等待管道的进程,返回已写入的字节数,退出。
	wake_up(&inode->i_wait);
	return written;
}

read_pipe

参数inode是管道对应的i节点,buf是用户数据缓冲区指针,count是读取的字节数

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

    // 如果需要读取的字节计数count大于0,我们就循环执行以下操作。在循环读操作
    // 过程中,若当前管道中没有数据(size=0),则唤醒等待该节点的进程,这通常
    // 是写管道进程。如果已没有写管道者,即i节点引用计数值小于2,则返回已读字
    // 节数退出。否则在该i节点上睡眠,等待信息。宏PIPE_SIZE定义在fs.h中。
	while (count>0) {
		while (!(size=PIPE_SIZE(*inode))) {
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) /* are there any writers? */
				return read;
			sleep_on(&inode->i_wait);
		}
        // 此时说明管道(缓冲区)中有数据。于是我们取管道尾指针到缓冲区末端的字
        // 节数chars。如果其大于还需要读取的字节数count,则令其等于count。如果
        // chars大于当前管道中含有数据的长度size,则令其等于size。然后把需读字
        // 节数count减去此次可读的字节数chars,并累加已读字节数read.
		chars = PAGE_SIZE-PIPE_TAIL(*inode);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;
		count -= chars;
		read += chars;
        // 再令size指向管道尾指针处,并调整当前管道尾指针(前移chars字节)。若尾
        // 指针超过管道末端则绕回。然后将管道中的数据复制到用户缓冲区中。对于
        // 管道i节点,其i_size字段中是管道缓冲块指针。
		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;
}