Отказ в обслуживании в функции sys_timer_create() в ядре Linux


Отказ в обслуживании в функции sys_timer_create() в ядре Linux

Компонуется при указании параметра -lrt.

Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

timer_create(): _POSIX_C_SOURCE >= 199309L

ОПИСАНИЕ

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

CLOCK_REALTIME Настраиваемые системные часы реального времени. CLOCK_MONOTONIC Ненастраиваемые, постоянно идущие вперёд часы, отсчитывающие время с некоторой неопределённой точки в прошлом, которая не изменяется с момент запуска системы. CLOCK_PROCESS_CPUTIME_ID (начиная с Linux 2.6.12) Часы, измеряющие время ЦП (пользовательское и системное), затраченное вызывающим процессом (всеми его нитями). CLOCK_THREAD_CPUTIME_ID (начиная с Linux 2.6.12) Часы, измеряющие время ЦП (пользовательское и системное), затраченное вызывающей нитью.

Помимо значений, перечисленных ранее, в clockid может быть указано clockid, возвращённое вызовом clock_getcpuclockid(3) или pthread_getcpuclockid(3).

Аргумент sevp указывает на структуру sigevent, которая задаёт способ уведомления вызывающего при срабатывании таймера. Определение и описание структуры смотрите в sigevent(7).

В поле sevp.sigev_notify можно указать следующие значения:

SIGEV_NONE Выполнять синхронное уведомление при срабатывании таймера. Ход таймера можно отслеживать с помощью timer_gettime(2). SIGEV_SIGNAL При срабатывании таймера генерировать для процесса сигнал sigev_signo. Подробности смотрите в sigevent(7). Полю si_code структуры siginfo_t присваивается значение SI_TIMER. В любой момент времени для таймера в очередь процесса ставится не более одного сигнала; подробности смотрите в timer_getoverrun(2). SIGEV_THREAD При срабатывании вызвать sigev_notify_function, как если бы это была начальная функция новой нити. Подробности смотрите в sigevent(7). SIGEV_THREAD_ID (есть только в Linux) Как для SIGEV_SIGNAL, но сигнал нацелен на нить, чей ID указывается в sigev_notify_thread_id, который должен быть нитью того же процесса что и вызывающий. В поле sigev_notify_thread_id указывается ID ядерной нити, то есть значение, возвращаемое clone(2) или gettid(2). Этот флаг предназначен только для использования в библиотеках нитей.

Таймеры в ядре Linux

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

Makefile будем использовать от старых примеров. Можете найти его тут или на GitHub по ссылке в конце статьи.

Объявляем структуру для работы с таймером и функцию — обработчик переполнения таймера.

Как видите функция обработчик принимает параметр.

Функция инициализации модуля.

Тут мы настраиваем таймер при помощи функции setup_timer. Передаем её нашу структуру, функцию обработчик и параметр (в данном случае 10).
Затем мы «заводим» таймер. Скармливаем структуру my_timer, и время срабатывания. Время вычисляем путем сложения текущего счетчика тиков jiffies со результатом функции msecs_to_jiffies, которая преобразовывает значение в миллисекундах в тики счетчика.

Как видите выгружая модуль нужно удалить таймер.

Отменяемые таймеры

Нужно создать отменяемый таймер. т.е. если таймер был запущен и до истечения заданного времени другая нить решила что таймер не нужен — он выключается или запускается заново с другим значением таймаута.


Я это пытался сделать с помощью clock_gettime / pthread_cond_timedwait
Не очень успешно. Оно в принципе работает, но есть проблема.
Работает только с CLOCK_REALTIME и категорически не желает работать с CLOCK_MONOTONIC.
Поскольку CLOCK_REALTIME страдает при смене системного времени этот вариант к использованию непригоден.

Что можно придумать для решения такой задачи ?

15.06.2011, 17:01

Таймеры
Здравствуйте, хочу сделать поле в которое вводят текст и не надо нажимать enter. Ставлю обработчики.

Таймеры
подскажите пожалуйста, не могу разобраться как правильно выставить таймеры событий. Условие: 2.

