void update_process_times(int user_tick) {
struct task_struct *p = current;
int cpu = smp_processor_id();
int system = user_tick ^ 1;
update_one_process(p, user_tick, system, cpu);
run_local_timers();
scheduler_tick(user_tick, system);
}
Функция update_process() собственно обновляет значения параметров времени выполнения процесса. Эта функция тщательно продумана. Следует обратить внимание, каким образом с помощью операции исключающее ИЛИ (XOR) достигается, что одна из переменных user_tick и system имеет значение, равное нулю, а другая— единице. Поэтому в функции update_one_process() можно просто прибавить необходимое значение к соответствующим счетчикам без использования оператора ветвления.
/*
* увеличиваем значения соответствующего
* счетчика импульсов таймера на единицу
*/
p->utime += user;
p->stime += system;
Необходимое значение увеличивается на 1, а другое остается без изменений. Легко заметить, что в таком случае предполагается, что за время импульса системного таймера процесс выполнялся в том же режиме, в котором он выполняется во время прихода прерывания. На самом деле процесс мог несколько раз переходить в режим задачи и в режим ядра за последний период системного таймера. Кроме того, текущий процесс может оказаться не единственным процессом, который выполнялся за последний период системного таймера. К сожалению, без применения более сложной системы учета, такой способ является лучшим из всех тех, которые предоставляет ядро. Это также одна из причин увеличения частоты системного таймера.
Далее функция run_local_timers() помечает отложенные прерывания, как готовые к выполнению (см. главу 7, "Обработка нижних половин и отложенные действия"), для выполнения всех таймеров, для которых закончился период времени ожидания. Таймеры будут рассмотрены ниже, в разделе "Таймеры".
Наконец, функция schedule_tick() уменьшает значение кванта времени для текущего выполняющегося процесса и устанавливает флаг need_resched при необходимости. Для SMP-машин в этой функции также при необходимости выполняется балансировка очередей выполнения. Все это обсуждалось в главе 4.
После возврата из функции update_process_times() вызывается функция update_times(), которая обновляет значение абсолютного времени.
void update_times(void) {
unsigned long ticks;
ticks = jiffies - wall_jiffies;
if (ticks) {
wall_jiffies += ticks;
update_wall_time(ticks);
}
last_time_offset = 0;
calc_load(ticks);
}
Значение переменной ticks вычисляется как изменение количества импульсов системного таймера с момента последнего обновления абсолютного времени. В нормальной ситуации это значение, конечно, равно 1. В редких случаях прерывание таймера может быть пропущено, и в таком случае говорят, что импульсы таймера потеряны. Это может произойти, если прерывания запрещены в течение длительного времени. Такая ситуация не является нормальной и часто указывает на ошибку программного кода. Значение переменной wall_jiffies увеличивается на значение ticks, поэтому она равна значению переменной jiffies в момент самого последнего обновления абсолютного времени. Далее вызывается функция update_wall_time() для того, чтобы обновить значение переменной xtime, которая содержит значение абсолютного времени. Наконец вызывается функция calc_load() для того, чтобы обновить значение средней загруженности системы, после чего функция update_times() возвращает управление.
Функция do_timer() возвращается в аппаратно-зависимый обработчик прерывания, который выполняет все необходимые завершающие операции, освобождает блокировку xtime_lock и в конце концов возвращает управление.
Всё это происходит каждые 1/HZ секунд, т.е. 1000 раз в секунду на машине типа PC.
Абсолютное время
Текущее значение абсолютного времени (time of day, wall time, время дня) определено в файле kernel/timer.c следующим образом.
struct timespec xtime;
Структура данных timespec определена в файле <linux/time.h> в следующем виде.
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
Поле xtime.tv_sec содержит количество секунд, которые прошли с 1 января 1970 года (UTC, Universal Coordinated Time, всеобщее скоординированное время). Указанная дата называется epoch (начало эпохи). В большинстве Unix-подобных операционных систем счет времени ведется с начала эпохи. В поле xtime.tv_nsec хранится количество наносекунд, которые прошли в последней секунде.
Чтение или запись переменной xtime требует захвата блокировки xtime_lock. Это блокировка — не обычная спин-блокировка, а секвентная блокировка, которая рассматривается в главе 9, "Средства синхронизации в ядре".
Для обновления значения переменной xtime необходимо захватить секвентную блокировку на запись следующим образом.
write_seqlock(&xtime_lock);
/* обновить значение переменной xtime ... */
write_sequnlock(&xtime_lock);
Считывание значения переменной xtime требует применения функций read_seqbegin() и read_seqretry() следующим образом.
do {
unsigned long lost;
seq = read_seqbegin(&xtime_lock);
usec = timer->get_offset();
lost = jiffies — wall_jiffies;
if (lost)
usec += lost * (1000000 / HZ);
sec = xtime.tv_sec;
usec += (xtime.tv_nsec / 1000);
} while (read_seqretry(&xtime_lock, seq));
Этот цикл повторяется до тех пор, пока не будет гарантии того, что во время считывания данных не было записи. Если во время выполнения цикла приходит прерывание таймера и переменная xtime обновляется во время выполнения цикла, возвращаемый номер последовательности будет неправильным и цикл повторится снова.
Главный пользовательский интерфейс для получения значения абсолютного времени — это системный вызов gettimeofday(), который реализован как функция sys_gettimeofday() следующим образом.
asmlinkage long sys_gettimeofday(struct timeval *tv,
struct timezone *tz) {
if (likely(tv !=NULL)) {
struct timeval_ktv;
do_gettimeofday(&ktv);
if (copy_to_userftv, &ktv, sizeof(ktv))
return -EFAULT;
}
if (unlikely(tz != NULL)) {