gfoot wrote:
It dropped into user mode briefly then back to super mode just before the interrupt - this is likely a system call - and then you can see it spent about 55us in the system call, leading to most of the delay in processing the IRQ. I need to look at getting interrupts enabled during system calls, then measure this again.
I made some changes to allow IRQs during system calls, and it improved these figures. From that last post I was observing about 65us duration of IRQB, and it's now down to about 30us-40us when supervisor mode had been active. It's about 20us when user mode was active, which won't have changed since the previous post. Here's an updated trace showing, top to bottom, VPB, SUPER, IRQB, and the serial transmit line:
Attachment:
20231219_113119.jpg [ 861.97 KiB | Viewed 1012 times ]
There are three pulses in VPB showing where interrupts or system calls occured. The first is an interrupt taken in user mode - probably the preempt timer expiring. This was serviced in about 20us, but the system then took about 35us to pick a new process (measuring from the rising edge of IRQB to the falling edge of SUPER). Most interrupts don't cause a process switch so avoid some of this cost in their exit code. Interrupts would be masked throughout this one.
Then the system went into user mode and executed a system call via BRK, putting it back in supervisor mode. Shortly after this started, IRQB went low, and - as I now allow interrupts during system calls - this was serviced as soon as interrupts were re-enabled, despite the system still being in supervisor mode - you can see the third pulse on VPB at this point. The IRQ itself cleared about 20us later, consistent with the first one, so overall IRQB was low for about 30us in this case.
I'm sure there's room for improvement in the IRQ response code in general but am glad to see enabling interrupts during syscall is both working and having a positive effect. I think there's potential to enable interrupts during task selection as well. The response times will also reduce if the clock speed goes up, of course - but at 4MHz it looks like the worst case is now about 40us.
These are the only changes needed to enable interrupts during syscalls - pleasantly few! I mark that an interrupt came from supervisor mode by decrementing a memory location to a negative number, which can be done without needing any registers; and it can then also be tested right at the start of the regular interrupt handler routine, without corrupting any registers either. It branches to a dedicaded routine to handle hardware interrupts from supervisor mode. The preempt interrupt is also disabled because we don't want that one to fire - we can't switch processes anyway while we're in a syscall, all we can do is lightweight processing of proper hardware interrupts.
Code:
@@ -26,6 +26,8 @@ initloop:
stz zp_runqueue_head
stz zp_runqueue_tail
+ stz var_interrupt_from_supervisor
+
rts
.)
@@ -59,12 +61,40 @@ resethandler:
jmp scheduler_run
.)
+superirqhandler:
+.(
+ ; Special interrupt service routine for interrupting the supervisor. We don't need to
+ ; worry as much about user process state, just run the regular hardware interrupt handler
+ ; and then return.
+ ;
+ ; It does need to run as PID 0 though, and it's possible another process was selected
+ ; even though we were in supervisor mode, so remember the old PID before setting it to
+ ; zero.
+ sta var_superirq_saveda
+
+ lda PID : sta var_superirq_saved_pid
+ stz PID
+
+ jsr irqhandler2
+
+ lda var_superirq_saved_pid : sta PID
+
+ lda var_superirq_saveda
+ rti
+.)
+
irqhandler:
.(
+ ; Support a nested hardware interrupt within a system call - if we interrupted the
+ ; supervisor then execute a bespoke version of the handler
+ bit var_interrupt_from_supervisor
+ bmi superirqhandler
+
; This could be an IRQ or a BRK. We can check the stack to find out which.
; There's no need to be reentrant here, but while the active process is still selected,
; we mustn't write to zero page or the stack.
@@ -99,8 +129,21 @@ isbrk:
; can be considered - but this is simple and efficient.
and #4 : bne killit
+ ; We allow interrupts during system calls in general, but we need to mark that this has
+ ; happened so that when the interrupt returns it leaves the system in supervisor mode.
+ ; Also, disable the preempt timer interrupt now because we don't want that one to fire.
+ dec var_interrupt_from_supervisor
+ lda #$40 : sta VIA_IER
+ cli
+
jsr syscall ; dispatch the system call
+ ; Remask interrupts, re-enable the premept timer, clear the flag saying any interrupt
+ ; was from supervisor mode
+ sei
+ lda #$c0 : sta VIA_IER
+ inc var_interrupt_from_supervisor
+
bcc resume ; resume current process if carry clear
; Carry set means the process is blocked, so we need to arrange for the syscall to repeat.