Таймеры
У меня опять вопрос по кишкам винды и книжке Марка Руссиновича (6 издание «Внутреннее устройство MS.

Таймеры
Здравствуйте, скажите какие есть готовые таймеры? Пользовался поиском, нашел только winapi и.

Таймеры
Здравствуйте. Можно ли получить с ножки МК прямоугольные импульсы с определенной частотой(до 1кГц).

Отказ в обслуживании в функции sys_timer_create() в ядре Linux

Добрый день! Объясните пожалуйста как работает вызов timer_create с заданными параметрами:

В чем заключается механизм асинхронности данного таймера?

От: Dez
Дата: 02.06.10 12:49
Оценка:

Здравствуйте, snedelko, Вы писали:

S>Добрый день! Объясните пожалуйста как работает вызов timer_create с заданными параметрами:
S>В чем заключается механизм асинхронности данного таймера?

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

К примеру у тебя таймер на 100 мс а функция может выполняться 10-70 мс.
в случай синхронного таймера — то интервалы вызова твоей функции будут 110-170 мс.

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

тут конечно учитываются погрешности операционной системы и механизма квантования процессорного времени но не будем углубляться в такие детали.

От: kpcb
Дата: 02.06.10 15:26
Оценка:

Здравствуйте, snedelko, Вы писали:

S>Добрый день! Объясните пожалуйста как работает вызов timer_create с заданными параметрами:

S>

S>В чем заключается механизм асинхронности данного таймера?

В данном случае никакой асинхронности нет — через определённый интервал будет приходить сигнал и выполнение главного потока (тот в котором вызвана ф-я int main(int, char**) )остановится на время выполнения сигнала, все остальные потоки продолжат работать. Если надо обработать сигнал асинхронно (в другом потоке) то в обработчике TimeProc можно переправить сигнал в другой поток при помощи pthread_kill (не забыть при этом установить обработчик сигнала в другом потоке)


От: Аноним
Дата: 03.06.10 07:41
Оценка:

Здравствуйте, kpcb, Вы писали:

K>В данном случае никакой асинхронности нет — через определённый интервал будет приходить сигнал и выполнение главного потока (тот в котором вызвана ф-я int main(int, char**) )остановится на время выполнения сигнала, все остальные потоки продолжат работать. Если надо обработать сигнал асинхронно (в другом потоке) то в обработчике TimeProc можно переправить сигнал в другой поток при помощи pthread_kill (не забыть при этом установить обработчик сигнала в другом потоке)

В функции create() и TimerProc(sigval) я добавил pthread_self() и сравнил ихние pthread_t: они отличаются! Значит потоки разные?

От: kpcb
Дата: 03.06.10 09:29
Оценка: 2 (1)

Здравствуйте, Аноним, Вы писали:

А>В функции create() и TimerProc(sigval) я добавил pthread_self() и сравнил ихние pthread_t: они отличаются! Значит потоки разные?

Возможно работа сигналов на линуксе несколько отличается (я проверял на Маке)
Вот пример:

Что происходит:
1) запускаются 3 дополнительных потока, все встают на sleep
2) в главном потоке запускается таймер, после чего главный поток тоже встаёт на sleep
3) по срабатыванию таймера приходит сигнал (из-за чего прерывается системный вызов в главном потоке)
4) сигнал переправлятся в произвольный (0ой) поток (из-за чего прерывается системный вызов этом потоке тоже)
5) работает обработчик сигнала в 0ом потоке
6) главный поток дожидается завершения остальных

Вот вывод в консоль:

От: snedelko
Дата: 03.06.10 10:02
Оценка: 2 (1)

Здравствуйте, kpcb, Вы писали:

K>Здравствуйте, Аноним, Вы писали:

А>>В функции create() и TimerProc(sigval) я добавил pthread_self() и сравнил ихние pthread_t: они отличаются! Значит потоки разные?

K>Возможно работа сигналов на линуксе несколько отличается (я проверял на Маке)

Спасибо за подробный пример
Но здесь вы используете интервальный таймер setitimer, завязанный на сигнале SIGALRM.
А мне интересно разобраться в более функциональном timer_create.
Опираясь на полезную статьюотвечу сам на свой пост:
асинхронность состоит в том, что при каждом срабатывании таймера создается поток с потоковой функцией TimerProc.

От: kpcb
Дата: 03.06.10 10:56
Оценка:

Здравствуйте, snedelko, Вы писали:

S>А мне интересно разобраться в более функциональном timer_create.

Всё понял. На маке такого (timer_create) нет

От: netch80 https://netch80.dreamwidth.org/
Дата: 10.06.10 21:34
Оценка:

Здравствуйте, Dez, Вы писали:

Dez>Может быть не в тему, но в свое время меня тоже интересовал вопрос — чем отличается синхронный таймер от асинхронного.
Dez>Ответ оказался в том — что с какой точностью таймер вызывает функцию или вернее в том от какого момента таймер начинает считать интервалы (до входа в функцию или после). это если говорить очень грубо.
Dez>К примеру у тебя таймер на 100 мс а функция может выполняться 10-70 мс.
Dez>в случай синхронного таймера — то интервалы вызова твоей функции будут 110-170 мс.

Это сильно необычное применение терминологии. Такое разделение есть, но обычно называется иначе — например, в ISC eventlib это due-mode и inter-mode.

Dez>а в случае асинхронного — таймер будет стараться делать так что между входами в твою функцию будет интервал времени 100 мс. не зависимо от того — как долго она выполнялась.

Мастер Йода рекомендует:  Ленивая загрузка для Wordpress как добавить ее на сайт

