20-bo‘lim
Amaliy loyiha - mini qobiq
Butun kursda o'rganilgan bilimlarni birlashtirib, ishlaydigan qobiq yozamiz: buyruq tahlili, fork/exec, konveyerlar, yo'naltirish va signallar.
Ushbu bo‘lim mundarijasi
Yigirma bo'lim davomida o'rgangan hamma narsani bitta dasturda birlashtiramiz. Qobiq - operatsion tizim bilan ishlashning eng yaxshi mashqi.
Nima quramiz #
| Imkoniyat | Qaysi bo'limdan |
|---|---|
| Buyruq bajarish | 4 - jarayon yaratish |
| Konveyer | 15 - jarayonlararo aloqa |
| Yo'naltirish | 12, 15 - fayllar va dup2 |
Ctrl+C boshqaruvi | 16 - signallar |
| Fon jarayonlari | 3, 16 - jarayonlar va SIGCHLD |
| Zombi larni yig'ish | 4, 16 - waitpid |
1-qadam: buyruqni tahlil qilish #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ARG 64
/* Qatorni bo'shliqlar bo'yicha argumentlarga ajratadi */
static int tahlil(char *qator, char **argv, int maks) {
int soni = 0;
char *belgi = strtok(qator, " \t\n");
while (belgi != NULL && soni < maks - 1) {
argv[soni++] = belgi;
belgi = strtok(NULL, " \t\n");
}
argv[soni] = NULL;
return soni;
}
int main(void) {
char qator[] = "ls -la /home ";
char *argv[MAX_ARG];
int soni = tahlil(qator, argv, MAX_ARG);
printf("Argumentlar soni: %d\n", soni);
for (int i = 0; i < soni; i++) {
printf(" argv[%d] = \"%s\"\n", i, argv[i]);
}
printf(" argv[%d] = NULL\n", soni);
printf("\nexecvp uchun tayyor: %s\n", argv[soni] == NULL ? "ha" : "yo'q");
return 0;
}
Argumentlar soni: 3
argv[0] = "ls"
argv[1] = "-la"
argv[2] = "/home"
argv[3] = NULL
execvp uchun tayyor: ha
NULLexecvp(argv[0], argv) massiv qayerda tugashini bilishi kerak.
Uzunlik uzatilmagani uchun NULL ko'rsatkichi chegara
belgisi bo'ladi.
NULL qo'yishni unutsangiz, exec massivdan tashqariga chiqib
o'qiydi - natija aniqlanmagan xatti-harakat.
strtok bilan ehtiyot bo'ling: u asl qatorni o'zgartiradi
(ajratuvchilar o'rniga \0 qo'yadi) va ichida global holat
saqlaydi. Ko'p oqimli kodda strtok_r ishlating.
2-qadam: buyruqni bajarish #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int bajarish(char **argv) {
fflush(stdout);
pid_t bola = fork();
if (bola < 0) {
perror("fork");
return -1;
}
if (bola == 0) {
execvp(argv[0], argv);
/* Bu yerga yetsak - exec muvaffaqiyatsiz */
fprintf(stderr, "%s: buyruq topilmadi\n", argv[0]);
fflush(stderr);
_exit(127);
}
int holat;
waitpid(bola, &holat, 0);
return WIFEXITED(holat) ? WEXITSTATUS(holat) : -1;
}
int main(void) {
char *echo_argv[] = { "echo", "salom dunyo", NULL };
char *yolgon_argv[] = { "bunday_buyruq_yoq_12345", NULL };
int birinchi = bajarish(echo_argv);
printf("echo chiqish kodi: %d\n", birinchi);
int ikkinchi = bajarish(yolgon_argv);
printf("Topilmagan buyruq kodi: %d\n", ikkinchi);
printf("127 mi? %s\n", ikkinchi == 127 ? "ha - qobiq an'anasi" : "yo'q");
return 0;
}
salom dunyo
echo chiqish kodi: 0
bunday_buyruq_yoq_12345: buyruq topilmadi
Topilmagan buyruq kodi: 127
127 mi? ha - qobiq an'anasi
| Kod | Ma'nosi |
|---|---|
0 | Muvaffaqiyat |
1-125 | Dasturning o'z xatosi |
126 | Fayl topildi, lekin bajarib bo'lmadi |
127 | Buyruq topilmadi |
128 + N | N signali bilan tugatildi |
Shuning uchun Ctrl+C (SIGINT = 2) bilan to'xtatilgan dastur
130 qaytaradi, SIGKILL (9) esa 137.
sleep 100
# Ctrl+C bosamiz
echo $?
130
19-bo'limdagi Docker ning Exit Code 137 aynan shu:
128 + 9 = SIGKILL.
3-qadam: yo'naltirish #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
/* argv dan > va < belgilarini ajratib oladi */
static void yonaltirishni_ajratish(char **argv, char **chiqish, char **kirish) {
*chiqish = NULL;
*kirish = NULL;
for (int i = 0; argv[i] != NULL; i++) {
if (strcmp(argv[i], ">") == 0 && argv[i + 1] != NULL) {
*chiqish = argv[i + 1];
argv[i] = NULL;
return;
}
if (strcmp(argv[i], "<") == 0 && argv[i + 1] != NULL) {
*kirish = argv[i + 1];
argv[i] = NULL;
return;
}
}
}
int main(void) {
char *argv[] = { "echo", "faylga yozildi", ">", "/tmp/qobiq_sinov.txt", NULL };
char *chiqish;
char *kirish;
yonaltirishni_ajratish(argv, &chiqish, &kirish);
printf("Buyruq : %s\n", argv[0]);
printf("Chiqish fayli: %s\n", chiqish != NULL ? chiqish : "(yo'q)");
fflush(stdout);
pid_t bola = fork();
if (bola == 0) {
if (chiqish != NULL) {
int fd = open(chiqish, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
_exit(1);
}
dup2(fd, STDOUT_FILENO);
close(fd);
}
execvp(argv[0], argv);
_exit(127);
}
waitpid(bola, NULL, 0);
/* Natijani o'qib ko'ramiz */
FILE *f = fopen("/tmp/qobiq_sinov.txt", "r");
char bufer[64] = "";
if (f != NULL) {
if (fgets(bufer, sizeof bufer, f) == NULL) {
bufer[0] = '\0';
}
fclose(f);
}
bufer[strcspn(bufer, "\n")] = '\0';
printf("Fayl mazmuni : %s\n", bufer);
printf("To'g'rimi? : %s\n",
strcmp(bufer, "faylga yozildi") == 0 ? "ha" : "yo'q");
unlink("/tmp/qobiq_sinov.txt");
return 0;
}
Buyruq : echo
Chiqish fayli: /tmp/qobiq_sinov.txt
Fayl mazmuni : faylga yozildi
To'g'rimi? : ha
4-qadam: konveyer #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
/* Ikki buyruqni quvur bilan bog'lab bajaradi */
static int konveyer(char **chap, char **ong) {
int quvur[2];
if (pipe(quvur) < 0) {
return -1;
}
fflush(stdout);
pid_t birinchi = fork();
if (birinchi == 0) {
close(quvur[0]);
dup2(quvur[1], STDOUT_FILENO);
close(quvur[1]);
execvp(chap[0], chap);
_exit(127);
}
pid_t ikkinchi = fork();
if (ikkinchi == 0) {
close(quvur[1]);
dup2(quvur[0], STDIN_FILENO);
close(quvur[0]);
execvp(ong[0], ong);
_exit(127);
}
/* Ota ikkala uchni ham yopishi SHART */
close(quvur[0]);
close(quvur[1]);
int holat;
waitpid(birinchi, NULL, 0);
waitpid(ikkinchi, &holat, 0);
return WIFEXITED(holat) ? WEXITSTATUS(holat) : -1;
}
int main(void) {
char *chap[] = { "printf", "olma\\nanor\\nolcha\\n", NULL };
char *ong[] = { "grep", "-c", "^ol", NULL };
printf("Bajarilmoqda: printf ... | grep -c \"^ol\"\n");
fflush(stdout);
int kod = konveyer(chap, ong);
printf("Chiqish kodi: %d\n", kod);
printf("grep mos qator topdimi? %s\n", kod == 0 ? "ha" : "yo'q");
return 0;
}
Bajarilmoqda: printf ... | grep -c "^ol"
2
Chiqish kodi: 0
grep mos qator topdimi? ha
Bu eng ko'p uchraydigan xato:
/* NOTO'G'RI - ota quvur[1] ni ochiq qoldirdi */
waitpid(ikkinchi, NULL, 0); /* abadiy kutish */
grep fayl oxirini kutadi. Fayl oxiri esa barcha yozish
uchlari yopilganda keladi.
Ota quvur[1] ni ochiq qoldirsa, grep hech qachon EOF
olmaydi va qobiq qotib qoladi.
Qoida: fork lardan keyin ota darhol ikkala uchni yopadi -
unga quvur kerak emas, u faqat bolalarga uzatgan edi.
Uch bosqichli konveyerda (a | b | c) bu yanada muhim: har bir
oraliq quvurni hamma keraksiz jarayonda yopish kerak.
5-qadam: ichki buyruqlar #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Ichki buyruq bo'lsa bajarib 1 qaytaradi */
static int ichki_buyruq(char **argv) {
if (argv[0] == NULL) {
return 1;
}
if (strcmp(argv[0], "cd") == 0) {
const char *yol = argv[1] != NULL ? argv[1] : getenv("HOME");
if (yol == NULL || chdir(yol) != 0) {
printf("cd: katalogga o'tib bo'lmadi\n");
}
return 1;
}
if (strcmp(argv[0], "pwd") == 0) {
char bufer[1024];
if (getcwd(bufer, sizeof bufer) != NULL) {
printf("%s\n", bufer);
}
return 1;
}
if (strcmp(argv[0], "exit") == 0) {
printf("(chiqish)\n");
return 1;
}
return 0;
}
int main(void) {
char *cd_argv[] = { "cd", "/tmp", NULL };
char *pwd_argv[] = { "pwd", NULL };
char *ls_argv[] = { "ls", NULL };
printf("cd ichki buyruqmi? %s\n", ichki_buyruq(cd_argv) ? "ha" : "yo'q");
printf("Joriy katalog : ");
ichki_buyruq(pwd_argv);
printf("ls ichki buyruqmi? %s\n", ichki_buyruq(ls_argv) ? "ha" : "yo'q");
printf("Demak ls uchun fork/exec kerak\n");
return 0;
}
cd ichki buyruqmi? ha
Joriy katalog : /tmp
ls ichki buyruqmi? yo'q
Demak ls uchun fork/exec kerak
cd nima uchun alohida dastur bo'la olmaydiJoriy katalog - jarayonning xossasi. fork qilingan bola
uni o'zgartirsa, bu faqat bolaga ta'sir qiladi.
qobiq (PID 100, /home)
└── cd dasturi (PID 101) - chdir("/tmp") qildi va o'ldi
qobiq hali ham /home da
Shuning uchun cd qobiqning o'zida bajarilishi shart.
Xuddi shu sababdan quyidagilar ham ichki buyruq:
| Buyruq | Nima uchun ichki |
|---|---|
cd | Joriy katalog jarayon xossasi |
export | Muhit o'zgaruvchisi meros qoladi |
exit | Qobiqning o'zi tugashi kerak |
umask | Jarayon xossasi |
ulimit | Jarayon chegaralari |
Buni tekshirish oson:
type cd
cd is a shell builtin
6-qadam: signallarni boshqarish #
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
static volatile sig_atomic_t uzildi = 0;
static volatile sig_atomic_t yigilgan = 0;
static void sigint_ishlovchi(int r) {
(void) r;
uzildi = 1;
}
static void sigchld_ishlovchi(int r) {
(void) r;
int saqlangan = errno;
while (waitpid(-1, NULL, WNOHANG) > 0) {
yigilgan++;
}
errno = saqlangan;
}
int main(void) {
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = sigint_ishlovchi;
sigaction(SIGINT, &sa, NULL);
sa.sa_handler = sigchld_ishlovchi;
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
printf("Qobiq signallarni sozladi:\n");
printf(" SIGINT - Ctrl+C qobiqni o'ldirmasin\n");
printf(" SIGCHLD - fon jarayonlarini yig'ish\n");
/* Ctrl+C simulyatsiyasi */
raise(SIGINT);
printf("\nCtrl+C keldi, qobiq tirikmi? %s\n", "ha");
printf("Bayroq o'rnatildimi? %s\n", uzildi ? "ha" : "yo'q");
printf("\nMuhim: bola jarayonlar SIGINT ni ODATIY qabul qilishi kerak\n");
printf("Shuning uchun fork dan keyin bolada:\n");
printf(" signal(SIGINT, SIG_DFL);\n");
return 0;
}
Qobiq signallarni sozladi:
SIGINT - Ctrl+C qobiqni o'ldirmasin
SIGCHLD - fon jarayonlarini yig'ish
Ctrl+C keldi, qobiq tirikmi? ha
Bayroq o'rnatildimi? ha
Muhim: bola jarayonlar SIGINT ni ODATIY qabul qilishi kerak
Shuning uchun fork dan keyin bolada:
signal(SIGINT, SIG_DFL);
fork dan keyin bola otaning signal ishlovchilarini
meros oladi. Agar qobiq SIGINT ni e'tiborsiz qoldirsa,
sleep 100 ni ham Ctrl+C to'xtata olmaydi.
Bolada ishlovchini odatiy holatga qaytaring:
if (bola == 0) {
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
execvp(argv[0], argv);
_exit(127);
}
Haqiqiy qobiqlar bundan ham ko'proq ish qiladi - ular jarayon
guruhlari yaratib, terminalni o'sha guruhga topshiradi
(tcsetpgrp). Shunda Ctrl+C butun konveyerga yetadi, faqat
oxirgi buyruqqa emas.
To'liq qobiq #
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_ARG 64
#define MAX_QATOR 1024
static volatile sig_atomic_t yigilgan = 0;
static void sigchld_ishlovchi(int r) {
(void) r;
int saqlangan = errno;
while (waitpid(-1, NULL, WNOHANG) > 0) {
yigilgan++;
}
errno = saqlangan;
}
static int tahlil(char *qator, char **argv, int maks) {
int soni = 0;
char *saqlash = NULL;
char *belgi = strtok_r(qator, " \t\n", &saqlash);
while (belgi != NULL && soni < maks - 1) {
argv[soni++] = belgi;
belgi = strtok_r(NULL, " \t\n", &saqlash);
}
argv[soni] = NULL;
return soni;
}
/* argv ni | bo'yicha ikkiga bo'ladi; topilmasa NULL qaytaradi */
static char **konveyerni_ajratish(char **argv) {
for (int i = 0; argv[i] != NULL; i++) {
if (strcmp(argv[i], "|") == 0) {
argv[i] = NULL;
return &argv[i + 1];
}
}
return NULL;
}
static void yonaltirish(char **argv) {
for (int i = 0; argv[i] != NULL; i++) {
int chiqishmi = strcmp(argv[i], ">") == 0;
int kirishmi = strcmp(argv[i], "<") == 0;
if ((chiqishmi || kirishmi) && argv[i + 1] != NULL) {
int fd = chiqishmi
? open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0644)
: open(argv[i + 1], O_RDONLY);
if (fd >= 0) {
dup2(fd, chiqishmi ? STDOUT_FILENO : STDIN_FILENO);
close(fd);
}
argv[i] = NULL;
return;
}
}
}
static void bolani_tayyorlash(void) {
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
}
static int ichki_buyruq(char **argv) {
if (argv[0] == NULL) {
return 1;
}
if (strcmp(argv[0], "cd") == 0) {
const char *yol = argv[1] != NULL ? argv[1] : getenv("HOME");
if (yol == NULL || chdir(yol) != 0) {
fprintf(stderr, "cd: xato\n");
}
return 1;
}
if (strcmp(argv[0], "pwd") == 0) {
char bufer[MAX_QATOR];
if (getcwd(bufer, sizeof bufer) != NULL) {
printf("%s\n", bufer);
}
return 1;
}
return 0;
}
static void bajarish(char **argv) {
char **ong = konveyerni_ajratish(argv);
if (ong == NULL) {
if (ichki_buyruq(argv)) {
return;
}
fflush(stdout);
pid_t b = fork();
if (b == 0) {
bolani_tayyorlash();
yonaltirish(argv);
execvp(argv[0], argv);
fprintf(stderr, "%s: buyruq topilmadi\n", argv[0]);
fflush(stderr);
_exit(127);
}
waitpid(b, NULL, 0);
return;
}
/* Konveyer */
int quvur[2];
if (pipe(quvur) < 0) {
return;
}
fflush(stdout);
pid_t chap = fork();
if (chap == 0) {
bolani_tayyorlash();
close(quvur[0]);
dup2(quvur[1], STDOUT_FILENO);
close(quvur[1]);
yonaltirish(argv);
execvp(argv[0], argv);
_exit(127);
}
pid_t ong_pid = fork();
if (ong_pid == 0) {
bolani_tayyorlash();
close(quvur[1]);
dup2(quvur[0], STDIN_FILENO);
close(quvur[0]);
yonaltirish(ong);
execvp(ong[0], ong);
_exit(127);
}
close(quvur[0]);
close(quvur[1]);
waitpid(chap, NULL, 0);
waitpid(ong_pid, NULL, 0);
}
int main(void) {
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sa.sa_handler = sigchld_ishlovchi;
sigaction(SIGCHLD, &sa, NULL);
signal(SIGINT, SIG_IGN);
/* Sinov buyruqlari - haqiqiy qobiqda bu fgets(stdin) bo'ladi */
const char *sinovlar[] = {
"echo Salom, mini qobiq!",
"cd /tmp",
"pwd",
"printf olma\\nanor\\nolcha\\n | grep -c ^ol",
"echo faylga > /tmp/mq.txt",
"cat /tmp/mq.txt",
"bunday_buyruq_yoq"
};
char qator[MAX_QATOR];
char *argv[MAX_ARG];
for (size_t i = 0; i < sizeof sinovlar / sizeof sinovlar[0]; i++) {
printf("mq$ %s\n", sinovlar[i]);
fflush(stdout);
strncpy(qator, sinovlar[i], sizeof qator - 1);
qator[sizeof qator - 1] = '\0';
tahlil(qator, argv, MAX_ARG);
bajarish(argv);
}
unlink("/tmp/mq.txt");
printf("mq$ exit\n");
return 0;
}
mq$ echo Salom, mini qobiq!
Salom, mini qobiq!
mq$ cd /tmp
mq$ pwd
/tmp
mq$ printf olma\nanor\nolcha\n | grep -c ^ol
2
mq$ echo faylga > /tmp/mq.txt
mq$ cat /tmp/mq.txt
faylga
mq$ bunday_buyruq_yoq
bunday_buyruq_yoq: buyruq topilmadi
mq$ exit
Yuqoridagi kod sinov buyruqlarini massivdan oladi. Interaktiv
qilish uchun main dagi siklni almashtiring:
char qator[MAX_QATOR];
char *argv[MAX_ARG];
while (1) {
printf("mq$ ");
fflush(stdout);
if (fgets(qator, sizeof qator, stdin) == NULL) {
break; /* Ctrl+D */
}
if (tahlil(qator, argv, MAX_ARG) == 0) {
continue;
}
if (strcmp(argv[0], "exit") == 0) {
break;
}
bajarish(argv);
}
Buyruqlar tarixi va o'q tugmalari uchun readline kutubxonasini
qo'shing:
#include <readline/readline.h>
#include <readline/history.h>
char *qator = readline("mq$ ");
if (qator != NULL && *qator) {
add_history(qator);
}
gcc -std=c17 -Wall qobiq.c -o qobiq -lreadline
Keyingi qadamlar #
| Imkoniyat | Kerakli bilim | ||
|---|---|---|---|
Ko'p bosqichli konveyer (a | b | c) | Rekursiya yoki sikl | ||
Fon jarayonlari (&) | waitpid siz fork | ||
jobs, fg, bg | Jarayon guruhlari, tcsetpgrp | ||
>> qo'shib yozish | O_APPEND | ||
2> xato yo'naltirish | dup2(fd, 2) | ||
| Qavslar va tirnoqlar | To'liq leksik tahlil | ||
O'zgaruvchilar ($HOME) | getenv, almashtirish | ||
| Fayl nomlarini to'ldirish | glob() | ||
&& va ` | ` | Chiqish kodini tekshirish | |
| Skript fayllarini bajarish | fopen + o'sha sikl |
Har bir imkoniyat kursdagi biror bo'limga tayanadi. Bu loyihani bosqichma-bosqich kengaytirish - operatsion tizimlarni o'rganishning eng samarali usuli.
tahlilfunksiyasini yozib, argumentlarga ajrating.forkvaexecvpbilan oddiy buyruqni bajaring.- Topilmagan buyruq uchun 127 kodini qaytaring.
>yo'naltirishnidup2bilan amalga oshiring.<kirish yo'naltirishini qo'shing.- Ikki buyruqli konveyer quring va ikkala uchni yoping.
cdvapwdichki buyruqlarini qo'shing.cdnima uchun alohida dastur bo'la olmasligini tushuntiring.SIGINTni qobiqda e'tiborsiz, bolada odatiy qiling.&bilan fon jarayonini qo'shib,SIGCHLDbilan yig'ing.
Xulosa #
- Qobiqning sikli: belgi → o'qish → tahlil → bajarish → kutish.
execvpuchun argumentlar massiviNULLbilan tugashi shart.- Ko'p oqimli kodda
strtokemas,strtok_rishlating. - Chiqish kodi 127 - buyruq topilmadi, 128+N - signal.
- Yo'naltirish -
open+dup2+close. - Konveyerda ota ikkala uchni ham yopishi shart.
cdichki buyruq bo'lishi kerak - joriy katalog jarayon xossasi.- Qobiq
SIGINTni e'tiborsiz qoldiradi, bola odatiy qabul qiladi. SIGCHLDishlovchisidawhile (waitpid(...))ishlating.- Har bir yangi imkoniyat kursdagi biror bo'limga tayanadi.
Tabriklaymiz! Siz operatsion tizimlarning asosiy tushunchalarini o'rgandingiz: jarayonlar va oqimlar, rejalashtirish, sinxronizatsiya, xotira boshqaruvi va virtual xotira, fayl tizimlari, kiritish-chiqarish, jarayonlararo aloqa, signallar, xavfsizlik, yuklanish va virtualizatsiya - va hammasini bitta ishlaydigan dasturda birlashtirdingiz.
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.