Що в Linux відбувається зі станом процесу, коли йому потрібно зчитувати блоки з диска? Це заблоковано? Якщо так, то як обраний інший процес для виконання?
Відповіді:
В очікуванні на read()
абоwrite()
повернення дескриптора файлу з / до нього процес буде переведено в спеціальний тип сну, відомий як "D" або "Дисковий режим сну". Це особливе, оскільки процес не може бути вбитий або перерваний, перебуваючи в такому стані. Процес, який чекає повернення з ioctl (), також буде призупинено таким чином.
Виняток з цього полягає у тому, коли файл (наприклад, термінал чи інший символьний пристрій) відкривається в O_NONBLOCK
режимі, передається тоді, коли передбачається, що пристрою (наприклад, модему) знадобиться час для ініціалізації. Однак у своєму запитанні ви вказали блокуючі пристрої. Крім того, я ніколи не пробував, ioctl()
що, ймовірно, може заблокувати fd, відкритий у неблокуючому режимі (принаймні не свідомо).
Вибір іншого процесу повністю залежить від планувальника, який ви використовуєте, а також від того, що інші процеси могли б зробити, щоб змінити свою вагу в межах цього планувальника.
Відомо, що деякі програми користувацького простору за певних обставин залишаються у такому стані назавжди, доки їх не перезавантажать. Вони, як правило, об’єднуються з іншими „зомбі”, але цей термін не буде правильним, оскільки вони технічно не існують.
Коли процесу потрібно отримати дані з диска, він фактично перестає запускатися на центральному процесорі, щоб дозволити іншим процесам запускатися, оскільки операція може зайняти багато часу - принаймні 5 мс шукає час для диска, а 5 мс - це 10 мільйонів Цикли процесора, вічність з точки зору програми!
З точки зору програміста (це також називається "в просторі користувачів"), це називається системним викликом блокування . Якщо ви зателефонуєте write(2)
(це тонка обгортка libc навколо однойменного системного виклику), ваш процес точно не зупиняється на цій межі; він продовжує в ядрі виконувати код системного виклику. Велику частину часу він проходить весь шлях до певного драйвера контролера диска (ім'я файлу → файлова система / VFS → блокувати пристрій → драйвер пристрою), де команда для отримання блоку на диску подається до відповідного обладнання, що є дуже швидке функціонування більшу частину часу.
ПОТІМ процес переходить у сплячий стан (у просторі ядра блокування називається сплячим - нічого ніколи не «блокується» з точки зору ядра). Він буде пробуджений після того, як обладнання остаточно отримає належні дані, тоді процес буде позначений як запущений і буде запланований. Згодом планувальник запустить процес.
Нарешті, в просторі користувачів блокуючий системний виклик повертається з належним статусом і даними, і потік програми продовжується.
Можна викликати більшість системних викликів вводу-виводу в неблокуючому режимі (див. O_NONBLOCK
В open(2)
та fcntl(2)
). У цьому випадку системні виклики повертаються негайно і повідомляють лише про передачу операції диска. Програмісту доведеться явно перевірити пізніше, чи завершена операція успішно чи ні, і отримати її результат (наприклад, за допомогою select(2)
). Це називається асинхронним або заснованим на подіях програмуванням.
Більшість відповідей тут, де згадується стан D (що називається TASK_UNINTERRUPTIBLE
в іменах стану Linux), є неправильними. Стан D - це спеціальний режим сну, який запускається лише в коді простору ядра, коли цей шлях коду не може бути перерваний (оскільки він буде занадто складним для програмування), з очікуванням, що він заблокує лише дуже короткий час. Я вважаю, що більшість "станів D" насправді невидимі; вони дуже короткочасні, і їх неможливо спостерігати за допомогою інструментів для відбору проб, таких як „верх”.
Ви можете зіткнутися з невдалими процесами в стані D у декількох ситуаціях. NFS цим славиться, і я вже неодноразово стикався з цим. Я думаю, є семантичне зіткнення між деякими шляхами коду VFS, які передбачають, що завжди досягають локальних дисків і швидке виявлення помилок (на SATA час очікування помилок становить близько 100 мс), і NFS, який фактично отримує дані з мережі, яка є більш еластичним і має повільне відновлення (частота очікування TCP становить 300 секунд). Прочитайте цю статтю про круте рішення, представлене в Linux 2.6.25 із TASK_KILLABLE
станом. До цієї епохи був хак, коли ви могли фактично надсилати сигнали клієнтам процесів NFS, надсилаючи SIGKILL в потік ядра rpciod
, але забути про цей потворний трюк ...
/proc/stat
?
Процес, що виконує введення-виведення, буде переведений у стан D (безперебійний режим сну) , що звільняє центральний процесор, поки не відбудеться апаратне переривання, яке повідомляє центральному процесору повернутися до виконання програми. Дивіться man ps
інші стани процесу.
Залежно від вашого ядра, існує планувальник процесів , який відслідковує чергу виконання процесів, готових до виконання. Він разом з алгоритмом планування повідомляє ядру, який процес призначити якому ЦП. Існують процеси ядра та процеси користувача, які слід враховувати. Кожному процесу присвоюється часовий зріз, який становить частину процесорного часу, яку йому дозволено використовувати. Як тільки процес використовує весь свій часовий зріз, він позначається як закінчився і надається нижчий пріоритет в алгоритмі планування.
У ядрі 2.6 існує планувальник складності часу O (1) , тому незалежно від того, скільки процесів у вас запущено, він призначатиме процесори за постійний час. Однак це складніше, оскільки введене попередження 2.6 та балансування навантаження процесора - непростий алгоритм. У будь-якому випадку, це ефективно, і процесори не залишатимуться в режимі очікування, поки ви чекаєте введення-виведення.
Як вже пояснювали інші, процеси у стані "D" (безперебійний сон) відповідають за зависання процесу ps. У мене це траплялося багато разів із RedHat 6.x та автоматичними домашніми каталогами NFS.
Для переліку процесів у стані D ви можете використовувати такі команди:
cd /proc
for i in [0-9]*;do echo -n "$i :";cat $i/status |grep ^State;done|grep D
Щоб знати поточний каталог процесу та, можливо, змонтований диск NFS, який має проблеми, ви можете скористатися командою, подібною до наступного прикладу (замініть 31134 на номер процесу сну):
# ls -l /proc/31134/cwd
lrwxrwxrwx 1 pippo users 0 Aug 2 16:25 /proc/31134/cwd -> /auto/pippo
Я виявив, що надання команди umount за допомогою перемикача -f (force) відповідній змонтованій файловій системі nfs змогло активувати процес сну:
umount -f /auto/pippo
файлову систему не демонтували, оскільки вона була зайнята, але пов'язаний з цим процес прокинувся, і я зміг вирішити проблему без перезавантаження.
Припускаючи, що ваш процес є єдиним потоком, і що ви використовуєте блокування вводу-виводу, ваш процес заблокує очікування завершення вводу-виводу. Ядро вибере інший процес, який тим часом буде запущений, виходячи зі слабкості, пріоритету, часу останнього запуску і т. Д. Якщо інших запущених процесів немає, ядро не запускатиме жодного; натомість він повідомляє апаратному забезпеченню, що машина не працює (що призведе до зниження енергоспоживання).
Процеси, які очікують завершення вводу-виводу, зазвичай відображаються у стані D у, наприклад, ps
і top
.
Так, завдання блокується під час системного виклику read (). Виконується інше готове завдання, або якщо жодне інше завдання не готове, виконується неактивне завдання (для цього процесора).
Звичайне блокування читання диска призводить до того, що завдання переходить у стан "D" (як зазначали інші). Такі завдання сприяють середньому навантаженню, навіть якщо вони не споживають центральний процесор.
Деякі інші типи вводу-виводу, особливо ttys та мережа, поводяться не зовсім однаково - процес закінчується у стані "S" і може бути перерваний і не враховується в середньому навантаженні.
Так, завдання, що очікують на введення-виведення, блокуються, а інші завдання виконуються. Вибір наступного завдання виконується планувальником Linux .
Як правило, процес блокується. Якщо операція зчитування виконується в дескрипторі файлу, позначеному як неблокуючий, або якщо процес використовує асинхронний введення-виведення, він не блокується. Також якщо в процесі є інші потоки, які не заблоковані, вони можуть продовжувати працювати.
Рішення щодо подальшого запуску процесу залежить від планувальника в ядрі.