PATCH to make all CPUs pick up a new profiling frequency

Dimitris Michailidis (dimitris@cthulhu.engr.sgi.com)
Sun, 11 Apr 1999 15:26:40 -0700


On SMP kernels it is possible to change the profiling frequency by writing a
new multiplier to /proc/profile. This causes the local APIC timers to be
reprogrammed to generate timeouts at the new frequency. The current
implementation has two problems.

First, only the CPU that processes the write to /proc/profile picks up the new
frequency. It makes sense that all CPUs should see the change, and in fact
there is a comment in the existing code that this should be the right behavior.

Second, the local APIC timer is currently reprogrammed in the middle of its
countdown thus making the timeout less accurate than it could be. As an
extreme case, by writing to /proc/profile frequently enough it is possible to
reset the timer so frequently that it never times out. Although only root can
do this the kernel can easily prevent it.

The patch below fixes these two problems --- and also moves some code around
to improve performance --- by (a) making sure all CPUs pick up the new
profiling frequency, and (b) reprogramming the local APIC timer only after a
timeout.

---
Dimitris Michailidis				dimitris@engr.sgi.com

--- /usr/tmp/p_rdiff_a007oA/smp.c Sun Apr 11 14:54:55 1999 +++ /kern/arch/i386/kernel/smp.c Sun Apr 11 14:51:34 1999 @@ -1166,6 +1166,7 @@ } unsigned int prof_multiplier[NR_CPUS]; +unsigned int prof_old_multiplier[NR_CPUS]; unsigned int prof_counter[NR_CPUS]; /* @@ -1189,6 +1190,7 @@ cpu_number_map[i] = -1; prof_counter[i] = 1; prof_multiplier[i] = 1; + prof_old_multiplier[i] = 1; } /* @@ -1665,6 +1667,10 @@ send_IPI_allbutself(MTRR_CHANGE_VECTOR); } +static unsigned int calibration_result; + +void setup_APIC_timer(unsigned int clocks); + /* * Local timer interrupt handler. It does both profiling and * process statistics/rescheduling. @@ -1677,6 +1683,7 @@ void smp_local_timer_interrupt(struct pt_regs * regs) { + int user = (user_mode(regs) != 0); int cpu = smp_processor_id(); /* @@ -1685,14 +1692,27 @@ * updated with atomic operations). This is especially * useful with a profiling multiplier != 1 */ - if (!user_mode(regs)) + if (!user) x86_do_profile(regs->eip); if (!--prof_counter[cpu]) { - int user=0,system=0; struct task_struct * p = current; /* + * The multiplier may have changed since the last time we got + * to this point as a result of the user writing to + * /proc/profile. In this case we need to adjust the APIC + * timer accordingly. + * + * Interrupts are already masked off at this point. + */ + prof_counter[cpu] = prof_multiplier[cpu]; + if (prof_counter[cpu] != prof_old_multiplier[cpu]) { + setup_APIC_timer(calibration_result/prof_counter[cpu]); + prof_old_multiplier[cpu] = prof_counter[cpu]; + } + + /* * After doing the above, we need to make like * a normal interrupt - otherwise timer interrupts * ignore the global interrupt lock, which is the @@ -1699,13 +1719,10 @@ * WrongThing (tm) to do. */ - if (user_mode(regs)) - user=1; - else - system=1; - - irq_enter(cpu, 0); if (p->pid) { + int system = 1 - user; + + irq_enter(cpu, 0); update_one_process(p, 1, user, system, cpu); p->counter -= 1; @@ -1713,7 +1730,10 @@ p->counter = 0; p->need_resched = 1; } - if (p->priority < DEF_PRIORITY) { + if (system) { + kstat.cpu_system += system; + kstat.per_cpu_system[cpu] += system; + } else if (p->priority < DEF_PRIORITY) { kstat.cpu_nice += user; kstat.per_cpu_nice[cpu] += user; } else { @@ -1721,12 +1741,8 @@ kstat.per_cpu_user[cpu] += user; } - kstat.cpu_system += system; - kstat.per_cpu_system[cpu] += system; - + irq_exit(cpu, 0); } - prof_counter[cpu]=prof_multiplier[cpu]; - irq_exit(cpu, 0); } /* @@ -1990,8 +2006,6 @@ return calibration_result; } -static unsigned int calibration_result; - void __init setup_APIC_clock(void) { unsigned long flags; @@ -2048,8 +2062,7 @@ */ int setup_profiling_timer(unsigned int multiplier) { - int cpu = smp_processor_id(); - unsigned long flags; + int i; /* * Sanity check. [at least 500 APIC cycles should be @@ -2059,11 +2072,14 @@ if ( (!multiplier) || (calibration_result/multiplier < 500)) return -EINVAL; - save_flags(flags); - cli(); - setup_APIC_timer(calibration_result/multiplier); - prof_multiplier[cpu]=multiplier; - restore_flags(flags); + /* + * Set the new multiplier for each CPU. CPUs don't start using the + * new values until the next timer interrupt in which they do process + * accounting. At that time they also adjust their APIC timers + * accordingly. + */ + for (i = 0; i < NR_CPUS; ++i) + prof_multiplier[i]=multiplier; return 0; }

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/