Linux内核源代码情景分析-进程间通信-管道

来源:互联网 发布:算法可以申请专利吗 编辑:程序博客网 时间:2024/05/16 09:42

    一、我们先来介绍一下init_pipe_fs。

static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super,FS_NOMOUNT|FS_SINGLE);
static int __init init_pipe_fs(void){int err = register_filesystem(&pipe_fs_type);if (!err) {pipe_mnt = kern_mount(&pipe_fs_type);err = PTR_ERR(pipe_mnt);if (IS_ERR(pipe_mnt))unregister_filesystem(&pipe_fs_type);elseerr = 0;}return err;}
struct vfsmount *kern_mount(struct file_system_type *type){kdev_t dev = get_unnamed_dev();struct super_block *sb;struct vfsmount *mnt;if (!dev)return ERR_PTR(-EMFILE);sb = read_super(dev, NULL, type, 0, NULL, 0);if (!sb) {put_unnamed_dev(dev);return ERR_PTR(-EINVAL);}mnt = add_vfsmnt(NULL, sb->s_root, NULL);if (!mnt) {kill_super(sb, 0);return ERR_PTR(-ENOMEM);}type->kern_mnt = mnt;return mnt;}
static struct super_block * read_super(kdev_t dev, struct block_device *bdev,       struct file_system_type *type, int flags,       void *data, int silent){struct super_block * s;s = get_empty_super();if (!s)goto out;s->s_dev = dev;s->s_bdev = bdev;s->s_flags = flags;s->s_dirt = 0;sema_init(&s->s_vfs_rename_sem,1);sema_init(&s->s_nfsd_free_path_sem,1);s->s_type = type;sema_init(&s->s_dquot.dqio_sem, 1);sema_init(&s->s_dquot.dqoff_sem, 1);s->s_dquot.flags = 0;lock_super(s);if (!type->read_super(s, data, silent))//指向pipefs_read_supergoto out_fail;unlock_super(s);/* tell bdcache that we are going to keep this one */if (bdev)atomic_inc(&bdev->bd_count);out:return s;out_fail:s->s_dev = 0;s->s_bdev = 0;s->s_type = NULL;unlock_super(s);return NULL;}
    type->read_super指向了pipefs_read_super,代码如下:

static struct super_block * pipefs_read_super(struct super_block *sb, void *data, int silent){struct inode *root = new_inode(sb);//分配根节点的inode结构if (!root)return NULL;root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;root->i_uid = root->i_gid = 0;root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;sb->s_blocksize = 1024;sb->s_blocksize_bits = 10;sb->s_magic = PIPEFS_MAGIC;sb->s_op= &pipefs_ops;sb->s_root = d_alloc(NULL, &(const struct qstr) { "pipe:", 5, 0 });//分配根节点的dentry结构if (!sb->s_root) {iput(root);return NULL;}sb->s_root->d_sb = sb;sb->s_root->d_parent = sb->s_root;d_instantiate(sb->s_root, root);//根节点的dentry和根节点的inode结构相连return sb;}

    二、我们来看应用管道通信的相关代码,假设系统中只有这两个进程:

#include <stdio.h>#include <unistd.h>int main(){int n,fd[2];pid_t pid;int i,j;char str1[]="ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE";char str2[512];if(pipe(fd)<0)//fd[0]是读端,fd[1]是写端{printf("pipe error\n");return -1;}if((pid = fork())<0)//父子进程都有了同样file结构{printf("fork error\n");return -1;}else if(pid > 0){close(fd[0]);//父进程做为写端,关闭读端        write(fd[1],str1,strlen(str1));}else{close(fd[1]);//子进程做为读端,关闭写端read(fd[0],str2,strlen(str2));}}

