Clock task split

- preemption handled in the clock timer interrupt handler, not in the clock task

- more achitecture independent clock timer handling code

- smp ready as each CPU can have its own timer
This commit is contained in:
Tomas Hruby 2009-11-06 09:04:15 +00:00
parent 6eebc03f88
commit f2a1f21a39
8 changed files with 195 additions and 172 deletions

View file

@ -6,6 +6,8 @@
#include "../../kernel.h"
#include "../../clock.h"
#define CLOCK_ACK_BIT 0x80 /* PS/2 clock interrupt acknowledge bit */
/* Clock parameters. */
@ -14,28 +16,30 @@
#define SQUARE_WAVE 0x36 /* ccaammmb, a = access, m = mode, b = BCD */
/* 11x11, 11 = LSB then MSB, x11 = sq wave */
#define TIMER_FREQ 1193182 /* clock frequency for timer in PC and AT */
#define TIMER_COUNT (TIMER_FREQ/system_hz) /* initial value for counter*/
#define TIMER_COUNT(freq) (TIMER_FREQ/(freq)) /* initial value for counter*/
PRIVATE irq_hook_t pic_timer_hook; /* interrupt handler hook */
/*===========================================================================*
* arch_init_clock *
* init_8235A_timer *
*===========================================================================*/
PUBLIC int arch_init_clock(void)
PUBLIC int init_8253A_timer(unsigned freq)
{
/* Initialize channel 0 of the 8253A timer to, e.g., 60 Hz,
* and register the CLOCK task's interrupt handler to be run
* on every clock tick.
*/
outb(TIMER_MODE, SQUARE_WAVE); /* run continuously */
outb(TIMER0, (TIMER_COUNT & 0xff)); /* timer low byte */
outb(TIMER0, TIMER_COUNT >> 8); /* timer high byte */
outb(TIMER0, (TIMER_COUNT(freq) & 0xff)); /* timer low byte */
outb(TIMER0, TIMER_COUNT(freq) >> 8); /* timer high byte */
return OK;
}
/*===========================================================================*
* clock_stop *
* stop_8235A_timer *
*===========================================================================*/
PUBLIC void clock_stop(void)
PUBLIC void stop_8253A_timer(void)
{
/* Reset the clock to the BIOS rate. (For rebooting.) */
outb(TIMER_MODE, 0x36);
@ -44,9 +48,9 @@ PUBLIC void clock_stop(void)
}
/*===========================================================================*
* read_clock *
* read_8235A_timer *
*===========================================================================*/
PUBLIC clock_t read_clock(void)
PUBLIC clock_t read_8253A_timer(void)
{
/* Read the counter of channel 0 of the 8253A timer. This counter
* counts down at a rate of TIMER_FREQ and restarts at
@ -63,3 +67,26 @@ PUBLIC clock_t read_clock(void)
return count;
}
PUBLIC int arch_init_local_timer(unsigned freq)
{
init_8253A_timer(freq);
return 0;
}
PUBLIC void arch_stop_local_timer(void)
{
stop_8253A_timer();
}
PUBLIC int arch_register_local_timer_handler(irq_handler_t handler)
{
/* Using PIC, Initialize the CLOCK's interrupt hook. */
pic_timer_hook.proc_nr_e = NONE;
put_irq_handler(&pic_timer_hook, CLOCK_IRQ, handler);
put_irq_handler(&pic_timer_hook, CLOCK_IRQ,
(irq_handler_t)bsp_timer_int_handler);
return 0;
}

8
kernel/arch/i386/clock.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef __CLOCK_X86_H__
#define __CLOCK_X86_H__
_PROTOTYPE(int init_8253A_timer, (unsigned freq));
_PROTOTYPE(void stop_8253A_timer, (void));
_PROTOTYPE(clock_t read_8253A_timer, (void));
#endif /* __CLOCK_X86_H__ */

View file

