Многозадачност в работната опашка на ядрото на Linux
Workqueueса по-сложни и тежки единици от tasklets. Дори няма да се опитвам да опиша всички подробности за изпълнението тук, но най-важното, надявам се, ще анализирам повече или по-малко подробно. Workqueues, подобно на tasklets, служат за обработка на забавено прекъсване (въпреки че могат да се използват и за други цели), но за разлика от tasklets, те се изпълняват в контекста на процеса на ядрото, съответно не е необходимо да бъдат атомарни и могат да използват функцията sleep(), различни инструменти за синхронизиране и т.н.
Нека първо разберем как като цяло е организиран процесът на обработка на работната опашка.На снимката е показано много приблизително и опростено, как всъщност се случва всичко, е описано подробно по-долу.
Няколко субекта са замесени в тази тъмна афера. Първо,work item(накратко просто работа) е структура, която описва функцията (например манипулатор на прекъсвания), която искаме да планираме. Може да се смята за аналогична на структурата на tasklet. Задачите по време на планирането бяха добавени към опашки, скрити от потребителя, но сега трябва да използваме специална опашка -workqueue. Задачите се сортират от функцията за планиране и работната опашка се обработва от специални нишки, наречени работници.Workers осигуряват асинхронно изпълнение на работи от работна опашка. Въпреки че наричат работа по приоритет, в общия случай не става дума за стриктно, последователно изпълнение: в края на краищата тук се извършват изпреварване, сън, чакане и т.н.
Като цяло, работниците са нишки на ядрото, тоест те се контролират от главния планировчик на ядрото на Linux. Но работниците частично се намесват в планирането на допълнителната организация на паралелното изпълнение на работите. Професионалисттова ще стане по-подробно по-долу.
За да очертая основните характеристики на механизма на работната опашка, предлагам да изучите API.
За опашката и нейното създаване
Опциите fmt и args са форматът printf за името и аргументите към него. Параметърът max_activate е отговорен за максималния брой работи, които могат да бъдат изпълнени паралелно на един CPU от тази опашка. Опашката може да бъде създадена със следните флагове:
- WQ_HIGHPRI
- WQ_UNBOUND
- WQ_CPU_INTENSIVE
- WQ_FREEZABLE
- WQ_MEM_RECLAIM
Важно свойство на изпълнението на workqueue в ядрото на Linux е допълнителната организация на паралелното изпълнение, което присъства в обвързаните опашки. Написано е по-подробно по-долу, сега ще кажа, че се извършва по такъв начин, че да се използва възможно най-малко памет и така че процесорът да не работи на празен ход. Всичко това се изпълнява с предположението, че една работа не използва твърде много процесорни цикли. Това не е така за неприкачени опашки. По същество такива опашки просто предоставят на работата контекст и я стартират възможно най-рано. По този начин трябва да се използват неприкачени опашки, ако се очаква интензивна работа на процесора, тъй като тогава планировчикът се грижи за паралелното изпълнение на множество ядра.
По аналогия със задачите може да се възлага работаприоритет на изпълнение, нормален или висок. Приоритетът е еднакъв за цялата опашка. По подразбиране опашката има нормален приоритет и ако зададете флагWQ_HIGHPRI, тогава съответно висок.
ФлагътWQ_CPU_INTENSIVEима значение само за обвързани опашки. Този флаг е отказ от участие в допълнителната организация на паралелното изпълнение. Този флаг трябва да се използва, когато се очаква работата да изразходва много процесорно време, в който случай е по-добре да прехвърлите отговорността на планировчика. Повече за това е написано по-долу.
ФлаговетеWQ_FREEZABLEиWQ_MEM_RECLAIMса специфични и излизат извън обхвата на темата, затова няма да се спираме подробно на тях.
Понякога има смисъл да не създавате свои собствени опашки, а да използвате общи. Основните са:
- system_wq - обвързана опашка за бързи работи
- system_long_wq - обвързана опашка за работи, чието изпълнение се очаква да отнеме много време
- system_unbound_wq - необвързана опашка
За работата и планирането им
Сега нека се заемем с произведенията. Първо, нека да разгледаме макросите за инициализация, декларация и подготовка:
Творбите се добавят към опашката с помощта на следните функции:
Тук си струва да се спрем на това по-подробно. Въпреки че посочваме опашка като параметър, всъщност работата не се поставя в самата работна опашка, както може да изглежда, а в съвсем различен обект - в списъка-опашка на структурата worker_pool. Структуратаworker_poolвсъщност е най-важният обект в организацията на механизма на работната опашка, въпреки че за потребителя тя остава зад кулисите. Работниците работят с тях и те съдържат цялата основна информация.
Сега нека видим какви пулове има в системата. Басейни за началоза обвързани опашки (на снимката). За всеки CPU статично се разпределят два работни пула: единият за работи с висок приоритет, другият за работи с нормален приоритет. Тоест, ако имаме четири ядра, тогава ще има само осем прикачени пула, въпреки факта, че може да има колкото искате работни опашки. Когато създаваме работна опашка, услугаpool_workqueue(pwq) се разпределя за всеки процесор. Всеки такъв pool_workqueue е свързан с работен пул, който е разпределен на същия CPU и съответства по приоритет на типа опашка. Чрез тях работната опашка взаимодейства с групата работници. Работниците изпълняват работи от групата работници безразборно, без да разграничават към коя работна опашка са принадлежали първоначално.
За неприкрепени опашки работните групи се разпределят динамично. Всички опашки могат да бъдат разделени на класове на еквивалентност според техните параметри и за всеки такъв клас се създава собствен пул от работници. Достъпът до тях се осъществява с помощта на специална хеш-таблица, където ключът е набор от параметри, а стойността, съответно, е работният пул. Всъщност нещата са малко по-сложни за необвързани опашки: ако pwq и опашки за всеки CPU са създадени за обвързани опашки, тогава тук те се създават за всеки NUMA възел, но това е допълнителна оптимизация, която няма да разглеждаме подробно.
Всякакви дреболии
Ще дам и няколко функции от API за пълнота, но няма да говоря подробно за тях:
Как работниците вършат работата си?
Сега, след като се запознахме с API, нека да разгледаме по-отблизо как всичко работи и се управлява. Всеки пул има набор от работници, които почистват задачите. Освен това броят на работниците се променя динамично, приспособявайки се към текущата ситуация. Както вечеустанови, че работниците са нишки, които извършват работа в контекста на ядрото. Работникът ги подрежда един по един от работния пул, свързан с него, и произведенията, както вече знаем, могат да принадлежат към различни опашки източници.
Работниците могат условно да бъдат в три логически състояния: те могат да бъдат бездействащи, работещи или управляващи. Работникът можеда бездействаи да не прави нищо. Това е, например, когато цялата работа вече е изпълнена. Когато работник влезе в това състояние, той заспива и съответно няма да бъде изпълнен, докато не бъде събуден; Ако не се изисква управление на пула и списъкът с планирани работи не е празен, тогава работникът започва да ги изпълнява. Условно ще наречем такива работницибягащи. Ако е необходимо, работникът поема ролята намениджърна пула. Един пул може да има само един управляващ работник или изобщо да няма такъв. Неговата задача е да поддържа оптимален брой работници на пул. Как го прави? Първо се отстраняват работници, които са престояли дълго време. Второ, нови работници се създават, ако са изпълнени едновременно три условия:
- все още има задачи за изпълнение (работи в басейна)
- без празни работници
- няма работещи работници (т.е. активни и не спят едновременно)
Отчитането на работещите работници се извършва директно от основния планировчик на ядрото на Linux. Този контролен механизъм осигурява оптимално ниво на едновременност (ниво на паралелност), предотвратявайки създаването на твърде много работници в работната опашка, но също така не принуждава работата да чака твърде дълго ненужно.
Който се интересува, може да разгледа работната функция в ядрото, нарича се worker_thread().
Всички описани функции и структури могат да бъдат намерени по-подробно във файловетеinclude/linux/workqueue.h,kernel/workqueue.cиkernel/workqueue_internal.h. Има също документация за workqueue вDocumentation/workqueue.txt.
Също така си струва да се отбележи, че механизмът на работната опашка се използва в ядрото не само за обработка на забавено прекъсване (въпреки че това е доста често срещан сценарий).