Затримка виконання

Часто коду ядра (особливо драйверам) необхідно затримувати виконання дій на деякий період часу без використання таймерів або механізму нижніх половин Це звичайно необхідно для того, щоб дати апаратного забезпечення час на завершення виконання завдання Такий інтервал часу зазвичай достатньо короткий Наприклад, у специфікації мережевий інтерфейсної плати може бути вказано час зміни режиму роботи Ethernel-контролера, рівне

2 мікросекунди, тобто після установки бажаної швидкості передачі драйвер повинен очікувати хоча б протягом двох мікросекунд перед тим, як продовжити роботу

Ядро надає кілька рішень цього завдання, залежно від семантики затримки Ці рішення мають різні властивості Деякі рішення під час затримки завантажують процесор, не даючи можливості виконувати іншу, більш корисну роботу Інші рішення не завантажують процесор, але не дають гарантії того, що код відновить виконання точно в необхідний момент времені8

Затримка за допомогою циклу

Найбільш просте для реалізації (хоча зазвичай не оптимальне) рішення – це використання затримки з допомогою циклу абоочікування в стані зайнятості (busy loop, busy wailing)Ця техніка працює, тільки якщо інтервал часу затримки є кратним періоду системного таймера або коли точність не дуже важлива

Ідея проста – виконати постійний цикл, поки не буде отримано необхідну кількість імпульсів системного таймера, як у наступному прикладі

unsigned long delay = jiffies + 10 / * Десять імпульсів таймера * /

while (time_before (jiffies, delay))

8 Насправді, жоден підхід не гарантує, що час затримки буде точно дорівнює вказаному значенню Деякі із е підходи забезпечують затримки, очен ь близькі до точного значення, проте всі підходи гарантують, що час очікування буде, по вкрай ї мере, не менше, ніж потрібно У деяких випадках період очікування виходить істотно більше зазначеного

Цикл буде виконуватися, поки значення змінної jiffie 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()

Виклик функції cond_resched () планує виконання іншого процесу, але тільки у випадку, якщо встановлено прапор need_resched Іншими словами, дане рішення дозволяє активізувати планувальник, але тільки у випадку, коли є більш важливе завдання, яке потрібно виконати Слід звернути увагу, що оскільки використовується планувальник, таке рішення не можна застосовувати в контексті переривання, а тільки в контексті процесу Затримки краще використовувати тільки в контексті процесу, оскільки обробники переривань повинні виконуватися по можливості швидко (а цикл затримки не дає такої можливості) Більш того, будь-які затримки виконання, по можливості, не повинні використовуватися при захоплених блокировках і при заборонених перериваннях

Шанувальники мови С можуть поцікавитися, які є гарантії, що зазначені цикли будуть дійсно виконуватися Зазвичай компілятор С може виконати читання зазначеної змінної всього один разів У звичайній ситуації немає ніякої гарантії, що змінна jiffie s буде зчитуватися на кожній ітерації циклу Нам же необхідно, щоб значення змінної jiffie s зчитувалося на кожній ітерації циклу, так як це значення збільшується в іншому місці, а саме в перериванні таймера Саме тому дана змінна визначена у файлі з атрибутом volatile Ключове слово volatil e вказує компілятору, що цю змінну необхідно зчитувати з того місця, де вона зберігається в оперативній памяті, і ніколи не використовувати копію, що зберігається в регістрі процесора Це гарантує, що вказаний цикл виконається, як і очікується

Короткі затримки

Іноді коду ядра (і снопа зазвичай драйверам) необхідні затримки на дуже короткі інтервали часу (коротше, ніж період системного таймера), причому інтервал повинен відслідковуватися з досить високою точністю Це часто необхідно для синхронізації з апаратним забезпеченням, для якого описано деякий мінімальний час виконання дій, і яке часто буває менше однієї мілісекунди У разі таких малих значень часу неможливо використовувати затримки на підставі змінної jiffies, як показано в попередньому прикладі При частоті системного таймера, рівний 100 Гц, значення періоду системного таймера досить велика-10 мілісекунд Навіть при частоті системного таймера

1000 Гц, період системного таймера дорівнює одній мілісекунді Ясно, що необхідно інше рішення, яке забезпечує більш короткі й точні затримки

Ядро надає дві функції для забезпечення мікросекундних і миллисекундних затримок, які визначені у файлі і не використовують змінну jiffies

