Механізм відкладених переривань (softirq) – ЧАСТИНА 2

Головна причина використання відкладених переривань – масштабованість Якщо немає необхідності масштабироваться на нескінченну кількість процесорів, то краще використовувати механізм тасклетов Тасклети – Це відкладені переривання, для яких обробник не може виконуватися паралельно на декількох процесорах

Генерація відкладених переривань

Після того як обробник доданий до переліку та зареєстрований за допомогою виклику open_softir q (), він готовий виконуватися Для того щоб відзначити його як очікує виконання і, відповідно, щоб він виконався при наступному виклику функції do_softirq (), необхідно викликати функцію raise_softirq () Наприклад, мережева підсистема повинна викликати цю функцію в наступному вигляді

raise_softirq(NET_TX_SOFTIRQ)

Цей виклик згенерує відкладене переривання з індексом NET_TX_SOFTIRQ Відповідний обробник net_tx_actio n () буде викликаний при наступному виконанні програмних переривань ї ядром Ця функція забороняє апаратні переривання перед тим, як згенерувати відкладене переривання, а потім відновлює їх в первинний стан Якщо апаратні переривання в даний

момент заборонені, то для невеликої оптимізації можна скористатися функцією raise_so f tirq_irqof f (), як показано в наступному прикладі

/*

* Переривання повинні бути заборонені

*/

raise_softirq_irqoff(NET_TX_SOFTIRQ)

Найбільш часто відкладені переривання генеруються з обробників апаратних переривань У цьому випадку обробник апаратного переривання виконує всю основну роботу, яка стосується апаратного забезпечення, генерує відкладене переривання і завершується Після завершення обробки апаратних переривань ядро ​​викликає функцію do_softirq () Оброблювач відкладеного переривання виконується і підхоплює роботу з того місця, де обробник апаратного переривання її відклав У такому прикладі розкривається зміст назв верхня половина і нижня половина.

Тасклети

Тасклети – це механізм обробки нижніх половин, побудований на основі механізму відкладених переривань Як вже зазначалося, вони не мають нічого спільного з завданнями (task) Тасклети за своєю природою і принципу роботи дуже схожі на відкладені переривання Проте вони мають більш простий інтерфейс і спрощені правила блокувань

Рішення про те, чи варто використовувати тасклети, прийняти досить просто: у більшості випадків необхідно використовувати тасклети Як було показано в попередньому розділі, приклади використання відкладених переривань можна порахувати на пальцях однієї руки Відкладені переривання необхідно використовувати тільки у випадку, коли необхідна дуже велика частота виконань і інтенсивно використовується багатопотокова обробка Тасклети використовуються в дуже великій кількості випадків – вони працюють досить добре і їх дуже просто використовувати

Реалізація тасклетов

Так як тасклети реалізовані на основі відкладених переривань, вони теж євідкладеними перериваннями (Softirq) Як вже розповідалося, тасклети представлені двома типами відкладених переривань: HI_SOFTIRQ і TASKLET_SOFTIRQ Єдина різниця між ними в тому, що тасклети типу HI_SOFTIRQ виконуються завжди раніше тасклетов типу TASKLET_SOFTIRQ

Структури тасклетов

Тасклети представлені за допомогою структури tasklet_struct Кожен екземпляр структури являє собою унікальний тасклет Ця структура визначена в заголовному файлі в наступному вигляді

struct tasklet_struct {

struct tasklet_struct * next / * Покажчик на наступний тасклет в списку * /

unsigned long state / * Стан тасклета * /

}

atomic_t count / * Лічильник посилань * /

void (* func) (unsigned long) / * Функція-обробник тасклета * /

unsigned long data / * Аргумент функції-обробника тасклета * /

)

Поле fun c – це функція-обробник тасклета (еквівалент поля actio n для структури, що представляє відкладене переривання), яка отримує поле dat a в якості єдиного аргументу при виклику

