• Ingo Molnar's avatar
    [PATCH] context-switching overhead in X, ioport() · a55702bb
    Ingo Molnar authored
    while debugging/improving scheduling latencies i got the following
    strange latency report from Lee Revell:
    
      http://krustophenia.net/testresults.php?dataset=2.6.8.1-P6#/var/www/2.6.8.1-P6
    
    this trace shows a 120 usec latency caused by XFree86, on a 600 MHz x86
    system. Looking closer reveals:
    
      00000002 0.006ms (+0.003ms): __switch_to (schedule)
      00000002 0.088ms (+0.082ms): finish_task_switch (schedule)
    
    it took more than 80 usecs for XFree86 to do a context-switch!
    
    it turns out that the reason for this (massive) context-switching
    overhead is the following change in 2.6.8:
    
          [PATCH] larger IO bitmaps
    
    To demonstrate the effect of this change i've written ioperm-latency.c
    (attached), which gives the following on vanilla 2.6.8.1:
    
      # ./ioperm-latency
      default no ioperm:             scheduling latency: 2528 cycles
      turning on port 80 ioperm:     scheduling latency: 10563 cycles
      turning on port 65535 ioperm:  scheduling latency: 10517 cycles
    
    the ChangeSet says:
    
            Now, with the lazy bitmap allocation and per-CPU TSS, this
            will really not drain any resources I think.
    
    this is plain wrong. An increase in the IO bitmap size introduces
    per-context-switch overhead as well: we now have to copy an 8K bitmap
    every time XFree86 context-switches - even though XFree86 never uses
    ports higher than 1024! I've straced XFree86 on a number of x86 systems
    and in every instance ioperm() was used - so i'd say the majority of x86
    Linux systems running 2.6.8.1 are affected by this problem.
    
    This not only causes lots of overhead, it also trashes ~16K out of the
    L1 and L2 caches, on every context-switch. It's as if XFree86 did a L1
    cache flush on every context-switch ...
    
    the simple solution would be to revert IO_BITMAP_BITS back to 1024 and
    release 2.6.8.2?
    
    I've implemented another solution as well, which tracks the
    highest-enabled port # for every task and does the copying of the bitmap
    intelligently. (patch attached) The patched kernel gives:
    
      # ./ioperm-latency
      default no ioperm:             scheduling latency: 2423 cycles
      turning on port 80 ioperm:     scheduling latency: 2503 cycles
      turning on port 65535 ioperm:  scheduling latency: 10607 cycles
    
    this is much more acceptable - the full overhead only occurs in the very
    unlikely event of a task using the high ioport range. X doesnt suffer
    any significant overhead.
    
    (tracking the maximum allowed port # also allows a simplification of
    io_bitmap handling: e.g. we dont do the invalid-offset trick anymore -
    the IO bitmap in the TSS is always valid and secure.)
    
    I tested the patch on x86 SMP and UP, it works fine for me. I tested
    boundary conditions as well, it all seems secure.
    
    	Ingo
    
    #include <errno.h>
    #include <stdio.h>
    #include <sched.h>
    #include <signal.h>
    #include <sys/io.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <linux/unistd.h>
    
    #define CYCLES(x) asm volatile ("rdtsc" :"=a" (x)::"edx")
    
    #define __NR_sched_set_affinity 241
    _syscall3 (int, sched_set_affinity, pid_t, pid, unsigned int, mask_len, unsigned long *, mask)
    
    /*
     * Use a pair of RT processes bound to the same CPU to measure
     * context-switch overhead:
     */
    static void measure(void)
    {
    	unsigned long i, min = ~0UL, pid, mask = 1, t1, t2;
    
    	sched_set_affinity(0, sizeof(mask), &mask);
    
    	pid = fork();
    	if (!pid)
    		for (;;) {
    			asm volatile ("sti; nop; cli");
    			sched_yield();
    		}
    
    	sched_yield();
    	for (i = 0; i < 100; i++) {
    		asm volatile ("sti; nop; cli");
    		CYCLES(t1);
    		sched_yield();
    		CYCLES(t2);
    		if (i > 10) {
    			if (t2 - t1 < min)
    				min = t2 - t1;
    		}
    	}
    	asm volatile ("sti");
    
    	kill(pid, 9);
    	printf("scheduling latency: %ld cycles\n", min);
    	sched_yield();
    }
    
    int main(void)
    {
    	struct sched_param p = { sched_priority: 2 };
    	unsigned long mask = 1;
    
    	if (iopl(3)) {
    		printf("need to run as root!\n");
    		exit(-1);
    	}
    	sched_setscheduler(0, SCHED_FIFO, &p);
    	sched_set_affinity(0, sizeof(mask), &mask);
    
    	printf("default no ioperm:             ");
    	measure();
    
    	printf("turning on port 80 ioperm:     ");
    	ioperm(0x80,1,1);
    	measure();
    
    	printf("turning on port 65535 ioperm:  ");
    	if (ioperm(0xffff,1,1))
    		printf("FAILED - older kernel.\n");
    	else
    		measure();
    
    	return 0;
    }
    Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
    Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
    Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
    a55702bb
ioport.c 3.58 KB