Асинхронный, jIMHO и по большинству известных источников, это «я отрапортовал, а вы дальше делайте что хотите» — то есть нет немедленного вызова обработки.

API ядра Linux, Часть 3: Таймеры и списки в ядре 2.6

Краткое содержание: Ядро Linux включает в себя разнообразные API, предназначенные помочь разработчикам создавать более простые и эффективные драйвера и приложения ядра. Два наиболее распространенных API, которые можно использовать для откладывания работ, являются API списков и API таймеров. Изучим эти API и научимся разрабатывать приложения ядра с использованием таймеров и списков.


В этой статье продолжается тема откладывания работ, которую я начал в статье «API ядра Linux, Часть 2: Функции отложенного выполнения, тасклеты ядра и очереди работ» (developerWorks, March 2010). На этот раз я расскажу об интерфейсе прикладного программирования (API) для таймеров, а также о ключевом элементе для всех схем откладывания работ — конструкте списков ядра. Я также рассмотрю API списков ядра, которые используются таймерами и другими механизмами откладывания работ (таких, как очереди работ).

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

Природа времени (в Linux)

В ядре Linux время измеряется с помощью глобальной переменной с именем jiffies , которая определяет количество временных тиков (тактов), прошедших после загрузки системы. Способ, каким подсчитываются тики, зависит на самом низком уровне от конкретной аппаратной платформы, на которой вы работаете, однако, обычно увеличение значения происходит с помощью прерываний. Частота тиков (младший значащий бит в jiffies ) конфигурируема, но в последнем ядре 2.6 для архитектуры x86 продолжительность тика равна 4 мсек (250Hz). Глобальная переменная jiffies используется в ядре для различных целей, одной из которых является хранение абсолютного текущего времени, которое нужно для вычисления значения тайм-аута для таймера (позже вы увидите примеры этого).

Таймеры ядра

В последних ядрах 2.6 имеются несколько различных схем, используемых для таймеров. Самой простой и наименее точной из всех схем таймеров (хотя и пригодной в большинстве случаев) является интерфейс API для таймеров (timer API). Этот API позволяет создавать таймеры, которые используют переменную jiffies (с минимальным тайм-аутом в 4 мсек). Также имеется API для таймеров высокого разрешения, который позволяет создавать таймеры, время в которых определяется в наносекундах. В зависимости от вашего процессора и скорости, с которой он работает, ваши возможности могут варьироваться, но API позволяет планировать тайм-ауты с интервалом меньшим, чем продолжительность тиков jiffies .

Стандартные таймеры

Стандартный API таймеров является частью ядра Linux в течение довольно продолжительного времени (начиная с самых ранних версий ядра Linux). Хотя этот интерфейс предлагает меньшую точность, чем таймеры высокого разрешения, он идеально подходит для определения тайм-аута в традиционных драйверах и компенсирует ошибки, возникающие при работе с физическими устройствами. Во многих случаях эти тайм-ауты никогда не возникают, а лишь устанавливаются, а затем отменяются.

Простые таймеры ядра реализованы с использованием алгоритма timer wheel («кольцо таймеров»). Идея впервые была предложена в 1997 году Финном Арне Генгстедом (Finn Arne Gangstad). В нем игнорируется проблема управления большим количеством таймеров, но для типичного случая — управление разумным количеством таймеров – он подходит. (Первоначально реализация таймеров представляла собой двусвязный список, упорядоченный в порядке истечения срока действия таймеров. Хотя этот подход концептуально прост, но он не масштабируем.) В алгоритме timer wheel используется набор слотов, где каждый слот представляет собой некоторый отсчет времени, по достижении которого в будущем истекает время таймера. Слоты определяются в соответствие с логарифмической шкалой времени, причем для этого используется пять слотов. С помощью переменной jiffies определяется количество групп, которые представляют собой время достижения срабатывания таймера в будущем (где каждая группа представляет собой список таймеров). Добавление таймера осуществляется с помощью операций со списками, сложность которых равна O (1) для случая, когда количество таймеров, время которых истекает, равно O (N). Истечение времен таймеров происходит в виде каскадных операций, когда при уменьшении срока действия таймеров они изымаются из слотов с высокой гранулярностью и переносятся в слоты с низкой гранулярностью. Теперь давайте посмотрим, как API используется для реализации этих таймеров.

API таймера

В Linux предлагается простой API для создания таймеров и управления ими. Интерфейс содержит функции (и helper-функции), создающие таймеры, удаляющие их и управляющие ими.

Таймеры задаются с помощью структуры timer_list , в которой хранятся все данные, необходимые для реализации таймера (в том числе указатели на списки и дополнительные статистические данные для таймера, собранные на этапе компиляции). Если рассматривать структуру timer_list с точки зрения ее использования, в ней хранится время истечения срока действия таймера, функция обратного вызова (действующую когда /если истечет время таймера), а также контекст, предоставленный пользователем. Затем нужно инициализировать таймер, что можно сделать несколькими способами. Простейшим способом является вызов функции setup_timer , которая инициализирует таймер и задает предоставленные пользователями функцию обратного вызова и контекст. Либо пользователь может задать эти значения (функцию и данные) непосредственно в таймере и просто вызвать функцию init_timer . Обратите внимание, что функция init_time вызывается внутри функции setup_timer .