Поле stat e може приймати одне з наступних значень: нуль, TASKLET_ STATE_SCHED або TASLET_STATE_RUN Значення TASKLET_STATE_SCHED вказує на те, що тасклет запланований на виконання, а значення TASLET_STATE_RUN – що тасклет виконується Для оптимізації значення TASLET_STATE RUN може використовуватися тільки на багатопроцесорної машині, так як на однопроцессорной машині і без цього точно відомо, чи виконується тасклет (справді, адже код, який виконується, або належить тасклету, або ні)

Поле coun t використовується як лічильник посилань на тасклет Якщо це значення не дорівнює нулю, то тасклет заборонений і не може виконуватися якщо воно дорівнює нулю, то тасклет дозволений і може виконуватися у випадку, коли він позначений як очікує виконання

Планування тасклетов на виконання

Заплановані (scheduled)на виконання е тасклети (еквівалент згенерованих відкладених переривань) 6 зберігаються у двох структурах, визначених для кожного процесора: структурі tasklet_ve c (для звичайних тасклетов) і структурі tasklet_hi_ve c (для високопріоритетних тасклетов) Кожна з цих структур – це звязаний список структур tasklet_struct Кожен екземпляр структури tasklet_struc t являє собою окремий тасклет

Тасклети можуть побут ь запланований и на виконання е з допомогу ю функци ї tasklet_schedul e () і tasklet_hi_schedul e (), які беруть єдиний аргумент-вказівник на структуру тасклета-tasklet_struct Ці функції дуже схожі (відмінність полягає в тому, що одна використовує відкладене переривання з номером TASKLET_SOFTIRQ, а інша – з номером HI_SOFTIRQ) До написання і використанню тасклетов ми повернемося в наступному розділі А зараз розглянемо деталі реалізації функції tasklet_hi_schedul e (), які полягають у наступному

• Перевіряється, чи у поле stat e в значення TASKLET_STATE_ SCHED Якщо встановлено, то тасклет вже запланований на виконання і функція може повернути управління

• Зберігається стан системи переривань і забороняються переривання на локальному процесорі Це гарантує, що ніщо на даному процесорі не заважатиме виконанню цього коду

• Додається тасклет, який планується на виконання, в початок повязаного списку структури tasklet_ve c або tasklet_hi_vec, які унікальні для кожного процесора в системі

6 Це ще один примі р поганий термінології Чому відкладені переривання я (softirq) генеруються (rise), а тасклети (lasklet) плануються (schedule) Хто знає Обидва термін а означають, що обробники нижніх полови н позначаються як очікують на виконання е і незабаром часів і будуть виконані

• Генерується відкладене переривання з номером TASKLET_SOFTIRQ або НI_ SOFTIRQ, щоб найближчим часом даний тасклет виконався при виклику функції do_softirq ()

• Встановлюється стан системи переривань в первісне значення і повертається управління

Пр і перші ж зручній нагоді функція do_softirq () виконається, як це обговорювалося в попередньому розділі Оскільки більшість тасклетов позначаються як готові до виконання в обробниках переривань, то, швидше за все, функція do_softir q () викликається відразу ж, як тільки повернеться останній обробник переривання Так як відкладені переривання з номерами TASKLET_SOFTIRQ або HI_SOFTIRQ до цього моменту вже згенеровані, то функція do_softir q () виконує відповідні обробники Ці обробники, а також функції tasklet_actio n () і tasklet_hi_actio n () є серцем механізму обробки тасклетовДавайте розглянемо, що вони роблять

• Забороняються переривання, і виходить весь список tasklet_ve c або tasklet _

hi_ve c для поточного процесора

• Список поточного процесора очищається шляхом привласнення значення нуль вказівником на нього

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

• Організовується цикл по всіх тасклетам в отриманому списку

• Якщо дана машина є багатопроцесорної, то потрібно перевірить ь не виконується чи поточний тасклет на іншому процесорі, тобто перевірити чи не встановлений прапор TASLET_STATE_RUN Якщо тасклет вже виконується, то його необхідно пропустити і перейти до наступного тасклету в списку (згадаймо, що тільки один тасклет даного типу може виконуватися в будь-який момент часу)

