From ba5bb147330a8737b6b5a812cc774c79c070704b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 21 Mar 2013 02:21:19 -0400 Subject: [PATCH] pipe: take allocation and freeing of pipe_inode_info out of ->i_mutex * new field - pipe->files; number of struct file over that pipe (all sharing the same inode, of course); protected by inode->i_lock. * pipe_release() decrements pipe->files, clears inode->i_pipe when if the counter has reached 0 (all under ->i_lock) and, in that case, frees pipe after having done pipe_unlock() * fifo_open() starts with grabbing ->i_lock, and either bumps pipe->files if ->i_pipe was non-NULL or allocates a new pipe (dropping and regaining ->i_lock) and rechecks ->i_pipe; if it's still NULL, inserts new pipe there, otherwise bumps ->i_pipe->files and frees the one we'd allocated. At that point we know that ->i_pipe is non-NULL and won't go away, so we can do pipe_lock() on it and proceed as we used to. If we end up failing, decrement pipe->files and if it reaches 0 clear ->i_pipe and free the sucker after pipe_unlock(). Signed-off-by: Al Viro --- fs/pipe.c | 72 +++++++++++++++++++++++++++------------ include/linux/pipe_fs_i.h | 2 ++ 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/fs/pipe.c b/fs/pipe.c index 357471db890..abaa9234d27 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -718,23 +718,30 @@ pipe_poll(struct file *filp, poll_table *wait) static int pipe_release(struct inode *inode, struct file *file) { - struct pipe_inode_info *pipe; + struct pipe_inode_info *pipe = inode->i_pipe; + int kill = 0; - mutex_lock(&inode->i_mutex); - pipe = inode->i_pipe; + pipe_lock(pipe); if (file->f_mode & FMODE_READ) pipe->readers--; if (file->f_mode & FMODE_WRITE) pipe->writers--; - if (!pipe->readers && !pipe->writers) { - free_pipe_info(inode); - } else { + if (pipe->readers || pipe->writers) { wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM | POLLERR | POLLHUP); kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN); kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT); } - mutex_unlock(&inode->i_mutex); + spin_lock(&inode->i_lock); + if (!--pipe->files) { + inode->i_pipe = NULL; + kill = 1; + } + spin_unlock(&inode->i_lock); + pipe_unlock(pipe); + + if (kill) + __free_pipe_info(pipe); return 0; } @@ -827,8 +834,9 @@ static struct inode * get_pipe_inode(void) pipe = alloc_pipe_info(inode); if (!pipe) goto fail_iput; - inode->i_pipe = pipe; + inode->i_pipe = pipe; + pipe->files = 2; pipe->readers = pipe->writers = 1; inode->i_fop = &pipefifo_fops; @@ -999,18 +1007,36 @@ static int fifo_open(struct inode *inode, struct file *filp) { struct pipe_inode_info *pipe; bool is_pipe = inode->i_sb->s_magic == PIPEFS_MAGIC; + int kill = 0; int ret; - mutex_lock(&inode->i_mutex); - pipe = inode->i_pipe; - if (!pipe) { - ret = -ENOMEM; + filp->f_version = 0; + + spin_lock(&inode->i_lock); + if (inode->i_pipe) { + pipe = inode->i_pipe; + pipe->files++; + spin_unlock(&inode->i_lock); + } else { + spin_unlock(&inode->i_lock); pipe = alloc_pipe_info(inode); if (!pipe) - goto err_nocleanup; - inode->i_pipe = pipe; + return -ENOMEM; + pipe->files = 1; + spin_lock(&inode->i_lock); + if (unlikely(inode->i_pipe)) { + inode->i_pipe->files++; + spin_unlock(&inode->i_lock); + __free_pipe_info(pipe); + pipe = inode->i_pipe; + } else { + inode->i_pipe = pipe; + spin_unlock(&inode->i_lock); + } } - filp->f_version = 0; + /* OK, we have a pipe and it's pinned down */ + + pipe_lock(pipe); /* We can only do regular read/write on fifos */ filp->f_mode &= (FMODE_READ | FMODE_WRITE); @@ -1080,7 +1106,7 @@ static int fifo_open(struct inode *inode, struct file *filp) } /* Ok! */ - mutex_unlock(&inode->i_mutex); + pipe_unlock(pipe); return 0; err_rd: @@ -1096,11 +1122,15 @@ err_wr: goto err; err: - if (!pipe->readers && !pipe->writers) - free_pipe_info(inode); - -err_nocleanup: - mutex_unlock(&inode->i_mutex); + spin_lock(&inode->i_lock); + if (!--pipe->files) { + inode->i_pipe = NULL; + kill = 1; + } + spin_unlock(&inode->i_lock); + pipe_unlock(pipe); + if (kill) + __free_pipe_info(pipe); return ret; } diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h index ad1a427b526..59778e1c9c0 100644 --- a/include/linux/pipe_fs_i.h +++ b/include/linux/pipe_fs_i.h @@ -34,6 +34,7 @@ struct pipe_buffer { * @tmp_page: cached released page * @readers: number of current readers of this pipe * @writers: number of current writers of this pipe + * @files: number of struct file refering this pipe (protected by ->i_lock) * @waiting_writers: number of writers blocked waiting for room * @r_counter: reader counter * @w_counter: writer counter @@ -47,6 +48,7 @@ struct pipe_inode_info { unsigned int nrbufs, curbuf, buffers; unsigned int readers; unsigned int writers; + unsigned int files; unsigned int waiting_writers; unsigned int r_counter; unsigned int w_counter; -- 2.20.1