Skip to contentSkip to conversation

OProfile update

PATCH New John Levon 1 participant 8 messages May 3, 2003

Subsystems (2)
lore
Share & tools
lore

John Levon

author
#0 lore raw

PATCH 1/8 OProfile update

The next 8 patches change the following files :

 arch/alpha/oprofile/Makefile      |    3 +
 arch/alpha/oprofile/common.c      |    2 -
 arch/i386/oprofile/Makefile       |    5 +-
 arch/i386/oprofile/init.c         |   11 ++----
 arch/i386/oprofile/nmi_int.c      |   14 +++----
 arch/i386/oprofile/timer_int.c    |   58
--------------------------------
 arch/parisc/oprofile/Makefile     |    5 +-
 arch/parisc/oprofile/init.c       |    3 -
 arch/parisc/oprofile/timer_int.c  |   58
--------------------------------
 arch/ppc64/oprofile/Makefile      |    5 +-
 arch/ppc64/oprofile/init.c        |    3 -
 arch/ppc64/oprofile/timer_int.c   |   59
---------------------------------
 arch/sparc64/oprofile/Makefile    |    5 +-
 arch/sparc64/oprofile/init.c      |    3 -
 arch/sparc64/oprofile/timer_int.c |   59
---------------------------------
 arch/x86_64/oprofile/Makefile     |    9 ++---
 drivers/oprofile/buffer_sync.c    |   67
+++++++++++++++++++++++++-------------
 drivers/oprofile/event_buffer.c   |    6 ++-
 drivers/oprofile/oprof.c          |   23 +++++++++----
 drivers/oprofile/oprofile_stats.c |    6 +--
 drivers/oprofile/oprofile_stats.h |    2 -
 drivers/oprofile/timer_int.c      |   56
+++++++++++++++++++++++++++++++
 22 files changed, 159 insertions(+), 303 deletions(-)

Convention is that error returns are negative.

arch/alpha/oprofile/common.c +1 -1
diff -Naur -X dontdiff linux-cvs/arch/alpha/oprofile/common.c linux-me/arch/alpha/oprofile/common.c
@@ -175,7 +175,7 @@
 	}
 
 	if (!lmodel)
-		return ENODEV;
+		return -ENODEV;
 	model = lmodel;
 
 	oprof_axp_ops.cpu_type = lmodel->cpu_type;
author
#1 lore raw

PATCH 2/8 OProfile update

Consolidate all the arch copies of timer_int.c into one place. Also fixes a problem with PA-RISC not using instruction_pointer() when it should.

arch/alpha/oprofile/Makefile +2 -1
diff -Naur -X dontdiff linux-cvs/arch/alpha/oprofile/Makefile linux-me/arch/alpha/oprofile/Makefile
@@ -5,7 +5,8 @@
 DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 		oprof.o cpu_buffer.o buffer_sync.o \
 		event_buffer.o oprofile_files.o \
-		oprofilefs.o oprofile_stats.o )
+		oprofilefs.o oprofile_stats.o \
+		timer_int.o )
 
 oprofile-y				:= $(DRIVER_OBJS) common.o
 oprofile-$(CONFIG_ALPHA_GENERIC)	+= op_model_ev4.o \
arch/i386/oprofile/init.c +4 -7
diff -Naur -X dontdiff linux-cvs/arch/i386/oprofile/init.c linux-me/arch/i386/oprofile/init.c
@@ -11,22 +11,19 @@
 #include <linux/init.h>
  
 /* We support CPUs that have performance counters like the Pentium Pro
- * with NMI mode samples. Other x86 CPUs use a simple interrupt keyed
- * off the timer interrupt, which cannot profile interrupts-disabled
- * code unlike the NMI-based code.
+ * with the NMI mode driver.
  */
  
 extern int nmi_init(struct oprofile_operations ** ops);
 extern void nmi_exit(void);
-extern void timer_init(struct oprofile_operations ** ops);
 
 int __init oprofile_arch_init(struct oprofile_operations ** ops)
 {
 #ifdef CONFIG_X86_LOCAL_APIC
-	if (!nmi_init(ops))
+	return nmi_init(ops);
+#else
+	return -ENODEV;
 #endif
-		timer_init(ops);
-	return 0;
 }
 
 
arch/i386/oprofile/Makefile +3 -2
diff -Naur -X dontdiff linux-cvs/arch/i386/oprofile/Makefile linux-me/arch/i386/oprofile/Makefile
@@ -3,8 +3,9 @@
 DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 		oprof.o cpu_buffer.o buffer_sync.o \
 		event_buffer.o oprofile_files.o \
