Главная » Ядро Linux » Абсолютное  время

0

Текущее  значение абсолютного времени (time  of day, wall time, время  дня)  определено  в файле  kernel/timer. с следующим образом.

struct timespec xtime;

Структура данных  timespe c  определена в  файле <linux/time.h > в  следующем виде.

struct timespec {

time_t tv_sec; /* seconds */

long tv_nsec; /* nanoseconds */

};

Поле   xtime.tv_se c   содержит количество секунд, которые прошли с  1  января

1970 года (UTC,  Universal Coordinated Time, всеобщее скоординированное время). Указанная  дата называется  epoch (начало эпохи). В  большинстве Unix-подобных операционных систем счет времени ведется с начала эпохи. В  поле xtime.tv_nse c  хранится количество наносекунд, которые прошли в  последней секунде.

Чтение или запись переменной  xtime требует захвата блокировки  xtime_lock. Это блокировка — не обычная спин-блокировка, а секвентпая блокировка, которая рассматривается в  главе 9, "Средства синхронизации  в  ядре".

Для обновления  значения  переменной  xtime необходимо захватить секвентную блокировку на запись следующим образом.

write_seqlock(&xtime_lock);

/* обновить значение переменной xtime … */

write_sequnlock(&xtime_lock);

Считывание значения переменной xtim e  требует применения  функций  read _

seqbegin( )  и read_seqretr y ()  следующим образом.

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 обновляется во время выполнения цикла, возвращаемый номер последовательности будет неправильным и цикл повторится снова.

Главный пользовательский интерфейс для получения значения абсолютного времени — это системный вызов gettimeofda y () , который реализован как функция 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)))

return -EFAULT;

}

return 0;

}

Если  из  пространства пользователя передано ненулевое значение параметра tv, то  вызывается аппаратно-зависимая функция do_gettimeofday( ) .  Эта функция главным  образом выполняет цикл  считывания переменной xtime , который был только  что рассмотрен. Аналогично, если  параметр tz не равен  нулю, пользователю возвращается значение часового  пояса  (time  zone), в котором находится операционная  система. Этот параметр хранится в переменной sys_tz . Если  при  копировании в пространство пользователя значения абсолютного времени или  часового  пояса возникли ошибки, то функция возвращает значение -EFAULT. В случае успеха возвращается нулевое  значение.

Ядро  предоставляет системный  вызов   time() 6 , однако   системный вызов gettimeofday( )   полностью перекрывает его возможности. Библиотека функций языка  С также  предоставляет другие функции, связанные с абсолютным временем, такие  как  ftime( )  и ctirae() .

Системный вызов   settimeofday( )   позволяет установить абсолютное время  в указанное значение. Для того чтобы  его выполнить, процесс должен  иметь  возможность  использования CAP_SYS_TIME.

Если  не считать  обновления переменной xtime, то ядро  не так часто  использует абсолютное время, как  пространство пользователя. Одно  важное  исключение— это код файловых систем, который хранят  в индексах файлов значения моментов времени доступа к файлам.

Таймеры

Таймеры (timers), или, как их еще иногда  называют, динамические таймеры, или таймеры ядра, необходимы для управления ходом  времени в ядре.  Коду ядра часто необходимо откладывать выполнение некоторых функций на более  позднее  время. Здесь намеренно выбрано не очень четкое  понятие "позже". Назначение механизма нижних половин — это не задерживать выполнение, а не выполнять работу прямо сейчас. В связи  с этим  необходим инструмент, который позволяет задержать  выполнение работы  на некоторый интервал времени. Если  этот интервал времени не очень маленький, но и не очень большой, то решение проблемы — таймеры ядра.

6    Дл я  некоторы х аппаратны х платфор м  функция  sys_time ()   не  реализована,  а  вместо  этого  она эмулируется   библиотекой  функций  языка   С   на   основании  вызова   gettimeofday ().

Таймеры очень легко использовать. Необходимо выполнить некоторые начальные действия, указать момент времени окончания ожидания, указать функцию, которая будет выполнена,  когда закончится интервал времени ожидания,  и активизировать таймер. Указанная  функция будет выполнена,  когда закончится интервал времени таймера. Таймеры не являются циклическими.  Когда заканчивается интервал  времени ожидания, таймер ликвидируется. Это одна из причин, почему таймеры называют динамическими7. Таймеры  постоянно  создаются и ликвидируются,  на количество таймеров не существует ограничений. Использование таймеров очень популярно во  всех частях ядра.

