Commit ab602f79 authored by Jack Miller's avatar Jack Miller Committed by Linus Torvalds

shm: make exit_shm work proportional to task activity

This is small set of patches our team has had kicking around for a few
versions internally that fixes tasks getting hung on shm_exit when there
are many threads hammering it at once.

Anton wrote a simple test to cause the issue:

  http://ozlabs.org/~anton/junkcode/bust_shm_exit.c

Before applying this patchset, this test code will cause either hanging
tracebacks or pthread out of memory errors.

After this patchset, it will still produce output like:

  root@somehost:~# ./bust_shm_exit 1024 160
  ...
  INFO: rcu_sched detected stalls on CPUs/tasks: {} (detected by 116, t=2111 jiffies, g=241, c=240, q=7113)
  INFO: Stall ended before state dump start
  ...

But the task will continue to run along happily, so we consider this an
improvement over hanging, even if it's a bit noisy.

This patch (of 3):

exit_shm obtains the ipc_ns shm rwsem for write and holds it while it
walks every shared memory segment in the namespace.  Thus the amount of
work is related to the number of shm segments in the namespace not the
number of segments that might need to be cleaned.

In addition, this occurs after the task has been notified the thread has
exited, so the number of tasks waiting for the ns shm rwsem can grow
without bound until memory is exausted.

Add a list to the task struct of all shmids allocated by this task.  Init
the list head in copy_process.  Use the ns->rwsem for locking.  Add
segments after id is added, remove before removing from id.

On unshare of NEW_IPCNS orphan any ids as if the task had exited, similar
to handling of semaphore undo.

