Tarkibga o'tish

Foreground va background job’lar: &, jobs, bg, fg

Bitta terminal oynasi sukut bo'yicha bir vaqtda aynan bitta buyruqni bajaradi — shell keyingisini o'qib, bajarishdan oldin joriy buyruq tugashini kutadi. Uzoq davom etadigan har qanday ish uchun (katta arxivni siqish, ma’lumotlar bazasini eksport qilish, paketli skriptni ishga tushirish) bu noqulay: buyruq tugamaguncha terminaldan boshqa hech narsa uchun foydalanib bo'lmaydi. Shell’ning job’larni boshqarish (job control) imkoniyati bu muammoni hal qiladi: u buyruqni oldingi planda (terminalga bog'langan, keyingi kiritishni bloklaydigan) va orqa fonda (ajratilgan, siz ishlashda davom etayotganingizda mustaqil ishlaydigan) ishlash o'rtasida ko'chirishga, shuningdek ishlab turgan buyruqni tugatmasdan butunlay pauza qilishga imkon beradi.

Bu maqolada shell’ning o'z job’larni boshqarish mexanizmini ko'rib chiqamiz. U Signallar va jarayonlarni boshqarish maqolasidagi kernel darajasidagi signal mexanizmidan farq qiladi, garchi aynan uning ustiga qurilgan bo'lsa ham. Shuningdek, tizimdan chiqqaningizdan keyin ham ishlashda davom etadigan fon job’i va davom etmaydigan fon job’i o'rtasidagi amaliy farqni ko'ramiz.

Job’ni fonda ishga tushirish: &

Buyruq oxiriga & qo'shish uni darhol fonda ishga tushiradi va shell kutib turish o'rniga boshqaruvni sizga darhol qaytaradi:

sleep 300 &
[1] 22110

Shell ikkita son chiqaradi: [1] — job raqami, kichik, faqat shu shell’ga tegishli identifikator bo'lib, bitta shell sessiyasidagi turli fon job’lari uchun qayta ishlatiladi; 22110 esa kernel shu jarayonga bergan haqiqiy PID. Ikkalasi ham aynan bitta ishlab turgan jarayonni bildiradi. Job raqami quyida ko'riladigan buyruqlar uchun shunchaki shell qulayligi, PID esa boshqa har bir vosita (ps, kill) tushunadigan identifikator.

Job’larni ro'yxatlash va ularga murojaat qilish: jobs

sleep 300 &
sleep 600 &
jobs
[1]-  Running                 sleep 300 &
[2]+  Running                 sleep 600 &

+ joriy job’ni belgilaydi — hech qanday job ko'rsatilmasa, fg yoki bg sukut bo'yicha aynan unga nisbatan ishlaydi. - esa oldingi job’ni belgilaydi — joriy job tugaganda yoki oldingi planga chiqarilganda joriyga aylanadigan job. Job’ga aniq murojaat qilish uchun % va undan keyin uning raqami yoziladi:

fg %1
kill %2

%1 va %2 — faqat shell’ga tegishli yozuv va ular faqat o'sha job’larni ishga tushirgan shell sessiyasida amal qiladi. ps yoki boshqa hech qanday dastur uchun ular hech narsa anglatmaydi: kill %2 ni kernelga yetib borishidan oldin shell’ning o'zi ostidagi PID ga nisbatan teng kill buyrug'iga aylantiradi.

Oldingi plan va fon o'rtasida ko'chirish: fg va bg

Ctrl+Z oldingi plandagi job’ga SIGTSTP yuboradi (Signallar va jarayonlarni boshqarish maqolasiga qarang). Bu uni pauza qiladi va hech narsani tugatmasdan, terminal boshqaruvini darhol shell’ga qaytaradi:

sleep 300

Bu yerda Ctrl+Z ni bossangiz:

^Z
[1]+  Stopped                 sleep 300

Job endi pauza qilingan (ps da STAT — T), lekin barcha holati saqlangan holda hali ham xotirada mavjud. Bu yerdan:

bg %1

uni davom ettiradi, lekin fonda — bu SIGCONT yuborish va uni keyingi terminal kiritishini bloklashdan ajratishga teng. Quyidagisi esa:

fg %1

uni oldingi planga qaytaradi, terminalga qayta bog'laydi va u tugamaguncha yoki yana pauza qilinmaguncha keyingi kiritishni bloklaydi.

Ctrl+Z dan keyin bg ketma-ketligi "uzoq davom etadigan buyruqni ishga tushirganda & qo'shishni unutibman" holatining standart yechimi — buyruqni o'ldirib, qayta ishga tushirish shart emas, faqat terminalni undan qaytarib olish kifoya.