Использование таймеров

Таймеры представлены с помощью структур time r  list , которая определена в файле <linux/timer.h > следующим образом.

struct tirner_list {

struct list_head entry; /* таймеры хранятся в связанном списке */

unsigned long expires;  /* время окончание срока ожидания в импульсах системного таймера (jiffies) */

spinlock_t lock; /* блокировка для защиты данного таймера */

void (*function) (unsigned long); /*функция-обработчик таймера*/ unsigned long data; /* единственный аргумент обработчика */ struct tvec_t_base_s *base; /*внутренние данные таймера, не трогать! */

};

К счастью, использование таймеров не требует глубокого понимания назначения полей этой структуры. На самом деле, крайне не рекомендуется использовать поля этой структуры не по назначению,  чтобы сохранить совместимость с  возможными будущими изменениями  кода. Ядро предоставляет семейство интерфейсов для работы с  таймерами,  чтобы упростить эту  работу. Все  необходимые определения  находятся в  файле <linux/timer.h> . Большинство реализаций находится в  файле kernel/timer.с .

Первый шаг в создании таймера — это его  объявление в следующем виде.

struct timer_list my_timer;

Далее должны быть инициализированы  поля структуры, которые предназначены для внутреннего использования.  Это делается с  помощью вспомогательной функции перед вызовом любых функций,  которые работают с  таймером.

init_timer(&my_timer) ;

Далее необходимо заполнить все  остальные поля структуры, например,  следующим образом.

my_timer.expire s  =  jiffie s  + delay ;  /*   интервал  времени  таймера закончится  через delay импульсов  */

7   Другая  причин а состоит в том, что в ядрах старых  версий (до 2.3) существовали  статические  таймеры . Такие таймер ы создавались во врем я компиляции , а не во врем я выполнения . Он и  имели ограниченны е возможности и из-за их отсутствия  сейчас никто не огорчается.

my_timer.data = 0; /* в функцию-обработчик Судет передан параметр, равный нулю */

my_timer.function = my_function; /* функция, которая будет выполнена,

когда интервал времени таймера истечет */

Значение поля   my_timer.expire s  указывает время  ожидания в  импульсах системного таймера  (необходимо указывать абсолютное количество импульсов).  Когда текущее   значение  переменной  jiffie s  становится большим или  равным значению поля  my_time r . expires ,  вызывается функция-обработчик my_timer.functio n  с параметром my_timer.data .  Как  видно   из  описания   структуры  timer_list ,  функция-обработчик должна   соответствовать  следующему  прототипу.

void my_timer_function(unsigned long data);

Параметр dat a  позволяет регистрировать несколько  таймеров с  одним   обработчиком и  отличать таймеры с  различными значениями этого  параметра. Если  в аргументе   нет  необходимости,  то  можно просто   указать  нулевое   (или  любое  другое) значение.

Последняя операция — это  активизация таймера.

add_timer(&my_timer);

И  таймер   запускается!  Следует  обратить внимание  на  важность значения  поля expired .  Ядро  выполняет обработчик,  когда  текущее   значение  счетчика импульсов системного таймера больше,  чем указанное значение времени срабатывания таймера, или  равно ему.  Хотя  ядро  и  гарантирует,  что  никакой  обработчик таймера не  будет выполняться до истечения срока  ожидания таймера, тем  не  менее  возможны задержки  с выполнением  обработчика таймера. Обычно обработчики таймеров выполняются  в момент времени, близкий к моменту времени срабатывания,  однако они  могут  быть  отложены и  до  следующего импульса системного  таймера.  Следовательно, таймеры нельзя использовать для  работы  в жестком режиме реального времени.

Иногда может  потребоваться изменить момент времени  срабатывания таймера, который уже активизирован. В ядре  реализована функция mod_time r () , которая позволяет  изменить момент времени срабатывания активного таймера.

i

mod_timer(&my_timer, jiffies + new_delay); /* установка нового времени срабатывания*/