-		oprofilefs.o oprofile_stats.o )
+		oprofilefs.o oprofile_stats.o  \
+		timer_int.o )
 
-oprofile-y				:= $(DRIVER_OBJS) init.o timer_int.o
+oprofile-y				:= $(DRIVER_OBJS) init.o
 oprofile-$(CONFIG_X86_LOCAL_APIC) 	+= nmi_int.o op_model_athlon.o \
 					   op_model_ppro.o op_model_p4.o
arch/i386/oprofile/nmi_int.c +7 -7
diff -Naur -X dontdiff linux-cvs/arch/i386/oprofile/nmi_int.c linux-me/arch/i386/oprofile/nmi_int.c
@@ -314,13 +314,13 @@
 	__u8 family = current_cpu_data.x86;
  
 	if (!cpu_has_apic)
-		return 0;
+		return -ENODEV;
  
 	switch (vendor) {
 		case X86_VENDOR_AMD:
 			/* Needs to be at least an Athlon (or hammer in 32bit mode) */
 			if (family < 6)
-				return 0;
+				return -ENODEV;
 			model = &op_athlon_spec;
 			nmi_ops.cpu_type = "i386/athlon";
 			break;
@@ -331,30 +331,30 @@
 				/* Pentium IV */
 				case 0xf:
 					if (!p4_init())
-						return 0;
+						return -ENODEV;
 					break;
 
 				/* A P6-class processor */
 				case 6:
 					if (!ppro_init())
-						return 0;
+						return -ENODEV;
 					break;
 
 				default:
-					return 0;
+					return -ENODEV;
 			}
 			break;
 #endif /* !CONFIG_X86_64 */
 
 		default:
-			return 0;
+			return -ENODEV;
 	}
 
 	init_driverfs();
 	using_nmi = 1;
 	*ops = &nmi_ops;
 	printk(KERN_INFO "oprofile: using NMI interrupt.\n");
-	return 1;
+	return 0;
 }
 
 
arch/i386/oprofile/timer_int.c +0 -58

58 lines not on this page · raw message

arch/parisc/oprofile/init.c +1 -2
diff -Naur -X dontdiff linux-cvs/arch/parisc/oprofile/init.c linux-me/arch/parisc/oprofile/init.c
@@ -15,8 +15,7 @@
 
 int __init oprofile_arch_init(struct oprofile_operations ** ops)
 {
-	timer_init(ops);
-	return 0;
+	return -ENODEV;
 }
 
 
arch/parisc/oprofile/Makefile +3 -2
diff -Naur -X dontdiff linux-cvs/arch/parisc/oprofile/Makefile linux-me/arch/parisc/oprofile/Makefile
@@ -3,6 +3,7 @@
 DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 		oprof.o cpu_buffer.o buffer_sync.o \
 		event_buffer.o oprofile_files.o \
-		oprofilefs.o oprofile_stats.o )
+		oprofilefs.o oprofile_stats.o \
+		timer_int.o )
 
-oprofile-y				:= $(DRIVER_OBJS) init.o timer_int.o
+oprofile-y				:= $(DRIVER_OBJS) init.o
arch/parisc/oprofile/timer_int.c +0 -58

58 lines not on this page · raw message

arch/ppc64/oprofile/init.c +1 -2
diff -Naur -X dontdiff linux-cvs/arch/ppc64/oprofile/init.c linux-me/arch/ppc64/oprofile/init.c
@@ -15,8 +15,7 @@
 
 int __init oprofile_arch_init(struct oprofile_operations ** ops)
 {
-	timer_init(ops);
-	return 0;
+	return -ENODEV;
 }
 
 
arch/ppc64/oprofile/Makefile +3 -2
diff -Naur -X dontdiff linux-cvs/arch/ppc64/oprofile/Makefile linux-me/arch/ppc64/oprofile/Makefile
@@ -3,6 +3,7 @@
 DRIVER_OBJS := $(addprefix ../../../drivers/oprofile/, \
 		oprof.o cpu_buffer.o buffer_sync.o \
 		event_buffer.o oprofile_files.o \
-		oprofilefs.o oprofile_stats.o )
+		oprofilefs.o oprofile_stats.o \
+		timer_int.o )
 
-oprofile-y := $(DRIVER_OBJS) init.o timer_int.o
+oprofile-y := $(DRIVER_OBJS) init.o
arch/ppc64/oprofile/timer_int.c +0 -59