Muhim farq: fon job’lari va boshqaruvchi terminal

Oddiy & bilan ishga tushirilgan fon job’i hali ham shell’ning bolasi va sukut bo'yicha hali ham terminal sessiyasiga bog'langan. Agar terminal yopilsa — SSH ulanishi uzilsa yoki terminal oynasi yopilsa — shell odatda unga hali bog'langan har bir job’ga SIGHUP yuboradi. Agar job’lar bu signalni maxsus qayta ishlamasa yoki e’tiborsiz qoldirmasa, Signallar va jarayonlarni boshqarish maqolasida ko'rilgan standart munosabatga ko'ra, signal ularni tugatadi.

long-export.sh &

Bu yerda hech qanday qo'shimcha ehtiyot chorasisiz terminalni yopish long-export.sh ni yarim yo'lda o'ldirib qo'yishi juda ehtimoli yuqori — bu SSH orqali masofaviy boshqaruvga yangi kelgan har bir kishi uchun keng tarqalgan, og'riqli kutilmagan holat.

Terminal uzilishidan omon qolish: nohup

nohup long-export.sh > export.log 2>&1 &

nohup o'ralgan buyruqni SIGHUP ni aniq e’tiborsiz qoldiradigan qiladi, shuning uchun u terminal yopilgandan keyin ham ishlashda davom etadi. Agar natijani o'zingiz yo'naltirmasangiz, u sukut bo'yicha chiqishni nohup.out ga ham yo'naltiradi. Har doim ko'rsatilganidek natijani aniq yo'naltiring, shunda log buyruqni ishga tushirgan paytda tasodifan qaysi katalogda bo'lgan bo'lsangiz, o'sha yerda emas, oldindan ma’lum joyda bo'ladi.

Shell darajasidagi muqobil: disown

long-export.sh &
disown

disown job’ni shell’ning o'z job’lar jadvalidan olib tashlaydi, shunda shell chiqishda uni signal yuboradigan "o'z" job’laridan biri deb hisoblamaydi. Aynan shu maqsad uchun bu nohup ga funksional jihatdan o'xshash, lekin buyruqni boshidanoq o'rab qo'yishni talab qilmasdan, allaqachon ishga tushirilgan job’ga keyinroq qo'llanadi.

Ishonchliroq javob: terminal multiplekseri yoki haqiqiy xizmat

nohup va disown "bitta uzilishdan omon qolish" muammosini hal qiladi, lekin ikkalasi ham keyinchalik qayta ulanib, job jarayonini interaktiv kuzatish imkonini bermaydi va ikkalasi ham haqiqiy infratuzilma sifatida uzluksiz ishlashi kerak bo'lgan narsa uchun to'g'ri vosita emas. Vaqti-vaqti bilan tekshirib turmoqchi bo'lgan interaktiv uzoq vazifa uchun tmux yoki screen mosroq: sessiya ochasiz, tugmalar birikmasi bilan undan ajralasiz (detach) va keyinroq istalgan terminaldan, hatto butunlay boshqa SSH ulanishidan qayta ulanasiz. Haqiqatan takrorlanuvchi yoki uzluksiz ishlaydigan ish yuki uchun to'g'ri uzoq muddatli javob — hech qanday terminal sessiyasidan mustaqil boshqariladigan to'g'ri systemd xizmati. Bu andoza kursning keyingi qismidagi systemd modulida ko'rib chiqiladi.

Intervyuda

"Zaxira nusxa skriptimni & bilan ishga tushirgan bo'lsam ham, SSH sessiyasini yopganimda u nega o'lib qoldi?" — erta karyeradagi eng keng tarqalgan yordam savollaridan biri va javob job’larni boshqarishni tushunadigan nomzodlarni faqat sintaksisni biladiganlardan ajratib turadi. Aniq javob: & ning o'zi buyruqni faqat keyingi terminal kiritishini bloklashdan ajratadi — u jarayonni terminal sessiyasidan ajratmaydi va uni aloqa uzilganda keladigan SIGHUP dan himoya qilmaydi. Uzilishdan omon qolishni haqiqatan ta’minlaydigan mexanizmlar — nohup, disown, multiplekser yoki to'g'ri xizmat unit’i — va ularning har biri turli holatga mos keladi.

Jarayonlar guruhi va sessiyalar: & aslida nimani o'zgartiradi