• Якщо тасклет не виконується, то потрібно встановити прапор TASLET_STATE_RUN, щоб інший процесор не міг виконати цей тасклет

• Перевіряється значення поля coun t на рівність нулю, щоб переконатися, що тасклет не заборонений Якщо тасклет заборонений (поле coun t не дорівнює нулю), то потрібно перейти до наступного тасклету, який очікує на виконання

• Тепер можна бути впевненим, що тасклет ніде не виконується, ніде не виконуватиметься (так як він позначений як виконувалися на даному процесорі) і що значення поля coun t одно нулю Необхідно виконати оброблювач тасклета Після того як тасклет виконався, слід очистити прапор TASLET_STATE_RUN і поле state

• Повторити описаний алгоритм для наступного тасклета, поки не залишиться жодного тасклета, що очікує виконання

Реалізація тасклетов проста, але в той же час дуже дотепна Як видно, всі тасклети реалізовані на базі двох відкладених переривань TASKLET_SOFTIRQ і HI_SOFTIRQ Коли тасклет запланований на виконання, ядро ​​генерує одне з цих двох відкладених переривань Відкладені переривання, в свою чергу, обробляються спеціальними функціями, які виконують всі заплановані на виконання тасклети Ці спеціальні функції гарантують, що тільки один тасклет даного типу виконується в будь-який момент часу (але тасклети різних типів можуть виконуватися одночасно) Вся ця складність захована за простим і ясним інтерфейсом

Використання тасклетов

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

Оголошення тасклетов

Тасклети можна створювати статично і динамічно Який варіант краще вибрати, залежить від того, як необхідно (або бажано) користувачеві звертатися до тасклету: прямо або через покажчик Для статичного створення тасклета (і відповідно, забезпечення прямого доступу до нього) необхідно використовувати один з двох наступних макросів, які визначені у файлі

DECLARE_TASKLET(name, func, data) DECLARE_TASKLET_DISABLED(name, func, data)

Обидва макросу статично створюють екземпляр структури struct_tasklet_struc t з вказаним імям (name) Коли тасклет запланований на виконання, то викликається функція func, якій передається аргумент data Різниця між цими макросами полягає в значенні лічильника посилань на тасклет (поле count) Перший макрос створює тасклет, у якого значення поля coun t дорівнює нулю, і, відповідно, цей тасклет дозволений Другий макрос створює тасклет і встановлює для нього значення поля count, рівне одиниці, і, відповідно, цей тасклет буде заборонений Можна навести наступний приклад

DECLARE_TASKLET (my_tasklet, my_tasklet_handler, dev) Этастрокаэквивалентнаследующейдекларации

struct tasklet_struct rny_tasklet = { NULL, 0, ATOMIC_INIT(0),

tasklet_handler, dev)

У даному прикладі створюється тасклет з імям my_tasklet, який дозволений для виконання Функція tasklet_handle r буде обробником цієї тасклета Значення параметра de v передається у функцію-обробник при виклику даної функції

Для ініціалізації тасклета, на який вказує заданий покажчик struc t tasklet_struct * t – непряме посилання на динамічно створену раніше структуру, необхідно використовувати наступний виклик

tasklet_init (t, tasklet_handler, dev) / * Динамічно, а не статично * /

Написання власної функції-обробника тасклета

Функція-обробник тасклета повинна відповідати правильному прототипу

void tasklet_handler(unsigned long data)

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

Планування тасклета на виконання

Для того щоб запланувати тасклет на виконання, повинна бути викликана функція tasklet_schedul e (), якою як аргумент передається покажчик на відповідний примірник структури tasklet_struct

tasklet_schedule (& my_tasklet) / * Зазначити, що тасклет my_tasklet очікує на виконання * /

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

