[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 };
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.