Главная страница
Навигация по странице:

  • Пространство пользователя и параметр HZ

  • Аппаратные часы и таймеры

  • Часы реального времени

  • Системный таймер

  • Абсолютное время

  • Второе издание


    Скачать 3.09 Mb.
    НазваниеВторое издание
    Дата08.09.2019
    Размер3.09 Mb.
    Формат файлаpdf
    Имя файлаLav_Robert_Razrabotka_yadra_Linux_Litmir.net_264560_original_254.pdf
    ТипДокументы
    #86226
    страница29 из 53
    1   ...   25   26   27   28   29   30   31   32   ...   53
    2 1 6 Глава 10

    /* выполним некоторые действия и проверим, не слишком ли это много заняло времени ... */
    if (time_after(jiffies, timeout}) {
    /* мы превысили лимит времени — это ошибка ... */
    } else {
    /* мы не превысили лимит времени — это хорошо ... */
    }
    Если любопытно, каким образом эти макросы предотвращают ошибки, связан- ные с переполнением, то попробуйте подставить различные значения параметров.
    А затем представьте, что один из параметров переполнился, и посмотрите, что при этом произойдет.
    Пространство пользователя и параметр HZ
    Раньше изменение параметра НZ приводило к аномалиям в пользовательских про- граммах. Это происходило потому, что значения параметров, связанных со време- нем, экспортировались в пространство пользователя в единицах, равных количеству импульсов системного таймера в секунду. Так как такой интерфейс использовался давно, то в пользовательских приложениях считалось, что параметр HZ имеет опре- деленное конкретное значение. Следовательно, при изменении значения параметра
    HZ изменялись значения, которые экспортируются в пространство пользователя,
    в одинаковое число раз. Информация о том, во сколько раз изменились значения, в пространство пользователя не передавалась! Полученное от ядра значение времени работы системы могло интерпретироваться как 20 часов, хотя на самом деле оно равнялось только двум часам.
    Чтобы исправить это, код ядра должен нормировать все значения переменной j i f f i e s , которые экспортируются в пространство пользователя. Нормировка реа- лизуется путем определения константы USER_HZ, равной значению параметра HZ,
    которое ожидается в пространстве пользователя. Та как для аппаратной платформы х86 значение параметра HZ исторически равно 100, то значение константы USER_
    HZ=100. Макрос j i f f i e s _ t o _ c l o c k _ t ( ) используется для нормировки значения счетчика импульсов системного таймера, выраженного в единицах HZ, в значение счетчика импульсов, выраженное в единицах USER_HZ. Используемый макрос зави- сит от того, кратны ли значения параметров HZ и USER_HZ один другому. Если крат- ны, то этот макрос имеет следующий очень простой вид.
    #define jiffies_to_clock_t(x) ((х) / (HZ / USER_HZ))
    Если не кратны, то используется более сложный алгоритм.
    Функция jiffies_64_to_clock_t () используется для конвертирования 64-бито- вого значения переменной j i f f i e s из единиц HZ в единицы USER_HZ.
    Эти функции используются везде, где значения данных, выраженных в единицах числа импульсов системного таймера в секунду, должны экспортироваться в про- странство пользователя, как в следующем примере.
    unsigned long start = jiffies;
    unsigned long total_time;
    /* выполнить некоторую работу ... */
    total_time = jiffies - start;
    printk("ЭTO заняло %lu импульсов таймера\n", jiffies_to_clock_t(total_time));
    Таймеры и управление временем 217

    В пространстве пользователя передаваемое значение должно быть таким, ка- ким оно было бы, если бы выполнялось равенство HZ=USER_HZ. Если это равенство не справедливо, то макрос выполнит нужную нормировку и все будут счастливы.
    Конечно, этот пример несколько нелогичный: больше смысла имело бы печатать значение времени не в импульсах системного таймера, а в секундах следующим об- разом.
    printk("Это заняло %lu секунд\n", total time / HZ);
    Аппаратные часы и таймеры
    Различные аппаратные платформы предоставляют два аппаратных устройства,
    которые помогают вести учет времени, — это системный таймер, о котором уже было рассказано, и часы реального времени. Реализация и поведение этих устройств могут быть различными для машин разного типа, но общее их назначение и принци- пы работы с ними почти всегда одинаковы.
    Часы реального времени
    Часы реального времени (real-time clock, RTC) представляют собой энергонеза- висимое устройство для сохранения системного времени. Устройство RTC продол- жает отслеживать время, даже когда система отключена, благодаря небольшой бата- рее, которая обычно находится на системной плате. Для аппаратной платформы PC
    устройство RTC интегриронано в КМОП-микросхему BIOS. При этом используется общая батарея и для работы устройства RTC и для сохранения установок BIOS.
    При загрузке ядро считывает информацию из устройства RTC и использует ее для инициализации значения абсолютного времени, которое хранится в переменной xtime. Обычно ядро не считывает это значение снова, однако для некоторых под- держиваемых аппаратных платформ, таких как х8б, значение абсолютного времени периодически записывается в устройство RTC. Тем не менее, часы реального вре- мени важны в первую очередь на этапе загрузки системы, когда инициализируется переменная xtime.
    Системный таймер
    Системный таймер играет более значительную роль для отслеживания хода вре- мени ядром. Независимо от аппаратной платформы, идея, которая лежит в основе системного таймера, одна и та же — это обеспечение механизма управления преры- ваниями, которые возникают периодически с постоянной частотой. Для некоторых аппаратных платформ это реализуется с помощью электронных часов, которые ге- нерируют колебания с программируемой частотой. В других аппаратных платфор- мах используется декрементный счетчик (decrementer), куда можно записать некото- рое начальное значение, которое будет периодически, с фиксированной частотой,
    уменьшаться на единицу, пока значение счетчика не станет равным нулю. Когда зна- чение счетчика становится равным нулю, генерируется прерывание. В любом случае эффект получается один и тот же.
    Для аппаратной платформы х86 главный системный таймер — это программируе- мый интервальный таймер (programmable interval timer, PIT). Таймер PIT существует
    218 Глава 10
    на всех машинах платформы PC. Co времен операционной системы DOS он исполь- зуется для управления прерываниями. Ядро программирует таймер PIT при загруз- ке, для того чтобы периодически генерировать прерывание номер нуль с частотой
    HZ. Этот таймер— простое устройство с ограниченными возможностями, но, тем не менее, хорошо выполняющее свою работу. Другие эталоны времени для аппаратной платформы х86 включают таймер APIC (Advanced Programmable Interrupt Controller,
    расширенный программируемый контроллер прерываний) и счетчик отметок време- ни (TSC, Time Stamp Counter).
    Обработчик прерываний таймера
    Теперь, когда мы разобрались, что такое j i f f i e s и HZ, а также какова роль си- стемного таймера, рассмотрим реализацию обработчика прерываний системного таймера. Обработчик прерываний таймера разбит на две части: часть, зависимую от аппаратной платформы, и независимую часть.
    Подпрограмма, которая зависит от аппаратной платформы, регистрируется в ка- честве обработчика прерываний системного таймера и выполняется, когда срабаты- вает системный таймер. Конкретная работа, конечно, зависит от аппаратной плат- формы, но большинство обработчиков выполняют следующие действия.
    • Захватывается блокировка xtime_lock, которая защищает доступ к перемен- ной jiffies_64 и значению текущего времени— переменной xtirne.
    • Считывается или сбрасывается состояние системного таймера, если это необ- ходимо.
    • Периодически записывается новое значение абсолютного времени в часы ре- ального времени.
    • Вызывается аппаратно-независимая подпрограмма таймера do_timer ().
    Аппаратно-независимая функция do_timer () выполняет значительно больше действий.
    • Увеличивается значение переменной jiffies_64 на единицу (это безопасная операция даже для 32-разрядных аппаратных платформ, так как блокировка xtime_lock была захвачена раньше).
    • Обновляется статистка использования системных ресурсов, таких как затра- ченное процессорное время в режиме пользователя и в режиме ядра, для про- цесса, который в данный момент выполняется.
    • Выполняются обработчики динамических таймеров, для которых истек период времени ожидания (это будет рассмотрено в следующем разделе).
    • Вызывается функция scheduler_tick () , как было рассмотрено в главе 4.
    • Обновляется значение абсолютного времени, которое хранится в переменной xtime.
    • Вычисляются значения печально известной средней загруженности системы
    (load average).
    Таймеры и управление временем 219

    Сама по себе подпрограмма очень проста, так как большинство рассмотренных действий выполняются другими функциями.
    void do_timer(struct pt_regs *regs)
    {
    jiffies_64++;
    update_process_times(user_mode(regs));
    update_times();
    }
    Макрос user_mode () просматривает состояние регистров процессора, r e g s , и возвращает значение 1, если прерывание таймера возникло в пространстве пользо- вателя, и значение 0— если в пространстве ядра. Это позволяет функции u p d a t e _
    p r o c e s s _ t i m e s О учесть, что за время между предыдущим и данным импульсами системного таймера процесс выполнялся в режиме задачи или в режиме ядра.
    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);
    }
    Функция u p d a t e _ p r o c e s s () собственно обновляет значения параметров вре- мени выполнения процесса. Эта функция тщательно продумана. Следует обратить внимание, каким образом с помощью операции исключающее ИЛИ (XOR) достига- ется, что одна из переменных u s e r _ t i c k и system имеет значение, равное нулю, а другая— единице. Поэтому в функции u p d a t e _ o n e _ p r o c e s s () можно просто при- бавить необходимое значение к соответствующим счетчикам без использования опе- ратора ветвления.
    /*
    * увеличиваем значения соответствующего счетчика импульсов таймера на единицу
    */
    p->utime += user;
    p->stime += system;
    Необходимое значение увеличивается на 1, а другое остается без изменений.
    Легко заметить, что в таком случае предполагается, что за время импульса систем- ного таймера процесс выполнялся в том же режиме, в котором он выполняется во время прихода прерывания. На самом деле процесс мог несколько раз переходить в режим задачи и в режим ядра за последний период системного таймера. Кроме того,
    текущий процесс может оказаться не единственным процессом, который выполнял- ся за последний период системного таймера. К сожалению, без применения более сложной системы учета, такой способ является лучшим из всех тех, которые предо- ставляет ядро. Это также одна из причин увеличения частоты системного таймера.
    Далее функция r u n _ l o c a l _ t i m e r s () помечает отложенные прерывания, как го- товые к выполнению (см. главу 7, "Обработка нижних половин и отложенные дей-
    220 Главз 10
    ствия"), для выполнения всех таймеров, для которых закончился период времени ожидания. Таймеры будут рассмотрены ниже, в разделе "Таймеры".
    Наконец, функция schedule_tick () уменьшает значение кванта времени для текущего выполняющегося процесса и устанавливает флаг need_resched при необ- ходимости. Для SMP-машин в этой функции также при необходимости выполняется балансировка очередей выполнения. Все это обсуждалось в главе 4.
    После возврата из функции u p d a t e _ p r o c e s s _ t i m e s () вызывается функция 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);
    }
    Значение переменной t i c k s вычисляется как изменение количества импульсов системного таймера с момента последнего обновления абсолютного времени. В нор- мальной ситуации это значение, конечно, равно 1. В редких случаях прерывание таймера может быть пропущено, и в таком случае говорят, что импульсы таймера
    потеряны. Это может произойти, если прерывания запрещены в течение длительно- го времени. Такая ситуация не является нормальной и часто указывает на ошибку программного кода. Значение переменной w a l l _ j i f f i e s увеличивается на зна- чение t i c k s , поэтому она равна значению переменной j i f f i e s в момент самого последнего обновления абсолютного времени. Далее вызывается функция update_
    wall_time () для того, чтобы обновить значение переменной xtime, которая со- держит значение абсолютного времени. Наконец вызывается функция calc_load ()
    для того, чтобы обновить значение средней загруженности системы, после чего функция update_times () возвращает управление.
    Функция do_timer () возвращается в аппаратао-зависимый обработчик преры- вания, который выполняет все необходимые завершающие операции, освобождает блокировку xtirae_lock и в конце концов возвращает управление.
    Всё это происходит каждые 1/HZ секунд, т.е. 1000 раз в секунду на машине типа
    PC.
    Абсолютное время
    Текущее значение абсолютного времени (time of day, wall time, время дня) опреде- лено в файле kernel/timer.с следующим образом.
    struct timespec xtime;
    Таймеры и управление временем 221

    Структура данных timespec определена в файле в следующем виде.
    struct timespec {
    time_t tv_sec; /* seconds */
    long tv_nsec; /* nanoseconds */
    };
    Поле x t i m e . t v _ s e c содержит количество секунд, которые прошли с 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 обновляется во время выполнения цикла,
    возвращаемый номер последовательности будет неправильным и цикл повторится снова.
    Главный пользовательский интерфейс для получения значения абсолютного вре- мени — это системный вызов g e t t i m e o f d a y () , который реализован как функция sys_gettimeofday() следующим образом.
    asmlinkage long sys_gettimeofday(struct timeval *tv, struct timezone *tz)
    222 Глава 10
    {
    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)))
    return -EFAULT;
    }
    return 0;
    }
    Если из пространства пользователя передано ненулевое значение параметра tv,
    то вызывается аппаратно-зависимая функция do_gettimeofday() . Эта функция главным образом выполняет цикл считывания переменной xtime, который был только что рассмотрен. Аналогично, если параметр tz не равен нулю, пользователю возвращается значение часового пояса (time zone), в котором находится операцион- ная система. Этот параметр хранится в переменной sys_tz. Если при копировании в пространство пользователя значения абсолютного времени или часового пояса возникли ошибки, то функция возвращает значение -EFAULT. В случае успеха воз- вращается нулевое значение.
    Ядро предоставляет системный вызов t i m e ( )
    6
    , однако системный вызов gettimeofday() полностью перекрывает его возможности. Библиотека функций языка С также предоставляет другие функции, связанные с абсолютным временем,
    такие как ftime() и ctirae().
    Системный вызов settimeofday() позволяет установить абсолютное время в указанное значение. Для того чтобы его выполнить, процесс должен иметь возмож- ность использования CAP_SYS_TIME.
    Если не считать обновления переменной xtime, то ядро не так часто использует абсолютное время, как пространство пользователя. Одно важное исключение— это код файловых систем, который хранят в индексах файлов значения моментов време- ни доступа к файлам.
    Таймеры
    Таймеры (timers), или, как их еще иногда называют, динамические таймеры, или
    таймеры ядра, необходимы для управления ходом времени в ядре. Коду ядра часто необходимо откладывать выполнение некоторых функций на более позднее время.
    Здесь намеренно выбрано не очень четкое понятие "позже". Назначение механизма нижних половин — это не задерживать выполнение, а не выполнять работу прямо сей-
    час. В связи с этим необходим инструмент, который позволяет задержать выполне- ние работы на некоторый интервал времени. Если этот интервал времени не очень маленький, но и не очень большой, то решение проблемы — таймеры ядра.
    6
    Для некоторых аппаратных платформ функция sys_time () не реализована, а вместо этого она эмулируется библиотекой функций языка С на основании вызова gettimeofday ().
    Таймеры и управление временем 223

    Таймеры очень легко использовать. Необходимо выполнить некоторые началь- ные действия, указать момент времени окончания ожидания, указать функцию, ко- торая будет выполнена, когда закончится интервал времени ожидания, и активи- зировать таймер. Указанная функция будет выполнена, когда закончится интервал времени таймера. Таймеры не являются циклическими. Когда заканчивается интер- вал времени ожидания, таймер ликвидируется. Это одна из причин, почему таймеры называют динамическими
    7
    . Таймеры постоянно создаются и ликвидируются, на коли- чество таймеров не существует ограничений. Использование таймеров очень попу- лярно во всех частях ядра.
    1   ...   25   26   27   28   29   30   31   32   ...   53


    написать администратору сайта