59 lines not on this page · raw message

arch/sparc64/oprofile/init.c +1 -2
diff -Naur -X dontdiff linux-cvs/arch/sparc64/oprofile/init.c linux-me/arch/sparc64/oprofile/init.c
@@ -15,8 +15,7 @@
 
 int __init oprofile_arch_init(struct oprofile_operations ** ops)
 {
-	timer_init(ops);
-	return 0;
+	return -ENODEV;
 }
 
 
arch/sparc64/oprofile/Makefile +3 -2
diff -Naur -X dontdiff linux-cvs/arch/sparc64/oprofile/Makefile linux-me/arch/sparc64/oprofile/Makefile
@@ -3,6 +3,7 @@
 DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 		oprof.o cpu_buffer.o buffer_sync.o \
 		event_buffer.o oprofile_files.o \
-		oprofilefs.o oprofile_stats.o )
+		oprofilefs.o oprofile_stats.o \
+		timer_int.o )
 
-oprofile-y				:= $(DRIVER_OBJS) init.o timer_int.o
+oprofile-y				:= $(DRIVER_OBJS) init.o
arch/sparc64/oprofile/timer_int.c +0 -59

59 lines not on this page · raw message

arch/x86_64/oprofile/Makefile +4 -5
diff -Naur -X dontdiff linux-cvs/arch/x86_64/oprofile/Makefile linux-me/arch/x86_64/oprofile/Makefile
@@ -9,9 +9,10 @@
 DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 	oprof.o cpu_buffer.o buffer_sync.o \
 	event_buffer.o oprofile_files.o \
-	oprofilefs.o oprofile_stats.o )
+	oprofilefs.o oprofile_stats.o \
+	timer_int.o )
  
-oprofile-objs := $(DRIVER_OBJS) init.o timer_int.o
+oprofile-objs := $(DRIVER_OBJS) init.o
 
 oprofile-$(CONFIG_X86_LOCAL_APIC) += nmi_int.o op_model_athlon.o
  
@@ -23,11 +24,9 @@
 	@ln -sf ../../i386/oprofile/op_model_athlon.c $(obj)/op_model_athlon.c
 $(obj)/init.c: ${INCL}
 	@ln -sf ../../i386/oprofile/init.c $(obj)/init.c
-$(obj)/timer_int.c: ${INCL}
-	@ln -sf ../../i386/oprofile/timer_int.c $(obj)/timer_int.c
 $(obj)/op_counter.h: 
 	@ln -sf ../../i386/oprofile/op_counter.h $(obj)/op_counter.h
 $(obj)/op_x86_model.h:
 	@ln -sf ../../i386/oprofile/op_x86_model.h $(obj)/op_x86_model.h	
-clean-files += op_x86_model.h op_counter.h timer_int.c init.c \
+clean-files += op_x86_model.h op_counter.h init.c \
 	       op_model_athlon.c nmi_int.c
drivers/oprofile/timer_int.c +56 -0

56 lines not on this page · raw message

drivers/oprofile/oprof.c +10 -1
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/oprof.c linux-me/drivers/oprofile/oprof.c
@@ -119,14 +119,23 @@
 }
 
  
+extern void timer_init(struct oprofile_operations ** ops);
+
+ 
 static int __init oprofile_init(void)
 {
 	int err;
 
 	/* Architecture must fill in the interrupt ops and the
-	 * logical CPU type.
+	 * logical CPU type, or we can fall back to the timer
+	 * interrupt profiler.
 	 */
 	err = oprofile_arch_init(&oprofile_ops);
+	if (err == -ENODEV) {
+		timer_init(&oprofile_ops);
+		err = 0;
+	}
+
 	if (err)
 		goto out;
 
author
#2 lore raw

PATCH 3/8 OProfile update

Clear up the code around start_sem so it's more obvious, and remove a pointless down/up pair on buffer_sem (shutdown is already synchronised in sync_stop()).

drivers/oprofile/oprof.c +7 -7
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/oprof.c linux-me/drivers/oprofile/oprof.c
@@ -28,6 +28,8 @@
 {
 	int err;
  
+	down(&start_sem);
+
 	if ((err = alloc_cpu_buffers()))
 		goto out;
 
@@ -45,7 +47,6 @@
 	if ((err = sync_start()))
 		goto out3;
 
-	down(&start_sem);
 	is_setup = 1;
 	up(&start_sem);
 	return 0;
@@ -58,6 +59,7 @@
 out1:
 	free_cpu_buffers();
 out:
+	up(&start_sem);
 	return err;
 }
 
@@ -106,22 +108,20 @@
 
 void oprofile_shutdown(void)
 {
+	down(&start_sem);
 	sync_stop();
 	if (oprofile_ops->shutdown)
 		oprofile_ops->shutdown(); 
-	/* down() is also necessary to synchronise all pending events
-	 * before freeing */
-	down(&buffer_sem);
 	is_setup = 0;
-	up(&buffer_sem);
 	free_event_buffer();
 	free_cpu_buffers();
+	up(&start_sem);
 }
 
- 
+
 extern void timer_init(struct oprofile_operations ** ops);
 
- 
+
 static int __init oprofile_init(void)
 {
 	int err;
author
#3 lore raw

PATCH 4/8 OProfile update

If there's are multiple tasks sleeping inside the read routine ever, this is necessary.

drivers/oprofile/event_buffer.c +5 -1
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/event_buffer.c linux-me/drivers/oprofile/event_buffer.c
@@ -151,11 +151,15 @@
 	if (count != max || *offset)
 		return -EINVAL;
 
-	/* wait for the event buffer to fill up with some data */
 	wait_event_interruptible(buffer_wait, atomic_read(&buffer_ready));
+
 	if (signal_pending(current))
 		return -EINTR;
 
+	/* can't currently happen */
+	if (!atomic_read(&buffer_ready))
+		return -EAGAIN;
+
 	down(&buffer_sem);
 
 	atomic_set(&buffer_ready, 0);
author
#4 lore raw

PATCH 5/8 OProfile update

Now we don't do buffer syncs whilst holding current's mmap_sem ever, we can wait to hold it. Also take task_lock and try to improve the docs a bit.

drivers/oprofile/buffer_sync.c +15 -19
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/buffer_sync.c linux-me/drivers/oprofile/buffer_sync.c
@@ -310,26 +337,23 @@
 /* Take the task's mmap_sem to protect ourselves from
  * races when we do lookup_dcookie().
  */
-static struct mm_struct * take_task_mm(struct task_struct * task)
+static struct mm_struct * take_tasks_mm(struct task_struct * task)
 {
-	struct mm_struct * mm = task->mm;
- 
-	/* if task->mm !NULL, mm_count must be at least 1. It cannot
-	 * drop to 0 without the task exiting, which will have to sleep
-	 * on buffer_sem first. So we do not need to mark mm_count
-	 * ourselves.
+	struct mm_struct * mm;
+       
+	/* Subtle. We don't need to keep a reference to this task's mm,
+	 * because, for the mm to be freed on another CPU, that would have
+	 * to go through the task exit notifier, which ends up sleeping
+	 * on the buffer_sem we hold, so we end up with mutual exclusion
+	 * anyway.
 	 */
+	task_lock(task);
+	mm = task->mm;
+	task_unlock(task);
+ 
 	if (mm) {
-		/* More ugliness. If a task took its mmap
-		 * sem then came to sleep on buffer_sem we
-		 * will deadlock waiting for it. So we can
-		 * but try. This will lose samples :/
-		 */
-		if (!down_read_trylock(&mm->mmap_sem)) {
-			/* FIXME: this underestimates samples lost */
-			atomic_inc(&oprofile_stats.sample_lost_mmap_sem);
-			mm = NULL;
-		}
+		/* needed to walk the task's VMAs */
+		down_read(&mm->mmap_sem);
 	}
  
 	return mm;
@@ -399,7 +423,7 @@
 				new = (struct task_struct *)s->event;
 
 				release_mm(mm);
-				mm = take_task_mm(new);
+				mm = take_tasks_mm(new);
 
 				cookie = get_exec_dcookie(mm);
 				add_user_ctx_switch(new->pid, cookie);
@@ -460,4 +484,3 @@
 	schedule_work(&sync_wq);
 	/* timer is re-added by the scheduled task */
 }
-
author
#5 lore raw

PATCH 6/8 OProfile update

We were doing del_timer_sync() on shutdown, but not ensuring that any queued work was done as well.

drivers/oprofile/buffer_sync.c +2 -0
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/buffer_sync.c linux-me/drivers/oprofile/buffer_sync.c
@@ -147,6 +170,8 @@
 	profile_event_unregister(EXIT_MMAP, &exit_mmap_nb);
 	profile_event_unregister(EXEC_UNMAP, &exec_unmap_nb);
 	del_timer_sync(&sync_timer);
+	/* timer might have queued work, make sure it's completed. */
+	flush_scheduled_work();
 }
 
  
author
#6 lore raw

PATCH 7/8 OProfile update

Change the lost_mmap_sem stat to lost_no_mm, and account it.

drivers/oprofile/buffer_sync.c +2 -0
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/buffer_sync.c linux-me/drivers/oprofile/buffer_sync.c
@@ -296,6 +321,8 @@
 		add_sample_entry(s->eip, s->event);
 	} else if (mm) {
 		add_us_sample(mm, s);
+	} else {
+		atomic_inc(&oprofile_stats.sample_lost_no_mm);
 	}
 }
  