Теперь, когда таймер инициализирован, необходимо задать время работы таймера, что делается с помощью вызова функции mod_timer . Поскольку обычно указывается время работы таймера, которое относится к будущему, то обычно здесь добавляется значение переменной jiffies к смещению от текущего момента времени. С помощью функции del_timer можно можно также удалить таймер (если срок его действия не истек):

Наконец, с помощью функции timer_pending можно определить, работает ли еще таймер (все еще включен) — функция возвращает 1, если таймер еще работает:

Пример таймера

Давайте на практике рассмотрим некоторые функции этого API. В листинге 1 представлен простой модуль ядра, в котором демонстрируются основные особенности использования API для простого таймера. В модуле init_module вы с помощью функции setup_timer инициализируете таймер, а затем с помощью обращения к функции mod_timer запускаете таймер. Когда истекает время, установленное в таймере, вызывается функция обратного вызова ( my_timer_callback ). И наконец, когда модуль удаляется, то происходит удаление таймера (через del_timer ). Обратите внимание на то, что функция del_timer возвращает значение, указывающее, продолжает ли таймер использоваться.

Листинг 1. Изучаем API простого таймера

Вы можете подробнее узнать об API таймеров в ./include/linux/timer.h. Хотя интерфейс API простых таймеров прост в использовании и эффективен, он не дает точности, необходимой для приложений реального времени. Поэтому давайте рассмотрим недавнее дополнение к Linux, поддерживающее таймеры высокого разрешения.

Таймеры высокого разрешения

Для таймеров высокого разрешения (или hrtimers) предлагается фреймворк, обеспечивающий высокую точность и управление таймерами и который реализован независимо от ранее рассмотренного фрейворка из-за сложностей, возникающих при объединении этих двух фреймворков. Хотя обычные таймеры работают с гранулярностью, определяемой переменной jiffies , таймеры hrtimers функционируют с гранулярностью в наносекунды.


Фреймворк hrtimer реализован иначе, чем API для традиционных таймеров. Вместо использования слотов и каскадной обработки таймеров, в таймерах hrtimers используются упорядоченные по времени структуры данных (таймеры вставляется в порядке их упорядочивания по времени с тем, чтобы минимизировать их обработку в момент их активации). Используемые структура данных представляют собой красно-черное дерево (одно из самобалансирующихся двоичных деревьев поиска, гарантирующих логарифмический рост высоты дерева от числа узлов и быстро выполняющее основные операции дерева поиска: добавление, удаление и поиск узла — прим.пер.), которое идеально подходит для приложений, предназначенных для достижения максимальной производительности (и обычно доступно в ядре в виде библиотеки).

Фреймворк для таймеров hrtimer имеется в ядре в виде API и может использоваться приложениями пользовательского пространства с помощью команд nanosleep , itimers и интерфейса таймеров POSIX (Portable Operating System Interface — переносимый интерфейс операционных систем Unix).

API таймеров высокого разрешения

Интерфейс hrtimer API имеет некоторое сходство с традиционным API, но также есть некоторые фундаментальные различия, связанные с дополнительным управлением временем. Первое, что вы заметите, что время представлено не в переменной jiffies , а в специальном типе данных, который называется ktime . Такое представление позволяет скрыть некоторые особенности, связанные с эффективным управлением временем на этом уровне гранулярности. В API формализовано различие между абсолютным и относительным временем и при вызове следует указать требуемый тип.

Как и в традиционном API, здесь таймеры представлены структурой, в данном случае — hrtimer . В этой структуре определяются компоненты таймера, необходимые пользователю (функция обратного вызова, срок действия и т. д.), а также указывается информация, используемая для его управления (где таймер размещается в структуре красно-черного дерева, дополнительные статистические данные и т. д.).

Процесс начинается с инициализации таймера с помощью hrtimer_init . В этом вызове указывается таймер, определяются часы (структура clockid_t ) и режим таймера (однократный или перезапускаемый). Часы, которые будут использоваться, определяются в файле ./include/linux/time.h и представляют собой различные часы, поддерживаемые системой (например, часы реального времени или часы с равномерной шкалой времени, которые просто представляет собой время, пройденного от некоторого начального момента, например, от загрузки системы). Как только таймер будет инициализирован, его можно запустить с помощью hrtimer_start . В этом вызове указывается время срабатывания таймера ( в ktime_t ) и режим для значения времени (абсолютное или относительное время).

