public inbox for linux-ia64@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero
@ 2006-02-01 19:57 Cliff Wickman
  2006-04-17 21:24 ` Stephane Eranian
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Cliff Wickman @ 2006-02-01 19:57 UTC (permalink / raw)
  To: linux-ia64



The problem occurs during exit processing when a task has
done a pfm_context_create() (and pfm_smpl_buffer_alloc()).

To trigger the problem requires:
 - that the task be interrupted (so that it does not unmap its smpl buffer)
 - that there is another process accessing its mm_struct

At exit:
  - sets its task->mm to null
  - calls mmput(). But the task's vmas are not unmapped because the mm_users
    count is still 1
    though pfm_flush() is unable to unmap the buffer vma, but it does
    "put" the buffer pages when it frees the kernel mapping (vm_struct).
  - closes the pfm file with pfm_flush() and pfm_close(), which un-reserve
    the smpl buffer pages, but the buffer vma is not released.

When that external task does the final mmput() on this mm_struct,
  the sampling buffer's vma is unmapped. This does the second put on its
  pages and trips the BUG_ON(page_count(p) = 0) in put_page_testzero(p)

This patch maintains a list of active pfm tasks and their mm_structs.
The list allows pfm_flush() to locate the exiting task's mm_struct so that
it can unmap the vma.
(this could be done without a list, but would require another field in the
 task_struct)
(this fix is not necessary for Stephane Eranian's perfmon2)

Diffed against 2.6.15-rc6

Signed-off-by: Cliff Wickman <cpw@sgi.com>
---

 arch/ia64/kernel/perfmon.c |  138 +++++++++++++++++++++++++++++++++++++++++----
 kernel/exit.c              |   10 +++
 2 files changed, 137 insertions(+), 11 deletions(-)

Index: linux-2.6.15-rc6/kernel/exit.c
=================================--- linux-2.6.15-rc6.orig/kernel/exit.c
+++ linux-2.6.15-rc6/kernel/exit.c
@@ -38,6 +38,11 @@
 extern void sem_exit (void);
 extern struct task_struct *child_reaper;
 
+#ifdef CONFIG_PERFMON
+void pfm_remove_task_from_list(struct task_struct *);
+extern int pfm_check_list;
+#endif
+
 int getrusage(struct task_struct *, int, struct rusage __user *);
 
 static void exit_mm(struct task_struct * tsk);
@@ -527,6 +532,11 @@ static void exit_mm(struct task_struct *
 	up_read(&mm->mmap_sem);
 	enter_lazy_tlb(mm, current);
 	task_unlock(tsk);
+#ifdef CONFIG_PERFMON
+	if (atomic_read(&mm->mm_users) = 1 && pfm_check_list) {
+		pfm_remove_task_from_list(current);
+	}
+#endif
 	mmput(mm);
 }
 
Index: linux-2.6.15-rc6/arch/ia64/kernel/perfmon.c
=================================--- linux-2.6.15-rc6.orig/arch/ia64/kernel/perfmon.c
+++ linux-2.6.15-rc6/arch/ia64/kernel/perfmon.c
@@ -535,6 +535,21 @@ static int pfm_flush(struct file *filp);
 #define pfm_get_cpu_var(v)		__ia64_per_cpu_var(v)
 #define pfm_get_cpu_data(a,b)		per_cpu(a, b)
 
+struct mm_struct	*pfm_lookup_mm(void);
+void			pfm_remove_task_from_list(struct task_struct *);
+void			pfm_record_tm(struct task_struct *);
+/* global:  for exit_mmap() to call us back when the mm_struct is removed */
+int			pfm_check_list=0;
+int			pfm_num_in_list;
+EXPORT_SYMBOL(pfm_check_list);
+struct pfm_task_mm {
+	struct list_head	pfm_tm_list;
+	struct task_struct	*pfm_taskp;
+ 	struct mm_struct	*pfm_mm;
+};
+rwlock_t		pfm_tmlist_lock;
+struct list_head	pfm_tm_list_head;
+
 static inline void
 pfm_put_task(struct task_struct *task)
 {
@@ -1394,13 +1409,13 @@ pfm_unreserve_session(pfm_context_t *ctx
  * a PROTECT_CTX() section.
  */
 static int
-pfm_remove_smpl_mapping(struct task_struct *task, void *vaddr, unsigned long size)
+pfm_remove_smpl_mapping(struct task_struct *task, struct mm_struct *mm, void *vaddr, unsigned long size)
 {
 	int r;
 
 	/* sanity checks */
-	if (task->mm = NULL || size = 0UL || vaddr = NULL) {
-		printk(KERN_ERR "perfmon: pfm_remove_smpl_mapping [%d] invalid context mm=%p\n", task->pid, task->mm);
+	if (mm = NULL || size = 0UL || vaddr = NULL) {
+		printk(KERN_ERR "perfmon: pfm_remove_smpl_mapping [%d] invalid context mm=%p\n", task->pid, mm);
 		return -EINVAL;
 	}
 
@@ -1409,13 +1424,13 @@ pfm_remove_smpl_mapping(struct task_stru
 	/*
 	 * does the actual unmapping
 	 */
-	down_write(&task->mm->mmap_sem);
+	down_write(&mm->mmap_sem);
 
 	DPRINT(("down_write done smpl_vaddr=%p size=%lu\n", vaddr, size));
 
-	r = pfm_do_munmap(task->mm, (unsigned long)vaddr, size, 0);
+	r = pfm_do_munmap(mm, (unsigned long)vaddr, size, 0);
 
-	up_write(&task->mm->mmap_sem);
+	up_write(&mm->mmap_sem);
 	if (r !=0) {
 		printk(KERN_ERR "perfmon: [%d] unable to unmap sampling buffer @%p size=%lu\n", task->pid, vaddr, size);
 	}
@@ -1493,6 +1508,12 @@ init_pfm_fs(void)
 		else
 			err = 0;
 	}
+
+	/* initialize task/mm list */
+	rwlock_init(&pfm_tmlist_lock);
+	INIT_LIST_HEAD(&pfm_tm_list_head);
+	pfm_num_in_list = 0;
+
 	return err;
 }
 
@@ -1773,6 +1794,7 @@ pfm_flush(struct file *filp)
 {
 	pfm_context_t *ctx;
 	struct task_struct *task;
+	struct mm_struct *mm;
 	struct pt_regs *regs;
 	unsigned long flags;
 	unsigned long smpl_buf_size = 0UL;
@@ -1876,11 +1898,14 @@ pfm_flush(struct file *filp)
 	 * ctx_smpl_vaddr must never be cleared because it is needed
 	 * by every task with access to the context
 	 *
-	 * When called from do_exit(), the mm context is gone already, therefore
-	 * mm is NULL, i.e., the VMA is already gone  and we do not have to
-	 * do anything here
+	 * When called from do_exit(), the mm context may still be present
+	 * if mm_users is not zero.  But the task mm pointer is set to null
+	 * at exit, so we have to use active_mm here.
+	 * (we are depending on the assumption that active_mm is not cleared
+	 *  at exit)
 	 */
-	if (ctx->ctx_smpl_vaddr && current->mm) {
+	mm = pfm_lookup_mm();
+	if (ctx->ctx_smpl_vaddr && mm && current->active_mm=mm) {
 		smpl_buf_vaddr = ctx->ctx_smpl_vaddr;
 		smpl_buf_size  = ctx->ctx_smpl_size;
 	}
@@ -1893,7 +1918,7 @@ pfm_flush(struct file *filp)
 	 * because some VM function reenables interrupts.
 	 *
 	 */
-	if (smpl_buf_vaddr) pfm_remove_smpl_mapping(current, smpl_buf_vaddr, smpl_buf_size);
+	if (smpl_buf_vaddr) pfm_remove_smpl_mapping(current, mm, smpl_buf_vaddr, smpl_buf_size);
 
 	return 0;
 }
@@ -1931,6 +1956,12 @@ pfm_close(struct inode *inode, struct fi
 		DPRINT(("bad magic\n"));
 		return -EBADF;
 	}
+
+	/* It is possible that this task went thru do_exit with
+	   mm_users > 1, in which case this task was not removed from
+           the list of pfm tasks with valid mm_struct's.  So make sure the
+	   task is removed from the list. */
+	pfm_remove_task_from_list(current);
 	
 	ctx = (pfm_context_t *)filp->private_data;
 	if (ctx = NULL) {
@@ -4333,6 +4364,9 @@ pfm_context_load(pfm_context_t *ctx, voi
 	 */
 	ctx->ctx_task = task;
 
+	/* record the task/mm for this task, as it now has a context */
+	pfm_record_tm(task);
+
 	if (is_system) {
 		/*
 		 * we load as stopped
@@ -6837,6 +6871,88 @@ pfm_inherit(struct task_struct *task, st
 	 * the psr bits are already set properly in copy_threads()
 	 */
 }
+
+/*
+ * record task_struct and mm_struct in the list of valid pfm mm_structs
+ */
+void
+pfm_record_tm(struct task_struct *task)
+{
+	struct pfm_task_mm *tm;
+	struct list_head *this, *next;
+
+	read_lock(&pfm_tmlist_lock);
+	list_for_each_safe(this, next, &pfm_tm_list_head) {
+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
+		if (tm->pfm_taskp = task) {
+			/* don't record it twice */
+			read_unlock(&pfm_tmlist_lock);
+			return;
+		}
+	}
+
+	tm = kmalloc(sizeof(struct pfm_task_mm), GFP_KERNEL);
+	if (!tm) {
+		read_unlock(&pfm_tmlist_lock);
+		return;
+	}
+	tm->pfm_taskp = task;
+	tm->pfm_mm = task->mm;
+	list_add_tail(&tm->pfm_tm_list, &pfm_tm_list_head);
+	pfm_num_in_list++;
+	read_unlock(&pfm_tmlist_lock);
+	return;
+}
+
+/*
+ * look up the current task in the list of valid pfm mm_structs
+ */
+struct mm_struct *
+pfm_lookup_mm()
+{
+	struct pfm_task_mm *tm;
+	struct list_head *this, *next;
+
+	read_lock(&pfm_tmlist_lock);
+	list_for_each_safe(this, next, &pfm_tm_list_head) {
+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
+		if (current = tm->pfm_taskp) {
+			read_unlock(&pfm_tmlist_lock);
+			return tm->pfm_mm;
+		}
+	}
+	read_unlock(&pfm_tmlist_lock);
+	return NULL;
+}
+
+/*
+ * called from exit_mmap when the task's mm_struct is removed
+ * (so that we do not use active_mm when it might point a freed mm_struct
+ *  (or another task's mm_struct))
+ */
+void
+pfm_remove_task_from_list(struct task_struct *task)
+{
+	struct pfm_task_mm *tm;
+	struct list_head *this, *next;
+
+	write_lock(&pfm_tmlist_lock);
+	list_for_each_safe(this, next, &pfm_tm_list_head) {
+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
+		if (current = tm->pfm_taskp) {
+			list_del(this);
+			kfree(tm);
+			pfm_num_in_list--;
+			/* clear the flag when all have been removed */
+			if (pfm_num_in_list = 0)
+				pfm_check_list=0;
+			write_unlock(&pfm_tmlist_lock);
+			return;
+		}
+	}
+	write_unlock(&pfm_tmlist_lock);
+	return;
+}
 #else  /* !CONFIG_PERFMON */
 asmlinkage long
 sys_perfmonctl (int fd, int cmd, void *arg, int count)

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero
  2006-02-01 19:57 [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero Cliff Wickman
@ 2006-04-17 21:24 ` Stephane Eranian
  2006-04-17 21:24 ` Keshavamurthy, Anil S
  2006-06-02  6:32 ` dann frazier
  2 siblings, 0 replies; 4+ messages in thread