drivers/oprofile/oprofile_stats.c +3 -3
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/oprofile_stats.c linux-me/drivers/oprofile/oprofile_stats.c
@@ -31,7 +31,7 @@
 		cpu_buf->sample_lost_task_exit = 0;
 	}
  
-	atomic_set(&oprofile_stats.sample_lost_mmap_sem, 0);
+	atomic_set(&oprofile_stats.sample_lost_no_mm, 0);
 	atomic_set(&oprofile_stats.event_lost_overflow, 0);
 }
 
@@ -68,8 +68,8 @@
 			&cpu_buf->sample_lost_task_exit);
 	}
  
-	oprofilefs_create_ro_atomic(sb, dir, "sample_lost_mmap_sem",
-		&oprofile_stats.sample_lost_mmap_sem);
+	oprofilefs_create_ro_atomic(sb, dir, "sample_lost_no_mm",
+		&oprofile_stats.sample_lost_no_mm);
 	oprofilefs_create_ro_atomic(sb, dir, "event_lost_overflow",
 		&oprofile_stats.event_lost_overflow);
 }
drivers/oprofile/oprofile_stats.h +1 -1
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/oprofile_stats.h linux-me/drivers/oprofile/oprofile_stats.h
@@ -13,7 +13,7 @@
 #include <asm/atomic.h>
  
 struct oprofile_stat_struct {
-	atomic_t sample_lost_mmap_sem;
+	atomic_t sample_lost_no_mm;
 	atomic_t event_lost_overflow;
 };
 
author
#7 lore raw

PATCH 8/8 OProfile update

Schedule work away on an unmap, instead of calling it directly. Should result in less lost samples, and it fixes a lock ordering problem buffer_sem <-> mmap_sem

drivers/oprofile/buffer_sync.c +26 -3
diff -Naur -X dontdiff linux-cvs/drivers/oprofile/buffer_sync.c linux-me/drivers/oprofile/buffer_sync.c
@@ -58,8 +58,8 @@
  * must concern ourselves with. First, when a task is about to
  * exit (exit_mmap()), we should process the buffer to deal with
  * any samples in the CPU buffer, before we lose the ->mmap information
- * we need. Second, a task may unmap (part of) an executable mmap,
- * so we want to process samples before that happens too
+ * we need. It is vital to get this case correct, otherwise we can
+ * end up trying to access a freed task_struct.
  */
 static int mm_notify(struct notifier_block * self, unsigned long val, void * data)
 {
@@ -67,6 +67,29 @@
 	return 0;
 }
 
+
+/* Second, a task may unmap (part of) an executable mmap,
+ * so we want to process samples before that happens too. This is merely
+ * a QOI issue not a correctness one.
+ */
+static int munmap_notify(struct notifier_block * self, unsigned long val, void * data)
+{
+	/* Note that we cannot sync the buffers directly, because we might end up
+	 * taking the the mmap_sem that we hold now inside of event_buffer_read()
+	 * on a page fault, whilst holding buffer_sem - deadlock.
+	 *
+	 * This would mean a threaded reader of the event buffer, but we should
+	 * prevent it anyway.
+	 *
+	 * Delaying the work in a context that doesn't hold the mmap_sem means
+	 * that we won't lose samples from other mappings that current() may
+	 * have. Note that either way, we lose any pending samples for what is
+	 * being unmapped.
+	 */
+	schedule_work(&sync_wq);
+	return 0;
+}
+
  
 /* We need to be told about new modules so we don't attribute to a previously
  * loaded module, or drop the samples on the floor.
@@ -92,7 +115,7 @@
 };
 
 static struct notifier_block exec_unmap_nb = {
-	.notifier_call	= mm_notify,
+	.notifier_call	= munmap_notify,
 };
 
 static struct notifier_block exit_mmap_nb = {