После того, как таймер hrtimer будет запущен, его можно отменить с помощью обращения к hrtimer_cancel или hrtimer_try_to_cancel . В каждой функции есть ссылка на hrtimer с тем, чтобы таймер можно было остановить. Эти функции отличаются тем, что функция hrtimer_cancel пытается отменить таймер, но если он уже запущен, то функция будет ждать завершения функции обратного вызова. Функция hrtimer_try_to_cancel отличается тем, что она также пытается отменить таймер, но она вернет значение, указывающее на неудачную попытку (failure) в случае, если таймер уже запущен.

Мастер Йода рекомендует:  Работа с WDDX в РНР

Для того, чтобы убедиться, что hrtimer активирован, вы можете проверить функцию обратного вызова с помощью обращения к функции hrtimer_callback_running . Обратите внимание, что эта функция вызывается внутри функции hrtimer_try_to_cancel и она вернет ошибку в случае, если была вызвана функция обратного вызова таймера.

Интерфейс ktime API

Здесь не обсуждался интерфейс ktime API, в котором имеется богатый набор функции для управления временем с высоким разрешением. Вы можете изучить интерфейс ktime API в ./linux/include/ktime.h.

Пример использования hrtimer

Как видно в листинге 2, использование hrtimer API довольно простое. В модуле init_module вы можете начать с задания определения относительного времени для вашего тайм-аута (в данном случае, 200 мс). Вы инициализируете hrtimer с помощью обращения к функции hrtimer_init (используются часы с равномерной шкалой времени), а затем указываете функцию обратного вызова. И наконец, вы можете запустить таймер, используя для этого ранее созданное значение ktime . Когда таймер запущен, вызывается функция my_hrtimer_callback , которая возвращает значение HRTIMER_NORESTART с тем, чтобы таймер нельзя было автоматически перезапустить снова. Внутри функции cleanup_module вы выполняете сброс таймера путем отмены таймера с помощью функции hrtimer_cancel .

Листинг 2. Исследуем hrtimer API

В интерфейсе hrtimer API есть много того, что здесь не было затронуто. Одной интересной особенностью является возможность задания контекста выполнения функции обратного вызова (например, в контексте softirq или в контексте hardiirq). Вы можете узнать больше об интерфейсе hrtimer API в /include/linux/hrtimer.h.

Списки ядра

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

В API предоставляется структура list_head , которая используется не только для представления заголовка списка (с которой начинает расти список), но также указателей, используемых в структуре списка. Давайте посмотрим на пример структуры, в котором показаны возможности работы со списком (см. листинг 3). Обратите внимание на дополнительные возможности структуры list_head , которые используются на этапе компоновки (откомпилированных — прим.пер.) объектов. И заметьте, вы можете добавить структуру list_head в любом месте вашей структуры данных, и, несмотря на некоторые магические возможности GCC ( list_entry и container_of , определеных в ./include/kernel/kernel.h), вы сможете разыменовывать указатель списка и получать доступ к супер-объекту.

Листинг 3. Пример структуры с ссылками на список

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

Можно также с помощью макроса LIST_HEAD_INIT создать заголовок списка вручную (например, если заголовок список содержится в другой структуре).

Когда первоначальная инициализация будет завершена, вы сможете манипулировать со списком с помощью функций list_add и list_del (и еще многими другими функциями). Теперь, давайте перейдем к примеру кода, который лучше проиллюстрирует использование API.


Пример использования API списков

В листинге 4 представлен простой модуль ядра, в котором используется ряд функций из API списков (хотя их гораздо больше можно найти в ./include/linux/list.h). В этом примере создается два списка, в функции init_module они заполняются, а затем в функции cleanup_module с ними манипулируют.

Сначала вы создаете свою структуру данных ( my_data_struct ), которая включает в себя некоторые данные, а также два заголовка списков. В этом примере демонстрируется, что вы можете добавлять объект одновременно в несколько списков. После этого вы можете создать два заголовка списков ( my_full_list и my_odd_list ).

В функции init_module вы создаете 10 объектов данных и с помощью функции list_add загружаете их в списки (все объекты — в список my_full_list и все объекты с нечетными значениями — в список my_odd_list ). Обратите внимание, что функция list_add имеет два аргумента, первый — ссылка на список, которая будет использоваться внутри объекта, а второй — указатель на начало списка. Это свидетельствует о возможности добавлять объект данных в несколько списков, используя для этого специальные внутренние возможности ядра, позволяющие обращаться к супер-объекту, в котором содержится ссылка на список.

В функции cleanup_module иллюстрируется несколько возможностей API списков, первой из которых является макрос list_for_each , который упрощает итерацию по элементам списка. В этом макросе вы указываете ссылку на текущий объект ( pos ) и ссылку на список, по которому нужно выполнить итерацию. При каждой итерации, вы получите ссылку на начало списка list_head , которая может быть передана в list_entry для определения объекта-контейнера (контейнера с вашей структурой данных). Укажите вашу структуру и переменную списка, находящуюся в вашей структуре и используемую внутри структуры для разыменования и обратного доступа к контейнеру.

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