Har bir jarayon jarayonlar guruhiga (process group), har bir jarayonlar guruhi esa odatda boshqaruvchi terminalga bog'langan sessiyaga tegishli. Har bir sessiyada bir vaqtda faqat bitta jarayonlar guruhi oldingi plandagi jarayonlar guruhi bo'la oladi — u SIGINT (Ctrl+C) va SIGTSTP (Ctrl+Z) kabi klaviatura yaratadigan signallarni oladi va terminaldan to'g'ridan-to'g'ri o'qishga ruxsatga ega.

ps -o pid,pgid,sid,tpgid,stat,cmd
    PID   PGID    SID  TPGID STAT CMD
   3990   3990   3990   3990 Ss   -bash
  22110  22110   3990   3990 R    sleep 300

TPGID (terminalning joriy oldingi plandagi jarayonlar guruhi) jarayonning o'z PGID iga mos kelishi o'sha jarayon hozir shu terminal uchun oldingi plandagi guruhda ekanini bildiradi. & fon job’i uchun yangi jarayonlar guruhini boshlaydi, lekin uni terminalning oldingi plandagi guruhiga aylantirmaydi. Aynan shu sababli terminaldagi Ctrl+C fonga yuborilgan job’ga ta’sir qilmaydi — klaviatura yaratgan signal faqat hozir TPGID ko'rsatib turgan jarayonlar guruhiga boradi.

Fon job’lari tugashini kutish: wait

Bir nechta fon job’ini ishga tushiradigan va ular hali ishlayotgan paytda darhol chiqib ketish o'rniga ularning hammasi qachon tugaganini bilishi kerak bo'lgan skript shell’ning o'z ichki wait buyrug'idan foydalanadi:

process-file-a.sh &
process-file-b.sh &
process-file-c.sh &
wait
echo "All three finished"

Argumentsiz wait shu shell ishga tushirgan har bir fon job’i tugamaguncha kutadi; wait %2 (yoki wait <pid>) esa bitta muayyan job’ni kutadi. Bu mustaqil ishni bir nechta fon jarayonlariga taqsimlaydigan va davom etishdan oldin bitta sinxronlash nuqtasiga muhtoj skript uchun standart andoza — hech qanday qo'shimcha vositasiz, to'liq shell skriptlari doirasidagi parallellikning oddiy shakli.

Real vaziyat: SSH orqali uzoq davom etadigan migratsiya skripti

Muammo. Masofaviy serverga SSH ulanishi orqali ko'p soatlik ma’lumotlar bazasi migratsiya skriptini ishga tushirish kerak. Ulanish butun vaqt davomida uzilmasligiga kafolat bera olmaysiz, lekin butun vaqt terminal oldida kutib o'tirmasdan, uning jarayonini vaqti-vaqti bilan tekshirib turishni ham xohlaysiz.

Rejani uzilishdan aslida nima omon qolishi bilan solishtiring:

Bu yerda oddiy & yetarli emas — SSH sessiyasining yopilishi (ataylab yoki tasodifan) SIGHUP yuboradi va migratsiyani o'rtasida o'ldirib qo'yishi juda ehtimol. Ma’lumotlar bazasi migratsiyasi uchun bu shunchaki noqulaylik emas, haqiqatan xavfli qisman bajarilgan holat.

Omon qolish va keyinchalik kuzatish uchun multiplekserdan foydalaning:

tmux new -s migration

Yangi tmux sessiyasi ichida:

./run-migration.sh 2>&1 | tee migration.log

Hech narsani to'xtatmasdan, tmux prefiks tugmasi (sukut bo'yicha Ctrl+b) va undan keyin d yordamida sessiyadan ajraling. Migratsiya SSH ulanishi holatidan butunlay mustaqil ravishda ajratilgan sessiya ichida ishlashda davom etadi.

Keyinroq xuddi shu yoki boshqa terminaldan qayta ulaning va sessiyaga qo'shiling:

tmux attach -t migration

Jarayonni interaktiv qayta ulanmasdan ham tekshiring:

tail -n 20 migration.log
ps -o pid,etime,cmd -C run-migration.sh

Xulosa. tmux (omon qolish va qayta ulanish) va log fayliga yozadigan tee (terminal tarixidan mustaqil doimiy qayd) birikmasi interaktiv, uzoq davom etadigan va tekshirib turiladigan vazifa uchun to'g'ri vosita. Faqat nohup uni uzilishdan omon qoldirgan bo'lardi, lekin keyinchalik uni interaktiv kuzatish imkonini bermasdi; oddiy & esa migratsiyani yarim yo'lda yo'qotib qo'yishi juda ehtimol edi.

Keng tarqalgan xatolar

Faqat & ning o'zi uzilishdan omon qoladi deb o'ylash

