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

时间:2022-12-17 23:36:36

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

#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时,分配时间片。此时读进程是唯一处于就绪态的进程,并且时间片已经用完了。重新分配读进程和写进程时间片,并且切换到读进程继续执行。以后的流程大体一致。