timers in unix/linux
TRANSCRIPT
Timers
� Time is kept relative to a particular point in time:
Jan 1, 1970 GMT
� Two kernel counters: one for the seconds since
Jan 1, 1970 and one for the microseconds since
Jan 1, 1970
Code
� #include <sys/time.h>
� structu timeval theTime;
� gettimeofday(&theTime, NULL);
� struct timeval { long tv_sec; long tv_usec;}
System code
� You can read the system code in kernel/time.c
� (if you are looking at linux, you'll realy need to
look in the appropriate arch directory.)
� For tv_usec to correct, it needs to be
incremented once every microsecond. The
system can track the passage of time by
counting the number of interrupts that have
occurred since system boot.
time of day clock
� i386 has a time of day clock (this may run
continuously or may be set at system boot
time). Although it may not have the accuracy
desired, it can be used as the base for
incrementing the 10-millisecond clock.
10 millisecond clock
� The hardware has a programmable time that
can be set to issue an interrupt every k time
units: in Linux this is set to 10 milliseconds.
� So the gettimeofday function determines how
many microseconds and seconds have passed
since the time of day clock.
� when is the system clock updated?
Updating the system clock
� the interrupt service routine for the clock
(timer_interrupt) calls the do_timer function in
kernel/sched.c (scheduler related functions).
� This increment the jiffies counter each time it
runs and marks a counter (TIMER_BH) for
execution in the ret_from_sys_call . For an
overview of the sequence of events in the case
of an interrupt see p 26 (Sec 4.1.1)
Task Control
ret_from_sys_call
� this is a standard set of tasks to complete
(dispatch) any accumulated system work:
− pending signals
− pending interrupt tasks
− schedule other tasks
� slow vs fast: fast interrupts don't do much work,
so interrupts are disabled. That means no
further work will need to be done.
slow
� slow interrupts have more to do. So the ISR is
divided into two pieces: the “upper” half that is
“fast”, so interrupts can be disabled, and the
“lower” half, which is “slow”, but the tasks do not
require non-interruptable status.
� These defered tasks can be scheduled later.
Since the kernel is really “multi-threaded”, more
than one ISR may have run with postponed
bottom half events: these are all handled when
the bottom half is scheduled.
timer bottom half
� time bottom half activities:
− system timer updates
− per process time updates
− stores the value in the struct timeval
Per process timers
� kernel needs to maintain time spent by each
process
� timer info saved in the process's descriptor
� when a new task is created, the kernel create's
a new task_struct
� the task_struct contains all the process specific
info, some of which is time info:
struct task_struct {
...
long counter;
...
unsigned long polkicy, rt_priority;
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct time_list real_timer;
// contains tms_utime, tms_stime. tmx_cstime
struct tms;
unsigned long start_time;
long per_cpu_utime[NR_CPUS], per_cpu_stime[], cutime, cstime;
...
}
// in timer.c (bottom half processing)
/*
* Called from the timer interrupt handler to charge one tick to the current
* process. user_tick is 1 if the tick is user time, 0 for system.
*/
void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id(), system = user_tick ^ 1;
update_one_process(p, user_tick, system, cpu);
run_local_timers();
scheduler_tick(user_tick, system);
}
static inline void do_it_prof(struct task_struct *p)
{
unsigned long it_prof = p->it_prof_value;
if (it_prof) {
if (--it_prof == 0) {
it_prof = p->it_prof_incr;
send_sig(SIGPROF, p, 1);
}
p->it_prof_value = it_prof;
}
}
static inline void do_it_virt(struct task_struct * p, unsigned long ticks)
{
unsigned long it_virt = p->it_virt_value;
if (it_virt) {
it_virt -= ticks;
if (!it_virt) {
it_virt = p->it_virt_incr;
send_sig(SIGVTALRM, p, 1);
}
p->it_virt_value = it_virt;
}
}
static inline void do_process_times(struct task_struct *p,
unsigned long user, unsigned long system)
{
unsigned long psecs;
psecs = (p->utime += user);
psecs += (p->stime += system);
if (psecs / HZ > p->rlim[RLIMIT_CPU].rlim_cur) {
/* Send SIGXCPU every second.. */
if (!(psecs % HZ))
send_sig(SIGXCPU, p, 1);
/* and SIGKILL when we go over max.. */
if (psecs / HZ > p->rlim[RLIMIT_CPU].rlim_max)
send_sig(SIGKILL, p, 1);
}
}
void update_one_process(struct task_struct *p, unsigned long user,
unsigned long system, int cpu)
{
do_process_times(p, user, system);
do_it_virt(p, user);
do_it_prof(p);
}
itimers
� the itimers use kernel time to keep track of
− itimer real: passage of real time
− itimer virtual: passage of virtual time (only when the
process is running)
− itimer prof: time the the process is actually running
and the time that the kernel is doing work on the
process's behalf
� (countdown timers)
Initializing timers
� setitimer (do a man)
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimer-
val *ovalue);
The system provides each process with three interval timers, each
decrementing in a distinct time domain. When any timer expires, a sig-
nal is sent to the process, and the timer (potentially) restarts.
ITIMER_REAL decrements in real time, and delivers SIGALRM upon expi-
ration.
ITIMER_VIRTUAL decrements only when the process is executing, and
delivers SIGVTALRM upon expiration.
ITIMER_PROF decrements both when the process executes and when the
system is executing on behalf of the process. Coupled
with ITIMER_VIRTUAL, this timer is usually used to pro-
file the time spent by the application in user and ker-
nel space. SIGPROF is delivered upon expiration.
Timer values are defined by the following structures:
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
itimers
� two values:
− current value
− interval: value to initialize the counter: it's a
countdown)
getitimer
� getitimer reads the value of a timer
#include <sys/time.h>
struct itimerval v;
v.it_interval.tv_sec = 9;
v.it_interval.tv_usec = 999999;
v.it_value.tv_sec = 9
v.it_value.tv_usec = 999999;
setitimer(ITIMER_REAL, &v, NULL);
getitimer(ITIMER_REAL, &v);
printf("... %ld seconds, $ld microseconds ...",
..., v.it_value.tv_sec, v.it_value.tv_usec, ...);
...
Signals
� A signal will be sent when a timer “runs out”. In
order to make use of this, you need to set your
own signal handler.
� this means that you define a function that will be
scheduled (invoked) when the signal is
received.
� It's the user version of an ISR.
How to add a handler
signal(SIGALRM, p_realt_handler);
signal(SIGVTALRM, p_virtt_handler);
signal(SIGPROF, p_proft_handler);
which signals?
� a signal can be sent from one process to
another
� do a man 7 signal to see all the signals
� You can use a signal for arbitrary events
(though there are better ways to indicate an
event and coordinate actions among processes)
� Specific SIGALRM: SIGPROF, SIGVTALRM,
SIGALRM
my sigA
� check sigA.c for an example of setting a handler
(his version has a “race” condition.
doing Part A
� Your part A should just set a real itimer to raise
a signal once every second.
� You should define a handler that increments a
counter each time it is interrupted.
� You can integrate this with your shell so that
you can do a variety of actions and then check
how much time you've used. (*You don't need
to convert to actual time of day.)