& job’ni faqat keyingi terminal kiritishini bloklashdan ajratadi; u terminal sessiyasi tugaganda jarayonni SIGHUP dan himoya qilmaydi. Uzilishdan omon qolishi kerak bo'lgan har qanday narsa uchun uni nohup yoki disown bilan birga ishlating yoki multiplekser ichida ishga tushiring.

Job raqamini PID bilan adashtirish

%1 faqat uni yaratgan shell sessiyasi ichida ma’noga ega. kill %1 ishlaydi, chunki shell uni kernelga biror narsa yuborishdan oldin tarjima qiladi, lekin kill -9 1 (job raqamini PID kabi ishlatish) butunlay boshqa, ehtimol mavjud bo'lmagan yoki muhim jarayonni nishonga oladi.

Ctrl+C fondagi job’ni to'xtatadi deb kutish

Ctrl+C SIGINT ni faqat terminalning joriy oldingi plandagi jarayonlar guruhiga yuboradi. & yoki bg bilan fonga o'tkazilgan job endi bu guruhda emas, shuning uchun Ctrl+C unga ta’sir qilmaydi — buning o'rniga kill %<job> yoki kill <pid> dan foydalaning.

Job’ni fonga yuborganda natijani hech qayerga yo'naltirmaslik

Natijasi yo'naltirilmagan fon job’i hali ham terminalga yozadi va bu keyin yozadigan narsangiz bilan chalkash aralashib ketishi mumkin. Agar job allaqachon boshqa narsaga o'tgan terminaldan o'qishga urinsa, u butunlay to'xtab qoladi. Haqiqatan ajratilgan job uchun stdout va stderr ikkalasini ham doim aniq yo'naltiring.

Multiplekser kerak bo'lgan joyda nohup ishlatish

nohup interaktiv qayta ulanish yoki jarayonni kuzatishni emas, omon qolishni hal qiladi. Vaqti-vaqti bilan tekshirib turmoqchi bo'lgan vazifa uchun tmux/screen mosroq; haqiqiy infratuzilma sifatida cheksiz ishlashi kerak bo'lgan vazifa uchun esa to'g'ri javob bulardan birortasi emas, systemd xizmati.

Mashqlar

  1. sleep 120 & ni ishga tushiring, jobs ni bajaring, so'ng kill %1 ni (kill <pid> ni emas) bajaring va ikkinchi jobs chaqiruvi bilan job yo'qolganini tasdiqlang. Shell %1 ni kernel tushunadigan narsaga aylantirish uchun aslida nima qilganini tushuntiring.
  2. Oldingi planda sleep 60 ni ishga tushiring, uni Ctrl+Z bilan pauza qiling, uning STAT ini ps -o stat bilan tasdiqlang, bg bilan fonda davom ettiring va u tugashidan oldin fg bilan oldingi planga qaytaring. Har bir holat o'zgarishini izohlab bering.
  3. Har biri turli davomiylikda uxlaydigan uchta fon job’ini ishga tushiring, uchalasi ham tugamaguncha kutish uchun wait dan foydalaning va agar wait tushirib qoldirilganda skriptning chiqish vaqti bilan nima bo'lishini tushuntiring.
  4. ps -o pid,pgid,tpgid dagi TPGID va PGID dan foydalanib, nega Ctrl+C siz bg bilan fonga o'tkazgan job’ga ta’sir qilmasligini tushuntiring va javobingizni hozir terminalning oldingi planiga qaysi jarayonlar guruhi egalik qilishi bilan bog'lang.

Mashqni bajardim deyish uchun: terminal yoki SSH uzilishidan omon qolishi kerak bo'lgan vazifa uchun faqat oddiy & yetarli deb da’vo qiladigan har qanday javob noto'g'ri — javobingiz bu omon qolish uchun aslida javobgar bo'lgan qo'shimcha mexanizmni (nohup, disown yoki multiplekser) nomlashi kerak.

Manbalar

  • Linux man-pages: bash(1) — Job Control bo'limi: https://man7.org/linux/man-pages/man1/bash.1.html
  • GNU Bash Reference Manual: Job Control: https://www.gnu.org/software/bash/manual/html_node/Job-Control.html
  • GNU Bash Reference Manual: nohup: https://www.gnu.org/software/coreutils/manual/html_node/nohup-invocation.html
  • Linux man-pages: nohup(1): https://man7.org/linux/man-pages/man1/nohup.1.html
  • Linux man-pages: setsid(2), setpgid(2): https://man7.org/linux/man-pages/man2/setpgid.2.html
  • tmux loyihasi wiki: https://github.com/tmux/tmux/wiki
  • GNU Screen qo'llanmasi: https://www.gnu.org/software/screen/manual/screen.html