void udelay(unsigned long usecs)

void mdelay(unsigned long msecs)

Перва я функція дозволяє затримати виконання е на вказану кількість мікросекунд звикористанням циклу Друга функція затримує виконання на вказану кількість мілісекундСлід згадати, що одна секунда дорівнює 1000 мілісекунд, що еквівалентно 1000000мікросекундВикористання цих функцій тривіально

udelay (150) / * Затримка на 150 ms * /

Функція udela y () виконана па основі циклу, для якого відомо, скільки ітерацій необхідно виконати за вказаний період часу Функція mdelay () виконана на основі функції udela y () Так як в ядрі відомо, скільки циклів процесор може виконати в одну секунду (дивіться нижче зауваження з приводу характеристики BogoMlPS), функція udela y () просто масштабує це значення для того, щоб скорегувати кількість ітерацій циклу для отримання зазначеної затримки

Мій BogoMIPS більше, ніж у Вас

Характеристика BogoMlPS завжди була джерелом непорозумінь і жартів Насправді розрахований значення BogoMlPS не має нічого спільного з продуктивністю компютера і використовується тільки для функцій udelay () і mdelay () Назва цього параметра складається з двох частин bogus (фіктивний) і MIPS (Million of instructions per second, мільйон інструкцій в секунду) Усі знайомі з повідомленням, яке видається при завантаженні системи і схоже на наступне (дане повідомлення відповідає процесору Pentium III з частотою 1 ГГц)

Detected  1004932  MHz processor Calibrating delay loop.. 199065 BogoMlPS

Значення параметра BogoMIPS це кількість циклів, які процесор може виконати

за вказаний період часу, Насправді ця характеристика показує, наскільки швидко процесор може нічого не робити Це значення зберігається у змінній loops_per _

jiffy, і його можна вважати з файлу / proc / cpuinfo

Таймери та управління часом

функції, що використовують цикли затримки, використовують дане значення, щоб обчислити (і це виходить досить точно), скільки ітерацій циклу необхідно виконати, щоб забезпечити необхідну затримку

Ядро обчислює значення змінної 1 oops_per_jiff у при завантаженні системи у функції calibrate_delay (), реалізація якої описана у файлі init / mainc

Функція udelay () повинна викликатися тільки для невеликих затримок, оскільки при великому часі затримки на швидкій машині може виникнути переповнення в змінних циклу Загальне правило: по можливості не використовувати функцію udelay () для затримок, більше однієї мілісекунди Для більш тривалих затримок добре працює функція mdelay () Так само як і інші методи затримки виконання, засновані на циклах, ці функції (особливо функція mdelay (), так як вона дає тривалі затримки) повинні використовуватися, тільки якщо це абсолютно необхідно Слід памятати, що дуже погано використовувати цикли затримок, коли утримується блокування або заборонені переривання, тому що це дуже сильно впливає на продуктивність і час реакції системи Якщо необхідно забезпечити точний час затримки, то ці функції – найкраще рішення Звичайне використання цих функцій – це затримки на не дуже короткий період часу, який лежить в мікросекундного діапазоні

Функція schedule_timeout ()

Більш оптимальний метод затримки виконання – це використання функції schedule_timeoui t () Цей виклик переводить викликає завдання в стан очікування (sleep) по крайней доти, поки не пройде вказаний період часу Немає ніякої гарантії, що час очікування будеточно дорівнює вказаному значенню, гарантується тільки, що затримка буде не менше зазначеної Коли проходить вказаний період часу, ядро ​​повертає завдання в стан готовності до виконання (wake up) і поміщає його в чергу виконання Використовувати цю функцію просто

/ * Встановити стан завдання в значення переривається очікування * /

set_current_state(TASK INTERRUPTIBLE)

/ * Перейти в призупинене стан на s секунд * /

schedule_timeout(s * HZ)

Єдиний параметр функції – пов про бажане щодо е час, виражене в кількості імпульсів системного таймера У цьому прикладі завдання переводиться в переривається стан очікування, яке триватиме s секунд Оскільки завдання відзначено як TASK_INTERRUPTIBLE, то воно може бути повернуто до виконання завчасно, як тільки воно отримає сигнал Якщо не потрібно, щоб код обробляв сигнали, то можна використати стан TASK_ UNINTERRUPTIBLE Перед викликом функції schedule_tirneout () завдання повинне бути в одному з цих двох станів, інакше завдання в стан очікування переведено не буде

