Чтение онлайн

ЖАНРЫ

Разработка ядра Linux
Шрифт:

 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)) {

if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))

Поделиться с друзьями: