From 323f0abdd6182b6705d6375850531797d69d0f87 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Sat, 15 Aug 2009 21:37:26 +0000 Subject: [PATCH] Support for setitimer(ITIMER_VIRTUAL/ITIMER_PROF). New test (41) for setitimer. --- commands/ash/mksignames.c | 4 +- commands/ash/trap.c | 2 + commands/simple/kill.c | 2 + include/minix/com.h | 12 +- include/minix/syslib.h | 6 +- include/signal.h | 4 +- kernel/clock.c | 38 +++- kernel/config.h | 1 + kernel/proc.h | 5 + kernel/proto.h | 4 +- kernel/system.c | 1 + kernel/system.h | 5 + kernel/system/Makefile | 4 + kernel/system/do_fork.c | 4 + kernel/system/do_vtimer.c | 116 +++++++++++ lib/syslib/Makefile.in | 1 + lib/syslib/sys_vtimer.c | 28 +++ man/man2/getitimer.2 | 11 +- man/man2/sigaction.2 | 2 + servers/pm/alarm.c | 113 ++++++++++- servers/pm/const.h | 1 + servers/pm/forkexit.c | 9 +- servers/pm/mproc.h | 4 +- servers/pm/proto.h | 1 + servers/pm/signal.c | 11 +- test/Makefile | 6 +- test/test41.c | 409 ++++++++++++++++++++++++++++++++++++++ 27 files changed, 772 insertions(+), 32 deletions(-) create mode 100644 kernel/system/do_vtimer.c create mode 100644 lib/syslib/sys_vtimer.c create mode 100644 test/test41.c diff --git a/commands/ash/mksignames.c b/commands/ash/mksignames.c index bbead25e1..b78786133 100755 --- a/commands/ash/mksignames.c +++ b/commands/ash/mksignames.c @@ -131,8 +131,8 @@ struct sig sigtab[] = { #ifdef SIGXFSZ SIGXFSZ, "XFSZ", NULL, #endif -#ifdef SIGVTALARM - SIGVTALARM, "VTALARM", "Virtual alarm", +#ifdef SIGVTALRM + SIGVTALRM, "VTALARM", "Virtual alarm", #endif #ifdef SIGPROF SIGPROF, "PROF", "Profiling alarm", diff --git a/commands/ash/trap.c b/commands/ash/trap.c index 83ce40991..9cddb6f37 100755 --- a/commands/ash/trap.c +++ b/commands/ash/trap.c @@ -548,6 +548,8 @@ int sig; case SIGTTIN: return "ttin"; /* 21 */ case SIGTTOU: return "ttou"; /* 22 */ case SIGWINCH: return "winch"; /* 23 */ + case SIGVTALRM: return "vtalrm"; /* 24 */ + case SIGPROF: return "prof"; /* 25 */ #ifdef __minix_vmd case SIGFPEMU: return "fpemu"; /* 30 */ #endif diff --git a/commands/simple/kill.c b/commands/simple/kill.c index ae6ab1e36..7f25e4eef 100755 --- a/commands/simple/kill.c +++ b/commands/simple/kill.c @@ -41,6 +41,8 @@ struct signames { #ifdef SIGWINCH { "WINCH", SIGWINCH }, #endif + { "VTALRM", SIGVTALRM }, + { "PROF", SIGPROF }, { NULL, 0 } }; diff --git a/include/minix/com.h b/include/minix/com.h index b640c046d..4402fa760 100755 --- a/include/minix/com.h +++ b/include/minix/com.h @@ -328,7 +328,9 @@ # define SYS_VMCTL (KERNEL_CALL + 43) /* sys_vmctl() */ # define SYS_SYSCTL (KERNEL_CALL + 44) /* sys_sysctl() */ -#define NR_SYS_CALLS 45 /* number of system calls */ +# define SYS_VTIMER (KERNEL_CALL + 45) /* sys_vtimer() */ + +#define NR_SYS_CALLS 46 /* number of system calls */ /* Pseudo call for use in kernel/table.c. */ #define SYS_ALL_CALLS (NR_SYS_CALLS) @@ -604,6 +606,14 @@ #define VMCTL_INCSP 16 #define VMCTL_NOPAGEZERO 18 +/* Field names for SYS_VTIMER. */ +#define VT_WHICH m2_i1 /* which timer to set/retrieve */ +# define VT_VIRTUAL 1 /* the ITIMER_VIRTUAL timer */ +# define VT_PROF 2 /* the ITIMER_PROF timer */ +#define VT_SET m2_i2 /* 1 for setting a timer, 0 retrieval only */ +#define VT_VALUE m2_l1 /* new/previous value of the timer */ +#define VT_ENDPT m2_l2 /* process to set/retrieve the timer for */ + /*===========================================================================* * Messages for the Reincarnation Server * *===========================================================================*/ diff --git a/include/minix/syslib.h b/include/minix/syslib.h index d4facdc2c..9b51b76ac 100755 --- a/include/minix/syslib.h +++ b/include/minix/syslib.h @@ -86,10 +86,14 @@ _PROTOTYPE(void *alloc_contig, (size_t len, int flags, phys_bytes *phys)); #define AC_LOWER16M 0x02 #define AC_ALIGN64K 0x04 -/* Clock functionality: get system times or (un)schedule an alarm call. */ +/* Clock functionality: get system times, (un)schedule an alarm call, or + * retrieve/set a process-virtual timer. + */ _PROTOTYPE( int sys_times, (endpoint_t proc_nr, clock_t *user_time, clock_t *sys_time, clock_t *uptime)); _PROTOTYPE(int sys_setalarm, (clock_t exp_time, int abs_time)); +_PROTOTYPE( int sys_vtimer, (endpoint_t proc_nr, int which, clock_t *newval, + clock_t *oldval)); /* Shorthands for sys_irqctl() system call. */ #define sys_irqdisable(hook_id) \ diff --git a/include/signal.h b/include/signal.h index 9b538190d..a52c77420 100755 --- a/include/signal.h +++ b/include/signal.h @@ -44,6 +44,8 @@ typedef unsigned long sigset_t; #define SIGEMT 16 /* EMT instruction */ #define SIGCHLD 17 /* child process terminated or stopped */ #define SIGWINCH 21 /* window size has changed */ +#define SIGVTALRM 24 /* virtual alarm */ +#define SIGPROF 25 /* profiler alarm */ /* POSIX requires the following signals to be defined, even if they are * not supported. Here are the definitions, but they are not supported. @@ -54,7 +56,7 @@ typedef unsigned long sigset_t; #define SIGTTIN 22 /* background process wants to read */ #define SIGTTOU 23 /* background process wants to write */ -#define _NSIG 24 /* highest signal number plus one */ +#define _NSIG 26 /* highest signal number plus one */ #ifdef _MINIX #define SIGIOT SIGABRT /* for people who speak PDP-11 */ diff --git a/kernel/clock.c b/kernel/clock.c index a1a65cd98..d5eb1ddfc 100755 --- a/kernel/clock.c +++ b/kernel/clock.c @@ -96,6 +96,8 @@ PUBLIC void clock_task() 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. */ @@ -120,6 +122,18 @@ message *m_ptr; /* pointer to request message */ } } + /* 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); @@ -180,11 +194,13 @@ irq_hook_t *hook; * realtime: * The current uptime is incremented with all outstanding ticks. * proc_ptr, bill_ptr: - * These are used for accounting. 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. + * 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. */ register unsigned ticks; + register int expired; if(minix_panicing) return; @@ -208,6 +224,20 @@ irq_hook_t *hook; bill_ptr->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. + */ + expired = 0; + if ((proc_ptr->p_misc_flags & VIRT_TIMER) && + (proc_ptr->p_virt_left -= ticks) <= 0) expired = 1; + if ((proc_ptr->p_misc_flags & PROF_TIMER) && + (proc_ptr->p_prof_left -= ticks) <= 0) expired = 1; + if (! (priv(proc_ptr)->s_flags & BILLABLE) && + (bill_ptr->p_misc_flags & PROF_TIMER) && + (bill_ptr->p_prof_left -= ticks) <= 0) expired = 1; + #if 0 /* Update load average. */ load_update(); @@ -216,7 +246,7 @@ irq_hook_t *hook; /* 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)) { + if ((next_timeout <= realtime) || (proc_ptr->p_ticks_left <= 0) || expired) { prev_ptr = proc_ptr; /* store running process */ lock_notify(HARDWARE, CLOCK); /* send notification */ } diff --git a/kernel/config.h b/kernel/config.h index 2c0203930..07b9c8770 100644 --- a/kernel/config.h +++ b/kernel/config.h @@ -29,6 +29,7 @@ #define USE_GETINFO 1 /* retrieve a copy of kernel data */ #define USE_TIMES 1 /* get process and system time info */ #define USE_SETALARM 1 /* schedule a synchronous alarm */ +#define USE_VTIMER 1 /* set or retrieve a process-virtual timer */ #define USE_DEVIO 1 /* read or write a single I/O port */ #define USE_VDEVIO 1 /* process vector with I/O requests */ #define USE_SDEVIO 1 /* perform I/O request on a buffer */ diff --git a/kernel/proc.h b/kernel/proc.h index df90bfed4..f08f434a5 100755 --- a/kernel/proc.h +++ b/kernel/proc.h @@ -33,6 +33,9 @@ struct proc { clock_t p_user_time; /* user time in ticks */ clock_t p_sys_time; /* sys time in ticks */ + clock_t p_virt_left; /* number of ticks left on virtual timer */ + clock_t p_prof_left; /* number of ticks left on profile timer */ + struct proc *p_nextready; /* pointer to next ready process */ struct proc *p_caller_q; /* head of list of procs wishing to send */ struct proc *p_q_link; /* link to next proc wishing to send */ @@ -169,6 +172,8 @@ struct proc { /* Misc flags */ #define REPLY_PENDING 0x01 /* reply to IPC_REQUEST is pending */ +#define VIRT_TIMER 0x02 /* process-virtual timer is running */ +#define PROF_TIMER 0x04 /* process-virtual profile timer is running */ #define MF_VM 0x08 /* process uses VM */ #define MF_ASYNMSG 0x10 /* Asynchrous message pending */ #define MF_FULLVM 0x20 diff --git a/kernel/proto.h b/kernel/proto.h index c22930991..88f1bb8f2 100755 --- a/kernel/proto.h +++ b/kernel/proto.h @@ -70,10 +70,12 @@ _PROTOTYPE( void clear_endpoint, (struct proc *rc) ); _PROTOTYPE( phys_bytes umap_bios, (vir_bytes vir_addr, vir_bytes bytes)); _PROTOTYPE( phys_bytes umap_verify_grant, (struct proc *rp, endpoint_t grantee, cp_grant_id_t grant, vir_bytes offset, vir_bytes bytes, int access)); - /* system/do_newmap.c */ _PROTOTYPE( int newmap, (struct proc *rp, struct mem_map *map_ptr) ); +/* system/do_vtimer.c */ +_PROTOTYPE( void vtimer_check, (struct proc *rp) ); + /* interrupt.c */ _PROTOTYPE( void intr_handle, (irq_hook_t *hook) ); _PROTOTYPE( void put_irq_handler, (irq_hook_t *hook, int irq, diff --git a/kernel/system.c b/kernel/system.c index 1af2b4920..992770ee7 100755 --- a/kernel/system.c +++ b/kernel/system.c @@ -239,6 +239,7 @@ PRIVATE void initialize(void) map(SYS_TIMES, do_times); /* get uptime and process times */ map(SYS_SETALARM, do_setalarm); /* schedule a synchronous alarm */ map(SYS_STIME, do_stime); /* set the boottime */ + map(SYS_VTIMER, do_vtimer); /* set or retrieve a virtual timer */ /* System control. */ map(SYS_ABORT, do_abort); /* abort MINIX */ diff --git a/kernel/system.h b/kernel/system.h index 10b4f4003..14f55df3c 100644 --- a/kernel/system.h +++ b/kernel/system.h @@ -173,6 +173,11 @@ _PROTOTYPE( int do_setalarm, (message *m_ptr) ); _PROTOTYPE( int do_stime, (message *m_ptr) ); +_PROTOTYPE( int do_vtimer, (message *m_ptr) ); +#if ! USE_VTIMER +#define do_vtimer do_unused +#endif + _PROTOTYPE( int do_safecopy, (message *m_ptr) ); _PROTOTYPE( int do_vsafecopy, (message *m_ptr) ); _PROTOTYPE( int do_iopenable, (message *m_ptr) ); diff --git a/kernel/system/Makefile b/kernel/system/Makefile index 02a7e0fe8..496663d1d 100644 --- a/kernel/system/Makefile +++ b/kernel/system/Makefile @@ -32,6 +32,7 @@ OBJECTS = \ $(SYSTEM)(do_times.o) \ $(SYSTEM)(do_setalarm.o) \ $(SYSTEM)(do_stime.o) \ + $(SYSTEM)(do_vtimer.o) \ $(SYSTEM)(do_irqctl.o) \ $(SYSTEM)(do_devio.o) \ $(SYSTEM)(do_vdevio.o) \ @@ -101,6 +102,9 @@ $(SYSTEM)(do_setalarm.o): do_setalarm.c $(SYSTEM)(do_stime.o): do_stime.c $(CC) do_stime.c +$(SYSTEM)(do_vtimer.o): do_vtimer.c + $(CC) do_vtimer.c + $(SYSTEM)(do_irqctl.o): do_irqctl.c $(CC) do_irqctl.c diff --git a/kernel/system/do_fork.c b/kernel/system/do_fork.c index a365ab77a..29b49c03e 100644 --- a/kernel/system/do_fork.c +++ b/kernel/system/do_fork.c @@ -57,6 +57,10 @@ register message *m_ptr; /* pointer to request message */ rpc->p_reg.psw &= ~TRACEBIT; /* clear trace bit */ + rpc->p_misc_flags &= ~(VIRT_TIMER | PROF_TIMER); + rpc->p_virt_left = 0; /* disable, clear the process-virtual timers */ + rpc->p_prof_left = 0; + /* Parent and child have to share the quantum that the forked process had, * so that queued processes do not have to wait longer because of the fork. * If the time left is odd, the child gets an extra tick. diff --git a/kernel/system/do_vtimer.c b/kernel/system/do_vtimer.c new file mode 100644 index 000000000..8904ab60d --- /dev/null +++ b/kernel/system/do_vtimer.c @@ -0,0 +1,116 @@ +/* The kernel call implemented in this file: + * m_type: SYS_VTIMER + * + * The parameters for this kernel call are: + * m2_i1: VT_WHICH (the timer: VT_VIRTUAL or VT_PROF) + * m2_i2: VT_SET (whether to set, or just retrieve) + * m2_l1: VT_VALUE (new/old expiration time, in ticks) + * m2_l2: VT_ENDPT (process to which the timer belongs) + */ + +#include "../system.h" + +#include +#include + +#if USE_VTIMER + +/*===========================================================================* + * do_vtimer * + *===========================================================================*/ +PUBLIC int do_vtimer(m_ptr) +message *m_ptr; /* pointer to request message */ +{ +/* Set and/or retrieve the value of one of a process' virtual timers. */ + struct proc *rrp; /* pointer to requesting process */ + struct proc *rp; /* pointer to process the timer belongs to */ + register int pt_flag; /* the misc on/off flag for the req.d timer */ + register clock_t *pt_left; /* pointer to the process' ticks-left field */ + clock_t old_value; /* the previous number of ticks left */ + int proc_nr, proc_nr_e; + + /* The requesting process must be privileged. */ + rrp = proc_addr(who_p); + if (! (priv(rrp)->s_flags & SYS_PROC)) return(EPERM); + + if (m_ptr->VT_WHICH != VT_VIRTUAL && m_ptr->VT_WHICH != VT_PROF) + return(EINVAL); + + /* The target process must be valid. */ + proc_nr_e = (m_ptr->VT_ENDPT == SELF) ? m_ptr->m_source : m_ptr->VT_ENDPT; + if (!isokendpt(proc_nr_e, &proc_nr)) return(EINVAL); + rp = proc_addr(proc_nr); + + /* Determine which flag and which field in the proc structure we want to + * retrieve and/or modify. This saves us having to differentiate between + * VT_VIRTUAL and VT_PROF multiple times below. + */ + if (m_ptr->VT_WHICH == VT_VIRTUAL) { + pt_flag = VIRT_TIMER; + pt_left = &rp->p_virt_left; + } else { /* VT_PROF */ + pt_flag = PROF_TIMER; + pt_left = &rp->p_prof_left; + } + + /* Retrieve the old value. */ + if (rp->p_misc_flags & pt_flag) { + old_value = *pt_left; + + if (old_value < 0) old_value = 0; + } else { + old_value = 0; + } + + /* Set the new value, if requested. This is called from the system task, so + * we can be interrupted by the clock interrupt, but not by the clock task. + * Therefore we only have to protect against interference from clock.c's + * clock_handler(). We can do this without disabling interrupts, by removing + * the timer's flag before changing the ticks-left field; in that case the + * clock interrupt will not touch the latter anymore. + */ + if (m_ptr->VT_SET) { + rp->p_misc_flags &= ~pt_flag; /* disable virtual timer */ + + if (m_ptr->VT_VALUE > 0) { + *pt_left = m_ptr->VT_VALUE; /* set new timer value */ + rp->p_misc_flags |= pt_flag; /* (re)enable virtual timer */ + } else { + *pt_left = 0; /* clear timer value */ + } + } + + m_ptr->VT_VALUE = old_value; + + return(OK); +} + +#endif /* USE_VTIMER */ + +/*===========================================================================* + * vtimer_check * + *===========================================================================*/ +PUBLIC void vtimer_check(rp) +struct proc *rp; /* pointer to the process */ +{ + /* This is called from the clock task, so we can be interrupted by the clock + * interrupt, but not by the system task. Therefore we only have to protect + * against interference from the clock handler. We can safely perform the + * following actions without locking as well though, as the clock handler + * never alters p_misc_flags, and only decreases p_virt_left/p_prof_left. + */ + + /* Check if the virtual timer expired. If so, send a SIGVTALRM signal. */ + if ((rp->p_misc_flags & VIRT_TIMER) && rp->p_virt_left <= 0) { + rp->p_misc_flags &= ~VIRT_TIMER; + rp->p_virt_left = 0; + cause_sig(rp->p_nr, SIGVTALRM); + } + + /* Check if the profile timer expired. If so, send a SIGPROF signal. */ + if ((rp->p_misc_flags & PROF_TIMER) && rp->p_prof_left <= 0) { + rp->p_misc_flags &= ~PROF_TIMER; + rp->p_prof_left = 0; + cause_sig(rp->p_nr, SIGPROF); + } +} diff --git a/lib/syslib/Makefile.in b/lib/syslib/Makefile.in index 8f4f339e6..c8882352f 100644 --- a/lib/syslib/Makefile.in +++ b/lib/syslib/Makefile.in @@ -71,6 +71,7 @@ libsys_FILES=" \ sys_voutb.c \ sys_voutl.c \ sys_voutw.c \ + sys_vtimer.c \ taskcall.c \ ds.c \ vm_allocmem.c \ diff --git a/lib/syslib/sys_vtimer.c b/lib/syslib/sys_vtimer.c new file mode 100644 index 000000000..9ec2efc33 --- /dev/null +++ b/lib/syslib/sys_vtimer.c @@ -0,0 +1,28 @@ +#include "syslib.h" + +PUBLIC int sys_vtimer(proc, which, newval, oldval) +endpoint_t proc; /* proc to retrieve/set the timer for */ +int which; /* timer to retrieve/set */ +clock_t *newval; /* if non-NULL, set to this new value */ +clock_t *oldval; /* if non-NULL, old value is stored here */ +{ + message m; + int r; + + m.VT_ENDPT = proc; + m.VT_WHICH = which; + if (newval != NULL) { + m.VT_SET = 1; + m.VT_VALUE = *newval; + } else { + m.VT_SET = 0; + } + + r = _taskcall(SYSTASK, SYS_VTIMER, &m); + + if (oldval != NULL) { + *oldval = m.VT_VALUE; + } + + return(r); +} diff --git a/man/man2/getitimer.2 b/man/man2/getitimer.2 index c30d2cd7b..76c783445 100644 --- a/man/man2/getitimer.2 +++ b/man/man2/getitimer.2 @@ -32,10 +32,14 @@ A timer that is decremented in realtime. When it expires, a signal is delivered to the process. .TP .B ITIMER_VIRTUAL -Not supported on Minix. +A timer that is decremented in process user time. When it expires, a +.BR SIGVTALRM +signal is delivered to the process. .TP .B ITIMER_PROF -Not supported on Minix. +A timer that is decremented in process user+system time. When it expires, a +.BR SIGPROF +signal is delivered to the process. .PP The specified timer will first expire after the time specified in the 'it_value' field of the itimerval structure. Similarly, upon retrieval the 'it_value' field will contain the time after which the timer will expire. .PP @@ -58,9 +62,6 @@ Either \fIwhich\fP is not one of the ITIMER_* constants above, or one of the tim .TP .B EFAULT Bad \fIvalue\fP or \fIovalue\fP address. -.TP -.B ENOSYS -The value of \fIwhich\fP is not ITIMER_REAL. .SH SEE ALSO .BR alarm (2) .SH AUTHOR diff --git a/man/man2/sigaction.2 b/man/man2/sigaction.2 index fdb6ad088..67dd272fb 100644 --- a/man/man2/sigaction.2 +++ b/man/man2/sigaction.2 @@ -148,6 +148,8 @@ SIGTSTP 20 ps Interactive stop signal SIGTTIN 21 ps Background read SIGTTOU 22 ps Background write SIGWINCH 23 xvi Window size change +SIGVTALRM 24 xk Virtual alarm clock +SIGPROF 25 xk Profiler alarm clock .ft R .fi .PP diff --git a/servers/pm/alarm.c b/servers/pm/alarm.c index 0fcc1d151..33a44176f 100644 --- a/servers/pm/alarm.c +++ b/servers/pm/alarm.c @@ -6,6 +6,7 @@ * do_itimer: perform the ITIMER system call * do_alarm: perform the ALARM system call * set_alarm: tell the timer interface to start or stop a process timer + * check_vtimer: check if one of the virtual timers needs to be restarted */ #include "pm.h" @@ -22,6 +23,8 @@ FORWARD _PROTOTYPE( clock_t ticks_from_timeval, (struct timeval *tv) ); FORWARD _PROTOTYPE( void timeval_from_ticks, (struct timeval *tv, clock_t ticks) ); FORWARD _PROTOTYPE( int is_sane_timeval, (struct timeval *tv) ); +FORWARD _PROTOTYPE( void getset_vtimer, (struct mproc *mp, int nwhich, + struct itimerval *value, struct itimerval *ovalue) ); FORWARD _PROTOTYPE( void get_realtimer, (struct mproc *mp, struct itimerval *value) ); FORWARD _PROTOTYPE( void set_realtimer, (struct mproc *mp, @@ -98,7 +101,7 @@ PUBLIC int do_itimer() int r; /* Make sure 'which' is one of the defined timers. */ - if (m_in.which_timer < ITIMER_REAL || m_in.which_timer > ITIMER_PROF) + if (m_in.which_timer < 0 || m_in.which_timer >= NR_ITIMERS) return(EINVAL); /* Determine whether to set and/or return the given timer value, based on @@ -134,8 +137,11 @@ PUBLIC int do_itimer() case ITIMER_VIRTUAL : case ITIMER_PROF : - /* Not implemented. */ - r = ENOSYS; + getset_vtimer(mp, m_in.which_timer, + (setval) ? &value : NULL, + (getval) ? &ovalue : NULL); + + r = OK; break; } @@ -172,6 +178,97 @@ PUBLIC int do_alarm() return(remaining); } +/*===========================================================================* + * getset_vtimer * + *===========================================================================*/ +PRIVATE void getset_vtimer(rmp, which, value, ovalue) +struct mproc *rmp; +int which; +struct itimerval *value; +struct itimerval *ovalue; +{ + clock_t newticks, *nptr; /* the new timer value, in ticks */ + clock_t oldticks, *optr; /* the old ticks value, in ticks */ + int r, num; + + /* The default is to provide sys_vtimer with two null pointers, i.e. to do + * nothing at all. + */ + optr = nptr = NULL; + + /* If the old timer value is to be retrieved, have 'optr' point to the + * location where the old value is to be stored, and copy the interval. + */ + if (ovalue != NULL) { + optr = &oldticks; + + timeval_from_ticks(&ovalue->it_interval, rmp->mp_interval[which]); + } + + /* If a new timer value is to be set, store the new timer value and have + * 'nptr' point to it. Also, store the new interval. + */ + if (value != NULL) { + newticks = ticks_from_timeval(&value->it_value); + nptr = &newticks; + + /* If no timer is set, the interval must be zero. */ + if (newticks <= 0) + rmp->mp_interval[which] = 0; + else + rmp->mp_interval[which] = + ticks_from_timeval(&value->it_interval); + } + + /* Find out which kernel timer number to use. */ + switch (which) { + case ITIMER_VIRTUAL: num = VT_VIRTUAL; break; + case ITIMER_PROF: num = VT_PROF; break; + default: panic(__FILE__, "invalid vtimer type", which); + } + + /* Make the kernel call. If requested, also retrieve and store + * the old timer value. + */ + if ((r = sys_vtimer(rmp->mp_endpoint, num, nptr, optr)) != OK) + panic(__FILE__, "sys_vtimer failed", r); + + if (ovalue != NULL) { + /* If the alarm expired already, we should take into account the + * interval. Return zero only if the interval is zero as well. + */ + if (oldticks <= 0) oldticks = rmp->mp_interval[which]; + + timeval_from_ticks(&ovalue->it_value, oldticks); + } +} + +/*===========================================================================* + * check_vtimer * + *===========================================================================*/ +PUBLIC void check_vtimer(proc_nr, sig) +int proc_nr; +int sig; +{ + register struct mproc *rmp; + int which, num; + + rmp = &mproc[proc_nr]; + + /* Translate back the given signal to a timer type and kernel number. */ + switch (sig) { + case SIGVTALRM: which = ITIMER_VIRTUAL; num = VT_VIRTUAL; break; + case SIGPROF: which = ITIMER_PROF; num = VT_PROF; break; + default: panic(__FILE__, "invalid vtimer signal", sig); + } + + /* If a repetition interval was set for this virtual timer, tell the + * kernel to set a new timeout for the virtual timer. + */ + if (rmp->mp_interval[which] > 0) + sys_vtimer(rmp->mp_endpoint, num, &rmp->mp_interval[which], NULL); +} + /*===========================================================================* * get_realtimer * *===========================================================================*/ @@ -195,7 +292,7 @@ struct itimerval *value; /* If the alarm expired already, we should take into account the * interval. Return zero only if the interval is zero as well. */ - if (remaining <= 0) remaining = rmp->mp_interval; + if (remaining <= 0) remaining = rmp->mp_interval[ITIMER_REAL]; } else { remaining = 0; } @@ -204,7 +301,7 @@ struct itimerval *value; timeval_from_ticks(&value->it_value, remaining); /* Similarly convert and store the interval of the timer. */ - timeval_from_ticks(&value->it_interval, rmp->mp_interval); + timeval_from_ticks(&value->it_interval, rmp->mp_interval[ITIMER_REAL]); } /*===========================================================================* @@ -226,7 +323,7 @@ struct itimerval *value; /* Apply these values. */ set_alarm(rmp, ticks); - rmp->mp_interval = interval; + rmp->mp_interval[ITIMER_REAL] = interval; } /*===========================================================================* @@ -270,8 +367,8 @@ struct timer *tp; * The set_alarm call will be calling pm_set_timer from within this callback * from the pm_expire_timers function. This is safe, but we must not use the * "tp" structure below this point anymore. */ - if (rmp->mp_interval > 0) - set_alarm(rmp, rmp->mp_interval); + if (rmp->mp_interval[ITIMER_REAL] > 0) + set_alarm(rmp, rmp->mp_interval[ITIMER_REAL]); else rmp->mp_flags &= ~ALARM_ON; check_sig(rmp->mp_pid, SIGALRM); diff --git a/servers/pm/const.h b/servers/pm/const.h index 64558475e..cf75e72c5 100644 --- a/servers/pm/const.h +++ b/servers/pm/const.h @@ -12,3 +12,4 @@ #define MAX_SECS (((1<<(sizeof(clock_t)*8-1))-1)/system_hz) /* max.secs for setitimer() ((2^31-1)/HZ) */ +#define NR_ITIMERS 3 /* number of supported interval timers */ diff --git a/servers/pm/forkexit.c b/servers/pm/forkexit.c index da8d8a9f8..5af2ac794 100644 --- a/servers/pm/forkexit.c +++ b/servers/pm/forkexit.c @@ -41,7 +41,7 @@ PUBLIC int do_fork() register struct mproc *rmc; /* pointer to child */ pid_t new_pid; static int next_child; - int n = 0, r, s; + int i, n = 0, r, s; endpoint_t child_ep; /* If tables might fill up during FORK, don't even start since recovery half @@ -86,7 +86,8 @@ PUBLIC int do_fork() rmc->mp_exitstatus = 0; rmc->mp_sigstatus = 0; rmc->mp_endpoint = child_ep; /* passed back by VM */ - rmc->mp_interval = 0; /* reset interval timer */ + for (i = 0; i < NR_ITIMERS; i++) + rmc->mp_interval[i] = 0; /* reset timer intervals */ /* Find a free pid for the child and put it in the table. */ new_pid = get_free_pid(); @@ -115,7 +116,7 @@ PUBLIC int do_fork_nb() int s; pid_t new_pid; static int next_child; - int n = 0, r; + int i, n = 0, r; endpoint_t child_ep; /* Only system processes are allowed to use fork_nb */ @@ -159,6 +160,8 @@ PUBLIC int do_fork_nb() rmc->mp_child_utime = 0; /* reset administration */ rmc->mp_child_stime = 0; /* reset administration */ rmc->mp_endpoint = child_ep; /* passed back by VM */ + for (i = 0; i < NR_ITIMERS; i++) + rmc->mp_interval[i] = 0; /* reset timer intervals */ /* Find a free pid for the child and put it in the table. */ new_pid = get_free_pid(); diff --git a/servers/pm/mproc.h b/servers/pm/mproc.h index 7e113680c..c50b3764d 100644 --- a/servers/pm/mproc.h +++ b/servers/pm/mproc.h @@ -7,6 +7,8 @@ #include #include +#include "const.h" + EXTERN struct mproc { char mp_exitstatus; /* storage for status when process exits */ char mp_sigstatus; /* storage for signal # for killed procs */ @@ -39,7 +41,7 @@ EXTERN struct mproc { * PM_UNPAUSE request is delivered. */ struct timer mp_timer; /* watchdog timer for alarm(2), setitimer(2) */ - clock_t mp_interval; /* repetition interval for setitimer(2) */ + clock_t mp_interval[NR_ITIMERS]; /* setitimer(2) repetition intervals */ unsigned mp_flags; /* flag bits */ vir_bytes mp_procargs; /* ptr to proc's initial stack arguments */ diff --git a/servers/pm/proto.h b/servers/pm/proto.h index 5bfee1eeb..3c091ea88 100644 --- a/servers/pm/proto.h +++ b/servers/pm/proto.h @@ -11,6 +11,7 @@ struct memory; _PROTOTYPE( int do_alarm, (void) ); _PROTOTYPE( int do_itimer, (void) ); _PROTOTYPE( void set_alarm, (struct mproc *rmp, clock_t ticks) ); +_PROTOTYPE( void check_vtimer, (int proc_nr, int sig) ); /* break.c */ _PROTOTYPE( int do_brk, (void) ); diff --git a/servers/pm/signal.c b/servers/pm/signal.c index 2a05b3723..aec9a670c 100644 --- a/servers/pm/signal.c +++ b/servers/pm/signal.c @@ -264,9 +264,10 @@ sigset_t sig_map; /* Check each bit in turn to see if a signal is to be sent. Unlike * kill(), the kernel may collect several unrelated signals for a * process and pass them to PM in one blow. Thus loop on the bit - * map. For SIGINT, SIGWINCH and SIGQUIT, use proc_id 0 to indicate - * a broadcast to the recipient's process group. For SIGKILL, use - * proc_id -1 to indicate a systemwide broadcast. + * map. For SIGVTALRM and SIGPROF, see if we need to restart a + * virtual timer. For SIGINT, SIGWINCH and SIGQUIT, use proc_id 0 + * to indicate a broadcast to the recipient's process group. For + * SIGKILL, use proc_id -1 to indicate a systemwide broadcast. */ for (i = 1; i <= _NSIG; i++) { if (!sigismember(&sig_map, i)) continue; @@ -279,6 +280,10 @@ sigset_t sig_map; case SIGQUIT: case SIGWINCH: id = 0; break; /* broadcast to process group */ + case SIGVTALRM: + case SIGPROF: + check_vtimer(proc_nr, i); + /* fall-through */ default: id = proc_id; break; diff --git a/test/Makefile b/test/Makefile index 5f788ced7..dde86756a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -7,7 +7,7 @@ OBJ= test1 test2 test3 test4 test5 test6 test7 test8 test9 \ test10 test12 test13 test14 test15 test16 test17 test18 test19 \ test21 test22 test23 test25 test26 test27 test28 test29 \ test30 test31 test32 test34 test35 test36 test37 test38 \ - test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f + test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 BIGOBJ= test20 test24 ROOTOBJ= test11 test33 @@ -28,7 +28,8 @@ $(ROOTOBJ): clean: cd select && make clean - -rm -rf *.o *.s *.bak test? test?? t10a t11a t11b t40a t40b t40c t40d t40e t40f DIR* + -rm -rf *.o *.s *.bak test? test?? t10a t11a t11b \ + t40a t40b t40c t40d t40e t40f DIR* test1: test1.c test2: test2.c @@ -79,3 +80,4 @@ t40c: t40c.c t40d: t40d.c t40e: t40e.c t40f: t40f.c +test41: test41.c diff --git a/test/test41.c b/test/test41.c new file mode 100644 index 000000000..2a0bc1064 --- /dev/null +++ b/test/test41.c @@ -0,0 +1,409 @@ +/* Tests for getitimer(2)/setitimer(2) - by D.C. van Moolenbroek */ +/* Warning: this test deals with (real and virtual) time, and, lacking a proper + * point of reference, its correctness depends on circumstances like CPU speed + * and system load. A succeeding test run says a lot - failure not so much. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ITERATIONS 3 +#define MAX_ERROR 4 + +/* we have to keep in mind the millisecond values are rounded up */ +#define UPPERUSEC(us) ((us)+(1000000/system_hz)) +#define EQUSEC(l,r) \ + ((l) <= ((r) + (1000000/system_hz)) && (l) >= ((r) - (1000000/system_hz))) + +#define FILLITIMER(it, vs, vu, is, iu) \ + (it).it_value.tv_sec = (vs); \ + (it).it_value.tv_usec = (vu); \ + (it).it_interval.tv_sec = (is); \ + (it).it_interval.tv_usec = (iu); + +/* these two macros are not fully working for all possible values; + * the tests only use values that the macros can deal with, though. */ +#define EQITIMER(it, vs, vu, is, iu) \ + ((it).it_value.tv_sec == (vs) && EQUSEC((it).it_value.tv_usec,vu) && \ + (it).it_interval.tv_sec == (is) && (it).it_interval.tv_usec == (iu)) + +#define LEITIMER(it, vs, vu, is, iu) \ + ((it).it_value.tv_sec > 0 && ((it).it_value.tv_sec < (vs) || \ + ((it).it_value.tv_sec == (vs) && (it).it_value.tv_usec <= \ + UPPERUSEC(vu))) && \ + (it).it_interval.tv_sec == (is) && EQUSEC((it).it_interval.tv_usec,iu)) + +_PROTOTYPE(int main, (int argc, char **argv)); +_PROTOTYPE(void test, (int m, int t)); +_PROTOTYPE(void test_which, (void)); +_PROTOTYPE(void test_getset, (void)); +_PROTOTYPE(void test_neglarge, (void)); +_PROTOTYPE(void test_zero, (void)); +_PROTOTYPE(void test_timer, (void)); +_PROTOTYPE(void test_alarm, (void)); +_PROTOTYPE(void test_fork, (void)); +_PROTOTYPE(void test_exec, (void)); +_PROTOTYPE(int do_check, (void)); +_PROTOTYPE(void got_alarm, (int sig)); +_PROTOTYPE(void busy_wait, (int secs)); +_PROTOTYPE(void e, (int n)); +_PROTOTYPE(void quit, (void)); + +static char *executable; +static int signals; +static int timer; +static int errct = 0, subtest; +static long system_hz; + +static int sigs[] = { SIGALRM, SIGVTALRM, SIGPROF }; +static const char *names[] = { "REAL", "VIRTUAL", "PROF" }; + +int main(argc, argv) +int argc; +char **argv; +{ + int i, m = 0xFFFF, n = 0xF; + + getsysinfo_up(PM_PROC_NR, SIU_SYSTEMHZ, sizeof(system_hz), &system_hz); + + if (strcmp(argv[0], "DO CHECK") == 0) { + timer = atoi(argv[1]); + + exit(do_check()); + } + + printf("Test 41 "); + fflush(stdout); + + executable = argv[0]; + + if (argc >= 2) m = atoi(argv[1]); + if (argc >= 3) n = atoi(argv[2]); + + for (i = 0; i < ITERATIONS; i++) { + if (n & 1) test(m, ITIMER_REAL); + if (n & 2) test(m, ITIMER_VIRTUAL); + if (n & 4) test(m, ITIMER_PROF); + } + + quit(); + return(-1); /* impossible */ +} + +void test(m, t) +int m; +int t; +{ + timer = t; + + if (m & 0001) test_which(); + if (m & 0002) test_getset(); + if (m & 0004) test_neglarge(); + if (m & 0010) test_zero(); + if (m & 0020) test_timer(); + if (m & 0040) test_alarm(); + if (m & 0100) test_fork(); + if (m & 0200) test_exec(); +} + +/* test invalid and unsupported 'which' values */ +void test_which() +{ + struct itimerval it; + + subtest = 0; + + errno = 0; if (!getitimer(-1, &it) || errno != EINVAL) e(1); + errno = 0; if ( getitimer(timer, &it) ) e(2); + errno = 0; if (!getitimer( 3, &it) || errno != EINVAL) e(3); +} + +/* test if we get back what we set */ +void test_getset() +{ + struct itimerval it, oit; + + subtest = 1; + + /* no alarm should be set initially */ + if (getitimer(timer, &it)) e(1); + if (!EQITIMER(it, 0, 0, 0, 0)) e(2); + + if (setitimer(timer, &it, &oit)) e(3); + if (setitimer(timer, &oit, NULL)) e(4); + if (!EQITIMER(oit, 0, 0, 0, 0)) e(5); + + FILLITIMER(it, 123, 0, 456, 0); + if (setitimer(timer, &it, NULL)) e(6); + + FILLITIMER(it, 987, 0, 654, 0); + if (setitimer(timer, &it, &oit)) e(7); + if (!LEITIMER(oit, 123, 0, 456, 0)) e(8); + + if (getitimer(timer, &oit)) e(9); + if (!LEITIMER(oit, 987, 0, 654, 0)) e(10); + + FILLITIMER(it, 0, 0, 0, 0); + if (setitimer(timer, &it, &oit)) e(11); + if (!LEITIMER(oit, 987, 0, 654, 0)) e(12); + + if (getitimer(timer, &oit)) e(13); + if (!EQITIMER(oit, 0, 0, 0, 0)) e(14); +} + +/* test negative/large values */ +void test_neglarge() +{ + struct itimerval it; + + subtest = 2; + + FILLITIMER(it, 4, 0, 5, 0); + if (setitimer(timer, &it, NULL)) e(1); + + FILLITIMER(it, 1000000000, 0, 0, 0); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(2); + + FILLITIMER(it, 0, 1000000, 0, 0); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(3); + + FILLITIMER(it, 0, 0, 0, 1000000); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(4); + + FILLITIMER(it, -1, 0, 0, 0); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(5); + + FILLITIMER(it, 0, -1, 0, 0); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(6); + + FILLITIMER(it, 0, 0, -1, 0); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(7); + + FILLITIMER(it, 0, 0, 0, -1); + if (!setitimer(timer, &it, NULL) || errno != EINVAL) e(8); + + if (getitimer(timer, &it)) e(9); + if (!LEITIMER(it, 4, 0, 5, 0)) e(10); +} + +/* setitimer with a zero timer has to set the interval to zero as well */ +void test_zero() +{ + struct itimerval it; + + subtest = 3; + + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + it.it_interval.tv_sec = 1; + it.it_interval.tv_usec = 1; + + if (setitimer(timer, &it, NULL)) e(1); + if (getitimer(timer, &it)) e(2); + if (!EQITIMER(it, 0, 0, 0, 0)) e(3); +} + +/* test actual timer functioning */ +void test_timer() +{ + struct itimerval it; + + subtest = 4; + + if (signal(sigs[timer], got_alarm) == SIG_ERR) e(1); + + FILLITIMER(it, 0, 1, 0, 1); + + if (setitimer(timer, &it, NULL)) e(2); + + signals = 0; + busy_wait(1); + + FILLITIMER(it, 0, 0, 0, 0); + if (setitimer(timer, &it, NULL)) e(3); + + /* we don't know how many signals we'll actually get in practice, + * so these checks more or less cover the extremes of the acceptable */ + if (signals < 2) e(4); + if (signals > system_hz * 2) e(5); + + /* only for REAL timer can we check against the clock */ + if (timer == ITIMER_REAL) { + FILLITIMER(it, 1, 0, 0, 0); + if (setitimer(timer, &it, NULL)) e(6); + + signals = 0; + busy_wait(1); + + FILLITIMER(it, 0, 0, 0, 0); + if (setitimer(timer, &it, NULL)) e(7); + + if (signals != 1) e(8); + } + + signals = 0; + busy_wait(1); + + if (signals != 0) e(9); +} + +/* test itimer/alarm interaction */ +void test_alarm(void) { + struct itimerval it; + + /* only applicable for ITIMER_REAL */ + if (timer != ITIMER_REAL) return; + + subtest = 5; + + if (signal(SIGALRM, got_alarm) == SIG_ERR) e(1); + + FILLITIMER(it, 3, 0, 1, 0); + if (setitimer(timer, &it, NULL)) e(2); + + if (alarm(2) != 3) e(3); + + if (getitimer(timer, &it)) e(4); + if (!LEITIMER(it, 2, 0, 0, 0)) e(5); + + signals = 0; + busy_wait(5); + + if (signals != 1) e(6); + + if (getitimer(timer, &it)) e(7); + if (!EQITIMER(it, 0, 0, 0, 0)) e(8); +} + +/* test that the timer is reset on forking */ +void test_fork(void) { + struct itimerval it, oit; + pid_t pid; + int status; + + subtest = 6; + + FILLITIMER(it, 12, 34, 56, 78); + + if (setitimer(timer, &it, NULL)) e(1); + + pid = fork(); + if (pid < 0) e(2); + + if (pid == 0) { + if (getitimer(timer, &it)) exit(5); + if (!EQITIMER(it, 0, 0, 0, 0)) exit(6); + + exit(0); + } + + if (wait(&status) != pid) e(3); + if (!WIFEXITED(status)) e(4); + if (WEXITSTATUS(status) != 0) e(WEXITSTATUS(status)); + + FILLITIMER(it, 0, 0, 0, 0); + if (setitimer(timer, &it, &oit)) e(7); + if (!LEITIMER(oit, 12, 34, 56, 78)) e(8); +} + +/* test if timer is carried over to exec()'ed process */ +void test_exec(void) { + struct itimerval it; + pid_t pid; + int status; + char buf[2]; + + subtest = 7; + + pid = fork(); + if (pid < 0) e(1); + + if (pid == 0) { + FILLITIMER(it, 3, 0, 1, 0); + if (setitimer(timer, &it, NULL)) exit(2); + + sprintf(buf, "%d", timer); + execl(executable, "DO CHECK", buf, NULL); + + exit(3); + } + + if (wait(&status) != pid) e(4); + if (WIFSIGNALED(status)) { + /* process should have died from corresponding signal */ + if (WTERMSIG(status) != sigs[timer]) e(5); + } + else { + if (WIFEXITED(status)) e(WEXITSTATUS(status)); + else e(6); + } +} + +/* procedure of the exec()'ed process */ +int do_check() +{ + struct itimerval it; + + if (getitimer(timer, &it)) return(81); + if (!LEITIMER(it, 3, 0, 1, 0)) return(82); + + busy_wait(60); + + return(83); +} + +void busy_wait(secs) +int secs; +{ + time_t now, exp; + int i; + + exp = time(&now) + secs + 1; + + while (now < exp) { + for (i = 0; i < 100000; i++); + + time(&now); + } +} + +void got_alarm(sig) +int sig; +{ + if (sig != sigs[timer]) e(1001); + + signals++; +} + +void e(n) +int n; +{ + + printf("Timer %s, subtest %d, error %d, errno %d: %s\n", + names[timer], subtest, n, errno, strerror(errno)); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(1); + } +} + +void quit() +{ + + if (errct == 0) { + printf("ok\n"); + exit(0); + } else { + printf("%d errors\n", errct); + exit(1); + } +}