Второе издание
Скачать 3.09 Mb.
|
Использование таймеров Таймеры представлены с помощью структур timer l i s t , которая определена в файле 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; /*внутренние данные таймера, не трогать! */ }; К счастью, использование таймеров не требует глубокого понимания назначения полей этой структуры. На самом деле, крайне не рекомендуется использовать поля этой структуры не по назначению, чтобы сохранить совместимость с возможными будущими изменениями кода. Ядро предоставляет семейство интерфейсов для ра- боты с таймерами, чтобы упростить эту работу. Все необходимые определения на- ходятся в файле < l i n u x / t i m e r . h > . Большинство реализаций находится в файле k e r n e l / t i m e r . с . Первый шаг в создании таймера — это его объявление в следующем виде. struct timer_list my_timer; Далее должны быть инициализированы поля структуры, которые предназначены для внутреннего использования. Это делается с помощью вспомогательной функции перед вызовом любых функций, которые работают с таймером. init_timer(&my_timer); Далее необходимо заполнить все остальные поля структуры, например, следую- щим образом. my_timer.expires = j i f f i e s + delay; /* интервал времени таймера закончится через delay импульсов */ 7 Другая причина состоит в том, что в ядрах старых версий (до 2.3) существовали статические тай- меры. Такие таймеры создавались во время компиляции, а не во время выполнения. Они имели ограниченные возможности и из-за их отсутствия сейчас никто не огорчается. 224 Глава 10 my_timer.data = 0; /* в функцию-обработчик Судет передан параметр, равный нулю */ my_timer.function = my_function; /* функция, которая будет выполнена, когда интервал времени таймера истечет */ Значение поля m y _ t i m e r . e x p i r e s указывает время ожидания в импульсах си- стемного таймера (необходимо указывать абсолютное количество импульсов). Когда текущее значение переменной j i f f i e s становится большим или равным значению поля my_timer . e x p i r e s , вызывается функция-обработчик m y _ t i m e r . f u n c t i o n с параметром m y _ t i m e r . d a t a . Как видно из описания структуры t i m e r _ l i s t , функ- ция-обработчик должна соответствовать следующему прототипу. void my_timer_function(unsigned long data); Параметр d a t a позволяет регистрировать несколько таймеров с одним обработ- чиком и отличать таймеры с различными значениями этого параметра. Если в ар- гументе нет необходимости, то можно просто указать нулевое (или любое другое) значение. Последняя операция — это активизация таймера. add_timer(&my_timer); И таймер запускается! Следует обратить внимание на важность значения поля e x p i r e d . Ядро выполняет обработчик, когда текущее значение счетчика импульсов системного таймера больше, чем указанное значение времени срабатывания таймера, или равно ему. Хотя ядро и гарантирует, что никакой обработчик таймера не будет выполняться до истечения срока ожидания таймера, тем не менее возможны задерж- ки с выполнением обработчика таймера. Обычно обработчики таймеров выполня- ются в момент времени, близкий к моменту времени срабатывания, однако они мо- гут быть отложены и до следующего импульса системного таймера. Следовательно, таймеры нельзя использовать для работы в жестком режиме реального времени. Иногда может потребоваться изменить момент времени срабатывания таймера, который уже активизирован. В ядре реализована функция mod_timer (), которая по- зволяет изменить момент времени срабатывания активного таймера. i mod_timer(&my_timer, jiffies + new_delay); /* установка нового времени срабатывания */ Функция mod_timer () позволяет также работать с таймером, который проини- циализирован, но не активен. Если таймер не активен, то функция m o d _ t i m e r ( ) активизирует его. Эта функция возвращает значение 0, если таймер был неактив- ным, и значение 1, если таймер был активным. В любом случае перед возвратом из функции mod_timer () таймер будут активизирован, и его время срабатывания будет установлено в указанное значение. Для того чтобы деактивизировать таймер до момента его срабатывания, необхо- димо использовать функцию d e l _ t i m e r () следующим образом. del_timer(&my_timer); Эта функция работает как с активными, так и неактивными таймерами. Если тай- мер уже неактивен, то функция возвращает значение 0, в другом случае возвраща- ется значение 1. Следует обратить внимание, что нет необходимости вызывать эту Таймеры и управление временем 225 функцию для таймеров, интервал ожидания которых истек, так как они автоматиче- ски деактивипируются. При удалении таймеров потенциально может возникнуть состояние конкурен- ции. Когда функция del_timer () возвращает управление, она гарантирует только то, что таймер будет неактивным (т.е. его обработчик не будет выполнен в будущем). Однако на многопроцессорной машине обработчик таймера может выполняться в этот момент на другом процессоре. Для того чтобы деактивизировать таймер и по- дождать, пока завершится его обработчик, который потенциально может выполнять- ся, необходимо использовать функцию del_timer_sync () : 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_timer _sync (), а не функцию del_timer (). В противном случае нельзя гаран- тировать, что обработчик таймера в данный момент не выполняется. Представьте себе, что после удаления таймера код освободит память или каким-либо другим об- разом вмешается в ресурсы, которые использует обработчик таймера. Поэтому син- хронная версия более предпочтительна. Наконец, необходимо гарантировать защиту всех совместно используемых дан- пых, к которым обращается функция-обработчик таймера. Ядро выполняет эту функ- цию асинхронно по отношению к другому коду. Совместно используемые данные должны защищаться так, как рассматривалось в главах 8 и 9. Реализация таймеров Ядро выполняет обработчики таймеров в контексте обработчика отложенного прерывания после завершения обработки прерывания таймера. Обработчик преры- вания таймера вызывает функцию update_process_times (), которая в свою оче- редь вызывает функцию run_local_timers () , имеющую следующий вид. void run_local_timers(void) { raise_softirq(TIMER_SOFTIRQ); } Отложенное прерывание с номером TIMER_SOFTIRQ обрабатывается функцией run_tirner_softirq (). Эта функция выполняет на локальном процессоре обработчи- ки всех таймеров, для которых истек период времени ожидания (если такие есть). 226 Глава 10 Таймеры хранятся в связанном списке. Однако в ядре было бы неразумным про- сматривать весь список в поисках таймеров, для которых истекло время ожидания, или поддерживать список в отсортированном состоянии на основании времени сра- батывания таймеров. В последнем случае вставка и удаление таймеров заняли бы много времени. Вместо этого таймеры разбиваются на 5 групп на основании вре- мени срабатывания. Таймеры перемещаются из одной группы в другую, по мере того как приближается момент времени срабатывания. Такое разбиение на группы гарантирует, что в большинстве случаев при выполнении обработчика отложенно- го прерывания, ответственного за выполнение обработчиков таймеров, ядро будет выполнять мало работы для поиска таймеров, у которых истек период ожидания. Следовательно, код управления таймерами очень эффективен. Задержка выполнения Часто коду ядра (особенно драйверам) необходимо задерживать выполнение дей- ствий на некоторый период времени без использования таймеров или механизма нижних половин. Это обычно необходимо для того, чтобы дать аппаратному обе- спечению время на завершение выполнения задачи. Такой интервал времени обыч- но достаточно короткий. Например, в спецификации сетевой интерфейсной платы может быть указано время изменения режима работы Ethernel-контроллера, равное 2 микросекундам, т.е. после установки желаемой скорости передачи драйвер должен ожидать хотя бы в течение двух микросекунд перед тем, как продолжить работу. Ядро предоставляет несколько решений этой задачи, в зависимости от семанти- ки задержки. Эти решения имеют разные свойства. Некоторые решения во время задержки загружают процессор, не давая возможности выполнять другую, более по- лезную работу. Другие решения не загружают процессор, но не дают гарантии того, что код возобновит выполнение точно в необходимый момент времени 8 Задержка с помощью цикла Наиболее простое для реализации (хотя обычно не оптимальное) решение — это использование задержки с помощью цикла или ожидания в состоянии занятости (busy loop, busy wailing). Эта техника работает, только если интервал времени задержки яв- ляется кратным периоду системного таймера или когда точность не очень важна. Идея проста — выполнить постоянный цикл, пока не будет получено необходимое количество импульсов системного таймера, как в следующем примере. unsigned long delay = jiffies + 10; /* десять импульсов таймера */ while (time_before (jiffies, delay)); 8 На самом деле, ни один подход не гарантирует, что время задержки будет точно равно указанному значению. Некоторые подходы обеспечивают задержки, очень близкие к точному значению, тем не менее все подходы гарантируют, что время ожидания будет, по крайней мере, не меньше, чем нужно. В некоторых случаях период ожидания получается существенно больше указанного. Таймеры и управление временем 227 Цикл будет выполняться, пока значение переменной j i f f i e s не станет больше, чем значение переменной delay, что может произойти только после того, как будут получены 10 импульсов системного таймера. Для аппаратной платформы х86 со зна- чением параметра HZ, равным 1000, этот интервал равен 10 миллисекунд. Аналогично можно поступить следующим образом. unsigned long delay = jiffies + 2*HZ; /* две секунды */ while (time_before(jiffies, delay)); В этом случае цикл будет выполняться, пока не поступит 2*Н2 импульсов систем- ного таймера, что всегда равно 2 секундам, независимо от частоты системного тай- мера. Такой подход не очень хорош для всей системы. Пока код ожидает, процессор загружен выполнением бесполезного цикла и никакой полезной работы при этом не выполняется! На самом деле к такому "глупому" подходу нужно прибегать по возмож- ности реже, и он показан здесь, потому что является понятным и простым способом осуществить задержку. Его можно встретить в чьем-нибудь не очеиь хорошем коде. Лучшим решением япляется перепланирование для того, чтобы процессор мог выполнить полезную работу, пока ваш код ожидает: unsigned long delay = jiffies + 5*HZ; while (time_before(jiffies, delay)) cond_reschcd(); Вызов функции c o n d _ r e s c h e d ( ) планирует выполнение другого процесса, но только в случае, если установлен флаг need_resched. Другими словами, данное ре- шение позволяет активизировать планировщик, но только в случае, когда есть бо- лее важное задание, которое нужно выполнить. Следует обратить внимание, что. поскольку используется планировщик, такое решение нельзя применять в контексте прерывания, а только в контексте процесса. Задержки лучше использовать только в контексте процесса, поскольку обработчики прерываний должны выполняться по возможности быстро (а цикл задержки не дает такой возможности!). Более того, лю- бые задержки выполнения, по возможности, не должны использоваться при захва- ченных блокировках и при запрещенных прерываниях. Поклонники языка С могут поинтересоваться, какие есть гарантии, что указан- ные циклы будут действительно выполняться? Обычно компилятор С может выпол- нить чтение указанной переменной всего один раз. В обычной ситуации нет никакой гарантии, что переменная j i f f i e s будет считываться на каждой итерации цикла. Нам же необходимо, чтобы значение переменной j i f f i e s считывалось на каждой итерации цикла, так как это значение увеличивается в другом месте, а именно в пре- рывании таймера. Именно поэтому данная переменная определена в файле 228 Глава 10 Короткие задержки Иногда коду ядра (и снопа обычно драйверам) необходимы задержки на очень ко- роткие интервалы времени (короче, чем период системного таймера), причем интер- вал должен отслеживаться с достаточно высокой точностью. Это часто необходимо для синхронизации с аппаратным обеспечением, для которого описано некоторое минимальное время выполнения действий, и которое часто бывает меньше одной миллисекунды. В случае таких малых значений времени невозможно использовать задержки на основании переменной j i f f i e s , как показано в предыдущем примере. При частоте системного таймера, равной 100 Гц, значение периода системного тай- мера достаточно большое— 10 миллисекунд! Даже при частоте системного таймера 1000 Гц, период системного таймера равен одной миллисекунде. Ясно, что необходи- мо другое решение, которое обеспечивает более короткие и точные задержки. Ядро предоставляет две функции для обеспечения микросекундных и миллисе- кундных задержек, которые определены в файле < l i n u x / d e l a y . h > и не используют переменную j i f f i e s . void udelay(unsigned long usecs); void mdelay(unsigned long msecs); Первая функция позволяет задержать выполнение на указанное количество микросекунд с использованием цикла. Вторая функция задерживает выполнение на указанное количество миллисекунд. Следует вспомнить, что одна секунда равна 1000 миллисекундам, что эквивалентно 1000000 микросекунд. Использование этих функций тривиально. udelay(150); /* задержка на 150 ms */ Функция u d e l a y () выполнена па основе цикла, для которого известно, сколько итераций необходимо выполнить за указанный период времени. Функция mdelay () выполнена на основе функции u d e l a y ( ) . Так как в ядре известно, сколько циклов процессор может выполнить в одну секунду (смотрите ниже замечание по поводу характеристики BogoMlPS), функция u d e l a y () просто масштабирует это значение для того, чтобы скорректировать количество итераций цикла для получения указан- ной задержки. Мой BogoMIPS больше, чем у Вас! Характеристика BogoMlPS всегда была источником недоразумений и шуток. На самом деле вы- численное значение BogoMlPS не имеет ничего общего с производительностью компьютера и используется только для функций u d e l a y ( ) и m d e l a y ( ) . Название этого параметра состоит из двух частей bogus (фиктивный) и MIPS (million of instructions per second, миллион инструкций в секунду). Все знакомы с сообщением, которое выдается при загрузке системы и похоже на следующее (данное сообщение соответствует процессору Pentium III с частотой 1 ГГц). Detected 1004.932 MHz processor. Calibrating delay loop... 1990.65 BogoMlPS Значение параметра BogoMIPS - это количество циклов, которые процессор может выполнить за указанный период времени, В действительности эта характеристика показывает, насколько быстро процессор может ничего не делать! Это значение хранится в переменной l o o p s _ p e r _ j i f f y , и его можно считать из файла / p r o c / c p u i n f o . Таймеры и управление временем функции, использующие циклы задержки, используют данное значение, чтобы вычислить (и это получается достаточно точно), сколько итераций цикла необходимо выполнить, чтобы обеспе- чить необходимую задержку. Ядро вычисляет значение переменной 1 o o p s _ p e r _ j i f f у при загрузке системы в функции c a l i b r a t e _ d e l a y ( ) , реализация которой описана в файле i n i t / m a i n . c . Функция u d e l a y ( ) должна вызываться только для небольших задержек, посколь- ку при большом времени задержки на быстрой машине может возникнуть переполне- ние в переменных цикла. Общее правило: по возможности не использовать функцию u d e l a y ( ) для задержек, больше одной миллисекунды. Для более продолжительных задержек хорошо работает функция m d e l a y ( ) . Так же как и другие методы задерж- ки выполнения, основанные на циклах, эти функции (особенно функция mdelay(), так как она дает длительные задержки) должны использоваться, только если это абсолютно необходимо. Следует помнить, что очень плохо использовать циклы за- держек, когда удерживается блокировка или запрещены прерывания, потому что это очень сильно влияет на производительность и время реакции системы. Если необ- ходимо обеспечить точное время задержки, то эти функции — наилучшее решение. Обычное использование этих функций — это задержки на не очень короткий период времени, который лежит в микросекундном диапазоне. Функция schedule_timeout() Более оптимальный метод задержки выполнения — это использование функции schedule_timeouit (). Этот вызов переводит вызывающее задание в состояние ожи- дания (sleep) по крайней до тех пор, пока не пройдет указанный период времени. Нет никакой гарантии, что время ожидания будет точно равно указанному значению, гарантируется только, что задержка будет не меньше указанной. Когда проходит ука- занный период времени, ядро возвращает задание в состояние готовности к выпол- нению (wake up) и помещает его в очередь выполнения. Использовать эту функцию просто. /* установить состояние задания в значение прерываемого ожидания */ set_current_state(TASK I N T E R R U P T I B L E ) ; /* перейти в приостановленное состояние на s секунд */ schedule_timeout(s * HZ); Единственный параметр функции — это желаемое относительное время, вы- раженное в количестве импульсов системного таймера. В этом примере задание переводится в прерываемое состояние ожидания, которое будет длиться s секунд. Поскольку задание отмечено как TASK_INTERRUPTIBLE, то оно может быть воз- вращено к выполнению раньше времени, как только оно получит сигнал. Если не нужно, чтобы код обрабатывал сигналы, то можно использовать состояние TASK_ UNINTERRUPTIBLE. Перед вызовом функции s c h e d u l e _ t i r n e o u t ( ) задание должно быть в одном из этих двух состояний, иначе задание в состояние ожидания переве- дено не будет. Следует обратить внимание, что поскольку функция s c h e d u l e _ t i r a e o u t ( ) ис- пользует планировщик, то код, который ее вызывает, должен быть совместим с со- стоянием ожидания. Обсуждение, посвященное атомарности и переходу в состояние 230 Глава 10 ожидания, приведено в главах 8 и 9. Если коротко, то эту функцию необходимо вы- зывать в контексте процесса и не удерживать при этом блокировку. Функция schedule_tiraeout() достаточно проста. Она просто использует тайме- ры ядра. Рассмотрим эту функцию подробнее. signed long schedule_timeout(signed long timeout) { timer_t timer; unsigned long expire; switch (timeout) { case MAX_SCHEDULE_TIMEOUT: schedule () ; goto out; default: if (timeout < 0) { printk(KERN_ERR "schedule_timeout: wrong timeout " "value %lx from %p\n", timeout, builtin_return_address(0)); current->state = TASK_RUNNING; goto out; } } expire = timeout + jiffies; init timer(&timer); timer.expires = expire; timer.data = (unsigned long) current; timer.function = process_timeout; add_timer(&timer); schedule() ; del_timer_sync(&timer) ; timeout = expire - jiffies; out: return timeout < 0 ? 0 : timeout; } Эта функция создает таймер timer и устанавливает время срабатывания в зна- чение timeout импульсов системного таймера в будущем. В качестве обработчика таймера устанавливается функция process timeout () , которая вызывается, ког- да истекает период времени таймера. Далее таймер активизируется, и вызывается функция schedule (). Так как предполагается, что текущее задание находится в со- стоянии TASK_INTERRUPTIBLE или TASK_UNINTERRUPTIBLE, то планировщик не бу- дет выполнять текущее задание, а выберет для выполнения другой процесс. Когда интервал времени таймера истекает, то вызывается функция process_ timeout (), которая имеет следующий вид. Таймеры и управление временем 231 void process_timeout(unsigned long data) { wake_up_process((task t *) data); } Эта функция устанавливает задание в состояние TASK_RUNNING и помещает его в очередь выполнения. Когда задание снова планируется на выполнение, то оно возвращается в функцию schedule_tiraeout () (сразу после вызова функции s c h e d u l e ()). Если задание воз- вращается к выполнению преждевременно, то таймер ликвидируется. После этого задание возвращается из функции ожидания по тайм-ауту. Код оператора s w i t c h () служит для обработки специальных случаев и не явля- ется основной частью функции. Проверка на значение MAX_SCHEDULE_TIMEOUT по- зволяет заданию находиться в состоянии ожидания неопределенное время. В этом случае таймер не устанавливается (поскольку нет ограничений на интервал времени ожидания), и сразу же активизируется планировщик. Если вы это применяете, то, наверное, у вас есть лучший способ вернуть задание в состояние выполнения! |