@ -12,8 +12,8 @@
* Sep 30, 2004 source code documentation updated (Jorrit N. Herder)
* Sep 24, 2004 redesigned alarm timers (Jorrit N. Herder)
*
* The function do_clocktick() is triggered by the clock's interrupt
* handler when a watchdog timer has expired or a process must be scheduled.
* Clock task is notified by the clock's interrupt handler when a timer
* has expired.
*
* In addition to the main clock_task() entry point, which starts the main
* loop, there are several other minor entry points:
@ -24,7 +24,6 @@
* read_clock: read the counter of channel 0 of the 8253A timer
*
* (+) The CLOCK task keeps tracks of watchdog timers for the entire kernel.
* The watchdog functions of expired timers are executed in do_clocktick().
* It is crucial that watchdog functions not block, or the CLOCK task may
* be blocked. Do not send() a message when the receiver is not expecting it.
* Instead, notify(), which always returns, should be used.
@ -37,11 +36,11 @@
#include <minix/endpoint.h>
#include <minix/portio.h>
#include "clock.h"
/* Function prototype for PRIVATE functions.
*/
FORWARD _PROTOTYPE( void init_clock, (void) );
FORWARD _PROTOTYPE( int clock_handler, (irq_hook_t *hook) );
FORWARD _PROTOTYPE( void do_clocktick, (message *m_ptr) );
FORWARD _PROTOTYPE( void load_update, (void));
/* The CLOCK's timers queue. The functions in <timers.h> operate on this.
@ -57,7 +56,6 @@ PRIVATE clock_t next_timeout; /* realtime that next timer expires */
/* The time is incremented by the interrupt handler on each clock tick.
*/
PRIVATE clock_t realtime = 0; /* real time clock */
PRIVATE irq_hook_t clock_hook; /* interrupt handler hook */
/*===========================================================================*
* clock_task *
@ -83,7 +81,9 @@ PUBLIC void clock_task()
if (is_notify(m.m_type)) {
switch (_ENDPOINT_P(m.m_source)) {
case HARDWARE:
do_clocktick(&m); /* handle clock tick */
tmrs_exptimers(&clock_timers, realtime, NULL);
next_timeout = (clock_timers == NULL) ?
TMR_NEVER : clock_timers->tmr_exp_time;
break;
default: /* illegal request type */
kprintf("CLOCK: illegal notify %d from %d.\n",
@ -98,169 +98,45 @@ PUBLIC void clock_task()
}
}
/*===========================================================================*
* do_clocktick *
*===========================================================================*/
PRIVATE void do_clocktick(m_ptr)
message *m_ptr; /* pointer to request message */
{
register struct proc *bill_copy = bill_ptr;
/* Despite its name, this routine is not called on every clock tick. It
* is called on those clock ticks when a lot of work needs to be done.
*/
/* A process used up a full quantum. The interrupt handler stored this
* process in 'prev_ptr'. First make sure that the process is not on the
* scheduling queues. Then announce the process ready again. Since it has
* no more time left, it gets a new quantum and is inserted at the right
* place in the queues. As a side-effect a new process will be scheduled.
*/
if (prev_ptr->p_ticks_left <= 0 && priv(prev_ptr)->s_flags & PREEMPTIBLE) {
if(prev_ptr->p_rts_flags == 0) { /* if it was runnable .. */
lock;
{
dequeue(prev_ptr); /* take it off the queues */
enqueue(prev_ptr); /* and reinsert it again */
}
unlock;
} else {
kprintf("CLOCK: %d not runnable; flags: %x\n",
prev_ptr->p_endpoint, prev_ptr->p_rts_flags);
}
}
/* Check if a process-virtual timer expired. Check prev_ptr, but also
* bill_ptr - one process's user time is another's system time, and the
* profile timer decreases for both! Do this before the queue operations
* below, which may alter bill_ptr. Note the use a copy of bill_ptr, because
* bill_ptr may have been changed above, and this code can't be put higher
* up because otherwise cause_sig() may dequeue prev_ptr before we do.
*/
vtimer_check(prev_ptr);
if (prev_ptr != bill_copy)
vtimer_check(bill_copy);
/* Check if a clock timer expired and run its watchdog function. */
if (next_timeout <= realtime) {
tmrs_exptimers(&clock_timers, realtime, NULL);
next_timeout = (clock_timers == NULL) ?
TMR_NEVER : clock_timers->tmr_exp_time;
}
return;
}
/*===========================================================================*
* init_clock *
*===========================================================================*/
PRIVATE void init_clock()
{
/* First of all init the clock system.
*
* Here the (a) clock is set to produce a interrupt at
* every 1/60 second (ea. 60Hz).
*
* Running right away.
*/
arch_init_clock(); /* architecture-dependent initialization. */
/* Initialize the CLOCK's interrupt hook. */
clock_hook.proc_nr_e = CLOCK;
put_irq_handler(&clock_hook, CLOCK_IRQ, clock_handler);
enable_irq(&clock_hook); /* ready for clock interrupts */
/* Set a watchdog timer to periodically balance the scheduling queues. */
balance_queues(NULL); /* side-effect sets new timer */
/* Set a watchdog timer to periodically balance the scheduling queues.
Side-effect sets new timer */
balance_queues(NULL);
}
/*===========================================================================*
* clock_handler *
*===========================================================================*/
PRIVATE int clock_handler(hook)
irq_hook_t *hook;
{
/* This executes on each clock tick (i.e., every time the timer chip generates
* an interrupt). It does a little bit of work so the clock task does not have
* to be called on every tick. The clock task is called when:
*
* (1) the scheduling quantum of the running process has expired, or
* (2) a timer has expired and the watchdog function should be run.
*
* Many global global and static variables are accessed here. The safety of
* this must be justified. All scheduling and message passing code acquires a
* lock by temporarily disabling interrupts, so no conflicts with calls from
* the task level can occur. Furthermore, interrupts are not reentrant, the
* interrupt handler cannot be bothered by other interrupts.
*
* Variables that are updated in the clock's interrupt handler:
* lost_ticks:
* Clock ticks counted outside the clock task. This for example
* is used when the boot monitor processes a real mode interrupt.
* realtime:
* The current uptime is incremented with all outstanding ticks.
* proc_ptr, bill_ptr:
* These are used for accounting and virtual timers. It does not
* matter if proc.c is changing them, provided they are always
* valid pointers, since at worst the previous process would be
* billed.
/*
* The boot processor timer interrupt handler. In addition to non-boot cpus it
* keeps real time and notifies the clock task if need be
*/
register unsigned ticks;
register int expired;
PUBLIC int bsp_timer_int_handler(void)
{
unsigned ticks;
if(minix_panicing) return;
if(minix_panicing)
return 0;
/* Get number of ticks and update realtime. */
ticks = lost_ticks + 1;
lost_ticks = 0;
realtime += ticks;
/* Get number of ticks and update realtime. */
ticks = lost_ticks + 1;
lost_ticks = 0;
realtime += ticks;
/* Update user and system accounting times. Charge the current process for
* user time. If the current process is not billable, that is, if a non-user
* process is running, charge the billable process for system time as well.
* Thus the unbillable process' user time is the billable user's system time.
*/
proc_ptr->p_user_time += ticks;
if (priv(proc_ptr)->s_flags & PREEMPTIBLE) {
proc_ptr->p_ticks_left -= ticks;
}
if (! (priv(proc_ptr)->s_flags & BILLABLE)) {
bill_ptr->p_sys_time += ticks;
bill_ptr->p_ticks_left -= ticks;
}
ap_timer_int_handler();
/* Decrement virtual timers, if applicable. We decrement both the virtual
* and the profile timer of the current process, and if the current process
* is not billable, the timer of the billed process as well.
* If any of the timers expire, do_clocktick() will send out signals.
*/
expired = 0;
if ((proc_ptr->p_misc_flags & MF_VIRT_TIMER) &&
(proc_ptr->p_virt_left -= ticks) <= 0) expired = 1;
if ((proc_ptr->p_misc_flags & MF_PROF_TIMER) &&
(proc_ptr->p_prof_left -= ticks) <= 0) expired = 1;
if (! (priv(proc_ptr)->s_flags & BILLABLE) &&
(bill_ptr->p_misc_flags & MF_PROF_TIMER) &&
(bill_ptr->p_prof_left -= ticks) <= 0) expired = 1;
/* if a timer expired, notify the clock task */
if ((next_timeout <= realtime)) {
mini_notify(proc_addr(HARDWARE), CLOCK); /* send notification */
}
/* Update load average. */
load_update();
/* Check if do_clocktick() must be called. Done for alarms and scheduling.
* Some processes, such as the kernel tasks, cannot be preempted.
*/
if ((next_timeout <= realtime) || (proc_ptr->p_ticks_left <= 0) || expired) {
prev_ptr = proc_ptr; /* store running process */
mini_notify(proc_addr(HARDWARE), CLOCK); /* send notification */
}
if (do_serial_debug)
do_ser_debug();
if (do_serial_debug)
do_ser_debug();
return(1); /* reenable interrupts */
return(1); /* reenable interrupts */
}
/*===========================================================================*
@ -334,3 +210,92 @@ PRIVATE void load_update(void)
kloadinfo.last_clock = realtime;
}
/*
* Timer interupt handler. This is the only think executed on non boot
* processors. It is called by bsp_timer_int_handler() on the boot processor
*/
PUBLIC int ap_timer_int_handler(void)
{
/* Update user and system accounting times. Charge the current process
* for user time. If the current process is not billable, that is, if a
* non-user process is running, charge the billable process for system
* time as well. Thus the unbillable process' user time is the billable
* user's system time.
*/
unsigned ticks = 1;
int expired = 0;
struct proc * p, * billp;
/* Update user and system accounting times. Charge the current process
* for user time. If the current process is not billable, that is, if a
* non-user process is running, charge the billable process for system
* time as well. Thus the unbillable process' user time is the billable
* user's system time.
*/
/* FIXME prepared for get_cpu_local_var() */
p = proc_ptr;
billp = bill_ptr;
p->p_user_time += ticks;
if (priv(p)->s_flags & PREEMPTIBLE) {
p->p_ticks_left -= ticks;
}
if (! (priv(p)->s_flags & BILLABLE)) {
billp->p_sys_time += ticks;
billp->p_ticks_left -= ticks;
}
/* Decrement virtual timers, if applicable. We decrement both the
* virtual and the profile timer of the current process, and if the
* current process is not billable, the timer of the billed process as
* well. If any of the timers expire, do_clocktick() will send out
* signals.
*/
if ((p->p_misc_flags & MF_VIRT_TIMER) &&
(p->p_virt_left -= ticks) <= 0) expired = 1;
if ((p->p_misc_flags & MF_PROF_TIMER) &&
(p->p_prof_left -= ticks) <= 0) expired = 1;
if (! (priv(p)->s_flags & BILLABLE) &&
(billp->p_misc_flags & MF_PROF_TIMER) &&
(billp->p_prof_left -= ticks) <= 0) expired = 1;
/*
* Check if a process-virtual timer expired. Check current process, but
* also bill_ptr - one process's user time is another's system time, and
* the profile timer decreases for both!
*/
vtimer_check(p);
if (p != billp)
vtimer_check(billp);
/* Update load average. */
load_update();
/* check if the process is still runnable after checking the vtimer */
if (p->p_rts_flags == 0 && p->p_ticks_left <= 0 &&
priv(p)->s_flags & PREEMPTIBLE) {
proc_ptr = NULL; /* no process is scheduled for dequeue and
enqueue */
dequeue(p); /* take it off the queues */
enqueue(p); /* and reinsert it again */
proc_ptr = p; /* restore some consitent state */
}
return 1;
}
PUBLIC int boot_cpu_init_timer(unsigned freq)
{
if (arch_init_local_timer(freq))
return -1;
if (arch_register_local_timer_handler(
(irq_handler_t) bsp_timer_int_handler))
return -1;
return 0;
}

16
kernel/clock.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef __CLOCK_H__
#define __CLOCK_H__
#include "kernel.h"
#include "arch/i386/clock.h"
_PROTOTYPE(int boot_cpu_init_timer, (unsigned freq));
_PROTOTYPE(int bsp_timer_int_handler, (void));
_PROTOTYPE(int ap_timer_int_handler, (void));
_PROTOTYPE(int arch_init_local_timer, (unsigned freq));
_PROTOTYPE(void arch_stop_local_timer, (void));
_PROTOTYPE(int arch_register_local_timer_handler, (irq_handler_t handler));
#endif /* __CLOCK_H__ */

View file

@ -32,7 +32,6 @@ EXTERN struct loadinfo kloadinfo; /* status of load average */
/* Process scheduling information and the kernel reentry count. */
EXTERN struct proc *proc_ptr; /* pointer to currently running process */
EXTERN struct proc *next_ptr; /* next process to run after restart() */
EXTERN struct proc *prev_ptr;
EXTERN struct proc *bill_ptr; /* process to bill for clock ticks */
EXTERN struct proc *vmrestart; /* first process on vmrestart queue */
EXTERN struct proc *vmrequest; /* first process on vmrequest queue */

View file

@ -18,6 +18,7 @@
#include <minix/endpoint.h>
#include "proc.h"
#include "debug.h"
#include "clock.h"
/* Prototype declarations for PRIVATE functions. */
FORWARD _PROTOTYPE( void announce, (void));
@ -195,6 +196,16 @@ PUBLIC void main()
*/
bill_ptr = proc_addr(IDLE); /* it has to point somewhere */
announce(); /* print MINIX startup banner */
/*
* enable timer interrupts and clock task on the boot CPU
*/
if (boot_cpu_init_timer(system_hz)) {
minix_panic("FATAL : failed to initialize timer interrupts, "
"cannot continue without any clock source!",
NO_NUM);
}
/* Warnings for sanity checks that take time. These warnings are printed
* so it's a clear warning no full release should be done with them
* enabled.
@ -208,6 +219,7 @@ PUBLIC void main()
#if DEBUG_PROC_CHECK
FIXME("PROC check enabled");
#endif
restart();
}
@ -257,7 +269,7 @@ timer_t *tp;
* monitor), RBT_MONITOR (execute given code), RBT_RESET (hard reset).
*/
intr_init(INTS_ORIG);
clock_stop();
arch_stop_local_timer();
arch_shutdown(tp ? tmr_arg(tp)->ta_int : RBT_PANIC);
}

View file

@ -1190,9 +1190,8 @@ register struct proc *rp; /* this process is now runnable */
* process yet or current process isn't ready any more, or
* it's PREEMPTIBLE.
*/
vmassert(proc_ptr);
if((proc_ptr->p_priority > rp->p_priority) &&
(priv(proc_ptr)->s_flags & PREEMPTIBLE))
if(!proc_ptr || (proc_ptr->p_priority > rp->p_priority) ||
(priv(proc_ptr)->s_flags & PREEMPTIBLE))
pick_proc();
#if DEBUG_SCHED_CHECK

View file

@ -132,9 +132,6 @@ _PROTOTYPE( int vm_phys_memset, (phys_bytes source, u8_t pattern,
phys_bytes count) );
_PROTOTYPE( vir_bytes alloc_remote_segment, (u32_t *, segframe_t *,
int, phys_bytes, vir_bytes, int));
_PROTOTYPE( int arch_init_clock, (void) );
_PROTOTYPE( clock_t read_clock, (void) );
_PROTOTYPE( void clock_stop, (void) );
_PROTOTYPE( int intr_init, (int) );
_PROTOTYPE( int intr_disabled, (void) );
_PROTOTYPE( void idle_task, (void) );