shm: optimize locking and ipc_namespace getting
[linux-2.6.git] / ipc / shm.c
index 23256b8..9fb044f 100644 (file)
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -74,6 +74,7 @@ void shm_init_ns(struct ipc_namespace *ns)
        ns->shm_ctlmax = SHMMAX;
        ns->shm_ctlall = SHMALL;
        ns->shm_ctlmni = SHMMNI;
+       ns->shm_rmid_forced = 0;
        ns->shm_tot = 0;
        ipc_init_ids(&shm_ids(ns));
 }
@@ -108,7 +109,11 @@ void __init shm_init (void)
 {
        shm_init_ns(&init_ipc_ns);
        ipc_init_proc_interface("sysvipc/shm",
-                               "       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime\n",
+#if BITS_PER_LONG <= 32
+                               "       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime        rss       swap\n",
+#else
+                               "       key      shmid perms                  size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime                   rss                  swap\n",
+#endif
                                IPC_SHM_IDS, sysvipc_shm_proc_show);
 }
 
@@ -126,6 +131,12 @@ static inline struct shmid_kernel *shm_lock(struct ipc_namespace *ns, int id)
        return container_of(ipcp, struct shmid_kernel, shm_perm);
 }
 
+static inline void shm_lock_by_ptr(struct shmid_kernel *ipcp)
+{
+       rcu_read_lock();
+       spin_lock(&ipcp->shm_perm.lock);
+}
+
 static inline struct shmid_kernel *shm_lock_check(struct ipc_namespace *ns,
                                                int id)
 {
@@ -183,6 +194,23 @@ static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)
 }
 
 /*
+ * shm_may_destroy - identifies whether shm segment should be destroyed now
+ *
+ * Returns true if and only if there are no active users of the segment and
+ * one of the following is true:
+ *
+ * 1) shmctl(id, IPC_RMID, NULL) was called for this shp
+ *
+ * 2) sysctl kernel.shm_rmid_forced is set to 1.
+ */
+static bool shm_may_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)
+{
+       return (shp->shm_nattch == 0) &&
+              (ns->shm_rmid_forced ||
+               (shp->shm_perm.mode & SHM_DEST));
+}
+
+/*
  * remove the attach descriptor vma.
  * free memory for segment if it is marked destroyed.
  * The descriptor has already been removed from the current->mm->mmap list
@@ -202,14 +230,87 @@ static void shm_close(struct vm_area_struct *vma)
        shp->shm_lprid = task_tgid_vnr(current);
        shp->shm_dtim = get_seconds();
        shp->shm_nattch--;
-       if(shp->shm_nattch == 0 &&
-          shp->shm_perm.mode & SHM_DEST)
+       if (shm_may_destroy(ns, shp))
                shm_destroy(ns, shp);
        else
                shm_unlock(shp);
        up_write(&shm_ids(ns).rw_mutex);
 }
 
+/* Called with ns->shm_ids(ns).rw_mutex locked */
+static int shm_try_destroy_current(int id, void *p, void *data)
+{
+       struct ipc_namespace *ns = data;
+       struct kern_ipc_perm *ipcp = p;
+       struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm);
+
+       if (shp->shm_creator != current)
+               return 0;
+
+       /*
+        * Mark it as orphaned to destroy the segment when
+        * kernel.shm_rmid_forced is changed.
+        * It is noop if the following shm_may_destroy() returns true.
+        */
+       shp->shm_creator = NULL;
+
+       /*
+        * Don't even try to destroy it.  If shm_rmid_forced=0 and IPC_RMID
+        * is not set, it shouldn't be deleted here.
+        */
+       if (!ns->shm_rmid_forced)
+               return 0;
+
+       if (shm_may_destroy(ns, shp)) {
+               shm_lock_by_ptr(shp);
+               shm_destroy(ns, shp);
+       }
+       return 0;
+}
+
+/* Called with ns->shm_ids(ns).rw_mutex locked */
+static int shm_try_destroy_orphaned(int id, void *p, void *data)
+{
+       struct ipc_namespace *ns = data;
+       struct kern_ipc_perm *ipcp = p;
+       struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm);
+
+       /*
+        * We want to destroy segments without users and with already
+        * exit'ed originating process.
+        *
+        * As shp->* are changed under rw_mutex, it's safe to skip shp locking.
+        */
+       if (shp->shm_creator != NULL)
+               return 0;
+
+       if (shm_may_destroy(ns, shp)) {
+               shm_lock_by_ptr(shp);
+               shm_destroy(ns, shp);
+       }
+       return 0;
+}
+
+void shm_destroy_orphaned(struct ipc_namespace *ns)
+{
+       down_write(&shm_ids(ns).rw_mutex);
+       if (&shm_ids(ns).in_use)
+               idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_orphaned, ns);
+       up_write(&shm_ids(ns).rw_mutex);
+}
+
+
+void exit_shm(struct task_struct *task)
+{
+       struct ipc_namespace *ns = task->nsproxy->ipc_ns;
+
+       /* Destroy all already created segments, but not mapped yet */
+       down_write(&shm_ids(ns).rw_mutex);
+       if (&shm_ids(ns).in_use)
+               idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_current, ns);
+       up_write(&shm_ids(ns).rw_mutex);
+}
+
 static int shm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
 {
        struct file *file = vma->vm_file;
@@ -273,16 +374,13 @@ static int shm_release(struct inode *ino, struct file *file)
        return 0;
 }
 