    pipe的系统调用是sys_pipe(),代码如下:

asmlinkage int sys_pipe(unsigned long * fildes){int fd[2];int error;error = do_pipe(fd);//fd[]返回代表着管道两端的两个已打开文件号if (!error) {if (copy_to_user(fildes, fd, 2*sizeof(int)))//将数组fd[]复制到用户空间error = -EFAULT;}return error;}
int do_pipe(int *fd){struct qstr this;char name[32];struct dentry *dentry;struct inode * inode;struct file *f1, *f2;int error;int i,j;error = -ENFILE;f1 = get_empty_filp();//获取读端空的file结构if (!f1)goto no_files;f2 = get_empty_filp();//获取写端空的file结构if (!f2)goto close_f1;inode = get_pipe_inode();//获取读端和写端共同使用的管道节点的inode结构if (!inode)goto close_f12;error = get_unused_fd();//获得读端文件号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);//分配管道节点的dentry结构if (!dentry)goto close_f12_inode_i_j;dentry->d_op = &pipefs_dentry_operations;d_add(dentry, inode);//互相关联f1->f_vfsmnt = f2->f_vfsmnt = mntget(mntget(pipe_mnt));f1->f_dentry = f2->f_dentry = dget(dentry);//f_dentry指向所打开文件的目录项dentry数据结构/* read file */f1->f_pos = f2->f_pos = 0;f1->f_flags = O_RDONLY;f1->f_op = &read_pipe_fops;//读端的f_opf1->f_mode = 1;f1->f_version = 0;/* write file */f2->f_flags = O_WRONLY;f2->f_op = &write_pipe_fops;//写端的f_opf2->f_mode = 2;f2->f_version = 0;fd_install(i, f1);//f1安装到current->files[i]上fd_install(j, f2);//f2安装到current->files[j]上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结构,代码如下:

static struct inode * get_pipe_inode(void){struct inode *inode = get_empty_inode();if (!inode)goto fail_inode;if(!pipe_new(inode))//分配所需要的缓冲区goto fail_iput;PIPE_READERS(*inode) = PIPE_WRITERS(*inode) = 1;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;}
    pipe_new,分配所需要的缓冲区,代码如下:
struct inode* pipe_new(struct inode* inode){unsigned long page;page = __get_free_page(GFP_USER);if (!page)return NULL;inode->i_pipe = kmalloc(sizeof(struct pipe_inode_info), GFP_KERNEL);//i_pipe是指向一个pipe_inode_info数据结构的指针if (!inode->i_pipe)goto fail_page;init_waitqueue_head(PIPE_WAIT(*inode));PIPE_BASE(*inode) = (char*) page;//(inode).i_pipe->base指向了页面的起始地址PIPE_START(*inode) = PIPE_LEN(*inode) = 0;PIPE_READERS(*inode) = PIPE_WRITERS(*inode) = 0;PIPE_WAITING_READERS(*inode) = PIPE_WAITING_WRITERS(*inode) = 0;PIPE_RCOUNTER(*inode) = PIPE_WCOUNTER(*inode) = 1;return inode;fail_page:free_page(page);return NULL;}
    pipe_inode_info结构如下:

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;};
    pipe_new里面宏定义如下:

#define PIPE_SEM(inode)(&(inode).i_sem)#define PIPE_WAIT(inode)(&(inode).i_pipe->wait)#define PIPE_BASE(inode)((inode).i_pipe->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))
    sys_pipe继续执行,对于管道的两端来说,管道是单向的,所以其f1一端设置成"只读",而另一端则设置成"只写"。同时,两端的文件操作也分别设置成read_pipe_fops和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,};
struct file_operations write_pipe_fops = {llseek:pipe_lseek,read:bad_pipe_r,write:pipe_write,poll:pipe_poll,ioctl:pipe_ioctl,open:pipe_write_open,release:pipe_write_release,};
   读者可能会问,前面管道节点的inode数据结构将指针i_fop设置成指向rdwr_pipe_fops,那显然是双向的。这里是不是有矛盾吗?其实不然,对于代表着管道两端的两个已打开文件来说,一个只能写而另一个只能读,这是事情的一个方面。可是另一方面,这两个逻辑上的已打开文件都通向同一个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,};
    

    fork,父子进程都有相同的file结构,因为copy_file中有如下代码:

new_fds = newf->fd;for (i = open_files; i != 0; i--) {          struct file *f = *old_fds++;          if (f)              get_file(f);          *new_fds++ = f;      }   tsk->files = newf; 

    我们假设父进程先执行,并且关闭了读端,执行write(fd[1],str1,strlen(str1)),映射到内核,sys_write,代码如下:

asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count){ssize_t ret;struct file * file;ret = -EBADF;file = fget(fd);if (file) {if (file->f_mode & FMODE_WRITE) {struct inode *inode = file->f_dentry->d_inode;ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file,file->f_pos, count);if (!ret) {ssize_t (*write)(struct file *, const char *, size_t, loff_t *);ret = -EINVAL;if (file->f_op && (write = file->f_op->write) != NULL)ret = write(file, buf, count, &file->f_pos);//pipe_write}}if (ret > 0)inode_dir_notify(file->f_dentry->d_parent->d_inode,DN_MODIFY);fput(file);}return ret;}
    write(file, buf, count, &file->f_pos)调用pipe_write(file, buf, count, &file->f_pos)。
static ssize_tpipe_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)goto out_nolock;ret = -ERESTARTSYS;if (down_interruptible(PIPE_SEM(*inode)))goto out_nolock;/* No readers yields SIGPIPE.  */if (!PIPE_READERS(*inode))goto sigpipe;/* If count <= PIPE_BUF, we have to make it atomic.  */free = (count <= PIPE_BUF ? count : 1);//如果要写入的字节数大于整个缓冲区的大小,那么free为1/* Wait, or check for, available space.  */if (filp->f_flags & O_NONBLOCK) {ret = -EAGAIN;if (PIPE_FREE(*inode) < free)goto out;} else {while (PIPE_FREE(*inode) < free) {//缓冲区剩余空间小于要写入的字节数,如果free为1,表示只要有一个字节的剩余空间,就不需要睡眠PIPE_WAITING_WRITERS(*inode)++;//waiting_writers++pipe_wait(inode);//那么就要睡眠等待PIPE_WAITING_WRITERS(*inode)--;ret = -ERESTARTSYS;if (signal_pending(current))goto out;if (!PIPE_READERS(*inode))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);if ((space = PIPE_FREE(*inode)) != 0) {//如果没有剩余空间了,那么就只说明,要写的字节大于缓冲区的总大小,执行下面的do_while循环if (chars > count)chars = count;if (chars > space)chars = space;if (copy_from_user(pipebuf, buf, chars))goto out;written += chars;PIPE_LEN(*inode) += chars;count -= chars;buf += chars;space = PIPE_FREE(*inode);continue;//由于是缓冲区是一个循环的页面,如果一次写操作,跨过页面开始位置,要分两次写,continue如果count>0,就表示分两次写,如果count不大于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)++;//waiting_writers++pipe_wait(inode);//写端进程睡眠等待PIPE_WAITING_WRITERS(*inode)--;if (signal_pending(current))goto out;if (!PIPE_READERS(*inode))goto sigpipe;} while (!PIPE_FREE(*inode));//只要有一个字节的剩余空间,就退出do_while循环,再进行一次外部while循环ret = -EFAULT;}/* Signal readers asynchronously that there is more data.  */wake_up_interruptible(PIPE_WAIT(*inode));//如果分一次写,分两次写,是不会执行上面的do_while循环的,但也要唤醒读端进程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:if (written)goto out;up(PIPE_SEM(*inode));send_sig(SIGPIPE, current, 0);return -EPIPE;}
    pipe_wait,睡眠等待,代码如下:

void pipe_wait(struct inode * inode){DECLARE_WAITQUEUE(wait, current);//把当前进程的task_struct结构和wait_queue连在一起,见下面代码current->state = TASK_INTERRUPTIBLE;//当前进程设置为可中断等待状态add_wait_queue(PIPE_WAIT(*inode), &wait);//把wait加入到(inode).i_pipe->waitup(PIPE_SEM(*inode));schedule();//切换进程remove_wait_queue(PIPE_WAIT(*inode), &wait);current->state = TASK_RUNNING;down(PIPE_SEM(*inode));}
#define DECLARE_WAITQUEUE(wait, current) \                              struct wait_queue wait = { current, NULL }
void add_wait_queue(wait_queue_head_t *q, wait_queue_t * wait){unsigned long flags;wq_write_lock_irqsave(&q->lock, flags);wait->flags = 0;__add_wait_queue(q, wait);wq_write_unlock_irqrestore(&q->lock, flags);}
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new){        ......list_add(&new->task_list, &head->task_list);}
    总结一下:

    1、如果剩余空间不足,那么就马上睡眠等待;

    2、如果剩余空间足够,分一次写或者分两次写后,唤醒读端进程,程序返回;

    3、如果剩余空间足够,但是要写入的字节数大于缓冲区大小,那么也要唤醒读端进程,并睡眠等待。

    读和写都是往一个循环的页面,读和写内容的,如下图:

     


    然后子进程开始执行,并且关闭了写端,执行read(fd[0],str2,strlen(str2)),映射到内核,sys_read,代码如下:

asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count){ssize_t ret;struct file * file;ret = -EBADF;file = fget(fd);if (file) {if (file->f_mode & FMODE_READ) {ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,file, file->f_pos, count);if (!ret) {ssize_t (*read)(struct file *, char *, size_t, loff_t *);ret = -EINVAL;if (file->f_op && (read = file->f_op->read) != NULL)ret = read(file, buf, count, &file->f_pos);//pipe_read}}if (ret > 0)inode_dir_notify(file->f_dentry->d_parent->d_inode,DN_ACCESS);fput(file);}return ret;}
    read(file, buf, count, &file->f_pos)指向pipe_read(file, buf, count, &file->f_pos),代码如下:
static ssize_tpipe_read(struct file *filp, char *buf, size_t count, loff_t *ppos){struct inode *inode = filp->f_dentry->d_inode;ssize_t size, read, ret;/* Seeks are not allowed on pipes.  */ret = -ESPIPE;read = 0;if (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)) {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)++;//waiting_readers++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;while (count > 0 && (size = PIPE_LEN(*inode))) {//如果缓冲区中有字节,分一次或者两次读完缓冲区的字节char *pipebuf = PIPE_BASE(*inode) + PIPE_START(*inode);ssize_t chars = PIPE_MAX_RCHUNK(*inode);if (chars > count)chars = count;if (chars > size)chars = size;if (copy_to_user(buf, pipebuf, chars))goto out;read += chars;PIPE_START(*inode) += chars;PIPE_START(*inode) &= (PIPE_SIZE - 1);PIPE_LEN(*inode) -= chars;count -= chars;buf += chars;}/* Cache behaviour optimization */if (!PIPE_LEN(*inode))PIPE_START(*inode) = 0;if (count && PIPE_WAITING_WRITERS(*inode) && !(filp->f_flags & O_NONBLOCK)) {//如果count大于0,并且有等待写的进程,那么说明count要读的字节数大于缓冲区现有的字节数,或者大于缓冲区的总大小,要睡眠等待,并唤醒写端进程/* * 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;//返回到do_more_read睡眠等待}/* Signal writers asynchronously that there is more room.  */wake_up_interruptible(PIPE_WAIT(*inode));//如果count为0,说明分一次或者分两次读完了所需要的字节数,那么也要唤醒写端进程ret = read;out:up(PIPE_SEM(*inode));out_nolock:if (read)ret = read;return ret;}

    总结一下:

    1、如果没有可读的字节,那么就马上睡眠等待;

    2、如果有可读的字节,分一次写或者分两次写后,唤醒写端进程,程序返回;

    3、如果又可读的字节,但是要读入的字节数大于缓冲区现有的字节数,或者大于缓冲区的总大小,那么也要唤醒写端进程,并睡眠等待。

0 1
原创粉丝点击