[PATCH] Reduction of interrupt latency time
Juan Perez-Sanchez <[email protected]>
| Newsgroups | org.kernel.vger.linux-8086 |
|---|---|
| Message-ID | <CAD6VGuaPuGw7LQ9=qFcqfrUiZyVQQ+S2yVKf_hfoyjcv+OCV+g@mail.gmail.com> |
Hi, Currently, interrupt handlers run with interrupts disabled, preventing servicing further interrupts (even if those interrupts have higher priority) until full completion of current interrupt routine. This patch makes the necessary changes to permit reenabling interrupts during interrupt handlers execution. Greetings, Juan PREVIOUS OPERATION AND BUGS 1. Flag can_tswitch is set to 1 if the interrupt service routine will return to user space, and to zero otherwise. Flag lastirq is set to interrupt number during the execution of interrupt handlers, and set to -1 at other times. 2. Interrupt handlers run with interrupts disabled. Also, handling of flags can_tswitch and lastirq is inadequate for nested interrupts, because they are not properly restored at the end of nested interrupts. 3. Schedule() is called at the end of any interrupt, if the interrupt routine will return to user space. During execution of function schedule() interrupts are reenabled because of a bug in that function. I think this is harmless because by then the EOI command was already sent to the interrupt controller. 4. Calling schedule by interrupt handlers is permitted, but the actual task switch inside schedule() will be skipped. This is controlled by flags can_tswitch and lastirq. 5. However, checking of can_tswitch and lastirq flags to enable task switching is flawed: it permits task switches if (can_tswitch == 1) and (lastirq != -1), clearly an error. Nothing happens now because the only two interrupt handlers presently used (timer and keyboard) do not call schedule. Other than that, flag can_tswitch is ignored by schedule(). NEW OPERATION 1. Schedule is called at the end of any interrupt, if the interrupt routine will return to user space. 2. Interrupts are reenabled before calling the interrupt handler. Interrupt nesting is permitted. Variable intr_count is a counter to keep track of the nesting level. It is incremented at the beginning of interrupt processing and decremented after issuing the EOI command to the interrupt controller. 3. Flags can_tswitch and lastirg are completely removed from the source tree. 4. In function schedule, task switches are skipped if intr_count > 0. OTHER CHANGES 1. An attempt was made to permit task switching if the interrupt routine will return to a system call. It succeeded up to the point of starting execution of the "init" program, then the system crashed. To get this far some bugs were fixed in files "arch/i86/kernel/process.c", "arch/i86/kernel/system.c" and "init/main.c" 2. In file init/main.c, in function init_task(), before attempting to load "bin/sh", opens console on device "/dev/tty1" and it should be "dev/tty0". Anyway, bin/sh fails to start, but now does not die silently. On the other hand, sash runs fine. 3. The purpose of this patch is to increase the functionality of the kernel and reduce interrupt latency time. Regardless, a side result of this modification is a reduction in code size of 32 bytes. The Image builded without errors. The kernel was tested with QEMU and dioscuri emulators. Also in a PPro pc booting from floppy.
elksG.patch
(application/octet-stream, 11.2 KB)
diff -Nurb elks.orig/arch/i86/kernel/irq.c elks/arch/i86/kernel/irq.c
--- elks.orig/arch/i86/kernel/irq.c 2012-05-30 13:39:34.000000000 -0500
+++ elks/arch/i86/kernel/irq.c 2012-06-20 13:45:28.000000000 -0500
@@ -118,14 +118,10 @@
* Called by the assembler hooks
*/
-int lastirq;
-
void do_IRQ(int i,void *regs)
{
register struct irqaction *irq = irq_action + i;
- lastirq = i;
-
if (irq->handler != NULL)
irq->handler(i,regs,irq->dev_id);
else
@@ -133,7 +129,6 @@
printk("Unexpected trap: %u\n", i-16);
else
printk("Unexpected interrupt: %u\n", i);
- lastirq = -1;
}
/*
diff -Nurb elks.orig/arch/i86/kernel/irqtab.c elks/arch/i86/kernel/irqtab.c
--- elks.orig/arch/i86/kernel/irqtab.c 2012-05-30 13:39:52.000000000 -0500
+++ elks/arch/i86/kernel/irqtab.c 2012-06-20 13:45:28.000000000 -0500
@@ -60,6 +60,7 @@
seg SEG_IRQ_DATA
mov stashed_ds,ds
mov bios_call_cnt_l,#5
+ mov _intr_count,#0
xor ax,ax
mov es,ax ;intr table
@@ -397,19 +398,19 @@
mov dx,ss ! Get current SS
mov bp,sp ! Get current SP
- movb cl,bios_call_cnt_l
!
! Set up task switch controller
!
xor ch,ch ! Assume we are not allowed to switch
!
-! See where we were (BX holds the SS on entry)
+! See where we were (DX holds the SS on entry)
!
cmp dx,bx ! SS = kernel SS ?
je ktask ! Kernel - no work
!
! User or BIOS etc
!
+ mov ss,bx ! /* Set SS: right */
mov bx,_current
cmp dx,TASK_USER_SS[bx] ! entry ss = current->t_regs.ss?
je utask ! Switch to kernel
@@ -417,7 +418,7 @@
! Bios etc - switch to interrupt stack
!
mov sp,#_intstack
- j switched
+ j ktask
!
! User task. Extract kernel SP. (BX already holds current)
! At this point, the kernel stack is empty. Thus, we can load
@@ -430,9 +431,6 @@
! In ktask state we have a suitable stack. It might be
! better to use the intstack..
!
-switched:
- mov bx,ds
- mov ss,bx ! /* Set SS: right */
ktask:
! /*
! Put the old SS;SP on the top of the stack. We can't
@@ -442,16 +440,19 @@
push bp ! push entry SP
push dx ! push entry SS
!
-! We are on a suitable stack and cx says whether we can
-! switch afterwards. The C code will want to eat CX so
-! we have to hide it
-!
-!
! The registers are now stored. Remember where
!
mov bp,sp
- mov _can_tswitch, ch
- push cx ! Save ch
+!
+! Update intr_count
+!
+ inc _intr_count
+!
+! We are on a suitable stack and ch says whether
+! we can switch afterwards.
+!
+ sti ! Reenable interrupts
+ push cx ! Switch flag
push ax ! IRQ for later
push bp ! Register base
push ax ! IRQ number
@@ -469,16 +470,23 @@
!
! Restore any chips
!
+ cli ! Disable interrupts to avoid reentering ISR
cmp ax,#16
jge was_trap ! Traps need no reset
or ax,ax ! Is int #0?
jnz a4
- dec cl ! Will call bios int?
- je was_trap
+!
+! IRQ 0 (timer) has to go on to the bios for some systems
+!
+ dec bios_call_cnt_l ! Will call bios int?
+ jne a4
+ mov bios_call_cnt_l,#5
+ pushf
+ callf [off_stashed_irq0_l]
+ jmp was_trap
a4:
- mov cl,al ! Save the IRQ number
+ cmp ax,#8
movb al,#0x20 ! EOI
- cmp cl,#8
jb a6 ! IRQ on low chip
!
! Reset secondary 8259 if we have taken an AT rather
@@ -489,27 +497,18 @@
jmp a5
a5: jmp a6
a6: outb 0x20,al ! Ack on primary controller
-
!
! And a trap does no hardware work
!
-
was_trap:
- orb cl,cl
- jnz no_bios_call
!
-! IRQ 0 (timer) has to go on to the bios for some systems
+! Restore intr_count
!
- dec bios_call_cnt_l
- jne no_bios_call
- mov bios_call_cnt_l,#5
- pushf
- callf [off_stashed_irq0_l]
-no_bios_call:
+ dec _intr_count
!
! Now look at rescheduling
!
- cmp ch,#0 ! Schedule allowed ?
+ orb ch,ch ! Schedule allowed ?
je nosched ! No
! mov bx,_need_resched ! Schedule needed
! cmp bx,#0 !
@@ -520,15 +519,15 @@
!
pop ax ! stacked SS
pop cx ! stacked SP
- mov bx,_current
#ifdef CONFIG_ADVANCED_MM
+ mov bx,_current
mov ax, TASK_USER_SS[bx] ! user ds
mov bp, sp
- mov 12[bp], ax ! change the es in the stack
- mov 14[bp], ax ! change the ds in the stack
+; mov 12[bp], ax ! change the es in the stack
+; mov 14[bp], ax ! change the ds in the stack
#endif
!
-! At this point, the kernel stack is empty. Thus, there in no
+! At this point, the kernel stack is empty. Thus, there is no
! need to save the kernel stack pointer.
!
j noschedpop
@@ -563,9 +562,9 @@
iret
.data
- .globl _can_tswitch
-_can_tswitch:
- .byte 0
+ .globl _intr_count
+_intr_count:
+ .word 0
off_stashed_irq0_l:
.word 0
diff -Nurb elks.orig/arch/i86/kernel/process.c elks/arch/i86/kernel/process.c
--- elks.orig/arch/i86/kernel/process.c 2012-05-30 13:39:52.000000000 -0500
+++ elks/arch/i86/kernel/process.c 2012-06-20 13:45:28.000000000 -0500
@@ -356,15 +356,15 @@
void kfork_proc(register struct task_struct *t,char *addr)
{
memset(t, 0, sizeof(struct task_struct));
+ t->t_regs.ds = t->t_regs.ss = get_ds();
t->t_regs.ksp = ((__u16) t->t_kstack) + KSTACK_BYTES;
t->t_regs.ksp -= fake_save_regs((FsR)t->t_regs.ksp,(FsR)addr);
- t->t_regs.ds = get_ds();
+
t->state = TASK_UNINTERRUPTIBLE;
t->pid = get_pid();
-
+ t->t_kstackm = KSTACK_MAGIC;
t->prev_run = t->next_run = NULL;
- t->t_kstackm = KSTACK_MAGIC;
wake_up_process(t);
schedule();
}
diff -Nurb elks.orig/arch/i86/kernel/system.c elks/arch/i86/kernel/system.c
--- elks.orig/arch/i86/kernel/system.c 2012-05-11 13:26:27.000000000 -0500
+++ elks/arch/i86/kernel/system.c 2012-06-20 13:45:28.000000000 -0500
@@ -61,14 +61,15 @@
* Now create task 0 to be ourself. Set the kernel SP,
* as we will need this in interrupts.
*/
-
taskp = &task[0];
- taskp->state = TASK_RUNNING;
+ taskp->t_regs.cs = get_cs();
+ taskp->t_regs.ds = taskp->t_regs.ss = get_ds(); /* Run in kernel space */
taskp->t_regs.ksp = ((__u16) taskp->t_kstack) + KSTACK_BYTES;
- taskp->t_regs.cs = get_cs();
- taskp->t_regs.ds = get_ds(); /* Run in kernel space */
+ taskp->state = TASK_RUNNING;
+ taskp->t_kstackm = KSTACK_MAGIC;
+ taskp->next_run = taskp->prev_run = taskp;
current = taskp;
diff -Nurb elks.orig/arch/i86/sibo/irqtab.c elks/arch/i86/sibo/irqtab.c
--- elks.orig/arch/i86/sibo/irqtab.c 2012-05-30 13:39:52.000000000 -0500
+++ elks/arch/i86/sibo/irqtab.c 2012-06-20 13:45:28.000000000 -0500
@@ -63,6 +63,7 @@
seg SEG_IRQ_DATA
mov stashed_ds,ds
+ mov _intr_count,#0
xor ax,ax
mov es,ax ;intr table
@@ -408,6 +409,7 @@
!
! User or BIOS etc
!
+ mov ss,bx ! /* Set SS: right */
mov bx,_current
cmp dx,TASK_USER_SS[bx] ! entry ss = current->t_regs.ss?
je utask ! Switch to kernel
@@ -415,7 +417,7 @@
! Bios etc - switch to interrupt stack
!
mov sp,#_intstack
- j switched
+ j ktask
!
! User task. Extract kernel SP. (BX already holds current)
! At this point, the kernel stack is empty. Thus, we can load
@@ -428,9 +430,6 @@
! In ktask state we have a suitable stack. It might be
! better to use the intstack..
!
-switched:
- mov bx,ds
- mov ss,bx ! /* Set SS: right */
ktask:
! /*
! Put the old SS;SP on the top of the stack. We can't
@@ -440,24 +439,26 @@
push bp ! push entry SP
push dx ! push entry SS
!
-! We are on a suitable stack and cx says whether we can
-! switch afterwards. The C code will want to eat CX so
-! we have to hide it
-!
-!
! The registers are now stored. Remember where
!
mov bp,sp
- mov _can_tswitch, ch
- push cx ! Save ch
+!
+! Update intr_count
+!
+ inc _intr_count
+!
+! We are on a suitable stack and ch says whether
+! we can switch afterwards.
+!
+ push cx ! Switch flag
push ax ! IRQ for later
push bp ! Register base
push ax ! IRQ number
!
! Call the C code
!
- call _do_IRQ ! Do the work
-!
+ call _do_IRQ ! Do the work. Interrupt handler should enable
+! interrupts after removing interrupt signal
! Return path
!
pop ax ! We want the ax value back
@@ -467,11 +468,17 @@
!
! Restore any chips
!
+ cli ! Disable interrupts to avoid reentering ISR
+!
! The individual IRQ now reset as the Psion has a weird structure
!
+! Restore intr_count
+!
+ dec _intr_count
+!
! Now look at rescheduling
!
- cmp ch,#0 ! Schedule allowed ?
+ orb ch,ch ! Schedule allowed ?
je nosched ! No
! mov bx,_need_resched ! Schedule needed
! cmp bx,#0 !
@@ -482,12 +489,12 @@
!
pop ax ! stacked SS
pop cx ! stacked SP
- mov bx,_current
#ifdef CONFIG_ADVANCED_MM
+ mov bx,_current
mov ax, TASK_USER_SS[bx] ! user ds
mov bp, sp
- mov 12[bp], ax ! change the es in the stack
- mov 14[bp], ax ! change the ds in the stack
+; mov 12[bp], ax ! change the es in the stack
+; mov 14[bp], ax ! change the ds in the stack
#endif
!
! At this point, the kernel stack is empty. Thus, there in no
@@ -525,9 +532,9 @@
iret
.data
- .globl _can_tswitch
-_can_tswitch:
- .byte 0
+ .globl _intr_count
+_intr_count:
+ .word 0
off_stashed_irq0_l:
.word 0
diff -Nurb elks.orig/init/main.c elks/init/main.c
--- elks.orig/init/main.c 2012-05-30 13:39:29.000000000 -0500
+++ elks/init/main.c 2012-06-20 14:21:35.000000000 -0500
@@ -67,8 +67,6 @@
printk("ELKS version %s\n", system_utsname.release);
- task[0].t_kstackm = KSTACK_MAGIC;
- task[0].next_run = task[0].prev_run = &task[0];
kfork_proc(&task[1], init_task);
/*
@@ -117,17 +115,18 @@
#ifdef CONFIG_CONSOLE_SERIAL
num = sys_open("/dev/ttyS0", 2, 0);
#else
- num = sys_open("/dev/tty1", 2, 0);
+ num = sys_open("/dev/tty0", 2, 0);
#endif
if (num < 0)
printk("Unable to open /dev/tty (error %u)\n", -num);
- if (sys_dup(0) != 1)
+ if (sys_dup(num) != 1)
printk("dup failed\n");
- sys_dup(0);
+ sys_dup(num);
+ sys_dup(num);
printk("No init - running /bin/sh\n");
- num = run_init_process("/bin/sh", args);
+ num = run_init_process("/bin/sash", args);
printk("sys_execve(\"/bin/sh\",args,18) => %d.\n",num);
panic("No init or sh found");
}
diff -Nurb elks.orig/kernel/sched.c elks/kernel/sched.c
--- elks.orig/kernel/sched.c 2012-05-30 13:39:34.000000000 -0500
+++ elks/kernel/sched.c 2012-06-20 13:45:28.000000000 -0500
@@ -22,8 +22,7 @@
__ptask current, next, previous;
-extern unsigned char can_tswitch;
-extern int lastirq;
+extern int intr_count;
extern int do_signal(void);
@@ -136,7 +135,7 @@
add_timer(&timer);
}
- if ((!can_tswitch) && (lastirq != -1))
+ if (intr_count > 0)
goto scheduling_in_interrupt;
#ifdef CONFIG_SWAP
@@ -167,9 +166,9 @@
/* Taking a timer IRQ during another IRQ or while in kernel space is
* quite legal. We just dont switch then */
- if (lastirq > 0)
+/* if (intr_count > 0) */
printk("Aiee: scheduling in interrupt %d - %d %d\n",
- lastirq, currentp->pid, prev->pid);
+ intr_count, currentp->pid, prev->pid);
}
struct timer_list tl_list = { NULL, NULL, 0L, 0, NULL };