-static int shm_fsync(struct file *file, struct dentry *dentry, int datasync)
+static int shm_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 {
-       int (*fsync) (struct file *, struct dentry *, int datasync);
        struct shm_file_data *sfd = shm_file_data(file);
-       int ret = -EINVAL;
 
-       fsync = sfd->file->f_op->fsync;
-       if (fsync)
-               ret = fsync(sfd->file, sfd->file->f_path.dentry, datasync);
-       return ret;
+       if (!sfd->file->f_op->fsync)
+               return -EINVAL;
+       return sfd->file->f_op->fsync(sfd->file, start, end, datasync);
 }
 
 static unsigned long shm_get_unmapped_area(struct file *file,
@@ -301,6 +399,7 @@ static const struct file_operations shm_file_operations = {
 #ifndef CONFIG_MMU
        .get_unmapped_area      = shm_get_unmapped_area,
 #endif
+       .llseek         = noop_llseek,
 };
 
 static const struct file_operations shm_file_operations_huge = {
@@ -308,6 +407,7 @@ static const struct file_operations shm_file_operations_huge = {
        .fsync          = shm_fsync,
        .release        = shm_release,
        .get_unmapped_area      = shm_get_unmapped_area,
+       .llseek         = noop_llseek,
 };
 
 int is_file_shm_hugepages(struct file *file)
@@ -344,7 +444,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
        struct file * file;
        char name[13];
        int id;
-       int acctflag = 0;
+       vm_flags_t acctflag = 0;
 
        if (size < SHMMIN || size > ns->shm_ctlmax)
                return -EINVAL;
@@ -401,6 +501,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
        shp->shm_segsz = size;
        shp->shm_nattch = 0;
        shp->shm_file = file;
+       shp->shm_creator = current;
        /*
         * shmid gets reported as "inode#" in /proc/pid/maps.
         * proc-ps tools use this. Changing this will break them.
@@ -476,6 +577,7 @@ static inline unsigned long copy_shmid_to_user(void __user *buf, struct shmid64_
            {
                struct shmid_ds out;
 
+               memset(&out, 0, sizeof(out));
                ipc64_perm_to_ipc_perm(&in->shm_perm, &out.shm_perm);
                out.shm_segsz   = in->shm_segsz;
                out.shm_atime   = in->shm_atime;
@@ -545,6 +647,34 @@ static inline unsigned long copy_shminfo_to_user(void __user *buf, struct shminf
 }
 
 /*
+ * Calculate and add used RSS and swap pages of a shm.
+ * Called with shm_ids.rw_mutex held as a reader
+ */
+static void shm_add_rss_swap(struct shmid_kernel *shp,
+       unsigned long *rss_add, unsigned long *swp_add)
+{
+       struct inode *inode;
+
+       inode = shp->shm_file->f_path.dentry->d_inode;
+
+       if (is_file_hugepages(shp->shm_file)) {
+               struct address_space *mapping = inode->i_mapping;
+               struct hstate *h = hstate_file(shp->shm_file);
+               *rss_add += pages_per_huge_page(h) * mapping->nrpages;
+       } else {
+#ifdef CONFIG_SHMEM
+               struct shmem_inode_info *info = SHMEM_I(inode);
+               spin_lock(&info->lock);
+               *rss_add += inode->i_mapping->nrpages;
+               *swp_add += info->swapped;
+               spin_unlock(&info->lock);
+#else
+               *rss_add += inode->i_mapping->nrpages;
+#endif
+       }
+}
+
+/*
  * Called with shm_ids.rw_mutex held as a reader
  */
 static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss,
@@ -561,30 +691,13 @@ static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss,
        for (total = 0, next_id = 0; total < in_use; next_id++) {
                struct kern_ipc_perm *ipc;
                struct shmid_kernel *shp;
-               struct inode *inode;
 
                ipc = idr_find(&shm_ids(ns).ipcs_idr, next_id);
                if (ipc == NULL)
                        continue;
                shp = container_of(ipc, struct shmid_kernel, shm_perm);
 
-               inode = shp->shm_file->f_path.dentry->d_inode;
-
-               if (is_file_hugepages(shp->shm_file)) {
-                       struct address_space *mapping = inode->i_mapping;
-                       struct hstate *h = hstate_file(shp->shm_file);
-                       *rss += pages_per_huge_page(h) * mapping->nrpages;
-               } else {
-#ifdef CONFIG_SHMEM
-                       struct shmem_inode_info *info = SHMEM_I(inode);
-                       spin_lock(&info->lock);
-                       *rss += inode->i_mapping->nrpages;
-                       *swp += info->swapped;
-                       spin_unlock(&info->lock);
-#else
-                       *rss += inode->i_mapping->nrpages;
-#endif
-               }
+               shm_add_rss_swap(shp, rss, swp);
 
                total++;
        }
@@ -608,7 +721,8 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
                        return -EFAULT;
        }
 
-       ipcp = ipcctl_pre_down(&shm_ids(ns), shmid, cmd, &shmid64.shm_perm, 0);
+       ipcp = ipcctl_pre_down(ns, &shm_ids(ns), shmid, cmd,
+                              &shmid64.shm_perm, 0);
        if (IS_ERR(ipcp))
                return PTR_ERR(ipcp);
 
@@ -722,7 +836,7 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
                        result = 0;
                }
                err = -EACCES;