From: Stephane Eranian @ 2006-04-17 21:24 UTC (permalink / raw)
  To: linux-ia64

Anil,

This patch does not seem to be needed anymore for 2.6.16. Neither
Cliff nor I were able to reproduce with that versionm. Cliff mentioned
that he wanted to try and get his patch into SLES9 which uses an
older version of ther kernel.

On Mon, Apr 17, 2006 at 02:24:28PM -0700, Keshavamurthy, Anil S wrote:
> I did not see this patch going any where? 
> 
> Looks like this patch needs Stephan's blessings.
> Stephan, can you ACK on this one.
> 
> Thanks,
> Anil Keshavamurthy
> 
> >-----Original Message-----
> >From: linux-ia64-owner@vger.kernel.org 
> >[mailto:linux-ia64-owner@vger.kernel.org] On Behalf Of Cliff Wickman
> >Sent: Wednesday, February 01, 2006 11:57 AM
> >To: linux-ia64@vger.kernel.org
> >Subject: [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero
> >
> >
> >
> >The problem occurs during exit processing when a task has
> >done a pfm_context_create() (and pfm_smpl_buffer_alloc()).
> >
> >To trigger the problem requires:
> > - that the task be interrupted (so that it does not unmap its 
> >smpl buffer)
> > - that there is another process accessing its mm_struct
> >
> >At exit:
> >  - sets its task->mm to null
> >  - calls mmput(). But the task's vmas are not unmapped 
> >because the mm_users
> >    count is still 1
> >    though pfm_flush() is unable to unmap the buffer vma, but it does
> >    "put" the buffer pages when it frees the kernel mapping 
> >(vm_struct).
> >  - closes the pfm file with pfm_flush() and pfm_close(), 
> >which un-reserve
> >    the smpl buffer pages, but the buffer vma is not released.
> >
> >When that external task does the final mmput() on this mm_struct,
> >  the sampling buffer's vma is unmapped. This does the second 
> >put on its
> >  pages and trips the BUG_ON(page_count(p) = 0) in 
> >put_page_testzero(p)
> >
> >This patch maintains a list of active pfm tasks and their mm_structs.
> >The list allows pfm_flush() to locate the exiting task's 
> >mm_struct so that
> >it can unmap the vma.
> >(this could be done without a list, but would require another 
> >field in the
> > task_struct)
> >(this fix is not necessary for Stephane Eranian's perfmon2)
> >
> >Diffed against 2.6.15-rc6
> >
> >Signed-off-by: Cliff Wickman <cpw@sgi.com>
> >---
> >
> > arch/ia64/kernel/perfmon.c |  138 
> >+++++++++++++++++++++++++++++++++++++++++----
> > kernel/exit.c              |   10 +++
> > 2 files changed, 137 insertions(+), 11 deletions(-)
> >
> >Index: linux-2.6.15-rc6/kernel/exit.c
> >=================================> >--- linux-2.6.15-rc6.orig/kernel/exit.c
> >+++ linux-2.6.15-rc6/kernel/exit.c
> >@@ -38,6 +38,11 @@
> > extern void sem_exit (void);
> > extern struct task_struct *child_reaper;
> > 
> >+#ifdef CONFIG_PERFMON
> >+void pfm_remove_task_from_list(struct task_struct *);
> >+extern int pfm_check_list;
> >+#endif
> >+
> > int getrusage(struct task_struct *, int, struct rusage __user *);
> > 
> > static void exit_mm(struct task_struct * tsk);
> >@@ -527,6 +532,11 @@ static void exit_mm(struct task_struct *
> > 	up_read(&mm->mmap_sem);
> > 	enter_lazy_tlb(mm, current);
> > 	task_unlock(tsk);
> >+#ifdef CONFIG_PERFMON
> >+	if (atomic_read(&mm->mm_users) = 1 && pfm_check_list) {
> >+		pfm_remove_task_from_list(current);
> >+	}
> >+#endif
> > 	mmput(mm);
> > }
> > 
> >Index: linux-2.6.15-rc6/arch/ia64/kernel/perfmon.c
> >=================================> >--- linux-2.6.15-rc6.orig/arch/ia64/kernel/perfmon.c
> >+++ linux-2.6.15-rc6/arch/ia64/kernel/perfmon.c
> >@@ -535,6 +535,21 @@ static int pfm_flush(struct file *filp);
> > #define pfm_get_cpu_var(v)		__ia64_per_cpu_var(v)
> > #define pfm_get_cpu_data(a,b)		per_cpu(a, b)
> > 
> >+struct mm_struct	*pfm_lookup_mm(void);
> >+void			pfm_remove_task_from_list(struct task_struct *);
> >+void			pfm_record_tm(struct task_struct *);
> >+/* global:  for exit_mmap() to call us back when the 
> >mm_struct is removed */
> >+int			pfm_check_list=0;
> >+int			pfm_num_in_list;
> >+EXPORT_SYMBOL(pfm_check_list);
> >+struct pfm_task_mm {
> >+	struct list_head	pfm_tm_list;
> >+	struct task_struct	*pfm_taskp;
> >+ 	struct mm_struct	*pfm_mm;
> >+};
> >+rwlock_t		pfm_tmlist_lock;
> >+struct list_head	pfm_tm_list_head;
> >+
> > static inline void
> > pfm_put_task(struct task_struct *task)
> > {
> >@@ -1394,13 +1409,13 @@ pfm_unreserve_session(pfm_context_t *ctx
> >  * a PROTECT_CTX() section.
> >  */
> > static int
> >-pfm_remove_smpl_mapping(struct task_struct *task, void 
> >*vaddr, unsigned long size)
> >+pfm_remove_smpl_mapping(struct task_struct *task, struct 
> >mm_struct *mm, void *vaddr, unsigned long size)
> > {
> > 	int r;
> > 
> > 	/* sanity checks */
> >-	if (task->mm = NULL || size = 0UL || vaddr = NULL) {
> >-		printk(KERN_ERR "perfmon: 
> >pfm_remove_smpl_mapping [%d] invalid context mm=%p\n", 
> >task->pid, task->mm);
> >+	if (mm = NULL || size = 0UL || vaddr = NULL) {
> >+		printk(KERN_ERR "perfmon: 
> >pfm_remove_smpl_mapping [%d] invalid context mm=%p\n", task->pid, mm);
> > 		return -EINVAL;
> > 	}
> > 
> >@@ -1409,13 +1424,13 @@ pfm_remove_smpl_mapping(struct task_stru
> > 	/*
> > 	 * does the actual unmapping
> > 	 */
> >-	down_write(&task->mm->mmap_sem);
> >+	down_write(&mm->mmap_sem);
> > 
> > 	DPRINT(("down_write done smpl_vaddr=%p size=%lu\n", 
> >vaddr, size));
> > 
> >-	r = pfm_do_munmap(task->mm, (unsigned long)vaddr, size, 0);
> >+	r = pfm_do_munmap(mm, (unsigned long)vaddr, size, 0);
> > 
> >-	up_write(&task->mm->mmap_sem);
> >+	up_write(&mm->mmap_sem);
> > 	if (r !=0) {
> > 		printk(KERN_ERR "perfmon: [%d] unable to unmap 
> >sampling buffer @%p size=%lu\n", task->pid, vaddr, size);
> > 	}
> >@@ -1493,6 +1508,12 @@ init_pfm_fs(void)
> > 		else
> > 			err = 0;
> > 	}
> >+
> >+	/* initialize task/mm list */
> >+	rwlock_init(&pfm_tmlist_lock);
> >+	INIT_LIST_HEAD(&pfm_tm_list_head);
> >+	pfm_num_in_list = 0;
> >+
> > 	return err;
> > }
> > 
> >@@ -1773,6 +1794,7 @@ pfm_flush(struct file *filp)
> > {
> > 	pfm_context_t *ctx;
> > 	struct task_struct *task;
> >+	struct mm_struct *mm;
> > 	struct pt_regs *regs;
> > 	unsigned long flags;
> > 	unsigned long smpl_buf_size = 0UL;
> >@@ -1876,11 +1898,14 @@ pfm_flush(struct file *filp)
> > 	 * ctx_smpl_vaddr must never be cleared because it is needed
> > 	 * by every task with access to the context
> > 	 *
> >-	 * When called from do_exit(), the mm context is gone 
> >already, therefore
> >-	 * mm is NULL, i.e., the VMA is already gone  and we do 
> >not have to
> >-	 * do anything here
> >+	 * When called from do_exit(), the mm context may still 
> >be present
> >+	 * if mm_users is not zero.  But the task mm pointer is 
> >set to null
> >+	 * at exit, so we have to use active_mm here.
> >+	 * (we are depending on the assumption that active_mm 
> >is not cleared
> >+	 *  at exit)
> > 	 */
> >-	if (ctx->ctx_smpl_vaddr && current->mm) {
> >+	mm = pfm_lookup_mm();
> >+	if (ctx->ctx_smpl_vaddr && mm && current->active_mm=mm) {
> > 		smpl_buf_vaddr = ctx->ctx_smpl_vaddr;
> > 		smpl_buf_size  = ctx->ctx_smpl_size;
> > 	}
> >@@ -1893,7 +1918,7 @@ pfm_flush(struct file *filp)
> > 	 * because some VM function reenables interrupts.
> > 	 *
> > 	 */
> >-	if (smpl_buf_vaddr) pfm_remove_smpl_mapping(current, 
> >smpl_buf_vaddr, smpl_buf_size);
> >+	if (smpl_buf_vaddr) pfm_remove_smpl_mapping(current, 
> >mm, smpl_buf_vaddr, smpl_buf_size);
> > 
> > 	return 0;
> > }
> >@@ -1931,6 +1956,12 @@ pfm_close(struct inode *inode, struct fi
> > 		DPRINT(("bad magic\n"));
> > 		return -EBADF;
> > 	}
> >+
> >+	/* It is possible that this task went thru do_exit with
> >+	   mm_users > 1, in which case this task was not removed from
> >+           the list of pfm tasks with valid mm_struct's.  So 
> >make sure the
> >+	   task is removed from the list. */
> >+	pfm_remove_task_from_list(current);
> > 	
> > 	ctx = (pfm_context_t *)filp->private_data;
> > 	if (ctx = NULL) {
> >@@ -4333,6 +4364,9 @@ pfm_context_load(pfm_context_t *ctx, voi
> > 	 */
> > 	ctx->ctx_task = task;
> > 
> >+	/* record the task/mm for this task, as it now has a context */
> >+	pfm_record_tm(task);
> >+
> > 	if (is_system) {
> > 		/*
> > 		 * we load as stopped
> >@@ -6837,6 +6871,88 @@ pfm_inherit(struct task_struct *task, st
> > 	 * the psr bits are already set properly in copy_threads()
> > 	 */
> > }
> >+
> >+/*
> >+ * record task_struct and mm_struct in the list of valid pfm 
> >mm_structs
> >+ */
> >+void
> >+pfm_record_tm(struct task_struct *task)
> >+{
> >+	struct pfm_task_mm *tm;
> >+	struct list_head *this, *next;
> >+
> >+	read_lock(&pfm_tmlist_lock);
> >+	list_for_each_safe(this, next, &pfm_tm_list_head) {
> >+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
> >+		if (tm->pfm_taskp = task) {
> >+			/* don't record it twice */
> >+			read_unlock(&pfm_tmlist_lock);
> >+			return;
> >+		}
> >+	}
> >+
> >+	tm = kmalloc(sizeof(struct pfm_task_mm), GFP_KERNEL);
> >+	if (!tm) {
> >+		read_unlock(&pfm_tmlist_lock);
> >+		return;
> >+	}
> >+	tm->pfm_taskp = task;
> >+	tm->pfm_mm = task->mm;
> >+	list_add_tail(&tm->pfm_tm_list, &pfm_tm_list_head);
> >+	pfm_num_in_list++;
> >+	read_unlock(&pfm_tmlist_lock);
> >+	return;
> >+}
> >+
> >+/*
> >+ * look up the current task in the list of valid pfm mm_structs
> >+ */
> >+struct mm_struct *
> >+pfm_lookup_mm()
> >+{
> >+	struct pfm_task_mm *tm;
> >+	struct list_head *this, *next;
> >+
> >+	read_lock(&pfm_tmlist_lock);
> >+	list_for_each_safe(this, next, &pfm_tm_list_head) {
> >+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
> >+		if (current = tm->pfm_taskp) {
> >+			read_unlock(&pfm_tmlist_lock);
> >+			return tm->pfm_mm;
> >+		}
> >+	}
> >+	read_unlock(&pfm_tmlist_lock);
> >+	return NULL;
> >+}
> >+
> >+/*
> >+ * called from exit_mmap when the task's mm_struct is removed
> >+ * (so that we do not use active_mm when it might point a 
> >freed mm_struct
> >+ *  (or another task's mm_struct))
> >+ */
> >+void
> >+pfm_remove_task_from_list(struct task_struct *task)
> >+{
> >+	struct pfm_task_mm *tm;
> >+	struct list_head *this, *next;
> >+
> >+	write_lock(&pfm_tmlist_lock);
> >+	list_for_each_safe(this, next, &pfm_tm_list_head) {
> >+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
> >+		if (current = tm->pfm_taskp) {
> >+			list_del(this);
> >+			kfree(tm);
> >+			pfm_num_in_list--;
> >+			/* clear the flag when all have been removed */
> >+			if (pfm_num_in_list = 0)
> >+				pfm_check_list=0;
> >+			write_unlock(&pfm_tmlist_lock);
> >+			return;
> >+		}
> >+	}
> >+	write_unlock(&pfm_tmlist_lock);
> >+	return;
> >+}
> > #else  /* !CONFIG_PERFMON */
> > asmlinkage long
> > sys_perfmonctl (int fd, int cmd, void *arg, int count)
> >-
> >To unsubscribe from this list: send the line "unsubscribe 
> >linux-ia64" in
> >the body of a message to majordomo@vger.kernel.org
> >More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >

-- 

-Stephane

^ permalink raw reply	[flat|nested] 4+ messages in thread

* RE: [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero
  2006-02-01 19:57 [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero Cliff Wickman
  2006-04-17 21:24 ` Stephane Eranian
@ 2006-04-17 21:24 ` Keshavamurthy, Anil S
  2006-06-02  6:32 ` dann frazier
  2 siblings, 0 replies; 4+ messages in thread
From: Keshavamurthy, Anil S @ 2006-04-17 21:24 UTC (permalink / raw)
  To: linux-ia64

I did not see this patch going any where? 

Looks like this patch needs Stephan's blessings.
Stephan, can you ACK on this one.

Thanks,
Anil Keshavamurthy

>-----Original Message-----
>From: linux-ia64-owner@vger.kernel.org 
>[mailto:linux-ia64-owner@vger.kernel.org] On Behalf Of Cliff Wickman
>Sent: Wednesday, February 01, 2006 11:57 AM
>To: linux-ia64@vger.kernel.org
>Subject: [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero
>
>
>
>The problem occurs during exit processing when a task has
>done a pfm_context_create() (and pfm_smpl_buffer_alloc()).
>
>To trigger the problem requires:
> - that the task be interrupted (so that it does not unmap its 
>smpl buffer)
> - that there is another process accessing its mm_struct
>
>At exit:
>  - sets its task->mm to null
>  - calls mmput(). But the task's vmas are not unmapped 
>because the mm_users
>    count is still 1
>    though pfm_flush() is unable to unmap the buffer vma, but it does
>    "put" the buffer pages when it frees the kernel mapping 
>(vm_struct).
>  - closes the pfm file with pfm_flush() and pfm_close(), 
>which un-reserve
>    the smpl buffer pages, but the buffer vma is not released.
>
>When that external task does the final mmput() on this mm_struct,
>  the sampling buffer's vma is unmapped. This does the second 
>put on its
>  pages and trips the BUG_ON(page_count(p) = 0) in 
>put_page_testzero(p)
>
>This patch maintains a list of active pfm tasks and their mm_structs.
>The list allows pfm_flush() to locate the exiting task's 
>mm_struct so that
>it can unmap the vma.
>(this could be done without a list, but would require another 
>field in the
> task_struct)
>(this fix is not necessary for Stephane Eranian's perfmon2)
>
>Diffed against 2.6.15-rc6
>
>Signed-off-by: Cliff Wickman <cpw@sgi.com>
>---
>
> arch/ia64/kernel/perfmon.c |  138 
>+++++++++++++++++++++++++++++++++++++++++----
> kernel/exit.c              |   10 +++
> 2 files changed, 137 insertions(+), 11 deletions(-)
>
>Index: linux-2.6.15-rc6/kernel/exit.c
>=================================>--- linux-2.6.15-rc6.orig/kernel/exit.c
>+++ linux-2.6.15-rc6/kernel/exit.c
>@@ -38,6 +38,11 @@
> extern void sem_exit (void);
> extern struct task_struct *child_reaper;
> 
>+#ifdef CONFIG_PERFMON
>+void pfm_remove_task_from_list(struct task_struct *);
>+extern int pfm_check_list;
>+#endif
>+
> int getrusage(struct task_struct *, int, struct rusage __user *);
> 
> static void exit_mm(struct task_struct * tsk);
>@@ -527,6 +532,11 @@ static void exit_mm(struct task_struct *
> 	up_read(&mm->mmap_sem);
> 	enter_lazy_tlb(mm, current);
> 	task_unlock(tsk);
>+#ifdef CONFIG_PERFMON
>+	if (atomic_read(&mm->mm_users) = 1 && pfm_check_list) {
>+		pfm_remove_task_from_list(current);
>+	}
>+#endif
> 	mmput(mm);
> }
> 
>Index: linux-2.6.15-rc6/arch/ia64/kernel/perfmon.c
>=================================>--- linux-2.6.15-rc6.orig/arch/ia64/kernel/perfmon.c
>+++ linux-2.6.15-rc6/arch/ia64/kernel/perfmon.c
>@@ -535,6 +535,21 @@ static int pfm_flush(struct file *filp);
> #define pfm_get_cpu_var(v)		__ia64_per_cpu_var(v)
> #define pfm_get_cpu_data(a,b)		per_cpu(a, b)
> 
>+struct mm_struct	*pfm_lookup_mm(void);
>+void			pfm_remove_task_from_list(struct task_struct *);
>+void			pfm_record_tm(struct task_struct *);
>+/* global:  for exit_mmap() to call us back when the 
>mm_struct is removed */
>+int			pfm_check_list=0;
>+int			pfm_num_in_list;
>+EXPORT_SYMBOL(pfm_check_list);
>+struct pfm_task_mm {
>+	struct list_head	pfm_tm_list;
>+	struct task_struct	*pfm_taskp;
>+ 	struct mm_struct	*pfm_mm;
>+};
>+rwlock_t		pfm_tmlist_lock;
>+struct list_head	pfm_tm_list_head;
>+
> static inline void
> pfm_put_task(struct task_struct *task)
> {
>@@ -1394,13 +1409,13 @@ pfm_unreserve_session(pfm_context_t *ctx
>  * a PROTECT_CTX() section.
>  */
> static int
>-pfm_remove_smpl_mapping(struct task_struct *task, void 
>*vaddr, unsigned long size)
>+pfm_remove_smpl_mapping(struct task_struct *task, struct 
>mm_struct *mm, void *vaddr, unsigned long size)
> {
> 	int r;
> 
> 	/* sanity checks */
>-	if (task->mm = NULL || size = 0UL || vaddr = NULL) {
>-		printk(KERN_ERR "perfmon: 
>pfm_remove_smpl_mapping [%d] invalid context mm=%p\n", 
>task->pid, task->mm);
>+	if (mm = NULL || size = 0UL || vaddr = NULL) {
>+		printk(KERN_ERR "perfmon: 
>pfm_remove_smpl_mapping [%d] invalid context mm=%p\n", task->pid, mm);
> 		return -EINVAL;
> 	}
> 
>@@ -1409,13 +1424,13 @@ pfm_remove_smpl_mapping(struct task_stru
> 	/*
> 	 * does the actual unmapping
> 	 */
>-	down_write(&task->mm->mmap_sem);
>+	down_write(&mm->mmap_sem);
> 
> 	DPRINT(("down_write done smpl_vaddr=%p size=%lu\n", 
>vaddr, size));
> 
>-	r = pfm_do_munmap(task->mm, (unsigned long)vaddr, size, 0);
>+	r = pfm_do_munmap(mm, (unsigned long)vaddr, size, 0);
> 
>-	up_write(&task->mm->mmap_sem);
>+	up_write(&mm->mmap_sem);
> 	if (r !=0) {
> 		printk(KERN_ERR "perfmon: [%d] unable to unmap 
>sampling buffer @%p size=%lu\n", task->pid, vaddr, size);
> 	}
>@@ -1493,6 +1508,12 @@ init_pfm_fs(void)
> 		else
> 			err = 0;
> 	}
>+
>+	/* initialize task/mm list */
>+	rwlock_init(&pfm_tmlist_lock);
>+	INIT_LIST_HEAD(&pfm_tm_list_head);
>+	pfm_num_in_list = 0;
>+
> 	return err;
> }
> 
>@@ -1773,6 +1794,7 @@ pfm_flush(struct file *filp)
> {
> 	pfm_context_t *ctx;
> 	struct task_struct *task;
>+	struct mm_struct *mm;
> 	struct pt_regs *regs;
> 	unsigned long flags;
> 	unsigned long smpl_buf_size = 0UL;
>@@ -1876,11 +1898,14 @@ pfm_flush(struct file *filp)
> 	 * ctx_smpl_vaddr must never be cleared because it is needed
> 	 * by every task with access to the context
> 	 *
>-	 * When called from do_exit(), the mm context is gone 
>already, therefore
>-	 * mm is NULL, i.e., the VMA is already gone  and we do 
>not have to
>-	 * do anything here
>+	 * When called from do_exit(), the mm context may still 
>be present
>+	 * if mm_users is not zero.  But the task mm pointer is 
>set to null
>+	 * at exit, so we have to use active_mm here.
>+	 * (we are depending on the assumption that active_mm 
>is not cleared
>+	 *  at exit)
> 	 */
>-	if (ctx->ctx_smpl_vaddr && current->mm) {
>+	mm = pfm_lookup_mm();
>+	if (ctx->ctx_smpl_vaddr && mm && current->active_mm=mm) {
> 		smpl_buf_vaddr = ctx->ctx_smpl_vaddr;
> 		smpl_buf_size  = ctx->ctx_smpl_size;
> 	}
>@@ -1893,7 +1918,7 @@ pfm_flush(struct file *filp)
> 	 * because some VM function reenables interrupts.
> 	 *
> 	 */
>-	if (smpl_buf_vaddr) pfm_remove_smpl_mapping(current, 
>smpl_buf_vaddr, smpl_buf_size);
>+	if (smpl_buf_vaddr) pfm_remove_smpl_mapping(current, 
>mm, smpl_buf_vaddr, smpl_buf_size);
> 
> 	return 0;
> }
>@@ -1931,6 +1956,12 @@ pfm_close(struct inode *inode, struct fi
> 		DPRINT(("bad magic\n"));
> 		return -EBADF;
> 	}
>+
>+	/* It is possible that this task went thru do_exit with
>+	   mm_users > 1, in which case this task was not removed from
>+           the list of pfm tasks with valid mm_struct's.  So 
>make sure the
>+	   task is removed from the list. */
>+	pfm_remove_task_from_list(current);
> 	
> 	ctx = (pfm_context_t *)filp->private_data;
> 	if (ctx = NULL) {
>@@ -4333,6 +4364,9 @@ pfm_context_load(pfm_context_t *ctx, voi
> 	 */
> 	ctx->ctx_task = task;
> 
>+	/* record the task/mm for this task, as it now has a context */
>+	pfm_record_tm(task);
>+
> 	if (is_system) {
> 		/*
> 		 * we load as stopped
>@@ -6837,6 +6871,88 @@ pfm_inherit(struct task_struct *task, st
> 	 * the psr bits are already set properly in copy_threads()
> 	 */
> }
>+
>+/*
>+ * record task_struct and mm_struct in the list of valid pfm 
>mm_structs
>+ */
>+void
>+pfm_record_tm(struct task_struct *task)
>+{
>+	struct pfm_task_mm *tm;
>+	struct list_head *this, *next;
>+
>+	read_lock(&pfm_tmlist_lock);
>+	list_for_each_safe(this, next, &pfm_tm_list_head) {
>+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
>+		if (tm->pfm_taskp = task) {
>+			/* don't record it twice */
>+			read_unlock(&pfm_tmlist_lock);
>+			return;
>+		}
>+	}
>+
>+	tm = kmalloc(sizeof(struct pfm_task_mm), GFP_KERNEL);
>+	if (!tm) {
>+		read_unlock(&pfm_tmlist_lock);
>+		return;
>+	}
>+	tm->pfm_taskp = task;
>+	tm->pfm_mm = task->mm;
>+	list_add_tail(&tm->pfm_tm_list, &pfm_tm_list_head);
>+	pfm_num_in_list++;
>+	read_unlock(&pfm_tmlist_lock);
>+	return;
>+}
>+
>+/*
>+ * look up the current task in the list of valid pfm mm_structs
>+ */
>+struct mm_struct *
>+pfm_lookup_mm()
>+{
>+	struct pfm_task_mm *tm;
>+	struct list_head *this, *next;
>+
>+	read_lock(&pfm_tmlist_lock);
>+	list_for_each_safe(this, next, &pfm_tm_list_head) {
>+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
>+		if (current = tm->pfm_taskp) {
>+			read_unlock(&pfm_tmlist_lock);
>+			return tm->pfm_mm;
>+		}
>+	}
>+	read_unlock(&pfm_tmlist_lock);
>+	return NULL;
>+}
>+
>+/*
>+ * called from exit_mmap when the task's mm_struct is removed
>+ * (so that we do not use active_mm when it might point a 
>freed mm_struct
>+ *  (or another task's mm_struct))
>+ */
>+void
>+pfm_remove_task_from_list(struct task_struct *task)
>+{
>+	struct pfm_task_mm *tm;
>+	struct list_head *this, *next;
>+
>+	write_lock(&pfm_tmlist_lock);
>+	list_for_each_safe(this, next, &pfm_tm_list_head) {
>+		tm = list_entry(this, struct pfm_task_mm, pfm_tm_list);
>+		if (current = tm->pfm_taskp) {
>+			list_del(this);
>+			kfree(tm);
>+			pfm_num_in_list--;
>+			/* clear the flag when all have been removed */
>+			if (pfm_num_in_list = 0)
>+				pfm_check_list=0;
>+			write_unlock(&pfm_tmlist_lock);
>+			return;
>+		}
>+	}
>+	write_unlock(&pfm_tmlist_lock);
>+	return;
>+}
> #else  /* !CONFIG_PERFMON */
> asmlinkage long
> sys_perfmonctl (int fd, int cmd, void *arg, int count)
>-
>To unsubscribe from this list: send the line "unsubscribe 
>linux-ia64" in
>the body of a message to majordomo@vger.kernel.org
>More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero
  2006-02-01 19:57 [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero Cliff Wickman
  2006-04-17 21:24 ` Stephane Eranian
  2006-04-17 21:24 ` Keshavamurthy, Anil S
@ 2006-06-02  6:32 ` dann frazier
  2 siblings, 0 replies; 4+ messages in thread
From: dann frazier @ 2006-06-02  6:32 UTC (permalink / raw)
  To: linux-ia64

On Mon, Apr 17, 2006 at 02:24:07PM -0700, Stephane Eranian wrote:
> Anil,
> 
> This patch does not seem to be needed anymore for 2.6.16. Neither
> Cliff nor I were able to reproduce with that versionm. Cliff mentioned
> that he wanted to try and get his patch into SLES9 which uses an
> older version of ther kernel.

Anyone know if this is a problem in 2.4?

-- 
dann frazier | HP Open Source and Linux Organization

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2006-06-02  6:32 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-02-01 19:57 [PATCH 1/1] ia64: perfmon.c trips BUG_ON in put_page_testzero Cliff Wickman
2006-04-17 21:24 ` Stephane Eranian
2006-04-17 21:24 ` Keshavamurthy, Anil S
2006-06-02  6:32 ` dann frazier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox