[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[linux-misc] linux timers - von den jiffies im drink
[ hallo leute,
ein buch ist unter den retina-scanner gekommen und deswegen noch ein
nachschlag zum timer verhalten unter linux. die diskussion um HZ
zeigt, dass noch etwas licht ins dunkle muss.
wie fnord es wollte, trudelte noch
"linux kernel programmierung -
algorithmen und strukturen der version 2.4"
ISBN 3-8373-1659-6
letzte woche ein.
hier ein ungenehmigter ausschnitt aus zwei kapiteln über die timer des
linux kernels. der author des artikels weigert sich einer
auspeitschung mit koax-kabeln, empfiehlt das buch aber als
standardwerk zur referenz von datenstrukturen und einfach erklärbaren
algorithmen. ca. 99 herringe kostet das teil bei eurem buchhändler. ]
[...]
3.1.6 Systemzeit und Zeitgeber (Timer)
Im LINUX-System gibt es genau eine interne Zeitbasis. Sie wird in
vergangenen Ticks seit dem Starten des Systems gemessen. Ein Tick
entspricht dabei 10 Millisekunden. Diese Ticks werden von einem
Zeitgeberbaustein der Hardware generiert und vom Timerinterrupt in der
globalen Variable jiffies gezählt. Alle im folgenden genannten
Systemzeiten beziehen sich immer auf diese Zeitbasis.
Wofür braucht man Timer? Viele Gerätetreiber möchten ein Meldung
erhalten, wenn das Gerät nicht bereit ist. Andererseits muss bei der
Bedienung eines langsamen Geräts vielleicht ewtas gewartet wreden, ehe
die nächsten Daten gesendet werden können.
Um dies zu unterstützen bietet LINUX die Möglichkeit, Funtionen zu einem
definiert zukünften Zeitpunkt zu starten. Dafür gibt es das Interface
der Form:
struct timer_list {
struct list_head list;
unsigned long expires;
unsinged long data;
void (*function)(unsigned long);
};
Der Eintrag list in dieser Struktur dient der internen Verwaltung der
Timer in einer dopppelt verketteten Liste. Die Komponente expires gibt
den Zeitpunkt an, zu dem die Funktion function mit dem Argument data
aufgerufen werden soll. Die Funktionen
extern void add_timer(struct timer_list * timer);
extern int del_timer(struct timer_list * timer);
extern int mod_timer(struct timer_list * timer, unsigned long expires);
dienen der Verwaltung einer globalen Timerlist. add_timer() aktiviert
einen Timer durch Eintrag in die globale Timerliste; del_timer()
entfernt ihn wieder, und mod_timer() ändern den expire-Zeitpunkt eines
aktivierten Timers.
Der Timerinterurupt ruft regelmässig die Funktion
static inline void run_timer_list(void);
auf, die nach abgelaufnenen Timern sucht und die zugehörigen Funktionen
aufruft.
[...]
3.2.5
Jedes Betriebssystem braucht eine Zeitmessung und eine Systemzeit.
Realisiert wird die Systmezeit in der Regel dadurch, dass die Hardware
in bestimmten Abständen einen Interrupt auslöst. Die so angestosene
Interruptroutine übernimmt das Zählen der Zeit. Die Systemzeit wird
unter LINUX in Ticks set dem Start des Systems gemessen. Ein Tick
entspricht 10 Millisekunden, der Timerinterrupt wird also 100-mal in der
Sekunde ausgelöst. Die Zeit wird in der Variablen
unsigned long volatile jiffies;
gespeichert, welche nur vom Timerinterupt modifziert werden darf. Dieser
Mechanismus stellt jedoch nur die interne Zeitbasis zur Verfügung.
Anwendungen interessieren sich aber bevorzugt für die "reale Zeit".
Diese wird in der Variablen
volatile struct timeval xtime;
mitgeführt und ebenfalls vom Timerinterrupt aktualisiert.
Der Timerinterrupt wird realtiv häufig aufgerufen und ist deswegen etwas
zeitkritisch. Deswegen ist auch hier die Implementierung zweigeteilt.
Die eigentliche Interrruptroutine do_timer() aktualsiert nur die
Variable jiffies und kennzeichnet die Bottom-Half-Routine des
Timerinterrupts als aktiv. Diese wird vom System zu einem späteren
Zeitpunkt aufgerufen und erldeigt den Rest der Arbeit.
void do_timer(struct pt_regs * regs)
{
(*(unsigned long *)&jiffies)++;
update_process_times(user_mode(regs));
mark_bh(TIMER_BH);
if (TQ_ACTIVE(tq_timer))
mark_bh(TQUEUE_BH);
}
update_process_times() wird weiter unten beschrieben. Wir wollen uns
aber zuerst die Bottom-Half Routinge des Timerrupts anschauen.
void timer_bh(void)
{
update_times();
run_timer_list();
}
run_timer_list() sorgt dabei für das Abarbeiten der im Abschnitt 3.1.6
geschriebenen Funktionen zur Aktualisierung systemweiter Timer.
Darunter fallen auch die Real-Zeit-Timer der aktuellen Task.
update_times() ist für das Aktualisieren der Zeiten verantwortlich.
static inline void update_times(void)
{
unsigned long ticks;
ticks = jiffies - wall_jiffies;
if (ticks) {
wall_jiffies += ticks;
update_wall_time(ticks);
}
calc_load(ticks);
}
update_wall_time() widmet sich nun dem Aktualisieren der realen Zeit xtime
und wird aufgerufen , wenn seit dem letzten Aurfurf dieser Funktion Zeit
vergangen ist.
Die Funktion update_process_time sammelt die Daten für den Scheduler und
entscheidet, ob dieser aufgerufen werden muss.
static void update_process_times(int user_ticks);
{
struct task_struct * p = current;
int cpu = smp_rpocessort_id();
unsigned long user = ticks -system;
}
Zuerst wird die Komponente counter der Task-Struktur aktualisiert. Wenn
counter gleich Null wird, ist die Zeitscheibe für den aktuellen
Prozess abgelaufen, und der Schedluler wir bei der nächsten
Gelegenheit aktiviert.
update_one_process(p, ticks, user, system, 0);
if(p->pid)
{
p->counter -= 1;
if (p->counter <= 0) {
p->counter = 0;
p->need_resched = 1;
}
Danach werden für Satistikzwecke die Komponenten per_cpu_user
Task-Struktur aktualisiert.
p->per_cpu_user[cpu] += user_ticks;
}
Unter LINUX ist es möglich, die Ressource "CPU-Verbrauch" eines
Prozesses zu beschränken. Das geschieht durch den Systemruf setrlimit,
mit welchem auch andere Ressourcen eines Prozesses beschränkt werden
können. Das überschreiten des Zeitlimits wird im Timerinterrupt geprüft
und der Prozess wird durch Senden des Signals SIGXCPU informiert bzw.
durch das Signal SIGKILL abgebrochen. Anschliessend müssen noch die
Intervalltimer für die laufende Task aktualisiert werden. Wenn diese
abgelaufen sind, wird die Task durch ein entsprechendes Signal
informiert.
[snip]