pipe: take allocation and freeing of pipe_inode_info out of ->i_mutex
authorAl Viro <viro@zeniv.linux.org.uk>
Thu, 21 Mar 2013 06:21:19 +0000 (02:21 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Tue, 9 Apr 2013 18:12:59 +0000 (14:12 -0400)
* 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 <viro@zeniv.linux.org.uk>
fs/pipe.c
include/linux/pipe_fs_i.h

index 357471db890dc5c75a18bc2705768b8ef0b4ae8a..abaa9234d27b194a88232d87018ae2c9199a50f8 100644 (file)
--- 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;
 }
 
index ad1a427b5267b3faef96444289eacbaf62880f75..59778e1c9c08d4d42e09bb4dc3ef0b147ba66779 100644 (file)
@@ -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;