-               if (ipcperms (&shp->shm_perm, S_IRUGO))
+               if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
                        goto out_unlock;
                err = security_shm_shmctl(shp, cmd);
                if (err)
@@ -758,14 +872,13 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
 
                audit_ipc_obj(&(shp->shm_perm));
 
-               if (!capable(CAP_IPC_LOCK)) {
+               if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {
                        uid_t euid = current_euid();
                        err = -EPERM;
                        if (euid != shp->shm_perm.uid &&
                            euid != shp->shm_perm.cuid)
                                goto out_unlock;
-                       if (cmd == SHM_LOCK &&
-                           !current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur)
+                       if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK))
                                goto out_unlock;
                }
 
@@ -874,7 +987,7 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)
        }
 
        err = -EACCES;
-       if (ipcperms(&shp->shm_perm, acc_mode))
+       if (ipcperms(ns, &shp->shm_perm, acc_mode))
                goto out_unlock;
 
        err = security_shm_shmat(shp, shmaddr, shmflg);
@@ -935,8 +1048,7 @@ out_nattch:
        shp = shm_lock(ns, shmid);
        BUG_ON(IS_ERR(shp));
        shp->shm_nattch--;
-       if(shp->shm_nattch == 0 &&
-          shp->shm_perm.mode & SHM_DEST)
+       if (shm_may_destroy(ns, shp))
                shm_destroy(ns, shp);
        else
                shm_unlock(shp);
@@ -1041,7 +1153,7 @@ SYSCALL_DEFINE1(shmdt, char __user *, shmaddr)
        /*
         * We need look no further than the maximum address a fragment
         * could possibly have landed at. Also cast things to loff_t to
-        * prevent overflows and make comparisions vs. equal-width types.
+        * prevent overflows and make comparisons vs. equal-width types.
         */
        size = PAGE_ALIGN(size);
        while (vma && (loff_t)(vma->vm_end - addr) <= size) {
@@ -1074,6 +1186,9 @@ SYSCALL_DEFINE1(shmdt, char __user *, shmaddr)
 static int sysvipc_shm_proc_show(struct seq_file *s, void *it)
 {
        struct shmid_kernel *shp = it;
+       unsigned long rss = 0, swp = 0;
+
+       shm_add_rss_swap(shp, &rss, &swp);
 
 #if BITS_PER_LONG <= 32
 #define SIZE_SPEC "%10lu"
@@ -1083,7 +1198,8 @@ static int sysvipc_shm_proc_show(struct seq_file *s, void *it)
 
        return seq_printf(s,
                          "%10d %10d  %4o " SIZE_SPEC " %5u %5u  "
-                         "%5lu %5u %5u %5u %5u %10lu %10lu %10lu\n",
+                         "%5lu %5u %5u %5u %5u %10lu %10lu %10lu "
+                         SIZE_SPEC " " SIZE_SPEC "\n",
                          shp->shm_perm.key,
                          shp->shm_perm.id,
                          shp->shm_perm.mode,
@@ -1097,6 +1213,8 @@ static int sysvipc_shm_proc_show(struct seq_file *s, void *it)
                          shp->shm_perm.cgid,
                          shp->shm_atim,
                          shp->shm_dtim,
-                         shp->shm_ctim);
+                         shp->shm_ctim,
+                         rss * PAGE_SIZE,
+                         swp * PAGE_SIZE);
 }
 #endif