I chose a define for the init sequence since its a simple list init,
otherwise it would require a function call to avoid include loops between
the semaphore code and the task struct.  Converting the list_del to
list_del_init for the unshare cases would remove the exit followed by
init, but I left it blow up if not inited.
Signed-off-by: default avatarMilton Miller <miltonm@bga.com>
Signed-off-by: default avatarJack Miller <millerjo@us.ibm.com>
Cc: Davidlohr Bueso <davidlohr@hp.com>
Cc: Manfred Spraul <manfred@colorfullife.com>
Cc: Anton Blanchard <anton@samba.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 9687fd91
...@@ -33,6 +33,7 @@ struct sched_param { ...@@ -33,6 +33,7 @@ struct sched_param {
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/sem.h> #include <linux/sem.h>
#include <linux/shm.h>
#include <linux/signal.h> #include <linux/signal.h>
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/completion.h> #include <linux/completion.h>
...@@ -1385,6 +1386,7 @@ struct task_struct { ...@@ -1385,6 +1386,7 @@ struct task_struct {
#ifdef CONFIG_SYSVIPC #ifdef CONFIG_SYSVIPC
/* ipc stuff */ /* ipc stuff */
struct sysv_sem sysvsem; struct sysv_sem sysvsem;
struct sysv_shm sysvshm;
#endif #endif
#ifdef CONFIG_DETECT_HUNG_TASK #ifdef CONFIG_DETECT_HUNG_TASK
/* hung task detection */ /* hung task detection */
......
#ifndef _LINUX_SHM_H_ #ifndef _LINUX_SHM_H_
#define _LINUX_SHM_H_ #define _LINUX_SHM_H_
#include <linux/list.h>
#include <asm/page.h> #include <asm/page.h>
#include <uapi/linux/shm.h> #include <uapi/linux/shm.h>
#include <asm/shmparam.h> #include <asm/shmparam.h>
...@@ -20,6 +21,7 @@ struct shmid_kernel /* private to the kernel */ ...@@ -20,6 +21,7 @@ struct shmid_kernel /* private to the kernel */
/* The task created the shm object. NULL if the task is dead. */ /* The task created the shm object. NULL if the task is dead. */
struct task_struct *shm_creator; struct task_struct *shm_creator;
struct list_head shm_clist; /* list by creator */
}; };
/* shm_mode upper byte flags */ /* shm_mode upper byte flags */
...@@ -44,11 +46,20 @@ struct shmid_kernel /* private to the kernel */ ...@@ -44,11 +46,20 @@ struct shmid_kernel /* private to the kernel */
#define SHM_HUGE_1GB (30 << SHM_HUGE_SHIFT) #define SHM_HUGE_1GB (30 << SHM_HUGE_SHIFT)
#ifdef CONFIG_SYSVIPC #ifdef CONFIG_SYSVIPC
struct sysv_shm {
struct list_head shm_clist;
};
long do_shmat(int shmid, char __user *shmaddr, int shmflg, unsigned long *addr, long do_shmat(int shmid, char __user *shmaddr, int shmflg, unsigned long *addr,
unsigned long shmlba); unsigned long shmlba);
extern int is_file_shm_hugepages(struct file *file); extern int is_file_shm_hugepages(struct file *file);
extern void exit_shm(struct task_struct *task); void exit_shm(struct task_struct *task);
#define shm_init_task(task) INIT_LIST_HEAD(&(task)->sysvshm.shm_clist)
#else #else
struct sysv_shm {
/* empty */
};
static inline long do_shmat(int shmid, char __user *shmaddr, static inline long do_shmat(int shmid, char __user *shmaddr,
int shmflg, unsigned long *addr, int shmflg, unsigned long *addr,
unsigned long shmlba) unsigned long shmlba)
...@@ -62,6 +73,9 @@ static inline int is_file_shm_hugepages(struct file *file) ...@@ -62,6 +73,9 @@ static inline int is_file_shm_hugepages(struct file *file)
static inline void exit_shm(struct task_struct *task) static inline void exit_shm(struct task_struct *task)
{ {
} }
static inline void shm_init_task(struct task_struct *task)
{
}
#endif #endif
#endif /* _LINUX_SHM_H_ */ #endif /* _LINUX_SHM_H_ */
...@@ -178,6 +178,7 @@ static void shm_rcu_free(struct rcu_head *head) ...@@ -178,6 +178,7 @@ static void shm_rcu_free(struct rcu_head *head)
static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s) static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s)
{ {
list_del(&s->shm_clist);
ipc_rmid(&shm_ids(ns), &s->shm_perm); ipc_rmid(&shm_ids(ns), &s->shm_perm);
} }
...@@ -268,14 +269,10 @@ static void shm_close(struct vm_area_struct *vma) ...@@ -268,14 +269,10 @@ static void shm_close(struct vm_area_struct *vma)
} }
/* Called with ns->shm_ids(ns).rwsem locked */ /* Called with ns->shm_ids(ns).rwsem locked */
static int shm_try_destroy_current(int id, void *p, void *data) static void shm_mark_orphan(struct shmid_kernel *shp, struct ipc_namespace *ns)
{ {
struct ipc_namespace *ns = data; if (WARN_ON(shp->shm_creator != current)) /* Remove me when it works */
struct kern_ipc_perm *ipcp = p; return;
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 * Mark it as orphaned to destroy the segment when
...@@ -289,13 +286,12 @@ static int shm_try_destroy_current(int id, void *p, void *data) ...@@ -289,13 +286,12 @@ static int shm_try_destroy_current(int id, void *p, void *data)
* is not set, it shouldn't be deleted here. * is not set, it shouldn't be deleted here.
*/ */
if (!ns->shm_rmid_forced) if (!ns->shm_rmid_forced)
return 0; return;
if (shm_may_destroy(ns, shp)) { if (shm_may_destroy(ns, shp)) {
shm_lock_by_ptr(shp); shm_lock_by_ptr(shp);
shm_destroy(ns, shp); shm_destroy(ns, shp);
} }
return 0;
} }
/* Called with ns->shm_ids(ns).rwsem locked */ /* Called with ns->shm_ids(ns).rwsem locked */
...@@ -333,14 +329,17 @@ void shm_destroy_orphaned(struct ipc_namespace *ns) ...@@ -333,14 +329,17 @@ void shm_destroy_orphaned(struct ipc_namespace *ns)
void exit_shm(struct task_struct *task) void exit_shm(struct task_struct *task)
{ {
struct ipc_namespace *ns = task->nsproxy->ipc_ns; struct ipc_namespace *ns = task->nsproxy->ipc_ns;
struct shmid_kernel *shp, *n;
if (shm_ids(ns).in_use == 0) if (shm_ids(ns).in_use == 0)
return; return;
/* Destroy all already created segments, but not mapped yet */ /* Destroy all already created segments, but not mapped yet */
down_write(&shm_ids(ns).rwsem); down_write(&shm_ids(ns).rwsem);
if (shm_ids(ns).in_use) list_for_each_entry_safe(shp, n, &task->sysvshm.shm_clist, shm_clist)
idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_current, ns); shm_mark_orphan(shp, ns);
/* remove the list head from any segments still attached */
list_del(&task->sysvshm.shm_clist);
up_write(&shm_ids(ns).rwsem); up_write(&shm_ids(ns).rwsem);
} }
...@@ -561,6 +560,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params) ...@@ -561,6 +560,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
shp->shm_nattch = 0; shp->shm_nattch = 0;
shp->shm_file = file; shp->shm_file = file;
shp->shm_creator = current; shp->shm_creator = current;
list_add(&shp->shm_clist, &current->sysvshm.shm_clist);
/* /*
* shmid gets reported as "inode#" in /proc/pid/maps. * shmid gets reported as "inode#" in /proc/pid/maps.
......
...@@ -1362,6 +1362,7 @@ static struct task_struct *copy_process(unsigned long clone_flags, ...@@ -1362,6 +1362,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
if (retval) if (retval)
goto bad_fork_cleanup_policy; goto bad_fork_cleanup_policy;
/* copy all the process information */ /* copy all the process information */
shm_init_task(p);
retval = copy_semundo(clone_flags, p); retval = copy_semundo(clone_flags, p);
if (retval) if (retval)
goto bad_fork_cleanup_audit; goto bad_fork_cleanup_audit;
...@@ -1913,6 +1914,11 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags) ...@@ -1913,6 +1914,11 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)
*/ */
exit_sem(current); exit_sem(current);
} }
if (unshare_flags & CLONE_NEWIPC) {
/* Orphan segments in old ns (see sem above). */
exit_shm(current);
shm_init_task(current);
}
if (new_nsproxy) if (new_nsproxy)
switch_task_namespaces(current, new_nsproxy); switch_task_namespaces(current, new_nsproxy);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment