Гладуване на процеси в операционните системи: причини, механизми и решения от производствен клас
Гладуването на процеси (process starvation) настъпва, когато на даден процес е отказано неопределено дълго процесорното време, паметта или I/O честотната лента, от които се нуждае за напредък — не защото ресурсите не съществуват, а защото политиката за планиране последователно облагодетелства други процеси. За разлика от задънената улица (deadlock), при която всички конкуриращи се процеси са блокирани, гладуването позволява на системата да изглежда функционална, докато безшумно деградира или спира конкретни работни натоварвания.
Това разграничение е важно от оперативна гледна точка: гладуващ процес не генерира грешки на ниво ядро, не създава crash dump-ове и може да не задейства стандартните прагове за аларми — което го прави една от най-коварните патологии на производителността в многонаемни и силно конкурентни сървърни среди.
Какво всъщност означава гладуване на ниво ядро
Терминът е заимстван от ресурсната екология: процес „гладува”, когато постоянно е надминаван в конкуренцията за краен ресурс. В съвременните операционни системи Linux Completely Fair Scheduler (CFS), приоритетните опашки на Windows NT и BSD ULE scheduler прилагат механизми за предотвратяване на гладуването — и въпреки това то се появява в производствена среда при специфични условия.
На ниво ядро гладуването се проявява като процес, чието виртуално време на изпълнение (в терминологията на CFS) или време на изчакване нараства неограничено, без никога да бъде избран за изпълнение. Процесът остава в състояние TASK_RUNNING — той е готов и допустим — но планировчикът никога не му предоставя CPU slice, тъй като задачи с по-висок приоритет или по-често изпълними задачи винаги го изпреварват.
Ключово техническо разграничение:
- Задънена улица (Deadlock): Два или повече процеса са взаимно блокирани, като всеки чака ресурс, притежаван от другия. Системата не прави никакъв напредък по тези задачи.
- Гладуване (Starvation): Един или повече процеси са постоянно заобикаляни от планировчика. Другите процеси продължават да работят нормално.
- Живо заключване (Livelock): Процесите не са блокирани, но непрекъснато сменят състоянието си в отговор един на друг, без да постигат реален напредък.
Основни причини за гладуване на процеси
Разбирането на гладуването изисква разглеждане на конкретните механизми, които го пораждат, а не само изброяване на „ограничени ресурси” като причина.
1. Статична инверсия на приоритета без стареене
Повечето планировчици, базирани на приоритети, присвояват фиксиран или полуфиксиран приоритет на всеки процес. Ако процес с нисък приоритет винаги бива изпреварван от поток от задачи със среден и висок приоритет, той никога не се изпълнява. Критичният режим на повреда тук е липсата на стареене (aging) — техника, при която ефективният приоритет на процеса се увеличава постепенно, колкото по-дълго чака. Без стареене, фонова задача с нисък приоритет на натоварен сървър може да чака безкрайно.
В Linux диапазонът на стойностите nice (-20 до +19) и приоритетите в реално време (SCHED_FIFO, SCHED_RR) създават точно този риск. Процес, изпълняван под SCHED_FIFO с приоритет 99, ще изпреварва всеки SCHED_OTHER процес на същото CPU ядро, докато доброволно не отстъпи или не се блокира.
2. Несправедливо опашкообразуване в I/O планировчиците
CPU гладуването е добре документирано, но I/O гладуването е също толкова разрушително и често пренебрегвано. Linux I/O планировчикът (исторически CFQ, сега BFQ или mq-deadline в зависимост от версията на ядрото и типа съхранение) управлява реда, в който се обслужват заявките към блоковото устройство. При интензивни последователни записи — характерни за сървъри с бази данни и приложения с интензивно логване — I/O планировчикът може да понижи приоритета на заявките за произволно четене от други процеси, като ефективно ги лишава от достъп до диска.
Това е честа проблем в среди за VPS Хостинг, където множество наематели споделят обща инфраструктура за съхранение и I/O конкуренцията е реален оперативен проблем.
3. Натиск върху паметта и OOM Killer
Когато физическата RAM е изчерпана, Out-Of-Memory (OOM) killer на Linux ядрото избира процес за прекратяване въз основа на oom_score. Въпреки че технически това е прекратяване, а не гладуване, предшестващото го състояние — при което процесът многократно се разменя на диска и никога не получава достатъчно резидентна памет за ефективно изпълнение — представлява гладуване на паметта. Процесът технически работи, но прави незначителен напредък поради постоянни грешки на страниците и swap I/O.
4. Конкуренция за заключвания и гладуване на мютекси
В многонишкови приложения гладуването настъпва на ниво примитив за синхронизация. Ако мютекс или семафор използва несправедлива политика за придобиване (последен влязъл — първи излязъл или произволен избор сред чакащите нишки), конкретна нишка може да бъде постоянно заобикаляна, дори ако заключването се освобождава често. Това е различно от планирането на ниво ОС и се случва изцяло в потребителското пространство или подсистемата за синхронизация на ядрото.
5. Гладуване на мрежова честотна лента
В контейнеризирани и виртуализирани среди, процес или контейнер, консумиращ цялата налична мрежова честотна лента, може да лиши другите процеси от мрежов I/O. Без оформяне на трафика чрез tc (traffic control) и cgroups, един неконтролиран процес може да монополизира пропускателната способност на NIC.
Гладуване срещу задънена улица срещу живо заключване: техническо сравнение
| Свойство | Гладуване | Задънена улица | Живо заключване |
|---|---|---|---|
| Напредък на системата | Да (другите процеси работят) | Не (блокираните процеси спират) | Привиден (без реален напредък) |
| Блокирано състояние | Не (процесът е изпълним) | Да (процесът чака ресурс) | Не (процесът е активен) |
| Притежаван ресурс | Не | Да (кръгово задържане и изчакване) | Не |
| Самостоятелно разрешаване | Понякога (при стареене) | Никога (изисква намеса) | Рядко |
| Трудност при откриване | Висока (без явна грешка) | Средна (засичане на цикъл) | Висока (изглежда като активност) |
| Основна причина | Несправедлива политика за планиране | Кръгова зависимост на ресурси | Реактивни цикли на смяна на състоянието |
| Сигнал от Linux ядрото | Няма | Няма (възможно е soft lockup) | Няма |
Как съвременните планировчици се справят с гладуването
Linux Completely Fair Scheduler (CFS)
CFS, въведен в Linux ядро 2.6.23, се справя с гладуването чрез проследяване на виртуалното време на изпълнение (vruntime) за всеки процес. Планировчикът винаги избира процеса с най-ниско vruntime — което означава, че процесите, получили по-малко CPU време, се приоритизират систематично. Този дизайн прави чистото CPU гладуване почти невъзможно под CFS за SCHED_OTHER процеси.
Въпреки това CFS не защитава от гладуване от страна на процеси в реално време. Всеки процес, планиран под SCHED_FIFO или SCHED_RR, изпреварва всички SCHED_OTHER задачи. Параметърът на ядрото /proc/sys/kernel/sched_rt_runtime_us (по подразбиране: 950 000 микросекунди в секунда) запазва 5% от CPU времето за задачи, които не са в реално време, именно за да предотврати това.
Стареене на приоритета
Класическите алгоритми за стареене увеличават ефективния приоритет на процеса с фиксирана стойност за всеки цикъл на планиране, в който той чака. Щом ефективният приоритет достигне най-високото ниво, на процеса е гарантирано изпълнение. След като се изпълни, приоритетът му се нулира до базовата стойност. Това е учебниковото решение за гладуване, базирано на приоритети, и е реализирано в различни форми в Windows NT, Solaris и по-стари Linux планировчици.
Справедливо опашкообразуване и претеглено справедливо опашкообразуване (WFQ)
За мрежови и I/O ресурси, Weighted Fair Queuing присвоява на всеки поток или процес дял от честотната лента, пропорционален на неговото тегло. Дори ако поток с голямо тегло генерира повече трафик, потоците с малко тегло имат гарантирана минимална скорост на обслужване. Linux реализира това чрез дисциплините Hierarchical Token Bucket (HTB) и Stochastic Fair Queuing (SFQ) в подсистемата tc.
Диагностициране на гладуването в производствени Linux системи
Идентифицирането на гладуването изисква едновременно съпоставяне на множество източници на данни.
Анализ на CPU планирането
# Check per-process CPU wait time and scheduling statistics
cat /proc/<PID>/schedstat
# Monitor scheduler latency with perf
perf sched latency --sort max
# Identify processes with high voluntary/involuntary context switches
pidstat -w 1 10
# Check real-time process priorities that may be starving others
ps -eo pid,comm,cls,pri,ni --sort=-pri | head -20Изходът от schedstat предоставя кумулативното време, което процесът е прекарал в изчакване в опашката за изпълнение (run_delay в наносекунди) — пряка мярка за гладуване при планиране.
Индикатори за гладуване на паметта
# Check swap activity — high si/so values indicate memory starvation
vmstat 1 10
# Identify processes with high major page fault rates
pidstat -r 1 10
# Check OOM kill history
dmesg | grep -i "oom|killed process"
# Inspect per-process memory pressure
cat /proc/<PID>/status | grep -E "VmRSS|VmSwap|VmPeak"Засичане на I/O гладуване
# Per-process I/O wait statistics
iotop -b -n 5
# Block device queue depth and wait times
iostat -x 1 5
# Check I/O scheduler in use for each block device
cat /sys/block/sda/queue/scheduler
# Identify processes blocked on I/O
ps aux | awk '$8 ~ /D/ {print}'Процесите в състояние D (непрекъсваем сън) са блокирани на I/O. Постоянна популация от процеси в състояние D е силен индикатор за I/O гладуване или насищане на подсистемата за съхранение.
Решения от производствен клас и стратегии за смекчаване
Реализиране на cgroups v2 за изолация на ресурсите
Control Groups (cgroups v2) осигуряват най-стабилния механизъм за предотвратяване на гладуването в многопроцесни и контейнеризирани среди. Чрез присвояване на явни квоти за CPU, памет и I/O на групи от процеси, вие гарантирате минимално разпределение на ресурсите независимо от натоварването на системата.
# Create a cgroup with CPU weight (higher weight = more CPU share)
mkdir /sys/fs/cgroup/my_service
echo "100" > /sys/fs/cgroup/my_service/cpu.weight
# Set memory limit to prevent memory starvation of other groups
echo "2G" > /sys/fs/cgroup/my_service/memory.max
# Assign process to cgroup
echo <PID> > /sys/fs/cgroup/my_service/cgroup.procsТеглото на CPU в cgroups v2 използва диапазон от 1 до 10000, като стойността по подразбиране е 100. Група процеси с тегло 200 получава двойно повече CPU дял от тази с тегло 100 при конкуренция.
Настройване на Linux планировчика за вашето работно натоварване
# Increase scheduler migration cost to reduce cache thrashing (latency-sensitive workloads)
echo 500000 > /proc/sys/kernel/sched_migration_cost_ns
# Reduce scheduler granularity for more frequent preemption (throughput workloads)
echo 1000000 > /proc/sys/kernel/sched_min_granularity_ns
# Ensure real-time tasks cannot starve normal tasks
echo 950000 > /proc/sys/kernel/sched_rt_runtime_usПрилагане на подходящи политики за планиране за всеки процес
# Set a process to batch scheduling (explicitly low-priority, won't starve interactive tasks)
chrt -b -p 0 <PID>
# Set a CPU-intensive background job to idle scheduling class
chrt -i -p 0 <PID>
# Adjust nice value for a running process
renice -n 10 -p <PID>
# Run a new command with reduced priority
nice -n 15 ./my_background_script.shКласът SCHED_IDLE (chrt -i) е правилният инструмент за наистина фонови задачи — той се изпълнява само когато не съществува друг изпълним процес, като напълно елиминира способността му да гладува другите работни натоварвания.
Избор на I/O планировчик
# For NVMe SSDs (low-latency, no rotational penalty): use none or mq-deadline
echo "mq-deadline" > /sys/block/nvme0n1/queue/scheduler
# For HDDs with mixed workloads: use bfq for fairness
echo "bfq" > /sys/block/sda/queue/scheduler
# Make persistent across reboots (add to /etc/udev/rules.d/)
echo 'ACTION=="add|change", KERNEL=="sda", ATTR{queue/scheduler}="bfq"'
> /etc/udev/rules.d/60-scheduler.rulesBFQ (Budget Fair Queuing) е специално проектиран да предотвратява I/O гладуването, като гарантира на всеки процес пропорционален дял от дисковата честотна лента. Той е препоръчителният планировчик за среди за споделен хостинг и сървъри с бази данни.
Контрол на мрежовата честотна лента с tc
# Create a root HTB qdisc on the primary interface
tc qdisc add dev eth0 root handle 1: htb default 30
# Add a parent class with total bandwidth
tc class add dev eth0 parent 1: classid 1:1 htb rate 1gbit
# Add child classes with guaranteed minimums (prevents starvation)
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 100mbit ceil 1gbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 100mbit ceil 1gbit
# Add SFQ leaf to each class for per-flow fairness
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10Тази конфигурация гарантира на всеки клас минимум 100 Mbps, като позволява пиково използване до пълния капацитет от 1 Gbps на връзката, когато честотната лента е налична.
Настройване на свръхангажиране на паметта и swap
# Reduce swappiness to minimize swap-induced memory starvation
echo 10 > /proc/sys/vm/swappiness
# Enable memory overcommit accounting (prevents OOM from surprising processes)
echo 2 > /proc/sys/vm/overcommit_memory
# Set overcommit ratio (total allocatable = RAM * ratio + swap)
echo 80 > /proc/sys/vm/overcommit_ratioЗадаването на vm.swappiness=10 инструктира ядрото да предпочита освобождаване на кеша на страниците пред размяната на паметта на процесите, като значително намалява вероятността от гладуване на паметта при умерено натоварване.
Гладуване във виртуализирани и контейнеризирани среди
На Dedicated сървъри, изпълняващи хипервайзори (KVM, VMware ESXi, Hyper-V), гладуването може да настъпи на два отделни слоя:
Гладуване на ниво хипервайзор: На виртуална машина се отказват CPU цикли от планировчика на хипервайзора. KVM използва CFS на хост ядрото за планиране на vCPU, което означава, че VM с по-ниско тегло на CPU дела може да бъде гладувана от VM с по-високи тегла при конкуренция. DRS (Distributed Resource Scheduler) на VMware използва дялове, резервации и ограничения за контролиране на това.
Гладуване на ниво гост ОС: В самата VM се прилагат същите динамики на планиране на ниво ОС. Контейнеризирано работно натоварване, изпълнявано под Docker или Kubernetes без явни ограничения на ресурсите, може да монополизира CPU и паметта на гост ОС, гладувайки съвместно разположените контейнери.
За Kubernetes среди, винаги дефинирайте requests и limits в спецификациите на pod-овете:
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"Стойността requests определя позиционирането при планиране и теглото на CPU дела в cgroup. Без нея Kubernetes планировчикът няма основа за справедливо позициониране, а средата за изпълнение на контейнери присвоява тегла по подразбиране (равни) — което все пак позволява гладуване, ако един контейнер постоянно насища своя CPU лимит.
Гладуване в сървъри за бази данни и приложения
Двигателите на бази данни реализират свои вътрешни планировчици, независими от планировчика на ОС. PostgreSQL използва модел процес-на-връзка, при който всеки backend процес се конкурира за ресурси на ОС нормално, но конкуренцията за заключвания в базата данни (заключвания на ниво ред, advisory locks) може да причини гладуване на ниво приложение, при което конкретни заявки чакат безкрайно за придобиване на заключване.
MySQL/InnoDB използва пул от нишки с конфигурируеми ограничения на конкурентността (innodb_thread_concurrency). Задаването на твърде ниска стойност причинява гладуване на заявките, тъй като нишките се нареждат в опашка, чакайки слотове за изпълнение. Задаването на твърде висока стойност причинява CPU thrashing. Препоръчителната начална стойност е 2 × number of CPU cores.
За уеб сървъри, Nginx и Apache имат различни профили на гладуване. Управляваният от събития модел на Nginx е по своята същност устойчив на гладуване на работниците, но изчерпването на пула за upstream връзки (например към PHP-FPM или backend API) създава гладуване на ниво приложение. Prefork MPM на Apache може да изчерпи своя лимит MaxRequestWorkers, карайки новите връзки да се нареждат в опашка безкрайно — форма на гладуване на връзките.
Тези съображения са пряко приложими при конфигуриране на VPS с cPanel за работни натоварвания на споделен уеб хостинг, където множество сайтове се конкурират за пулове от PHP-FPM работници и лимити за MySQL връзки.
Инфраструктура за наблюдение за предотвратяване на гладуването
Реактивната диагностика е недостатъчна за производствени системи. Проактивният стек за наблюдение трябва да включва:
Метрики на Prometheus + Node Exporter за наблюдение:
node_schedstat_waiting_seconds_total— кумулативно време на изчакване в опашката за изпълнение на CPU за всеки CPUnode_vmstat_pgmajfault— основни грешки на страниците, указващи натиск върху паметтаnode_disk_io_time_weighted_seconds_total— насищане на I/O опашкатаnode_pressure_cpu_waiting_seconds_total— Linux PSI (Pressure Stall Information) натиск върху CPUnode_pressure_memory_full_seconds_total— PSI пълно спиране на паметта
Linux PSI (наличен от ядро 4.20) е най-прекият индикатор за гладуване, наличен в ядрото. Той отчита процента от времето, в което задачите са спрели да чакат за CPU, памет или I/O ресурси:
# Real-time PSI monitoring
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/ioФормат на изхода: some avg10=X.XX avg60=X.XX avg300=X.XX total=NNNN, където some указва, че поне една задача е спряла. Стойности над 10–15% на avg60 изискват незабавно разследване.
За екипи, управляващи VPS контролни панели или персонализирани сървърни стекове, интегрирането на PSI метрики в Grafana табла осигурява ранно предупреждение преди гладуването да деградира производителността, видима за потребителите.
Практическа матрица за вземане на решения: избор на правилния механизъм срещу гладуването
| Симптом | Тип ресурс | Препоръчан инструмент | Цел на конфигурацията |
|---|---|---|---|
| Фоновите задачи никога не завършват | CPU | SCHED_IDLE или nice +19 | Елиминиране на фоновата CPU конкуренция |
| Пикове в латентността при интерактивна работа под натоварване | CPU | Настройване на CFS + тегло на CPU в cgroups v2 | Гарантиране на дял за интерактивен процес |
| Изтичане на времето за заявки към базата данни | CPU + Заключване | innodb_thread_concurrency, таймаут на заключването | Ограничаване на времето за изчакване на заключване |
| Задачи с интензивен дисков достъп блокират уеб обслужването | I/O | BFQ планировчик + cgroups v2 io.weight | Пропорционално разпределение на I/O |
| OOM убивания на контейнери под натоварване | Памет | cgroups v2 memory.min + vm.swappiness | Гарантиране на минимална резидентна памет |
| Процес с интензивна мрежова активност гладува другите | Мрежа | HTB + SFQ чрез tc | Гаранция за честотна лента за всеки клас |
| VM е гладувана от хипервайзора | vCPU | Резервации/дялове на CPU в хипервайзора | Резервиране на минимални vCPU цикли |
Ключови технически изводи
- Никога не разчитайте на планирането по подразбиране за сървъри със смесено работно натоварване. Явно класифицирайте процесите, използвайки
chrt,niceи cgroups v2 въз основа на тяхната чувствителност към латентност и бизнес приоритет. - Активирайте PSI наблюдение (
/proc/pressure/*) на всички производствени Linux системи. Това е най-точният индикатор за гладуване в реално време в ядрото и има почти нулево натоварване. - Използвайте BFQ за въртящи се дискове и всяко NVMe устройство, обслужващо смесени произволни/последователни работни натоварвания в многонаемни среди. Гаранциите за справедливост си заслужават пределните разходи за пропускателна способност.
- Задавайте заявки за ресурси в Kubernetes без изключение. Незададена
requests.cpuне е „неограничена” — тя е задължение при планирането, което позволява гладуване на CPU на ниво контейнер. - Разграничавайте гладуването от задънената улица преди да се намесите. Убиването и рестартирането на гладуващ процес не поправя основния дисбаланс в планирането; то само временно премахва симптома.
- Одитирайте присвояванията на приоритет в реално време (
SCHED_FIFO/SCHED_RR) на всяка система, в която се използват. Един неправилно конфигуриран процес в реално време може да гладува всички работни натоварвания с нормален приоритет на CPU ядро безкрайно. - За среди за споделен уеб хостинг, налагайте квоти за CPU и I/O на ниво cgroup за всеки акаунт, вместо да разчитате единствено на ограничаване на скоростта на ниво приложение.
Често задавани въпроси
Каква е разликата между гладуването и задънената улица в операционна система?
Задънената улица настъпва, когато два или повече процеса са постоянно блокирани, като всеки притежава ресурс, от който другият се нуждае — нито един процес не прави напредък. Гладуването настъпва, когато процес е постоянно заобикалян от планировчика въпреки че е изпълним; другите процеси продължават да се изпълняват нормално. Задънената улица изисква прекъсване на кръговата зависимост; гладуването изисква поправяне на политиката за планиране, обикновено чрез реализиране на стареене или справедливо опашкообразуване.
Как Linux CFS планировчикът предотвратява CPU гладуването?
CFS проследява виртуалното време на изпълнение (vruntime) за всеки процес и винаги избира процеса с най-ниско vruntime за изпълнение. Това гарантира, че процесите, получаващи по-малко CPU време, се приоритизират систематично, правейки безкрайното CPU гладуване на SCHED_OTHER процеси почти невъзможно. Въпреки това процесите в реално време (SCHED_FIFO, SCHED_RR) заобикалят CFS изцяло и все още могат да гладуват нормалните процеси, ако параметърът sched_rt_runtime_us не е зададен правилно.
Как мога да открия дали процес е гладуван на Linux сървър?
Прочетете /proc/<PID>/schedstat, за да проверите кумулативното време на изчакване в опашката за изпълнение. Наблюдавайте /proc/pressure/cpu за PSI метрики за спиране. Използвайте perf sched latency --sort max, за да идентифицирате процеси с необичайно висока латентност при планиране. Процеси в постоянно състояние D, видими в изхода от ps aux, указват I/O гладуване, а не CPU гладуване.
Засяга ли гладуването на процеси VPS и облачни сървърни среди по различен начин от bare metal?
Да. На VPS гладуването може да настъпи както на ниво хипервайзор (планировчикът на хипервайзора отказва vCPU време на вашата VM), така и в рамките на гост ОС. Гладуването на ниво хипервайзор е невидимо за стандартните инструменти за наблюдение на ОС и изисква специфични за хипервайзора метрики или забележимо steal time (%st в изхода от top). Високото steal time — обикновено над 5–10% продължително — указва, че хипервайзорът не доставя vCPU циклите, на които вашата VM има право.
Какъв е най-бързият начин да предотвратя конкретен процес да гладува другите на натоварен сървър?
Присвоете го към класа за планиране SCHED_IDLE с chrt -i -p 0 <PID>. Този клас се изпълнява само когато не съществува друг изпълним процес, гарантирайки, че не може да гладува никакво друго работно натоварване. За фонови процеси с интензивен I/O, допълнително задайте техния I/O приоритет на idle клас: ionice -c 3 -p <PID>. Комбинирането на двете елиминира процеса като източник на CPU и I/O гладуване с две команди и нулеви промени в приложението.