Зазначений тасклет може бути заборонений за допомогою виклику функції tasklet _ disabl e () Якщо тасклет в даний момент часу виконується, то ця функція не поверне управління, поки тасклет закінчить виконуватися Як альтернативу можна використовувати функцію tasklet_disable_nosyn c (), яка забороняє зазначений тасклет, але повертається відразу і не чекає, поки тасклет завершить виконання Це звичайно небезпечно, так як в даному випадку не можна гарантувати, що тасклет не закінчив виконання Виклик функції tasklet_enabl e () дозволяє тасклет Ця функція також повинна бути викликана для того, щоб можна було використовувати тасклет, створений за допомогою макросу DECLARE_TASKLET_DISABLED (), як показано в наступному прикладі

tasklet_disable (& my_tasklet) / * Тасклет тепер заборонений * /

/ * Ми можемо робити все, що завгодно, знаючи, що тасклет не може виконуватися * /

tasklet_enable (& my_tasklet) / * Тепер тасклет дозволений * /

З черги тасклетов, які очікують на виконання, тасклет може бути вилучений за допомогою функції tasklet_kil l () Ця функція отримує покажчик на відпо-

ствующуго структуру tasklet_struc t в якості єдиного аргументу Видалення запланованого на виконання тасклета з черги дуже корисно в разі, коли використовуються тасклети, які самі себе планують на виконання Ця функція спочатку очікує, поки тасклет закінчить виконання, а потім видаляє його з черги Однак це, звичайно, не може запобігти можливості, що інший код запланує цей же тасклет на виконання Так як дана функція може переходити в стан очікування, то її не можна викликати з контексту переривання

Демон ksoftirqd

Обробка відкладених переривань (softirq) і, відповідно, тасклетов може здійснюватися за допомогою набору потоків простору ядра (по одному потоку на кожен процесор) Потоки простору ядра допомагають обробляти відкладені переривання, коли система перевантажена великою кількістю відкладених переривань

Як уже згадувалося, ядро ​​обробляє відкладені переривання в декількох місцях, найбільш часто це відбувається після повернення з обробника переривання Відкладені переривання можуть генеруватися з дуже великими частотами (як, наприклад, в разі інтенсивного мережевого трафіку) Гірше того, функції-обробники відкладених переривань можуть самостійно відновлювати своє виконання (реактівізіровать себе) Іншими словами, під час виконання функції-обробники відкладених переривань можуть генерувати своє відкладене переривання для того, щоб виконатися знову (насправді, мережева підсистема саме так і робить) Можливість великих частот генерації відкладених переривань в поєднанні з їх можливістю активізувати самих себе може призвести до того, що програми, що працюють в просторі користувача, будуть страждати від нестачі процесорного часу У свою чергу, не своєчасна обробка відкладених переривань також не припустима Виникає дилема, яка потребує вирішення, але жодне з двох очевидних рішень не є підходящим Давайте розглянемо обидва цих очевидних рішення

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

Друге рішення – це взагалі не обробляти реактівізірованние відкладені переривання Після повернення з чергового обробника переривання ядро ​​просто

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

Необхідний небудь компроміс Рішення, яке реалізовано в ядрі, – не обробляти негайно знову активізовані відкладені переривання Бместо цього, якщо сильно зростає кількість відкладених переривань, ядро ​​повертає до виконання (wake up) сімейство потоків простору ядра, щоб вони впоралися з навантаженням Дані потоки ядра працюють з самим мінімально можливим пріоритетом (значення параметра nice одно 19) Це гарантує, що вони не будуть виконуватися замість чогось більш важливого Але вони зрештою теж коли-небудь обовязково виконуються Це запобігає ситуацію нестачі процесорних ресурсів для користувача програм З іншого боку, це також гарантує, що навіть у випадку великої кількості відкладених переривань вони все врешті-решт будуть виконані І нарешті, таке рішення гарантує, що в разі незавантаженої системи відкладені переривання також обробляються досить швидко (бо відповідні потоки простору ядра будуть заплановані на виконання негайно)

Для кожного процесора існує свій потік Кожен потік має імя у вигляді ksoftirqd / n, деп –номер процесора Так в двухпроцессорной системі будуть запущені два потоки з іменами ksoftiqd / 0 і ksoftirqd / 1 To, що на кожному процесорі виконується свій потік, гарантує, що якщо в системі є вільний процесор, то він завжди буде взмозі виконувати відкладені переривання Після того як потоки запущені, вони виконують замкнутий цикл, схожий на наступний