Слід звернути увагу, що оскільки функція schedule_tiraeout () використовує планувальник, то код, який її викликає, повинен бути сумісний з станом очікування Обговорення, присвячене атомарности і переходу в стан

очікування, наведено в розділах 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 &lt 0)

{

printk(KERN_ERR &quotschedule_timeout: wrong timeout &quot &quotvalue %lx from %p\n&quot, timeout,

builtin_return_address(0))

current-&gtstate = TASK_RUNNING

goto out

}

}

expire = timeout + jiffies

init timer(&amptimer)

timerexpires = expire

timerdata = (unsigned long) current

timerfunction = process_timeout

add_timer(&amptimer)

schedule()

del_timer_sync(&amptimer)

out:

}

timeout = expire jiffies

return timeout &lt 0 0 : timeout

Ця функція створює таймер time r і встановлює час спрацьовування в значення timeou t імпульсів системного таймера в майбутньому В якості обробника таймера встановлюється функція proces s timeou t (), яка викликається, коли закінчується період часу таймера Далі таймер активізується, і викликається функція schedul e () Так як передбачається, що поточне завдання знаходиться в стані TASK_INTERRUPTIBLE або TASK_UNINTERRUPTIBLE, то планувальник не виконуватиме поточне завдання, а вибере для виконання інший процес

Коли інтервал часу таймера закінчується, то викликається функція process _

timeout (), яка має наступний вигляд

void process_timeout(unsigned long data)

{

wake_up_process((task t *) data)

}

Ця функція встановлює завдання в стан TASK_RUNNING і поміщає його в чергу виконання

Коли завдання знову планується на виконання, то воно повертається у функцію schedule_tiraeou t () (відразу після виклику функції schedul e ()) Якщо завдання повертається до виконання передчасно, то таймер ліквідується Після цього завдання повертається з функції очікування по тайм-ауту

Код оператора switc h () служить для обробки спеціальних випадків і не є основною частиною функції Перевірка на значення MAX_SCHEDULE_TIMEOUT дозволяє завданням перебувати в стані очікування невизначений час У цьому випадку таймер не встановлюється (оскільки немає обмежень на інтервал часу очікування), і відразу ж активізується планувальник Якщо ви це застосовуєте, то, напевно, у вас є кращий спосіб повернути завдання в стан виконання

Очікування в черзі wait queue протягом інтервалу часу

У розділі 4 розглядалося, як контекст процесу в ядрі може помістити себе в чергу очікування для того, щоб чекати настання деякої події, а потім викликати планувальник, який обере нове завдання для виконання Якщо десь в іншому місці відбудеться вказану подію, то викликається функція wake_up () для всіх завдань, які очікують у черзі Ці завдання повертаються до виконання і можуть продовжувати роботу

Іноді бажано чекати настання деякої подіїабо поки не пройде певний інтервал часу, в залежності від того, що настане раніше, У цьому випадку код повинен просто викликати функцію schedule_timeou t () замість функції schedul e () після того, як він помістив себе в чергу очікування Завдання буде повернуто до виконання, коли відбудеться бажане подія або пройде вказаний інтервал часу Код обовязково повинен перевірити, чому він повернувся до виконання – це може статися тому, що відбулася подія, пройшов інтервал часу або був отриманий сигнал – після цього необхідно відповідним чином продовжити виконання

Час вийшов

У цьому розділі були розглянуті поняття, повязані з поданням про час в ядрі і з тим, як при цьому відбувається управління абсолютним і відносним ходом часу Були показані відмінності абсолютного і відносного часу, а також періодичних і відносних подій Далі були розглянуті переривання таймера, імпульси таймера, константа HZ і змінна jiffies

Після цього було розказано про те, як реалізовані таймери ядра і як їх можна використовувати у власному коді ядра Наприкінці глави були представлені інші методи, які розробники можуть використовувати для обліку часу

Велика частина коду ядра, який вам доведеться писати, вимагає розуміння того, як час тече в ядрі і як його відстежувати З дуже великою ймовірністю, особливо при розробці драйверів, вам необхідно буде мати справу з таймерами ядра Матеріал цієї глави принесе практичну користь

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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*