Наконец, для обхода списка с целью убирания из него ранее размещенных элементов можно использовать макрос list_for_each_safe . Этот макрос выполняет итерацию по списку с гарантированным удалением элементов списка (что вы должны сделать как часть вашей итерации). Вы используете list_entry для получения вашего объекта данных (перемещая его обратно в пул ядра), а затем используете list_del для освобождения позиции в списке.

Листинг 4. Изучаем API списков

Существует много других функции для добавления элементов к концу списка, а не в его начало ( list_add_tail ), объединения списков ( list_splice ) и проверки содержимого списка ( list_empty ). Детали, касающиеся функций, работающих со списками ядра, смотрите в разделе «Ресурсы» в оригинале статьи.

Двигаемся дальше

В данной статье изучено несколько API, в которых продемонстрированы возможность разделения функциональности в случае, если это необходимо (API таймеров и API таймеров высокого разрешения hrtimer), и возможность обобщения кода для его повторного использования (API списков). Традиционные таймеры обеспечивают эффективный механизм для типичных тайм-аутов, используемых в драйверах, тогда как таймеры высокого разрешения hrtimer обеспечивают другой уровень качества обслуживания, необходимые для более точных таймеров. Интерфейс API списков является очень обобщенным, но эффективным интерфейсом с богатыми функциональными возможностями. Если вы пишете код ядра, вы воспользуетесь хотя бы одним или всеми тремя этими API, так что их определенно стоит изучать.

Инструменты программирования в ядре: Часть 69. Таймеры ядра

Нам осталось рассмотреть таймерные функции — последний (из 3-х ранее названных) класс задач, связанных с отслеживанием времени. Функциональность таймера существенно шире и сложнее с точки зрения реализации, чем просто выжидание некоторого интервала времени, о чём рассказывалось в предыдущей статье. Основной задачей таймера является асинхронное возбуждение в указанный момент времени в будущем некоторого, заранее предписанного ему действия.

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

Ядро предоставляет драйверам API для работы с таймерами, так как в системе параллельно может существовать множество экземпляров таймеров. В файле содержится структура и ряд функций для декларации, регистрации и удаления таймеров ядра:

  • expires — значение jiffies , наступления которого таймер ожидает для срабатывания (абсолютное время);
  • function — указатель на функцию, которая вызывается при срабатывании с data в качестве аргумента;
  • data — этот параметр чаще всего содержит преобразованный указатель на структуру.

Функция таймера в ядре выполняется в контексте прерывания, если конкретно то в контексте обработчика прерывания системного таймера, но не в контексте процесса! Что накладывает на неё дополнительные ограничения:

  1. для данной функции запрещён доступ к пользовательскому пространству, так как из-за отсутствия контекста процесса, невозможно определить путь к пользовательскому пространству, связанному с любым определённым процессом;
  2. указатель current не имеет смысла и не может быть использован, так как соответствующий код не имеет связи с процессом, который был прерван;
  3. не может быть выполнен переход в блокированное состояние и переключение контекста.


Код в контексте прерывания не может вызывать schedule() или какую-то из форм wait_event() или любые другие функции, которые могли бы перевести его в пассивное состояние. Также нельзя использовать семафоры и им подобные примитивы синхронизации, которые могут переключить выполнение в пассивное состояние.

Код ядра может понять, работает ли он в контексте прерывания, используя макрос in_interrupt() .

Таймеры высокого разрешения

Таймеры высокого разрешения появились, начиная с версии ядра 2.6.16, а структуры представления времени для них определены в файле . Их поддержка присутствует только в архитектурах, поддерживающих аппаратные таймеры высокого разрешения. Также для них определяется новый временной тип данных ktime_t — временной интервал в наносекундном выражении, и его представления сильно отличаются в зависимости от архитектуры. Здесь же определяются множество функций для установки значений и преобразований представлений времени (многие из них определены как макросы, но ниже показаны как прототипы):

Сами операции с таймерами высокого разрешения определены в файле в структуре, напоминающей модель таймеров реального времени, вводимую для пространства пользователя стандартом POSIX 1003b:

Единственным определяемым пользователем полем этой структуры является функция реакции function , прототип которой приведён ниже:

Мастер Йода рекомендует:  Представлена стабильная версия Linux 4.16

Параметр which_clock типа clockid_t , относится к области стандарта POSIX и называется в нём временным базисом (тип задатчика времени): какую шкалу времени использовать из общего числа определённых в . Ниже перечислены возможные значения, часть из которых заимствована из POSIX, а другие расширяют существующие определения.

  • CLOCK_REALTIME — системные часы со всеми их плюсами и минусами. Могут быть переведены вперёд или назад, в этой шкале могут попадаться «вставные секунды», предназначенные для корректировки неточностей представления периода системного тика. Эта шкала времени чаще всего и используется в таймерах.
  • CLOCK_MONOTONIC — подобны CLOCK_REALTIME , но отличаются тем, что, представляют собой постоянно увеличивающийся счётчик, и поэтому не могут быть изменены при переводе времени. Обычно это счётчик времени от загрузки системы.
  • CLOCK_PROCESS_CPUTIME_ID — возвращает время, затраченное процессором на работу только с данным приложением в независимости от других задач системы. Именно этот базис чаще всего используется для пользовательского адресного пространства.
  • CLOCK_THREAD_CPUTIME_ID — похоже на CLOCK_PROCESS_CPUTIME_ID , но отсчитывается время, затрачиваемое только на один текущий поток.
  • CLOCK_MONOTONIC_RAW — то же что и CLOCK_MONOTONIC , но в отличии от первого не подвержен изменению через сетевой протокол точного времени NTP.

Последние два базиса CLOCK_REALTIME_COARSE и CLOCK_MONOTONIC_COARSE были добавлены в 2009 году, и, как утверждается их авторами, они могут обеспечить гранулярность шкалы мельче, чем предыдущие базисы. Работу с различными базисами времени обеспечивают в пространстве пользователя не очень известные API вида clock_*() (clock_gettext() , clock_nanosleep() , и т.д.). Разрешение каждого из базисов можно получить вызовом:

Для наших примеров таймеров можно использовать базисы CLOCK_REALTIME или CLOCK_MONOTONIC . В модуле htick.c представлен пример использования таймеров высокого разрешения в периодическом режиме, хотя он и не раскрывает все возможности базисов высокого расширения. Полный код примера можно найти в архиве time.tgz в разделе «Материалы для скачивания».

Листинг 1. Пример использования системных часов в модуле ядра

Запустим наш модуль и изучим полученные результаты:

Часы реального времени (RTC)

Часы реального времени — это исключительно аппаратное расширение, принципиально зависящее от аппаратной платформы, на которой установлена ОС Linux. Это одно из расширений службы системных часов, поэтому оно может отсутствовать на некоторых архитектурах. Используя часы реального времени можно создать ещё одну независимую шкалу отсчётов времени, с которой можно связать измерения или асинхронную активацию действий.

Убедиться в наличии такого расширения на используемой аппаратной платформе можно по присутствию интерфейса к таймеру часов реального времени в пространстве пользователя. Такой интерфейс предоставляется через функции ioctl() драйвера присутствующего в системе устройства /dev/rtc:

В архитектуре Intel x86 устройство этого драйвера называется Real Time Clock (RTC). RTC предоставляет функцию для работы со 114-битовым значением в NVRAM. На входе этого устройства установлен осциллятор с частотой 32768 КГц, подсоединенный к энергонезависимой батарее. Некоторые дискретные модели RTC имеют встроенные осциллятор и батарею, тогда как другие RTC встраиваются прямо в контроллер периферийной шины (например, южный мост) чипсета процессора. RTC возвращает не только время суток, но, также является и программируемым таймером, способным посылать системные прерывания (IRQ 8). Частота прерываний варьируется от 2 до 8192 Гц. Также RTC может посылать прерывания ежедневно, наподобие будильника. Все определения находятся в файле :

Ниже перечислены некоторые важные коды команд ioctl() :

В листинге 2 приведён пример использования RTC из пользовательской программы для считывания абсолютного значения времени (полный код примера можно найти в архиве time.tgz в разделе «Материалы для скачивания»):

Листинг 2. Пример использования часов реального времени из пространства пользователя (файл rtcr.c)


Результат запуска данного примера:

В ещё одном примере в листинге 3 показывается, как часы RTC могут использоваться как независимый источник времени в программе, генерирующей периодические прерывания с высокой (значительно выше системного таймера) частотой следования:

Листинг 3. Использование часов реального времени в качестве независимого источника времени (файл rtprd.c)

В этом примере прерывания RTC прерывают блокирующую операцию read() гораздо чаще периода системного тика. В листинге 3 наглядно демонстрируется запуск процесса без перевода (что делается по умолчанию) в реал-тайм диспетчирование (ключ -n), когда дисперсия временной латентности возрастает сразу на 2 порядка (эти эффекты вытесняющего диспетчирования, должны всегда приниматься во внимание при планировании измерений временных интервалов):

Запустим данный пример и рассмотрим полученные результаты:

Код пользовательского процесса, представленный в листинге 3, проясняет, как и на каких интервалах могут использоваться часы реального времени. Точный способ, используемый для считывания RTC-времени в ядре, не скрывается никакими обёртками и зависит от использованного оборудования RTC. Для наиболее используемого чипа Motorola 146818 (который в таком наименовании уже не производится и заменяется аналогами) соответствующие макросы и другую информацию можно найти в файле :

А определения, необходимые для понимания происходящего, находятся в файле :

Это значит, что в порт 0х70 записывается номер требуемого параметра, а по порту 0х71 считывается/записывается требуемое значение — так традиционно организуется обмен данными в памяти CMOS.

Примечание: Часы RTC, как уже было отмечено, синхронизируются от другого кварцевого резонатора, чем системный таймер. А, поскольку никакой резонатор не может иметь абсолютно точную частоту, то на расхождении времени RTC и системного может быть построена идентификация конкретного экземпляра системной платы, например, для целей привязки к оборудованию и защиты от копирования.

Заключение

В этой части было рассмотрен последний, и самый сложный механизм определения временных интервалов — таймеры. На этом завершается обзор службы времени в Linux, хотя в ней сокрыто ещё множество любопытных вещей.

Где в Linux взять таймер вызывающий события?

Мне понадобился таймер который по истечению какого то времени вызывает событие, например std::function. В Win32 API и в C# я очень часто использовал такие таймеры, но тут сходу их найти не смог -_-. Была идея реализовать такой класс самому запустив поток и всё время проверяя текущее время, но в моём проекте очень важна производительность и мне кажется это не самым разумным решением. Из сторонних библиотек я могу использовать только boost (я знаю, что подобный таймер есть в qt, но его я использовать никак не могу). Подскажите пожалуйста где мне взять в Linux дешёвый с точки зрения производительности таймер на события которого можно подписываться функторами std::function .

4 ответа 4

В Linux есть posix функция

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

Таймеры в ядре Linux

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

Makefile будем использовать от старых примеров. Можете найти его тут или на GitHub по ссылке в конце статьи.

Объявляем структуру для работы с таймером и функцию — обработчик переполнения таймера.


Как видите функция обработчик принимает параметр.

Функция инициализации модуля.

Тут мы настраиваем таймер при помощи функции setup_timer. Передаем её нашу структуру, функцию обработчик и параметр (в данном случае 10).
Затем мы «заводим» таймер. Скармливаем структуру my_timer, и время срабатывания. Время вычисляем путем сложения текущего счетчика тиков jiffies со результатом функции msecs_to_jiffies, которая преобразовывает значение в миллисекундах в тики счетчика.

Как видите выгружая модуль нужно удалить таймер.

Отказ в обслуживании в функции sys_timer_create() в ядре Linux

clock_t times(struct tms * buf );

ОПИСАНИЕ

Поле tms_utime содержит процессорное время, прошедшее с момента выполнения команд вызывающего процесса. Поле tms_stime содержит процессорное время, прошедшее в системе с момента запуска задач вызывающего процесса. Поле tms_cutime содержит сумму значений полей tms_cutime и tms_stime для всех ожидающих завершения дочерних процессов. Поле tms_cstime содержит сумму значений полей tms_stime и tms_cstime для всех ожидающих завершения дочерних процессов.

Времена для завершенных процессов (и их потомков) добавляются в момент вызова функции wait (2) или waitpid (2), возвращающая идентификатор процесса.

Все значения времени возвращаются в тиках.

ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ


ЗАМЕЧАНИЯ

Если в Linux расположение SIGCHLD установлено в SIG_IGN , то время завершенных дочерних процессов автоматически включается в поля tms_cstime и tms_cutime хотя POSIX 1003.1-2001 говорит, что так должно быть только если вызывающий процесс исполнил wait () на своих потомков.

Заметим, что clock (3) возвращает значения в типе clock_t, который измеряется не в тиках, а в CLOCKS_PER_SEC.

СООТВЕТСТВИЕ СТАНДАРТАМ


ПРИМЕЧАНИЯ ПО ИСТОРИИ

На старых системах число тиков часов в секунду можно получить из переменной HZ.

Проблема posix timer_create() в linux

Я использую функцию posix timer_create() для функции таймера в моем приложении. Я использую параметр SIGEV_THREAD для сценария тайм-аута. Но поток, созданный ядром во время выполнения, когда тайм-аут происходит, не выходит. Также нет проблем с функцией запуска потока. Для целей тестирования я сделал это фиктивной функцией. Поскольку этот поток не выходит, он вызывает утечку memroy около 10 мб. Состояние отсоединения для потока, созданного по умолчанию, — PTHREAD_CREATE_DETACHED. Может кто-нибудь сказать мне, что делать, чтобы избавиться от утечки памяти?

Это библиотека libc, создающая поток для обработки таймеров для вас. Этот поток не разрушается после того, как ваш таймер выстрелил, чтобы избежать создания/уничтожения потоков каждый раз, когда вы используете timer_create() . Другими словами, как только поток таймера был создан, он запускается до завершения программы.

Однако сам поток таймера не вызывает утечек памяти. Утечка памяти, скорее всего, вызвана вашим кодом. Отправьте свой код, пожалуйста.

Добавить комментарий