4-bo‘lim
Jarayon yaratish - fork va exec
fork bilan jarayon nusxalash, exec bilan dasturni almashtirish, wait bilan kutish, zombi va yetim jarayonlar.
Ushbu bo‘lim mundarijasi
UNIX'da yangi jarayon yaratishning g'alati, lekin juda kuchli usuli bor: avval o'zingizni nusxalaysiz, keyin nusxani boshqa dasturga almashtirasiz.
fork - jarayonni ikkiga bo'lish #
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
printf("Boshlanish\n");
fflush(stdout); /* fork dan oldin buferni bo'shatamiz */
pid_t natija = fork();
if (natija < 0) {
printf("fork muvaffaqiyatsiz\n");
return 1;
}
if (natija == 0) {
printf(" Men bola jarayonman\n");
return 0;
}
wait(NULL); /* bolani kutamiz */
printf("Men ota jarayonman\n");
return 0;
}
Boshlanish
Men bola jarayonman
Men ota jarayonman
fork dan oldin fflush chaqiringfork xotiraning hamma narsasini nusxalaydi - shu jumladan
printf ning bo'shatilmagan buferini ham.
fflush bo'lmasa va chiqish faylga yo'naltirilgan bo'lsa, natija
shunday chiqadi:
Boshlanish
Men bola jarayonman
Boshlanish
Men ota jarayonman
«Boshlanish» ikki marta chiqdi, chunki u fork paytida hali
buferda edi. Bola buferning nusxasini oldi va tugaganda uni ham yozdi.
Terminalda bu muammo ko'rinmaydi (u qator bo'yicha buferlaydi), lekin
faylga yozganda darhol paydo bo'ladi. Aynan shu sababdan fork dan
oldin doim fflush(stdout) chaqiring.
fork ning qaytgan qiymati| Qaytdi | Kim uchun | Ma'nosi |
|---|---|---|
> 0 | Ota | Yaratilgan bolaning PID i |
0 | Bola | «Sen bolasan» |
< 0 | Ota | Xato - jarayon yaratilmadi |
Bola o'z PID ini bilishi uchun getpid() chaqiradi. Ota esa fork
qaytargan qiymatdan biladi.
Bola nimani meros oladi? #
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int global_son = 100;
int main(void) {
int mahalliy_son = 200;
pid_t bola = fork();
if (bola == 0) {
global_son += 1;
mahalliy_son += 1;
printf(" Bola : global=%d mahalliy=%d\n", global_son, mahalliy_son);
return 0;
}
wait(NULL);
printf("Ota : global=%d mahalliy=%d\n", global_son, mahalliy_son);
return 0;
}
Bola : global=101 mahalliy=201
Ota : global=100 mahalliy=200
Bola otaning xotirasining nusxasini oladi. Bola o'zgaruvchini o'zgartirsa, otaning nusxasi o'zgarmaydi.
Bu C darsligidagi «qiymat bo'yicha uzatish» qoidasiga o'xshaydi - faqat bu yerda butun jarayon nusxalanadi.
| Nusxalanadi | Bo'lishiladi |
|---|---|
| Xotira (butun manzil fazosi) | Ochiq fayl deskriptorlari |
| O'zgaruvchilar | Fayl ichidagi joriy o'rin |
| Joriy papka | Terminal |
Ma'lumot almashish kerak bo'lsa - quvur (pipe) yoki umumiy xotira kerak. Buni 15-bo'limda ko'ramiz.
1 GB xotira ishlatayotgan jarayonni fork qilsangiz, yadro 1 GB ni
darhol ko'chirmaydi - bu juda sekin bo'lardi.
Buning o'rniga copy-on-write (yozishda nusxalash) ishlatiladi:
- Ikkala jarayon ham bir xil sahifalarga ko'rsatadi;
- Sahifalar «faqat o'qish» deb belgilanadi;
- Biri yozmoqchi bo'lganda - yadro o'sha bitta sahifani nusxalaydi.
Shu tufayli fork juda tez ishlaydi. Buni 10-bo'limda batafsil
ko'ramiz.
Ko'p bola yaratish #
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
for (int i = 1; i <= 3; i++) {
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
printf(" %d-bola ishladi\n", i);
return 0; /* bola siklni davom ettirmasin! */
}
wait(NULL); /* har bir bolani navbatma-navbat kutamiz */
}
printf("Barcha bolalar tugadi\n");
return 0;
}
1-bola ishladi
2-bola ishladi
3-bola ishladi
Barcha bolalar tugadi
for (int i = 0; i < 3; i++) {
fork(); /* bola ham siklni davom ettiradi! */
}
Bu kod 3 ta emas, 8 ta jarayon yaratadi (2³). Har bir fork da
jarayonlar soni ikki barobar oshadi.
Chegarani olib tashlasangiz - fork bombasi hosil bo'ladi va tizim
qotadi. Shuning uchun bola shohobchasi doim return yoki _exit bilan
tugashi kerak.
exec - dasturni almashtirish #
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
printf("exec dan oldin\n");
fflush(stdout); /* buferni bo'shatamiz */
pid_t bola = fork();
if (bola == 0) {
char *argumentlar[] = {"echo", " Men endi echo dasturiman", NULL};
execvp("echo", argumentlar);
/* bu yerga faqat exec muvaffaqiyatsiz bo'lganda yetamiz */
printf(" exec ishlamadi\n");
_exit(1);
}
wait(NULL);
printf("exec dan keyin (ota)\n");
return 0;
}
exec dan oldin
Men endi echo dasturiman
exec dan keyin (ota)
exec dan keyingi kod bajarilmaydiexecvp("echo", argumentlar);
printf("Bu chiqmaydi\n"); /* exec muvaffaqiyatli bo'lsa - yo'q */
exec joriy jarayonning butun kodini almashtiradi. Qaytadigan joy
qolmaydi.
Shuning uchun exec dan keyingi kod - bu xato ishlovchi. U faqat
exec muvaffaqiyatsiz bo'lganda (dastur topilmadi, huquq yo'q)
bajariladi.
exec oilasi #
| Funksiya | Argumentlar | PATH da qidiradi |
|---|---|---|
execl | Ro'yxat: execl(yol, a0, a1, NULL) | yo'q |
execlp | Ro'yxat | ha |
execv | Massiv: execv(yol, argv) | yo'q |
execvp | Massiv | ha |
execve | Massiv + muhit | yo'q |
execl("/bin/ls", "ls", "-l", NULL); /* to'liq yo'l */
execlp("ls", "ls", "-l", NULL); /* PATH dan topadi */
char *argv[] = {"ls", "-l", NULL};
execvp("ls", argv); /* massiv bilan */
l(list) - argumentlar ro'yxat bo'lib beriladi;v(vector) - argumentlar massiv bo'lib beriladi;p(path) - dasturniPATHo'zgaruvchisidan qidiradi;e(environment) - muhit o'zgaruvchilari ham beriladi.
Amalda execvp eng ko'p ishlatiladi.
fork + exec naqshi #
Bu naqsh butun UNIX'ning asosi. Terminalda buyruq yozganingizda aynan shu sodir bo'ladi.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int buyruq_bajar(char *argumentlar[]) {
fflush(stdout); /* bola buferni takrorlamasin */
pid_t bola = fork();
if (bola < 0) {
return -1;
}
if (bola == 0) {
execvp(argumentlar[0], argumentlar);
_exit(127); /* exec ishlamadi */
}
int holat;
waitpid(bola, &holat, 0);
if (WIFEXITED(holat)) {
return WEXITSTATUS(holat);
}
return -1;
}
int main(void) {
char *birinchi[] = {"echo", "Birinchi buyruq", NULL};
char *ikkinchi[] = {"echo", "Ikkinchi buyruq", NULL};
char *xato[] = {"bunday_buyruq_yoq_12345", NULL};
printf("Natija 1: %d\n", buyruq_bajar(birinchi));
printf("Natija 2: %d\n", buyruq_bajar(ikkinchi));
printf("Natija 3: %d\n", buyruq_bajar(xato));
return 0;
}
Birinchi buyruq
Natija 1: 0
Ikkinchi buyruq
Natija 2: 0
Natija 3: 127
fork va exec alohida?Windows'da CreateProcess bitta chaqiruvda hamma narsani qiladi.
UNIX ularni ataylab ajratgan.
Sabab: oraliqdagi joyda ko'p narsa qilish mumkin.
fork va exec orasida bola:
- fayl deskriptorlarini qayta yo'naltirishi (
>va|shunday ishlaydi); - huquqlarini pasaytirishi;
- muhit o'zgaruvchilarini o'zgartirishi;
- joriy papkani almashtirishi mumkin.
Shuning uchun qobiqdagi ls > natija.txt shunday oddiy ishlaydi -
bola exec dan oldin chiqishni faylga bog'laydi.
wait va waitpid #
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
pid_t bola = fork();
if (bola == 0) {
printf(" Bola 42 kodi bilan tugaydi\n");
exit(42);
}
int holat;
waitpid(bola, &holat, 0);
if (WIFEXITED(holat)) {
printf("Bola normal tugadi, kodi: %d\n", WEXITSTATUS(holat));
} else if (WIFSIGNALED(holat)) {
printf("Bola signal bilan to'xtadi: %d\n", WTERMSIG(holat));
}
return 0;
}
Bola 42 kodi bilan tugaydi
Bola normal tugadi, kodi: 42
| Makros | Nima aytadi |
|---|---|
WIFEXITED(h) | Normal tugadimi? |
WEXITSTATUS(h) | Qaytgan kod (0-255) |
WIFSIGNALED(h) | Signal bilan to'xtadimi? |
WTERMSIG(h) | Qaysi signal |
WIFSTOPPED(h) | To'xtatildimi? |
holat - oddiy son emasint holat;
wait(&holat);
printf("%d\n", holat); /* 42 emas, 10752 chiqadi! */
holat ichida bir necha ma'lumot bitlarga joylashtirilgan: chiqish
kodi, signal raqami, core dump bo'lgani.
Shuning uchun uni doim makroslar orqali o'qing.
Zombi jarayon #
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
pid_t bola = fork();
if (bola == 0) {
printf(" Bola darhol tugaydi\n");
return 0;
}
/* Ota bolani kutmasdan ishlashda davom etadi.
Shu payt bola ZOMBI holatida. */
sleep(1);
printf("Ota endi bolani o'qiydi\n");
wait(NULL); /* zombi tozalandi */
printf("Zombi yo'q qilindi\n");
return 0;
}
Bola darhol tugaydi
Ota endi bolani o'qiydi
Zombi yo'q qilindi
ps aux | awk '$8 ~ /^Z/ {print}'
Server dasturi har bir mijoz uchun fork qilib, wait chaqirmasa,
zombi lar to'planadi. PID lar tugagach, tizim yangi jarayon yarata
olmaydi.
Uch yechim:
waitchaqirish - eng oddiy, lekin ota bloklanadi;SIGCHLDishlovchisi - bola tugaganda avtomatik tozalash;- Ikki marta
fork- bolani darhol yetim qilib,systemdga topshirish.
signal(SIGCHLD, SIG_IGN); /* eng oson: bolalarni o'zi tozalasin */
Signallarni 16-bo'limda batafsil ko'ramiz.
Yetim jarayon #
#include <stdio.h>
#include <unistd.h>
int main(void) {
pid_t bola = fork();
if (bola == 0) {
pid_t oldingi_ota = getppid();
sleep(2); /* ota bu vaqtda tugaydi */
pid_t yangi_ota = getppid();
printf(" Bola: ota o'zgardimi? %s\n",
oldingi_ota != yangi_ota ? "ha" : "yo'q");
printf(" Bola: yangi ota PID 1 mi? %s\n",
yangi_ota == 1 ? "ha" : "yo'q (init boshqa jarayon)");
return 0;
}
printf("Ota tugadi (bola hali ishlayapti)\n");
return 0;
}
Ota tugadi (bola hali ishlayapti)
Bola: ota o'zgardimi? ha
Bola: yangi ota PID 1 mi? yo'q (init boshqa jarayon)
An'anaviy UNIX'da yetim jarayonni PID 1 (init yoki systemd)
asrab oladi.
Zamonaviy Linux'da esa subreaper mexanizmi bor: sessiya boshqaruvchisi
(masalan systemd --user yoki konteyner boshlig'i) o'zini «asrab oluvchi»
deb belgilashi mumkin. Shunda yetim unga o'tadi, PID 1 ga emas.
Shuning uchun yuqoridagi misolda javob muhitga qarab o'zgaradi.
Demon yaratish #
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
int demon_bol(void) {
pid_t bola = fork();
if (bola < 0) {
return -1;
}
if (bola > 0) {
_exit(0); /* ota chiqadi - bola yetim bo'ladi */
}
/* Endi biz yetimmiz. Yangi sessiya boshlaymiz. */
if (setsid() < 0) {
return -1;
}
/* Ikkinchi fork - terminalga qayta ulanib qolmaslik uchun */
bola = fork();
if (bola < 0) {
return -1;
}
if (bola > 0) {
_exit(0);
}
umask(0);
if (chdir("/") < 0) {
return -1;
}
return 0;
}
int main(void) {
printf("Demon yaratish qadamlari tayyor\n");
printf("Haqiqiy demon uchun demon_bol() chaqiriladi\n");
return 0;
}
Demon yaratish qadamlari tayyor
Haqiqiy demon uchun demon_bol() chaqiriladi
Demon (daemon) - orqa fonda ishlaydigan, terminalga bog'lanmagan
jarayon. sshd, cron, nginx - hammasi demon.
Klassik demon yaratish qadamlari:
forkva otaning chiqishi - qobiq buyruqni tugagan deb hisoblaydi;setsid- yangi sessiya, terminaldan uzilish;- Ikkinchi
fork- endi hech qachon terminal ololmaydi; chdir("/")- fayl tizimini band qilib turmaslik;umask(0)- fayl huquqlarini o'zi belgilash;stdin,stdout,stderrni/dev/nullga yo'naltirish.
Zamonaviy Linux'da bularni systemd o'zi qiladi - siz oddiy
dastur yozasiz, u esa fon rejimini boshqaradi. 18-bo'limda ko'ramiz.
Jarayon yaratish narxi #
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#define MARTA 200
int main(void) {
struct timespec boshlandi, tugadi;
clock_gettime(CLOCK_MONOTONIC, &boshlandi);
for (int i = 0; i < MARTA; i++) {
pid_t bola = fork();
if (bola == 0) {
_exit(0);
}
wait(NULL);
}
clock_gettime(CLOCK_MONOTONIC, &tugadi);
double vaqt = (double) (tugadi.tv_sec - boshlandi.tv_sec) +
(double) (tugadi.tv_nsec - boshlandi.tv_nsec) / 1e9;
printf("%d ta jarayon yaratildi\n", MARTA);
printf("Vaqt musbatmi? %s\n", vaqt > 0.0 ? "ha" : "yo'q");
/* aniq raqamni ko'rish uchun: */
/* printf("Bittasiga o'rtacha: %.1f mikrosoniya\n", vaqt / MARTA * 1e6); */
return 0;
}
200 ta jarayon yaratildi
Vaqt musbatmi? ha
Izohdagi qatorni yoqsangiz, bitta jarayon yaratish odatda 50-500 mikrosoniya olishini ko'rasiz. Bu oqim (thread) yaratishdan ancha qimmat - shuning uchun keyingi bo'limda oqimlarni ko'ramiz.
forkbilan bola jarayon yarating va ikkalasining PID sini chiqaring.forkdan keyin o'zgaruvchini o'zgartirib, ota ko'radimi tekshiring.- Sikl ichida
forkqilib,returnsiz nechta jarayon paydo bo'lishini hisoblang. execvpbilanls -lni ishga tushiring.execdan keyingiprintfbajarilmasligini tasdiqlang.waitpidvaWEXITSTATUSbilan bolaning chiqish kodini oling.- Bolani kutmasdan zombi yarating va
psda toping. SIGCHLDniSIG_IGNqilib, zombi yo'qolishini kuzating.- Yetim jarayon yaratib, uning PPID i o'zgarishini ko'ring.
forkva oddiy funksiya chaqiruvi tezligini taqqoslang.
Xulosa #
forkjoriy jarayonning to'liq nusxasini yaratadi.- U bir marta chaqiriladi, ikki marta qaytadi: otaga bolaning PID i, bolaga 0.
- Bola xotiraning nusxasini oladi - o'zgarishlar bo'lishilmaydi.
- Amalda nusxalash copy-on-write bilan kechiktiriladi.
- Sikl ichida
forkqilsangiz, bolanireturnbilan chiqaring. execjarayonning kodini almashtiradi, yangi jarayon yaratmaydi.execmuvaffaqiyatli bo'lsa, keyingi qatorga qaytmaydi.fork+exec- UNIX'ning asosiy naqshi; oradagi joy qayta yo'naltirish uchun ishlatiladi.waitbolani kutadi va uning yozuvini tozalaydi.- Kutilmagan bola - zombi; otasiz bola - yetim.
- Zombi to'planishi PID larni tugatib, tizimni to'xtatadi.
Keyingi bo'limda oqimlarni - bitta jarayon ichidagi parallel ishni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.