Функция mod_time r ()   позволяет также  работать  с таймером, который проинициализирован, но  не  активен. Если  таймер  не  активен, то  функция mod_timer( ) активизирует его.  Эта  функция  возвращает значение 0, если  таймер   был  неактивным, и значение 1, если  таймер  был  активным. В любом  случае  перед  возвратом из функции mod_time r ()  таймер  будут активизирован, и его время  срабатывания будет установлено в указанное значение.

Для  того  чтобы  деактивизировать таймер  до  момента его  срабатывания, необходимо  использовать  функцию  del_time r ()   следующим образом.

del_timer(&my_timer);

Эта функция работает  как  с активными, так и неактивными таймерами. Если  таймер  уже  неактивен, то  функция  возвращает значение 0, в другом  случае  возвращается  значение  1.  Следует  обратить внимание,  что  нет  необходимости вызывать эту

функцию для таймеров, интервал ожидания которых  истек, так как они автоматически деактивипируются.

При  удалении таймеров потенциально может  возникнуть состояние конкуренции.  Когда  функция del_time r ()  возвращает управление, она гарантирует только то, что таймер  будет неактивным (т.е.  его обработчик не будет выполнен в будущем). Однако на многопроцессорной машине обработчик таймера  может  выполняться в этот  момент  на другом  процессоре. Для того чтобы  деактивизировать таймер  и подождать, пока завершится его обработчик, который потенциально может  выполняться, необходимо использовать функцию del_timer_syn c ()  :

del_timer_sync(&my_timer);

В отличие  от функции del_timer() , функция del_timer_sync( )  не может  вызываться из контекста прерывания.

Состояния конкуренции, связанные с таймерами

Так как таймеры выполняются асинхронно по отношению к выполняемому в данный  момент  коду, то потенциально могут  возникнуть несколько типов  состояний конкуренции за ресурсы.  Во-первых, никогда нельзя  использовать следующий код, как замену  функции inod_timer ().

del_timer (my_timer) ;

my_timer->expires = jiffies + new_delay;

add_timer(my_timer);

Во-вторых,  практически  во  всех  случаях   следует  использовать  функцию del_time r _sync (), а не функцию del_time r ().  В противном случае нельзя  гарантировать, что обработчик таймера  в данный момент  не выполняется. Представьте себе, что после  удаления таймера  код  освободит память  или  каким-либо другим  образом  вмешается в ресурсы, которые использует обработчик таймера. Поэтому синхронная версия  более  предпочтительна.

Наконец,  необходимо гарантировать защиту  всех совместно используемых данпых, к которым обращается функция-обработчик таймера. Ядро выполняет эту функцию  асинхронно по  отношению к другому коду.  Совместно используемые данные должны  защищаться так, как рассматривалось в главах 8 и 9.

Реализация таймеров

Ядро  выполняет обработчики таймеров в контексте обработчика отложенного прерывания после  завершения обработки прерывания таймера. Обработчик прерывания  таймера  вызывает функцию  update_process_time s () , которая в свою  очередь вызывает функцию run_local_timer s () , имеющую  следующий вид.

void run_local_timers(void)

{

raise_softirq(TIMER_SOFTIRQ);

}

Отложенное прерывание с номером TIMER_SOFTIRQ обрабатывается функцией run_tirner_softir q (). Эта функция выполняет на локальном процессоре обработчики всех таймеров, для которых  истек  период  времени ожидания (если  такие  есть).

Таймеры хранятся в связанном списке. Однако в ядре  было  бы  неразумным  просматривать весь  список в поисках таймеров,  для  которых истекло время  ожидания, или  поддерживать список в отсортированном состоянии на основании времени срабатывания таймеров.  В  последнем случае  вставка и удаление таймеров  заняли  бы много   времени. Вместо  этого  таймеры  разбиваются на  5  групп  на  основании времени   срабатывания.  Таймеры перемещаются из  одной   группы   в другую,  по  мере того  как  приближается момент времени срабатывания.  Такое  разбиение на  группы гарантирует,  что  в  большинстве случаев  при  выполнении  обработчика отложенного  прерывания,  ответственного за  выполнение обработчиков таймеров, ядро  будет выполнять мало  работы   для  поиска таймеров, у которых истек  период   ожидания. Следовательно, код  управления таймерами очень  эффективен.

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии