linux内核情景分析之匿名管道
管道的机制由pipe()创建,由pipe()所建立的管道两端都在同一进程.所以必须在fork的配合下,才可以在具有亲缘关系的进程通信
/*
* sys_pipe() is the normal C calling standard for creating
* a pipe. It's not the way Unix traditionally does this, though.
*/
asmlinkage int sys_pipe(unsigned long * fildes)
{
int fd[2];//fd表示,一个读,一个写
int error;
error = do_pipe(fd);
if (!error) {
if (copy_to_user(fildes, fd, 2*sizeof(int)))//从内核态拷贝到用户态
error = -EFAULT;
}
return error;
}
sys_pipe()主体是调用do_pipe()
int do_pipe(int *fd)
{
struct qstr this;
char name[32];//目录项名字
struct dentry *dentry;//目录结构
struct inode * inode;//inode对应一个管道,不过管道无实际存储,不在硬盘也不在文件系统
struct file *f1, *f2;//父子进程操作管道对应的文件对象
int error;
int i,j;
error = -ENFILE;
f1 = get_empty_filp();//获取一个file对象,因为管道在不同的进程,两端不可以共享一个file
if (!f1)
goto no_files;
f2 = get_empty_filp();//获取1个file对象,同上
if (!f2)
goto close_f1;
inode = get_pipe_inode();//获取一个inode用于表示管道这个无形文件,分配缓存,初始化
if (!inode)
goto close_f12;
error = get_unused_fd();//获取一个fd用于绑定file对象
if (error < 0)
goto close_f12_inode;
i = error;
error = get_unused_fd();//同上
if (error < 0)
goto close_f12_inode_i;
j = error;
error = -ENOMEM;
sprintf(name, "[%lu]", inode->i_ino);//操作
this.name = name;
this.len = strlen(name);
this.hash = inode->i_ino; /* will go */
dentry = d_alloc(pipe_mnt->mnt_sb->s_root, &this);//分配一个目录,因为file无直接指向inode.file只能通过指向目录的指针指向目录项中转找到inode
if (!dentry)
goto close_f12_inode_i_j;
dentry->d_op = &pipefs_dentry_operations;
d_add(dentry, inode);//将目录与inode挂钩
f1->f_vfsmnt = f2->f_vfsmnt = mntget(mntget(pipe_mnt));//挂载到vfs
f1->f_dentry = f2->f_dentry = dget(dentry);//设置file对象的指向目录指针
/*设置只读属性, read file */
f1->f_pos = f2->f_pos = 0;
f1->f_flags = O_RDONLY;//设置只读属性
f1->f_op = &read_pipe_fops;//设置f1的读管道指针操作
f1->f_mode = 1;
f1->f_version = 0;
/* 写文件相关对象,只写数学,操作为write_pipe_fopswrite file */
f2->f_flags = O_WRONLY;//设置只可写属性
f2->f_op = &write_pipe_fops;//设置写操作
f2->f_mode = 2;
f2->f_version = 0;
fd_install(i, f1);//fd与file对象绑定
fd_install(j, f2);//fd与file对象绑定
fd[0] = i;
fd[1] = j;
return 0;
close_f12_inode_i_j:
put_unused_fd(j);
close_f12_inode_i:
put_unused_fd(i);
close_f12_inode:
free_page((unsigned long) PIPE_BASE(*inode));
kfree(inode->i_pipe);
inode->i_pipe = NULL;
iput(inode);
close_f12:
put_filp(f2);
close_f1:
put_filp(f1);
no_files:
return error;
}
接下来查看下get_pipe_inode()表示inode对应的文件管道,我们对于inode先关心下第一个成分i_pipe指向一个pipe_inode_info,只有inode表示为一个管道才使用,否则一般文件将此位设置为NULL
struct pipe_inode_info {
wait_queue_head_t wait;//等待队列
char *base;//用于指向一页的缓冲区
unsigned int start;
unsigned int readers;//读的个数
unsigned int writers;//写的个数
unsigned int waiting_readers;//等待读的个数
unsigned int waiting_writers;//等待写的个数
unsigned int r_counter;//读的次数
unsigned int w_counter;//写的次数
};
另外还是用一些宏定义,用来设置i_pipe结构
#define PIPE_SEM(inode) (&(inode).i_sem)//inode信号量
#define PIPE_WAIT(inode) (&(inode).i_pipe->wait)//inode的i_pipe的管道等待队列
#define PIPE_BASE(inode) ((inode).i_pipe->base)//inode管道的base缓冲区指针
#define PIPE_START(inode) ((inode).i_pipe->start)//管道的起始地址
#define PIPE_LEN(inode) ((inode).i_size)//管道剩余数据
#define PIPE_READERS(inode) ((inode).i_pipe->readers)//管道读的对象个数
#define PIPE_WRITERS(inode) ((inode).i_pipe->writers)//管道写的对象个数
#define PIPE_WAITING_READERS(inode) ((inode).i_pipe->waiting_readers)//管道等待的对象个数
#define PIPE_WAITING_WRITERS(inode) ((inode).i_pipe->waiting_writers)
#define PIPE_RCOUNTER(inode) ((inode).i_pipe->r_counter)
#define PIPE_WCOUNTER(inode) ((inode).i_pipe->w_counter)
#define PIPE_EMPTY(inode) (PIPE_LEN(inode) == 0)//管道是否为空
#define PIPE_FULL(inode) (PIPE_LEN(inode) == PIPE_SIZE)//管道是否满了
#define PIPE_FREE(inode) (PIPE_SIZE - PIPE_LEN(inode))//管道剩余空间
#define PIPE_END(inode) ((PIPE_START(inode) + PIPE_LEN(inode)) & (PIPE_SIZE-1))
#define PIPE_MAX_RCHUNK(inode) (PIPE_SIZE - PIPE_START(inode))
#define PIPE_MAX_WCHUNK(inode) (PIPE_SIZE - PIPE_END(inode))
static struct inode * get_pipe_inode(void)
{
struct inode *inode = get_empty_inode();//分配一空的inode节点
//inode第一个成分i_pipe指向一个pipe_inode_info,只有inode表示为一个管道才使用
if (!inode)
goto fail_inode;
if(!pipe_new(inode))//分配缓冲区,以及i_ipipe结构初始化
goto fail_iput;
PIPE_READERS(*inode) = PIPE_WRITERS(*inode) = 1;//设置write
inode->i_fop = &rdwr_pipe_fops;//设置管道相关读写操作
inode->i_sb = pipe_mnt->mnt_sb;
/*
* Mark the inode dirty from the very beginning,
* that way it will never be moved to the dirty
* list because "mark_inode_dirty()" will think
* that it already _is_ on the dirty list.
*/
inode->i_state = I_DIRTY;
inode->i_mode = S_IFIFO | S_IRUSR | S_IWUSR;
inode->i_uid = current->fsuid;
inode->i_gid = current->fsgid;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
inode->i_blksize = PAGE_SIZE;
return inode;
fail_iput:
iput(inode);
fail_inode:
return NULL;
}
用于管道相关操作函数指针,值得注意的是代码并没有设置inode结构中的inode_operaions结构指针i_op.所以该指针为0,对于用于实现管道的inode并不允许对这里的inode进行常规操作,只有当inode代表有形文件才可以使用
struct file_operations rdwr_pipe_fops = {
llseek: pipe_lseek,
read: pipe_read,
write: pipe_write,
poll: pipe_poll,
ioctl: pipe_ioctl,
open: pipe_rdwr_open,
release: pipe_rdwr_release,
};
struct inode* pipe_new(struct inode* inode)
{
unsigned long page;
page = __get_free_page(GFP_USER);//获取一页用作管道的缓冲区
if (!page)
return NULL;
//再分配一缓冲区用作pipe_inode_info数据结构
inode->i_pipe = kmalloc(sizeof(struct pipe_inode_info), GFP_KERNEL);
if (!inode->i_pipe)
goto fail_page;
init_waitqueue_head(PIPE_WAIT(*inode));//初始化倒艿廊待队列
PIPE_BASE(*inode) = (char*) page;//指向缓冲区
PIPE_START(*inode) = PIPE_LEN(*inode) = 0;//长度与起始地址
PIPE_READERS(*inode) = PIPE_WRITERS(*inode) = 0;//设置为0
PIPE_WAITING_READERS(*inode) = PIPE_WAITING_WRITERS(*inode) = 0;//等待读写数目设置为0
PIPE_RCOUNTER(*inode) = PIPE_WCOUNTER(*inode) = 1;//计数设置为1
return inode;
fail_page:
free_page(page);
return NULL;
}
fd1绑定的read_pipe_fops的写操作为bad_pipe_w,如果fd1使用了写操作,那么将返回错误,fd2的write_pipe_fops一样.以下就不举例了
struct file_operations read_pipe_fops = {
llseek: pipe_lseek,
read: pipe_read,
write: bad_pipe_w,
poll: pipe_poll,
ioctl: pipe_ioctl,
open: pipe_read_open,
release: pipe_read_release,
};
前面inode将结构中的i_fop指针设置为rdwr_pipe_fops,那是双向,而对于代表管道两端的两个已打开文件来说,一个只可写,一个只可读.一般来说file结构的指针f_op只来自inode的i_fop都指向同一个file_operations结构,而这里对于管道这么一种特殊的文件,则使得管道两端的file结构各自指向不同的file_operation,以此确保一端只可写,一端只可读,管道只是一种特殊的文件不属于特定的文件系统,而自己构成一种独立的文件系统,也有自身的数据结构pipe_fs_type
接下来查看有关关闭管道函数
pipe_read_release与pipe_write_release
static int
pipe_read_release(struct inode *inode, struct file *filp)
{
return pipe_release(inode, 1, 0);//1表示读的相关描述符减1,因为关闭,写端设置为0
}
static int
pipe_write_release(struct inode *inode, struct file *filp)
{
return pipe_release(inode, 0, 1);
}
上面2个函数的主体部分是pipe_release
static int
pipe_release(struct inode *inode, int decr, int decw)
{
down(PIPE_SEM(*inode));
PIPE_READERS(*inode) -= decr;//共享计数-decr
PIPE_WRITERS(*inode) -= decw;//共享计数-decw
if (!PIPE_READERS(*inode) && !PIPE_WRITERS(*inode)) {//如果读端跟写端的相关fd都关闭了
struct pipe_inode_info *info = inode->i_pipe;
inode->i_pipe = NULL;//
free_page((unsigned long) info->base);//将页面释放
kfree(info);//将inode释放
} else {
wake_up_interruptible(PIPE_WAIT(*inode));
}
up(PIPE_SEM(*inode));
return 0;
}
接下来看管道特有的读写操作
pipe_read操作
static ssize_t
pipe_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
struct inode *inode = filp->f_dentry->d_inode;//获取inode
ssize_t size, read, ret;
//不允许seek操作
/* Seeks are not allowed on pipes. */
ret = -ESPIPE;
read = 0;
if (ppos != &filp->f_pos)//ppos必须指向filp->f_pos
goto out_nolock;
/* Always return 0 on null read. */
ret = 0;
if (count == 0)
goto out_nolock;
/* Get the pipe semaphore */
ret = -ERESTARTSYS;
if (down_interruptible(PIPE_SEM(*inode)))
goto out_nolock;
if (PIPE_EMPTY(*inode)) {//管道中的字节数如果等于0,表示为空管道
do_more_read:
ret = 0;
if (!PIPE_WRITERS(*inode))//如果管道无人写,那就等于写端关闭,那么客户端也要关闭
goto out;
ret = -EAGAIN;
if (filp->f_flags & O_NONBLOCK)//设置非阻塞直接返回,因为管道为空
goto out;
for (;;) {
PIPE_WAITING_READERS(*inode)++;
pipe_wait(inode);//休眠,因为没有数据可读
PIPE_WAITING_READERS(*inode)--;
ret = -ERESTARTSYS;
if (signal_pending(current))//当前进程有信号未处理
goto out;
ret = 0;
if (!PIPE_EMPTY(*inode))//如果管道不为空,跳出这循环
break;
if (!PIPE_WRITERS(*inode))//没有写端,直接跳出
goto out;
}
}
/* Read what data is available. */
ret = -EFAULT; //如果读取
//count表示剩余数不为0,并且pipe还有数据
while (count > 0 && (size = PIPE_LEN(*inode))) {
char *pipebuf = PIPE_BASE(*inode) + PIPE_START(*inode);//起始位置
ssize_t chars = PIPE_MAX_RCHUNK(*inode);//start到base
if (chars > count)//如果start到base的数据大于count
chars = count;
if (chars > size)//
chars = size;
//有3种情况.(1)读取到要求长度,刚好或者还有剩余,直接返回要求长度,否则返回实际长度
if (copy_to_user(buf, pipebuf, chars))
goto out;
read += chars;//read等于实际读取长度
PIPE_START(*inode) += chars;//起始位置更改
PIPE_START(*inode) &= (PIPE_SIZE - 1);//对齐
PIPE_LEN(*inode) -= chars;//长度更爱
count -= chars;//要求长度-chars长度
buf += chars;//用户缓冲+chars
}
/* Cache behaviour optimization */
if (!PIPE_LEN(*inode))//如果长度为0,就把start设置到页开头
PIPE_START(*inode) = 0;
//如果读取的数据不够要求的长度并且还有等待写进程并且未设置阻塞
if (count && PIPE_WAITING_WRITERS(*inode) && !(filp->f_flags & O_NONBLOCK)) {
/*
* We know that we are going to sleep: signal
* writers synchronously that there is more
* room.
*/
wake_up_interruptible_sync(PIPE_WAIT(*inode));//唤醒
if (!PIPE_EMPTY(*inode))//管道必须为空
BUG();
goto do_more_read;//继续读
}
/* Signal writers asynchronously that there is more room. */
wake_up_interruptible(PIPE_WAIT(*inode));
ret = read;
out:
up(PIPE_SEM(*inode));
out_nolock:
if (read)
ret = read;
return ret;
}
管道读操作:(管道为空)管道不允许seek操作,
1.管道如果为空但通过pipe_writers判断,没有写的file对象那就直接返回
2.管道为空并且设置了非阻塞,直接返回
3.管道数据为空,但有相关fd会进行写操作.休眠等待被唤醒读取数据
4.管道不为空,有3种情况,管道读取到了要求长度,刚好为空或者有剩余,直接返回
5.管道读取的数据没有达到要求,,并且设置了非阻塞,那就读多少返回多少
5.如果管道读取的数据没达到要求(读取数据大于剩余数据),并且还有写fd在等待,并且没有设置非阻塞标志
则唤醒写fd进程,继续循环读.直到读完或者file对象没了(数据还未达到要求).
pipe_write操作
static ssize_t
pipe_write(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
struct inode *inode = filp->f_dentry->d_inode;//获取节点
ssize_t free, written, ret;
/* Seeks are not allowed on pipes. */
ret = -ESPIPE;
written = 0;
if (ppos != &filp->f_pos)
goto out_nolock;
/* Null write succeeds. */
ret = 0;
if (count == 0)//写的数据要求为0,直接跳到out_nolock
goto out_nolock;
ret = -ERESTARTSYS;
if (down_interruptible(PIPE_SEM(*inode)))//枷锁
goto out_nolock;
/* No readers yields SIGPIPE. */
if (!PIPE_READERS(*inode))//如果没有读的fd了,直接发送sigpipe信号
goto sigpipe;
//是否超过缓冲区大小,超过设置为1
/* If count <= PIPE_BUF, we have to make it atomic. */
free = (count <= PIPE_BUF ? count : 1);
/* Wait, or check for, available space. */
if (filp->f_flags & O_NONBLOCK) {//表示即使读不到东西,也不该阻塞
ret = -EAGAIN;
//PIPE_SIZE - PIPE_LEN(inode)
if (PIPE_FREE(*inode) < free)//管道剩余的空间小于要写入的数据,直接退出
goto out;
} else {
while (PIPE_FREE(*inode) < free) {//如果要写入的字节数大于整个缓冲区的大小,那就睡眠
PIPE_WAITING_WRITERS(*inode)++;//等待写++
pipe_wait(inode);//睡眠
PIPE_WAITING_WRITERS(*inode)--;
ret = -ERESTARTSYS;
if (signal_pending(current))//有信号要处理
goto out;
if (!PIPE_READERS(*inode))//如果不存在读的fd,发送sigpipe信号
goto sigpipe;
}
}
/* Copy into available space. */
ret = -EFAULT;
while (count > 0) {
int space;
char *pipebuf = PIPE_BASE(*inode) + PIPE_END(*inode);
ssize_t chars = PIPE_MAX_WCHUNK(*inode);
////如果没有剩余空间了,那么就只说明,要写的字节大于缓冲区的总大小,执行下面的do_while循环
if ((space = PIPE_FREE(*inode)) != 0) {//space获取剩余空间
if (chars > count)
chars = count;
if (chars > space)
chars = space;//space与count中选取最小的那个
//拷贝到管道
if (copy_from_user(pipebuf, buf, chars))
goto out;
written += chars;//写入多少数据
PIPE_LEN(*inode) += chars;//长度++
count -= chars;
buf += chars;
space = PIPE_FREE(*inode);
continue;
}
//如果剩余空间等于0
ret = written;
if (filp->f_flags & O_NONBLOCK)
break;
do {
/*
* Synchronous wake-up: it knows that this process
* is going to give up this CPU, so it doesnt have
* to do idle reschedules.
*/
wake_up_interruptible_sync(PIPE_WAIT(*inode));//唤醒等待的进程
PIPE_WAITING_WRITERS(*inode)++;
pipe_wait(inode);//睡眠等待
PIPE_WAITING_WRITERS(*inode)--;
if (signal_pending(current))//唤醒很可能是有信号
goto out;
if (!PIPE_READERS(*inode))//如果没inode读管道
goto sigpipe;
} while (!PIPE_FREE(*inode));//如果管道一直是满的,继续do_while循环,直到有剩余空间
ret = -EFAULT;
}
/* Signal readers asynchronously that there is more data. */
wake_up_interruptible(PIPE_WAIT(*inode));//唤醒等待读的进程
inode->i_ctime = inode->i_mtime = CURRENT_TIME;
mark_inode_dirty(inode);
out:
up(PIPE_SEM(*inode));
out_nolock:
if (written)
ret = written;
return ret;
sigpipe://读端都关闭了,那就发送sigpipe信号
if (written)
goto out;
up(PIPE_SEM(*inode));
send_sig(SIGPIPE, current, 0);
return -EPIPE;
}
管道写相关操作:(以下阻塞未默认设置)
1.写入的数据参数为0,直接返回
2.判断了管道没有读的fd,直接返回并发送SIGPIPE信号表示管道破裂
3.是否超过了管道的缓存大小,超过了则不保证其原子性并将free设置为1,并将要读取的字节限制为一页大小,这时候能有多大空间就写多少
字节,余下的等消费者度偶一些字节再继续写
4.设置了不阻塞位,但管道剩余空间小于要写入空间直接退出
5.如果要写入的字节大于整个缓冲区剩余空间,那当前写管道进程睡眠,直到缓冲区有剩余空间
6.如果写入的字节数等于要求的字节数,那就返回
在阻塞的情况下:
· 如果write的字节数小于等于PIPE_BUF,那么write会阻塞到写入所有数据,并且 写入操作是原子的。
· 如果write的字节数大于PIPE_BUF,那么write会阻塞到写入所有数据,但写入操作不是原子的,即write会根据当前缓冲区剩余的大小,写入相应的字节数,然后等待下一次有空余的缓冲区,这中间可能会有其他进程进行write操作。
在非阻塞的情况下:
· 如果write的字节数小于等于PIPE_BUF,且管道或FIFO有足以存放要写入数据大小的空间,那么就写入所有数据;
· 如果write的字节数小于等于PIPE_BUF,且管道或FIFO没有足够存放要写入数据大小的空间,那么就会立即返回EAGAIN错误。
· 如果write的字节数大于PIPE_BUF,且管道或FIFO有至少1B的空间,那么就内核就会写入相应的字节数,然后返回已写入的字节数;
· 如果write的字节数大于PIPE_BUF,且管道或FIFO无任何的空间,那么就会立即返回EAGAIN错误。