for (;) {

set_task_state(current, TASK_INTERRUPTIBLE)

add_wait_queue(&ampcwq-&gtmore_work, &ampwait)

if (list_empty(&ampcwq-&gtworklist))

schedule()

else

set_task_state(current, TASK_RUNNING)

remove_wait_queue(&ampcwq-&gtmore_work, &ampwait)

if  (list_empty(&ampcwq-&gtworklist))

run workqueue(cwq)

}

Якщо є відкладені переривання, які очікують на обробку (що визначає виклик функції softirq_pendin g ()), т о потік ядра ksoftirq d викликає функцію do_softir q (), яка ці переривання обробляє Зауважимо, що це робиться періодично, щоб обробити також знову активізовані відкладені переривання Після кожної ітерації при необхідності викликається функція schedul e (), щоб дати можливість виконуватися більш важливим процесам Після того як вся обробка виконана, потік ядра встановлює свій стан в значення TASK_INTERRUPTIBLE і активізує планіровцік для вибору нового готового до виконання процесу

Потік обробки відкладених переривань знову повертається в стан готовності до виконання, коли функція do_softir q () визначає, що відкладене переривання реактівізіровало себе

Старий механізм ВН

Хоча старий інтерфейс ВН, на щастя, вже відсутня в ядрах серії 26, проте їм користувалися дужедовгий час – з перших версій ядра Враховуючи, що цього інтерфейсу вдалося протриматися дуже довго, він, звичайно, являє собою історичну цінність і заслуговує більшого, ніж просто побіжного розгляду Цей розділ ніяким чином не стосується ядер серії 26, але значення історії переоцінити важко

Інтерфейс ВН дуже древній, і це помітно Кожен обробник ВН повинен бути визначений статично, і кількість цих обробників обмежено максимальним значенням 32 Так як всі обробники ВН повинні бути визначені на етапі компіляції, завантажувані модулі ядра не могли безпосередньо використовувати інтерфейс ВН Проте можна було вбудовувати функції у вже існуючі обробники ВН З часом необхідність статичного оголошення і максимальна кількість обробників нижніх половин, рівне 32, стали набридати

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

В іншому, за винятком зазначених обмежень, механізм ВН був схожий на механізм тасклетов Насправді, в ядрах серії 24 механізм ВН був реалізований па основі тасклетов Максимальне кількість обробників нижніх половин, рівне 32, забезпечувалося значеннями констант, визначених у заголовному файлі Для того щоб відзначити обробник ВН як очікує на виконання, необхідно було викликати функцію mark_bh () з передачею номера обробника ВН як параметр У ядрах серії 24 при цьому планувався на виконання тасклет ВН, який виконувався за допомогою обробника bh_actio n () До серії ядер 24 механізм ВН існував самостійно, як зараз механізм відкладених переривань

У звязку з недоліками цього типу обробників нижніх половин, розробники ядра запропонували механізм черг завдань (task queue), щоб замінити механізм нижніх половин Черги завдань так і не змогли впоратися з цим завданням, хоча і завоювали прихильність великої кількості користувачів При розробці серії

ядер 23 були запропоновані механізми відкладених переривань (softirq) і механізм тасклетов (lasklet), для того щоб покласти край механізму ВН Механізм ВН при цьому був реалізований на основі механізму тасклетов На жаль, досить складно переносити обробники нижніх половин з використання інтерфейсу ВН на використання механізм тасклетов або відкладених переривань, у звязку з тим що у нових інтерфейсів немає властивості суворій послідовності виполненія7

Однак при розробці ядер серії 25 необхідну конвертацію все ж зробили, коли таймери ядра і підсистему SCSI (єдині залишилися системи, які використовували механізм ВН) нарешті перевели на використання відкладених переривань І на завершення, розробники ядра зовсім прибрали інтерфейс ВН Скатертиною дорога тобі, інтерфейс ВН

Джерело: Лав, Роберт Розробка ядра 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>

*

*