# Topshiriqlarning yechimlari: OTda ko'p vazifalilikni ta'minlash usullari. Jarayonlar, oqimlar, tolalar. Jarayonlararo aloqa. Sinxronizatsiya

**O'qituvchi uchun.** Fayl `6.os.process.tasks.uz.md` dagi topshiriqlarning to'liq yechimlari va baholash mezonlarini o'z ichiga oladi. Raqamlash topshiriqlar fayli bilan bir xil.

**Natijalar haqida:** barcha C dasturlari kompilyatsiya qilingan va tekshirilgan; hisoblash masalalarining javoblari dastur yordamida qayta hisoblab tasdiqlangan. Amaliy topshiriqlardagi buyruqlar chiqishi **namunaviy**: PID, vaqt, jarayonlar soni va boshqa raqamlar talabaning kompyuterida boshqacha bo'ladi. Muhimi — natijaning tuzilishi va xulosa.

---

## 1-qism. Test

**1. Javob: B (Konkurentlik)**
*Izoh:* bitta yadroda vazifalar navbatlashadi — bu konkurentlik.
A — noto'g'ri: parallellik jismoniy bir vaqtdalikni talab qiladi, bitta yadroda bu mumkin emas. C — multiprotsessorlik bir nechta protsessorni anglatadi. D — noto'g'ri: OT protsessorni tez almashtirib, buni bitta yadroda ham ta'minlaydi.

**2. Javob: C (Apparat taymerining uzilishi orqali)**
*Izoh:* taymer davriy ravishda uzilish hosil qiladi, boshqaruv yadroga o'tadi va rejalashtiruvchi protsessorni boshqa jarayonga beradi.
A — bu kooperativ ko'p vazifalilik. B — bu umuman ko'p vazifalilik emas. D — `SIGKILL` jarayonni o'ldiradi, vaqtincha to'xtatmaydi.

**3. Javob: B (Ready)**
*Izoh:* I/O tugagach, jarayon Waiting → Ready o'tishini bajaradi va protsessorni navbatda kutadi.
A — to'g'ridan-to'g'ri Running ga o'tish mumkin emas: protsessorni kim olishini rejalashtiruvchi hal qiladi. C — New faqat yaratilish paytida. D — jarayon hali ishini tugatmagan.

**4. Javob: D (16)**
*Izoh:* `n` ta ketma-ket `fork()` dan keyin `2ⁿ` jarayon bo'ladi: `2⁴ = 16`; har biri bitta satr chop etadi.
A — `n` emas, `2ⁿ`. B — bu 3 ta `fork` uchun javob. C — 15 — bu faqat **yangi** jarayonlar soni, lekin `printf` ni asl jarayon ham bajaradi.

**5. Javob: B (Eski qiymatni)**
*Izoh:* `fork()` dan keyin bolada manzil maydonining o'z nusxasi bo'ladi (copy-on-write); bolaning yozuvi faqat uning nusxasini o'zgartiradi.
A — xotira oqimlar orasida umumiy, jarayonlar orasida emas. C, D — bunday sabab yo'q.

**6. Javob: C (Stekni)**
*Izoh:* har bir oqimning o'z steki (va registrlari) bor — aks holda funksiya chaqiruvlari aralashib ketardi.
A, B, D — global o'zgaruvchilar, ochiq fayllar va uyum barcha oqimlar uchun umumiy.

**7. Javob: A (FCFS)**
*Izoh:* FCFS da uzun vazifa oldinda bo'lsa, qisqa vazifalar uning orqasida "karvon" bo'lib kutadi.
B — RR har kimga kvant beradi, uzun vazifa boshqalarni to'sib qo'ymaydi. C — SRTF qisqa vazifalarni oldinga o'tkazadi. D — MLFQ uzun vazifalarni pastki navbatga tushiradi.

**8. Javob: C (`SIGKILL`)**
*Izoh:* `SIGKILL` va `SIGSTOP` ni ushlab ham, e'tiborsiz qoldirib ham bo'lmaydi — bu administratorning oxirgi chorasi.
A, B — `SIGTERM` va `SIGINT` ni ushlash mumkin (aynan toza yakunlash uchun). D — `SIGCHLD` odatda e'tiborsiz qoldiriladi va uni ushlash mumkin.

**9. Javob: B**
*Izoh:* `counter++` = LOAD, ADD, STORE; ikki oqim bir xil eski qiymatni o'qisa, bitta oshirish yo'qoladi (lost update).
A — kompilyator ikkinchi oqimni o'chirmaydi. C — global o'zgaruvchi oqimlar uchun umumiy. D — Linux oqimlarni bir nechta yadroda parallel bajaradi.

**10. Javob: D (Siklik kutish)**
*Izoh:* hamma resurslarni bitta global tartibda egallasa, "A ni ushlab B ni kutish" va "B ni ushlab A ni kutish" bir vaqtda bo'lolmaydi — sikl hosil bo'lmaydi.
A — myuteks baribir eksklyuziv. B — oqim hamon A ni ushlab B ni kutishi mumkin. C — myuteksni hamon tortib olib bo'lmaydi.

---

## 2-qism. Nazariy savollar

**1. Dastur va jarayon. Manzil maydoni.**
Dastur — diskdagi passiv fayl: mashina kodi va ma'lumotlar. Jarayon — bajarilish holatidagi dastur va OT unga ajratgan resurslar: virtual manzil maydoni (kod, ma'lumotlar, uyum, stek), ochiq fayllar, registrlar holati, PID, hisob ma'lumotlari. Bitta dasturdan bir vaqtda bir nechta jarayon ishga tushirish mumkin. Har bir jarayonning o'z virtual manzil maydoni **izolyatsiyani** ta'minlaydi: bir jarayon boshqasining xotirasini o'qiy yoki buza olmaydi. Bu ishonchlilik (bitta dasturning qulashi boshqalarga ta'sir qilmaydi) va xavfsizlik (parollar va maxfiy ma'lumotlar boshqa jarayonlardan himoyalangan) uchun muhim.

*Asosiy fikrlar:*
* dastur — passiv, jarayon — faol, resurslar bilan;
* bitta dastur → ko'p jarayon;
* o'z manzil maydoni → izolyatsiya → ishonchlilik va xavfsizlik.

**2. Jarayonning beshta holati.**
New — jarayon yaratilmoqda (`fork` ichida). New → Ready: OT tuzilmalarni tayyorladi. Ready → Running: rejalashtiruvchi uni tanladi (dispatch). Running → Ready: kvant tugadi yoki ustuvorroq jarayon paydo bo'ldi. Running → Waiting: jarayon `read()` bilan diskdan o'qishni yoki `sleep` ni chaqirdi. Waiting → Ready: disk ma'lumotni berdi, uzilish keldi. Running → Terminated: `exit()` chaqirildi yoki signal bilan o'ldirildi. Waiting → Running o'tishi yo'q, chunki I/O tugagan paytda protsessorda boshqa jarayon ishlayapti; jarayon avval navbatga turadi va protsessorni kimga berishni rejalashtiruvchi hal qiladi.

*Asosiy fikrlar:*
* 5 holat to'g'ri nomlangan;
* har bir o'tishga real misol;
* Waiting → Running yo'qligining sababi — qarorni rejalashtiruvchi qabul qiladi.

**3. Oqim va tola.**
Oqimni OT yadrosi rejalashtiradi va siqib chiqaradi; tolani dasturning o'zi rejalashtiradi, almashtirish kooperativ (`SwitchToFiber`, `yield`). Oqim almashtirish tizim chaqiruvini va yadroni talab qiladi (mikrosekundlar), tola almashtirish — bir nechta registrni saqlash (nanosekundlar). Oqimlar turli yadrolarda parallel ishlaydi; bitta oqimdagi tolalar — yo'q. Bitta oqim bloklovchi chaqiruv qilsa, faqat o'zi to'xtaydi; tola bloklovchi chaqiruv qilsa, shu oqimdagi **barcha** tolalar to'xtaydi. Oqimlar CPU-bound hisoblash va parallellik uchun afzal; tolalar va korutinalar asosan tarmoqni kutadigan juda ko'p (yuz minglab) yengil vazifalar uchun afzal.

*Asosiy fikrlar:*
* kim rejalashtiradi va almashtirish turi;
* narx va parallellik;
* bloklovchi chaqiruvning ta'siri;
* qo'llanish sohalari.

**4. Myuteks, semafor, shartli o'zgaruvchi.**
Myuteks — o'zaro istisno uchun qulf; uning egasi bor, uni faqat qulflagan oqim ochadi. Semafor — butun sonli hisoblagich (`wait` kamaytiradi yoki bloklaydi, `signal` oshiradi); egasi yo'q, N ta resursni sanash yoki oqimlar orasida signal berish (tartiblash) uchun ishlatiladi. Shartli o'zgaruvchi qulf emas: u oqimga ma'lum **shart** bajarilguncha (masalan, navbat bo'sh emas) uxlashga imkon beradi va doim myuteks bilan birga ishlatiladi. `pthread_cond_wait` `while` ichida chaqiriladi, chunki: (1) soxta uyg'onishlar (spurious wakeups) bo'lishi mumkin; (2) `signal` dan keyin myuteksni qayta egallashgacha boshqa oqim shartni o'zgartirishga (masalan, elementni olib ketishga) ulgurishi mumkin. Shuning uchun shartni har safar qayta tekshirish kerak.

*Asosiy fikrlar:*
* myuteks — egasi bor, istisno;
* semafor — hisoblagich, egasi yo'q, sanash/signal;
* shartli o'zgaruvchi — shartni kutish, myuteks bilan;
* `while` ning ikki sababi.

**5. IPC modellari.**
Xabar uzatishda ma'lumot yadro orqali o'tadi: jarayon `write`/`send` qiladi, yadro nusxalaydi, boshqasi `read`/`recv` qiladi. Misollar: `pipe`, FIFO, POSIX xabarlar navbati, soketlar. Afzalligi — oddiylik va yadro tomonidan sinxronizatsiya (bo'sh kanaldan o'qish o'zi kutadi); kamchiligi — har bir xabar nusxalanadi. Umumiy xotirada (`shm_open` + `mmap`, `mmap` qilingan fayl) jarayonlar bitta jismoniy xotirani ko'radi — nusxalash yo'q, eng tez. Lekin yadro kirishni muvofiqlashtirmaydi: o'quvchi yarim yozilgan ma'lumotni ko'rishi yoki ikkita yozuvchi bir-birining ma'lumotini buzishi mumkin. Shuning uchun umumiy xotira nomlangan semafor (`sem_open`) yoki `PTHREAD_PROCESS_SHARED` myuteks bilan himoyalanadi.

*Asosiy fikrlar:*
* ikki model va har biriga 2 misol;
* tezlik va oddiylik o'rtasidagi kelishuv;
* nima uchun umumiy xotiraga sinxronizatsiya kerak.

---

## 3-qism. Hisoblash va tahlil masalalari

### 3.1. Rejalashtirish

**Javob (o'rtacha qiymatlar):**

| Algoritm | O'rtacha kutish | O'rtacha aylanish |
|---|---|---|
| FCFS | 7,4 | 11,6 |
| SJF | 5,0 | 9,2 |
| **SRTF** | **4,4** | **8,6** |
| RR (q = 3) | 10,0 | 14,2 |

Eng yaxshisi — **SRTF**. RR da P4 ning javob vaqti — **7 ms**.

**Bosqichma-bosqich yechim.**

Formulalar: **aylanish = tugash − kelish**, **kutish = aylanish − burst**.

**FCFS** — kelish tartibida:

```
| P1        | P2    | P3      |P4| P5  |
0           7       11        17 18    21
```

| | Tugash | Aylanish | Kutish |
|---|---|---|---|
| P1 | 7 | 7 − 0 = 7 | 7 − 7 = 0 |
| P2 | 11 | 11 − 2 = 9 | 9 − 4 = 5 |
| P3 | 17 | 17 − 3 = 14 | 14 − 6 = 8 |
| P4 | 18 | 18 − 5 = 13 | 13 − 1 = 12 |
| P5 | 21 | 21 − 6 = 15 | 15 − 3 = 12 |

O'rtacha kutish = (0 + 5 + 8 + 12 + 12) / 5 = 37 / 5 = **7,4**. O'rtacha aylanish = 58 / 5 = **11,6**.

**SJF (siqib chiqarmaydigan)** — 0 da faqat P1 bor, u oxirigacha (0–7) bajariladi. 7 da tayyorlar: P2 (4), P3 (6), P4 (1), P5 (3) → P4, keyin P5, keyin P2, keyin P3:

```
| P1        |P4| P5  | P2    | P3      |
0           7  8     11      15        21
```

Kutish: P1 = 0, P4 = 7 − 5 = 2, P5 = 8 − 6 = 2, P2 = 11 − 2 = 9, P3 = 15 − 3 = 12. Yig'indi 25 → **5,0**. Aylanish: 7, 3, 5, 13, 18 → 46 / 5 = **9,2**.

**SRTF** — har bir kelishda qolgan vaqtlar solishtiriladi:

* 0: faqat P1 (qolgan 7) — ishlaydi.
* 2: P2 keladi (4); P1 da 5 qoldi. 4 < 5 → **P1 siqib chiqariladi**, P2 ishlaydi.
* 3: P3 keladi (6); P2 da 3 qoldi — P2 davom etadi.
* 5: P4 keladi (1); P2 da 1 qoldi — teng, kelishuv bo'yicha oldin kelgan P2 davom etadi va 6 da tugaydi.
* 6: P5 keladi (3). Tayyorlar: P1 (5), P3 (6), P4 (1), P5 (3) → P4 (6–7), P5 (7–10), P1 (10–15), P3 (15–21).

```
|P1 | P2    |P4| P5  | P1      | P3      |
0   2       6  7     10        15        21
```

| | Tugash | Aylanish | Kutish |
|---|---|---|---|
| P1 | 15 | 15 | 15 − 7 = 8 |
| P2 | 6 | 4 | 0 |
| P3 | 21 | 18 | 18 − 6 = 12 |
| P4 | 7 | 2 | 2 − 1 = 1 |
| P5 | 10 | 4 | 4 − 3 = 1 |

Kutish yig'indisi 22 → **4,4**; aylanish 43 → **8,6**.

**RR (q = 3)** — navbatning holati har bir kvantdan keyin:

| Vaqt | Bajariladi | Kvantdan keyin navbat |
|---|---|---|
| 0–3 | P1 (qoldi 4) | P2, P3, P1 |
| 3–6 | P2 (qoldi 1) | P3, P1, P4, P5, P2 |
| 6–9 | P3 (qoldi 3) | P1, P4, P5, P2, P3 |
| 9–12 | P1 (qoldi 1) | P4, P5, P2, P3, P1 |
| 12–13 | P4 (tugadi) | P5, P2, P3, P1 |
| 13–16 | P5 (tugadi) | P2, P3, P1 |
| 16–17 | P2 (tugadi) | P3, P1 |
| 17–20 | P3 (tugadi) | P1 |
| 20–21 | P1 (tugadi) | — |

(3 da P3 keladi va P1 dan oldin navbatga qo'yiladi; 6 da P4 va P5 P2 dan oldin qo'yiladi — kelishuvga ko'ra.)

```
| P1  | P2  | P3  | P1  |P4| P5  |P2| P3  |P1|
0     3     6     9     12 13    16 17    20 21
```

Kutish: P1 = 21 − 0 − 7 = 14, P2 = 17 − 2 − 4 = 11, P3 = 20 − 3 − 6 = 11, P4 = 13 − 5 − 1 = 7, P5 = 16 − 6 − 3 = 7. Yig'indi 50 → **10,0**; aylanish 71 → **14,2**.

P4 ning javob vaqti: birinchi marta 12 da ishga tushdi, keldi 5 da → **12 − 5 = 7**.

**Odatiy xato:** SRTF da 2-vaqtda P1 ni siqib chiqarishni unutish (natija SJF bilan bir xil chiqadi); SJF da 0-vaqtda hali kelmagan P4 ni tanlash; RR da yangi kelganlarni siqib chiqarilgan jarayondan keyin navbatga qo'yish (bunda javob boshqacha bo'ladi — kelishuvga qarang).

**Baholash:** to'rtta to'g'ri Gantt diagrammasi — 2 ball, to'g'ri o'rtacha qiymatlar — 1 ball, d) band — 1 ball.

### 3.2. Jarayonlarni sanash

**Javob:** A — **4** satr, B — **6** satr, C — **5** satr. (Kompilyatsiya qilib tekshirilgan.)

**Bosqichma-bosqich yechim.**

**A.** Sikl 2 marta aylanadi, ya'ni ketma-ket 2 ta `fork`: `2² = 4` jarayon, har biri `printf` qiladi.

```
P0 ─fork(i=0)─┬─ P0 ─fork(i=1)─┬─ P0
              │                └─ P2
              └─ P1 ─fork(i=1)─┬─ P1
                               └─ P3          → 4 ta "A"
```

**B.** Birinchi `fork()` → 2 jarayon (P0, P1). Ikkalasi ham `if (fork() == 0)` ni bajaradi → har biri bitta bola yaratadi (P2, P3) — jami 4. Faqat **yangi bolalarda** (`fork() == 0`) ichki `fork()` bajariladi → P2 va P3 yana bittadan bola yaratadi (P4, P5).

```
P0 ─┬─ P0 (if: ota) ─────────────── 
    │   └─ P2 (if: bola) ─┬─ P2
    │                     └─ P4
    └─ P1 ─┬─ P1 (if: ota)
           └─ P3 (if: bola) ─┬─ P3
                             └─ P5        → 6 ta "B"
```

**C.** `fork() || fork()`:
* P0 da birinchi `fork()` > 0 → `||` ning chap qismi rost → ikkinchisi **bajarilmaydi**, shart rost → `if` ichidagi `fork()` → P0 yana bola yaratadi (P2).
* P1 (birinchi `fork` ning bolasi) da birinchi `fork()` = 0 → ikkinchi `fork()` bajariladi → bola P3. P1 da u > 0 → shart rost → yana `fork()` → P4.
* P3 da ikkinchi `fork()` = 0 → shart yolg'on → hech narsa.

Jarayonlar: P0, P1, P2, P3, P4 → **5** ta "C".

**Odatiy xato:** C da 8 javobini berish (`||` ning qisqa tutashuvini hisobga olmaslik) yoki B da 8 (ichki `fork` faqat bolalarda bajarilishini e'tiborsiz qoldirish).

### 3.3. Jarayonlar va o'zgaruvchilar

**Javob:** 3 ta jarayon, uchta satr: `x=1`, `x=2`, `x=6` (tartibi har xil bo'lishi mumkin).

**Bosqichma-bosqich yechim.**
* Ota-jarayon: `fork()` > 0, `if` ga kirmaydi → `x=1`.
* 1-bola: `x = 1 · 2 = 2`; ichki `fork()`: bola uchun > 0 → `x=2` ni chop etadi.
* 2-bola (1-bolaning bolasi): 1-boladan `x = 2` ni meros oladi (nusxa!), `x = 2 · 3 = 6` → `x=6`.

Har bir jarayon `x` ning o'z nusxasini o'zgartiradi — ota-jarayon hech qachon 2 yoki 6 ni ko'rmaydi.

**Odatiy xato:** `x=1, x=2, x=6, x=6` yoki to'rtta jarayon — ichki `fork` ni faqat 1-bola bajarishini unutish.

### 3.4. Poyga holati

**Javob:** a) **15**; b) mumkin bo'lgan qiymatlar **12, 15, 7**; to'g'risi — **12**.

**Bosqichma-bosqich yechim.**

a)

| Qadam | A (R_A) | B (R_B) | balance |
|---|---|---|---|
| A.LOAD | 10 | | 10 |
| B.LOAD | | 10 | 10 |
| B.SUB | | 7 | 10 |
| B.STORE | | | 7 |
| A.ADD | 15 | | 7 |
| A.STORE | | | **15** |

B ning yozuvi yo'qoldi (A eski qiymat 10 ga asoslanib hisobladi).

b) Uch xil natija mumkin:
* amallar ketma-ket (biri ikkinchisining LOAD idan oldin to'liq tugaydi): 10 + 5 − 3 = **12** — to'g'ri;
* ikkalasi 10 ni o'qidi, A oxirgi yozdi: **15** (B ning −3 i yo'qoldi);
* ikkalasi 10 ni o'qidi, B oxirgi yozdi: **7** (A ning +5 i yo'qoldi).

c)

```c
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

// A oqim
pthread_mutex_lock(&m);
balance += 5;
pthread_mutex_unlock(&m);

// B oqim
pthread_mutex_lock(&m);
balance -= 3;
pthread_mutex_unlock(&m);
```

Muhim: **ikkala** oqim ham **bitta** myuteksdan foydalanishi kerak. Muqobil: `atomic_fetch_add(&balance, 5)` va `atomic_fetch_sub(&balance, 3)`.

**Odatiy xato:** faqat bitta oqimga qulf qo'yish yoki har bir oqimga alohida myuteks berish — bu hech narsani himoya qilmaydi.

### 3.5. Semafor izi

**Javob:** bir vaqtda resursdan **ko'pi bilan 2 ta** oqim foydalanadi; oxirida `S = 2`.

**Bosqichma-bosqich yechim** (ma'ruzadagi ta'rif: `S ≥ 0`, `wait` `S = 0` da bloklaydi; `signal` kutayotgan bo'lsa, birini uyg'otadi va u darhol `S` ni kamaytiradi):

| Qadam | Amal | S | Bloklangan | Resursda |
|---|---|---|---|---|
| — | boshlanish | 2 | — | — |
| 1 | T1: wait | 1 | — | T1 |
| 2 | T2: wait | 0 | — | T1, T2 |
| 3 | T3: wait | 0 | T3 | T1, T2 |
| 4 | T4: wait | 0 | T3, T4 | T1, T2 |
| 5 | T1: signal → T3 uyg'onadi | 0 | T4 | T2, T3 |
| 6 | T2: signal → T4 uyg'onadi | 0 | — | T3, T4 |
| 7 | T3: signal | 1 | — | T4 |
| 8 | T4: signal | 2 | — | — |

5- va 6-qadamlarda `S` oshmaydi: `signal` uni 1 ga oshiradi, uyg'ongan oqim darhol 0 ga kamaytiradi.

*Eslatma:* ba'zi darsliklarda semafor manfiy bo'lishi mumkin (manfiy qiymat = kutayotganlar soni). Bunday yozuvda 3–4-qadamlarda `S = −1, −2`, 5–6-qadamlarda `S = −1, 0` bo'ladi. Ikkala yondashuv ham to'g'ri, agar talaba qaysi ta'rifdan foydalanganini aytsa.

**Odatiy xato:** 5-qadamda `S = 1` deb yozish (uyg'ongan oqim joyni egallashini unutish) va natijada "3 ta oqim bir vaqtda" degan noto'g'ri xulosa.

### 3.6. Bankir algoritmi

**Javob:** a) Need: P0 (3,2,2), P1 (1,1,1), P2 (4,1,2), P3 (2,1,2); jami (6, 4, 6). b) Xavfsiz, **⟨P1, P2, P0, P3⟩**. c) **Ha**, berish mumkin (xavfsiz ketma-ketlik ⟨P1, P3, P0, P2⟩). d) **Yo'q** — holat xavfli, P0 kutadi.

**Bosqichma-bosqich yechim.**

a) Need = Max − Allocation:

| | Max | Allocation | Need |
|---|---|---|---|
| P0 | 4 3 2 | 1 1 0 | **3 2 2** |
| P1 | 3 1 2 | 2 0 1 | **1 1 1** |
| P2 | 5 2 3 | 1 1 1 | **4 1 2** |
| P3 | 2 2 4 | 0 1 2 | **2 1 2** |

Jami = Allocation ustunlari yig'indisi + Available = (4, 3, 4) + (2, 1, 2) = **(6, 4, 6)**.

b) Work = (2, 1, 2). Har safar eng kichik raqamli mos jarayonni tanlaymiz:

| Qadam | Tekshiruv | Natija | Work |
|---|---|---|---|
| 1 | P0 (3,2,2) ≤ (2,1,2)? A: 3 > 2 ✗; P1 (1,1,1) ≤ (2,1,2) ✓ | P1 tugaydi | (2,1,2) + (2,0,1) = (4,1,3) |
| 2 | P0 (3,2,2): B 2 > 1 ✗; P2 (4,1,2) ≤ (4,1,3) ✓ | P2 tugaydi | + (1,1,1) = (5,2,4) |
| 3 | P0 (3,2,2) ≤ (5,2,4) ✓ | P0 tugaydi | + (1,1,0) = (6,3,4) |
| 4 | P3 (2,1,2) ≤ (6,3,4) ✓ | P3 tugaydi | + (0,1,2) = (6,4,6) |

Hamma tugadi → **xavfsiz**, ⟨P1, P2, P0, P3⟩. (Boshqa xavfsiz ketma-ketliklar ham bor, masalan ⟨P3, P1, …⟩ — tanlash qoidasiga ko'ra yuqoridagi kutiladi.)

c) P3 so'rovi (1, 0, 1):
1. Request ≤ Need(P3) = (2,1,2) ✓.
2. Request ≤ Available = (2,1,2) ✓.
3. Shartli beramiz: Available = (1,1,1), Allocation(P3) = (1,1,3), Need(P3) = (1,1,1).
4. Xavfsizlik: Work (1,1,1) → P1 (1,1,1) ✓ → (3,1,2) → P0 ✗ (B), P2 ✗ (A 4 > 3), P3 (1,1,1) ✓ → (4,2,5) → P0 (3,2,2) ✓ → (5,3,5) → P2 ✓ → (6,4,6). Xavfsiz → **so'rov qondiriladi**.

d) P0 so'rovi (1, 1, 0):
1. Request ≤ Need(P0) = (3,2,2) ✓; Request ≤ Available ✓.
2. Shartli: Available = (1, **0**, 2); Need(P0) = (2,1,2).
3. Xavfsizlik: barcha jarayonlarning Need ida B ≥ 1 (P0: 1, P1: 1, P2: 1, P3: 1), Available da esa B = 0. Hech bir jarayon tugay olmaydi → **xavfli** → ajratish bekor qilinadi, P0 **kutadi**.

**Odatiy xato:** d) da "Available yetarli, demak berish mumkin" deb javob berish. Bankir algoritmi faqat 2-qadamni emas, **xavfsizlikni** ham tekshiradi. Shuningdek: xavfli holatni o'zaro bloklanish deb atash — hali bloklanish yo'q, faqat kafolat yo'q.

### 3.7. Resurslarni taqsimlash grafi

**Javob:** a) O'zaro bloklanish **yo'q**; tugash tartibi **P4 → P3 → P2 → P1**. b) Sikl paydo bo'ladi — **o'zaro bloklanish**, **to'rttala** jarayon bloklangan.

**Bosqichma-bosqich yechim.**

a)
```
P1 ──so'rov──▶ R2 ──▶ P2 ──so'rov──▶ R3 ──▶ P3 ──so'rov──▶ R4 ──▶ P4
▲                                                                (hech narsa so'ramaydi)
└── R1 (P1 da)
```
Zanjir bor, lekin sikl yo'q. P4 ga hech narsa kerak emas — u tugaydi va R4 ni bo'shatadi → P3 R4 ni oladi va tugaydi, R3 ni bo'shatadi → P2 tugaydi → P1 tugaydi.

b) P4 → R1 strelkasi qo'shiladi: P1 → R2 → P2 → R3 → P3 → R4 → P4 → R1 → P1. Har bir resurs bitta nusxada bo'lgani uchun sikl = o'zaro bloklanish. Siklda to'rttala jarayon ham bor → hammasi bloklangan.

**Odatiy xato:** a) da "zanjir bor — demak deadlock" deyish. Deadlock uchun yopiq **sikl** kerak.

---

## 4-qism. Amaliy laboratoriya topshiriqlari

### 4.1. Jarayonlar daraxti va `/proc`

**Buyruqlar va namunaviy natija:**

```bash
pstree -p $$
```
```
bash(2311)───pstree(2547)
```

```bash
ps -o pid,ppid,stat,ni,cmd --forest
```
```
    PID    PPID STAT  NI CMD
   2311    2305 Ss     0 -bash
   2548    2311 R+     0  \_ ps -o pid,ppid,stat,ni,cmd --forest
```

Ota-jarayonlar zanjiri `pstree -s -p $$` bilan ko'rinadi, masalan: `systemd(1)───sshd(812)───sshd(2290)───sshd(2305)───bash(2311)` (grafik muhitda: `systemd(1)───systemd(1050)───gnome-terminal-(...)───bash(...)`).

* `-p` — PID larni ko'rsatish; `-s` — ota-jarayonlarni ildizgacha ko'rsatish.
* `--forest` — `ps` da daraxt ko'rinishi; `STAT` dagi `s` — sessiya yetakchisi, `+` — oldingi plandagi guruh.

```bash
sleep 300 &
P=$!
grep -E 'Name|State|PPid|Threads|ctxt' /proc/$P/status
```
```
Name:	sleep
State:	S (sleeping)
PPid:	2311
Threads:	1
voluntary_ctxt_switches:	1
nonvoluntary_ctxt_switches:	0
```

* `$!` — oxirgi fon jarayonining PID i.
* `S (sleeping)` — `sleep` taymerni kutmoqda (Waiting).
* `voluntary_ctxt_switches` — jarayon o'zi necha marta protsessorni bo'shatgan.

```bash
kill -STOP $P; grep State /proc/$P/status     # State:	T (stopped)
kill -CONT $P; grep State /proc/$P/status     # State:	S (sleeping)
kill $P
```

**Xulosa:** `SIGSTOP` jarayonni `T` (to'xtatilgan) holatiga o'tkazadi, `SIGCONT` qaytaradi. PPid — qobiqning PID i, chunki aynan u `sleep` ni `fork` + `exec` qildi.

**Tekshirish:** `PPid` `echo $$` bilan mos kelishi kerak.

**Odatiy xatolar:** `/proc/$!/status` ni boshqa terminalda bajarish (`$!` faqat o'sha qobiqda ma'noga ega); `kill -STOP` dan keyin `kill -CONT` ni unutish.

### 4.2. Zombi va yetim

**Kod (`zombie.c`):**

```c
// gcc zombie.c -o zombie
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    pid_t pid = fork();
    if (pid == 0) {                    // bola darhol tugaydi
        printf("child %d: exit\n", getpid());
        exit(7);
    }
    printf("parent %d: child=%d, 30 s uxlayman\n", getpid(), pid);
    sleep(30);                         // bu vaqtda bola — zombi
    if (argc > 1) {                    // ./zombie w — wait bilan
        int st;
        waitpid(pid, &st, 0);
        printf("child exit code = %d\n", WEXITSTATUS(st));
    }
    return 0;
}
```

```bash
gcc zombie.c -o zombie
./zombie &
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'
```
```
   2601    2600 Z+   [zombie] <defunct>
```

* `Z` — zombi; `<defunct>` — jarayon tugagan, faqat jadval yozuvi qolgan.
* PPID = ota-jarayon (2600), u `wait` qilmagan.

`./zombie w` varianti 30 soniyadan keyin chop etadi:
```
child exit code = 7
```
Shundan keyin `ps` da zombi yo'q: `waitpid` yozuvni o'chirib tashladi va `exit(7)` dagi kodni oldi (`WEXITSTATUS`).

**Kod (`orphan.c`):**

```c
// gcc orphan.c -o orphan
#include <stdio.h>
#include <unistd.h>

int main(void) {
    if (fork() == 0) {
        for (int i = 0; i < 3; i++) {
            printf("child: PID=%d PPID=%d\n", getpid(), getppid());
            sleep(2);
        }
        return 0;
    }
    sleep(1);                          // ota 1 soniyadan keyin tugaydi
    printf("parent exits\n");
    return 0;
}
```

Namunaviy natija:
```
child: PID=2655 PPID=2654
parent exits
child: PID=2655 PPID=1
child: PID=2655 PPID=1
```

**Xulosa:** ota tugagach, bola yetim bo'ldi va uni boshqa jarayon asrab oldi. Serverda (SSH orqali) bu odatda `systemd` (PID 1). Grafik muhitda PPID `systemd --user` ning PID i bo'lishi mumkin: u **subreaper** sifatida ro'yxatdan o'tgan va yetimlarni o'zi qabul qiladi. `ps -p <PPID> -o cmd` bilan tekshiring.

**Odatiy xatolar:** zombini `kill -9 <zombi_PID>` bilan o'ldirishga urinish (hech narsa o'zgarmaydi — otani tugatish yoki `wait` kerak); natijani `| head` ga yo'naltirish (buferlash tufayli satrlar takrorlanishi mumkin).

### 4.3. Poyga va uni bartaraf etish

**Kod (`race3.c`):**

```c
// gcc -O0 -pthread race3.c -o race3      ./race3 0|1|2|3
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>

#define N 10000000
long counter = 0;
atomic_long acounter = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
int mode;

void *worker(void *arg) {
    if (mode == 0) {                       // himoyasiz
        for (int i = 0; i < N; i++) counter++;
    } else if (mode == 1) {                // myuteks
        for (int i = 0; i < N; i++) {
            pthread_mutex_lock(&m); counter++; pthread_mutex_unlock(&m);
        }
    } else if (mode == 2) {                // atomar
        for (int i = 0; i < N; i++) atomic_fetch_add(&acounter, 1);
    } else {                               // lokal yig'indi
        long local = 0;
        for (int i = 0; i < N; i++) local++;
        pthread_mutex_lock(&m); counter += local; pthread_mutex_unlock(&m);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    mode = argc > 1 ? atoi(argv[1]) : 0;
    pthread_t t1, t2;
    pthread_create(&t1, NULL, worker, NULL);
    pthread_create(&t2, NULL, worker, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("mode %d: counter = %ld\n", mode, mode == 2 ? (long)acounter : counter);
    return 0;
}
```

* `-O0` — optimallashtirishni o'chirish: aks holda kompilyator 0-rejimdagi siklni bitta qo'shishga aylantirib, poygani "yashirishi" mumkin.
* `-pthread` — POSIX oqimlari kutubxonasini ulash.

**Namunaviy natija** (vaqtlar kompyuterga bog'liq):

| Rejim | counter | real | To'g'rimi? |
|---|---|---|---|
| 0 — himoyasiz | 10 197 490 (har safar boshqacha) | ~0,3 s | Yo'q |
| 1 — myuteks | 20 000 000 | ~0,3–1,5 s | Ha |
| 2 — atomar | 20 000 000 | ~0,2–0,5 s | Ha |
| 3 — lokal yig'indi | 20 000 000 | ~0,01–0,05 s | Ha |

**Xulosa:**
* 0-rejim noto'g'ri: yo'qolgan yangilanishlar.
* 1 va 2 to'g'ri, lekin har bir oshirishda sinxronizatsiya narxi to'lanadi; ikkala oqim bitta kesh qatori uchun "kurashadi". Myuteks va atomarning nisbiy tezligi protsessor va yadroga bog'liq.
* 3-rejim eng tez va to'g'ri: oqimlar deyarli butun vaqt **umumiy ma'lumotga tegmaydi**, sinxronizatsiya faqat bir marta. Asosiy saboq: eng yaxshi sinxronizatsiya — **umumiy holatni kamaytirish**.

**Tekshirish:** 1–3-rejimlarda natija har doim aynan 20 000 000.

**Odatiy xatolar:** `-O2` bilan kompilyatsiya qilib "poyga yo'q" degan xulosaga kelish; 2-rejimda `atomic_long` o'rniga oddiy `long` ga `atomic_fetch_add` ni qo'llashga urinish; bitta yadroli VM da poyga kamroq ko'rinadi (VM ga kamida 2 ta CPU bering).

### 4.4. Kanal orqali konveyer

**Kod (`pipeline.c`):**

```c
// gcc pipeline.c -o pipeline      —  ps aux | wc -l
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    int fd[2];
    if (pipe(fd) == -1) { perror("pipe"); return 1; }

    if (fork() == 0) {                 // 1-bola: ps aux
        dup2(fd[1], STDOUT_FILENO);    // stdout -> kanalning yozish uchi
        close(fd[0]); close(fd[1]);
        execlp("ps", "ps", "aux", (char *)NULL);
        perror("execlp ps"); _exit(127);
    }
    if (fork() == 0) {                 // 2-bola: wc -l
        dup2(fd[0], STDIN_FILENO);     // stdin <- kanalning o'qish uchi
        close(fd[0]); close(fd[1]);
        execlp("wc", "wc", "-l", (char *)NULL);
        perror("execlp wc"); _exit(127);
    }
    close(fd[0]); close(fd[1]);        // ota ikkala uchni yopadi!
    while (wait(NULL) > 0) { }         // ikkala bolani kutish
    return 0;
}
```

Har bir qadamning ma'nosi:
* `pipe(fd)` — kanal; `fd[0]` o'qish, `fd[1]` yozish.
* `dup2(fd[1], STDOUT_FILENO)` — 1-deskriptor (stdout) endi kanalga yo'naltirilgan; `ps` o'zgartirishsiz kanalga yozadi — u buni bilmaydi ham.
* `dup2` dan keyin asl deskriptorlar yopiladi: ular endi keraksiz va ochiq qolsa, EOF ga xalaqit beradi.
* `execlp` — `PATH` bo'yicha dasturni topib, jarayonni u bilan almashtiradi.
* Ota-jarayon ham ikkala uchni yopadi va ikkala bolani kutadi (zombi qolmasligi uchun).

**Namunaviy natija:**
```
$ ./pipeline
187
$ ps aux | wc -l
187
```
(Raqamlar biroz farq qilishi mumkin: `pipeline` ning o'zi va uning bolalari ham hisoblanadi.)

**Tajriba:** otadagi `close(fd[1])` izohga olinsa, dastur **qotib qoladi**. Sabab: `wc` kanalni EOF gacha o'qiydi. EOF faqat **barcha** yozish uchlari yopilganda keladi. `ps` tugab, o'z uchini yopdi, lekin ota-jarayonda `fd[1]` hali ochiq — yadro "yana yozuvchi bor" deb hisoblaydi. `wc` abadiy kutadi, ota esa `wait` da `wc` ni kutadi — ikkalasi to'xtab qoladi.

**Tekshirish:** `strace -f ./pipeline 2>&1 | grep -E 'pipe|dup2|execve'` — `pipe2`, `dup2` va ikkita `execve` ko'rinadi.

**Odatiy xatolar:** `dup2` argumentlari tartibini adashtirish (`dup2(eski, yangi)`); bolalarda keraksiz uchlarni yopmaslik; ota-jarayonda `wait` ni chaqirmaslik.

### 4.5. Signallar va toza yakunlash

**Kod (`worker.c`):**

```c
// gcc worker.c -o worker
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

volatile sig_atomic_t stop = 0;
void on_term(int sig) { stop = 1; }

int main(void) {
    struct sigaction sa = {0};
    sa.sa_handler = on_term;
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);

    long n = 0;
    printf("worker PID=%d\n", getpid());
    while (!stop) { n++; usleep(100000); }      // "ish": 0,1 s da bitta qadam

    FILE *f = fopen("state.txt", "w");          // holatni saqlash
    fprintf(f, "%ld\n", n);
    fclose(f);
    printf("saved n=%ld, bye\n", n);
    return 0;
}
```

* Ishlovchi faqat bayroq qo'yadi — `fopen`/`fprintf` xavfsiz bo'lmagan funksiyalar, ular asosiy kodda.
* `volatile sig_atomic_t` — kompilyator o'zgaruvchini registrda "qotirib" qo'ymasligi va yozish bo'linmas bo'lishi uchun.

```bash
./worker & P=$!; sleep 3; kill -TERM $P; wait $P; cat state.txt
```
```
worker PID=2810
saved n=30, bye
30
```

```bash
rm -f state.txt
./worker & P=$!; sleep 3; kill -KILL $P; wait $P; ls state.txt
```
```
worker PID=2815
[1]+  Killed                  ./worker
ls: cannot access 'state.txt': No such file or directory
```

**Xulosa:** `SIGTERM` ni dastur ushladi va ishini saqlab chiqdi. `SIGKILL` ni ushlab bo'lmaydi — yadro jarayonni darhol yo'q qiladi, ishlovchi ham, saqlash kodi ham bajarilmaydi. Shuning uchun xizmatlarni to'xtatish tartibi: avval `SIGTERM`, kutish, faqat keyin `SIGKILL` (`systemctl stop` aynan shunday qiladi).

**Skript (`cleanup.sh`):**

```bash
#!/bin/bash
TMP=$(mktemp /tmp/lab6.XXXXXX)
trap 'echo "Ctrl+C: $TMP o'\''chirilmoqda"; rm -f "$TMP"; exit 130' INT TERM
echo "vaqtinchalik fayl: $TMP"
while true; do date >> "$TMP"; sleep 1; done
```

* `mktemp` — noyob vaqtinchalik fayl yaratadi.
* `trap '…' INT TERM` — `SIGINT` va `SIGTERM` uchun ishlovchi.
* `exit 130` — an'anaga ko'ra 128 + signal raqami (2).

**Tekshirish:** Ctrl+C dan keyin `ls /tmp/lab6.*` — fayl yo'q.

**Odatiy xatolar:** ishlovchida `printf` yoki `fopen` ni chaqirish; `signal()` va `sigaction()` ni adashtirish (ikkalasi ham ishlaydi, lekin `sigaction` ning xatti-harakati aniqroq).

### 4.6. Rejalashtiruvchi va `nice`

```bash
taskset -c 0 nice -n 0  yes > /dev/null &
taskset -c 0 nice -n 19 yes > /dev/null &
ps -o pid,ni,pcpu,psr,cmd -C yes
```

Namunaviy natija (`top` dan):
```
    PID  NI  %CPU  P  COMMAND
   3001   0  98.7  0  yes
   3002  19   1.3  0  yes
```

* `taskset -c 0` — jarayonni faqat 0-yadroda ishlashga majbur qilish; aks holda ular turli yadrolarda ishlab, har biri 100% oladi va farq ko'rinmaydi.
* `nice -n 19` — eng past ustuvorlik.

**Kutilgan ulushlar** (CFS/EEVDF CPU ni vaznlarga proporsional taqsimlaydi):

* nice 0 va 19: 1024 / (1024 + 15) = **98,6 %** va 15 / 1039 = **1,4 %**.
* `renice -n 5 -p 3002` dan keyin: nice 0 va 5: 1024 / (1024 + 335) = **75,3 %** va 335 / 1359 = **24,7 %**.

```
    PID  NI  %CPU  COMMAND
   3001   0  75.1  yes
   3002   5  24.8  yes
```

**Xulosa:** o'lchangan qiymatlar nazariyaga ±1–2 % aniqlikda mos keladi. Nice shkalasi bir pog'onada taxminan 1,25 marta vazn o'zgarishiga mo'ljallangan, shuning uchun 0 va 19 orasidagi farq juda katta.

**Tozalash:**
```bash
killall yes
```

**Odatiy xatolar:** `taskset` siz o'lchash (farq ko'rinmaydi); nice ni manfiy qiymatga o'zgartirishga oddiy foydalanuvchi sifatida urinish (`Permission denied` — faqat root).

### Laboratoriyadan keyin tozalash

```bash
killall yes sleep 2>/dev/null
rm -f ~/lab6/state.txt /tmp/lab6.*
```

---

## 5-qism. Keys topshiriqlari

### 5.1. Loyihalash: avtomatik dasturlarni tekshirish tizimi

**Namunaviy yechim.**

| Komponent | Birlik | Asos |
|---|---|---|
| HTTP qabul qiluvchi (300 ulanish) | **Oqimlar hovuzi** (masalan, 16 oqim) yoki asinxron korutinalar | Asosan tarmoqni kutadi (I/O-bound); har ulanishga jarayon — qimmat |
| Tekshiruvchi ishchilar (kompilyatsiya + testlar) | **Jarayonlar hovuzi**, CPU yadrolari soniga teng (masalan, 8) | CPU-bound; parallellik kerak |
| Talaba kodini ishga tushirish | **Har bir test uchun alohida jarayon** (`fork` + `exec`) | Ishonchsiz kod: izolyatsiya, cheklovlar, o'ldirish imkoniyati faqat jarayonda |
| Jurnal yozuvchi | Alohida oqim (yoki jarayon) | Yagona yozuvchi — fayldagi aralashuvning oldini oladi |
| Ma'lumotlar bazasi | Alohida jarayon (MBBT) | Tayyor, o'zi tranzaksiyalarni sinxronlaydi |

**IPC:**
* qabul qiluvchi → ishchilar: **navbat** (POSIX xabarlar navbati yoki Unix soketi; yirik tizimda — Redis/RabbitMQ) — ishlab chiqaruvchi–iste'molchi modeli;
* ishchi → talaba dasturi: **kanallar** — test kirishi `stdin` ga, natija `stdout` dan (`pipe` + `dup2`, 4.4 dagi kabi);
* ishchilar → statistika: umumiy xotira + semafor yoki MB ga yozish;
* ishchilar → jurnal: kanal yoki Unix soketi orqali jurnal yozuvchiga.

**Sinxronizatsiya:**
* vazifalar navbati — myuteks + shartli o'zgaruvchi (yoki navbatning o'zi bloklovchi IPC bo'lsa — yadro sinxronlaydi);
* statistika hisoblagichlari ("tekshirildi", "o'tdi") — atomar o'zgaruvchilar (yoki har bir ishchi lokal sanab, davriy qo'shadi);
* jurnal — bitta yozuvchi jarayon yoki `O_APPEND` + bitta `write()` chaqiruvida butun satr;
* MB — tranzaksiyalar.

**Talaba kodini xavfsiz ishga tushirish:**
* `fork()` → bolada cheklovlar → `exec()`;
* `setrlimit(RLIMIT_CPU, 2 s)` — CPU vaqti (oshsa, `SIGXCPU`/`SIGKILL`);
* `RLIMIT_AS` — xotira (masalan, 256 MiB);
* `RLIMIT_NPROC` — fork bombaga qarshi (masalan, 1);
* `RLIMIT_FSIZE` — yoziladigan fayl hajmi;
* devor soati bo'yicha taymaut: ota `alarm()` yoki `waitpid` + taymer, oshsa `kill(SIGKILL)` (talaba kodi `SIGTERM` ni ushlab olishi mumkin, shuning uchun `SIGKILL`);
* imtiyozsiz foydalanuvchi (`nobody`), alohida vaqtinchalik katalog, tarmoqsiz; zamonaviy variant — cgroups v2 va namespaces (konteyner, `nsjail`, `firejail`);
* har bir test jarayoni uchun `waitpid` — zombilar yo'q, `WIFSIGNALED` orqali "vaqt oshib ketdi"/"xotira yetmadi" ni aniqlash.

**O'zaro bloklanish xavflari va oldini olish:**
* navbat va statistika myutekslarini turli tartibda egallash → **yagona tartib** yoki bir vaqtda faqat bitta myuteks ushlash;
* ishchi talaba dasturiga katta kirish yozmoqda (`write` bloklandi, kanal to'la), talaba dasturi esa natijani yozmoqda va ota uni o'qimayapti (u ham bloklandi) → kirish va chiqishni alohida oqimlarda yoki `poll()` bilan bajarish, kirishni fayl orqali berish;
* qulfni ushlab turib MB ga yozish (sekin I/O) → qulfdan tashqarida yozish.

**Baholash mezonlari (8 ball):**

| Mezon | Ball |
|---|---|
| a) Jarayon/oqim tanlovi asoslangan, ishonchsiz kod uchun jarayon tanlangan | 2 |
| b) IPC mexanizmlari mos va asoslangan | 1,5 |
| c) Kamida 3 ta umumiy resurs va mos primitivlar | 1,5 |
| d) Kamida 3 ta cheklov (CPU, xotira, jarayonlar soni, taymaut + `SIGKILL`) | 2 |
| e) Kamida bitta real deadlock xavfi va oldini olish usuli | 1 |

Boshqa asoslangan variantlar ham qabul qilinadi (masalan, butunlay asinxron server yoki konteynerlar). Asosiy talab — ishonchsiz kod izolyatsiya qilingan **jarayonda** cheklovlar bilan ishlaydi.

### 5.2. Kodni tahlil qilish: xatolarni toping

**Topilishi kerak bo'lgan xatolar:**

| № | Joy | Xato | Oqibat |
|---|---|---|---|
| 1 | `on_term` | Ishlovchida `printf` va `exit` — async-signal-safe emas | Signal `printf` yoki `malloc` ichida kelsa — libc ichida bloklanish yoki buzilgan bufer; `exit` atexit funksiyalarini va stdio ni ishga tushiradi |
| 2 | `transfer` | Myutekslar argumentlar tartibida olinadi | `transfer(a,b)` va `transfer(b,a)` bir vaqtda → **o'zaro bloklanish** |
| 3 | `transfer` | `return -1` dan oldin `unlock` yo'q | Ikkala hisob myuteksi abadiy band — keyingi barcha o'tkazmalar qotadi |
| 4 | `take_job` | `pthread_cond_wait` oldida `if` | Soxta uyg'onish yoki boshqa oqim ishni olib ketsa — `queue_len` manfiy bo'ladi, mavjud bo'lmagan ish olinadi |
| 5 | `worker` | `done_jobs++` sinxronizatsiyasiz | Poyga — statistika yo'qoladi |
| 6 | `count_processes` | Otada `fd[1]` yopilmaydi | `read` hech qachon 0 qaytarmaydi — funksiya **abadiy qotadi** |
| 7 | `count_processes` | `waitpid` yo'q | Har bir chaqiruv zombi qoldiradi → minglab zombi, PID lar tugaydi |
| 8* | `count_processes` | `fd[0]` (va xatoda `fd[1]`) yopilmaydi; `pipe`/`fork` natijalari tekshirilmaydi | Har chaqiruvda 2 ta deskriptor oqib ketadi → `EMFILE` ("Too many open files") |
| 9* | bola | `dup2` dan keyin `fd[0]`, `fd[1]` yopilmaydi | `ps` keraksiz deskriptorlarni meros qiladi (zararsizroq, lekin noto'g'ri) |

\* — qo'shimcha xatolar, bonus sifatida hisoblanadi.

**Tuzatilgan kod:**

```c
volatile sig_atomic_t stop = 0;
atomic_long done_jobs = 0;                       // (5) atomar

void on_term(int sig) { stop = 1; }              // (1) faqat bayroq

int transfer(account_t *from, account_t *to, long sum) {
    account_t *first  = from < to ? from : to;   // (2) yagona tartib: manzil bo'yicha
    account_t *second = from < to ? to : from;
    int rc = 0;
    pthread_mutex_lock(&first->m);
    pthread_mutex_lock(&second->m);
    if (from->balance < sum) {
        rc = -1;                                 // (3) chiqishdan oldin unlock
    } else {
        from->balance -= sum;
        to->balance   += sum;
    }
    pthread_mutex_unlock(&second->m);
    pthread_mutex_unlock(&first->m);
    return rc;
}

void take_job(void) {
    pthread_mutex_lock(&qm);
    while (queue_len == 0)                       // (4) while
        pthread_cond_wait(&qc, &qm);
    queue_len--;
    pthread_mutex_unlock(&qm);
}

void *worker(void *arg) {
    while (!stop) {
        take_job();
        atomic_fetch_add(&done_jobs, 1);         // (5)
    }
    return NULL;
}

long count_processes(void) {
    int fd[2];
    if (pipe(fd) == -1) return -1;               // (8)
    pid_t pid = fork();
    if (pid == -1) { close(fd[0]); close(fd[1]); return -1; }
    if (pid == 0) {
        dup2(fd[1], STDOUT_FILENO);
        close(fd[0]); close(fd[1]);              // (9)
        execlp("ps", "ps", "aux", (char *)NULL);
        _exit(127);
    }
    close(fd[1]);                                // (6) EOF uchun
    char buf[4096]; long lines = 0; ssize_t n;
    while ((n = read(fd[0], buf, sizeof buf)) > 0)
        for (ssize_t i = 0; i < n; i++) if (buf[i] == '\n') lines++;
    close(fd[0]);                                // (8) deskriptor oqmasin
    waitpid(pid, NULL, 0);                       // (7) zombi qolmasin
    return lines;
}
```

Izohlar:
* (1) Ishlovchi faqat bayroq qo'yadi; asosiy sikllar `stop` ni tekshiradi va toza yakunlaydi. Ishlovchini `sigaction` bilan o'rnatish yaxshiroq.
* (2) Myutekslar har doim bir xil tartibda olinadi (manzil yoki hisob ID si bo'yicha) — siklik kutish imkonsiz.
* (4) `while` shartni uyg'ongandan keyin qayta tekshiradi.

**Baholash (7 ball):** har bir to'g'ri topilgan va tushuntirilgan xato — 1 ball (ko'pi bilan 5), tuzatilgan kod — 2 ball. 6 va undan ko'p xato topilsa — maksimal ball.

---

## 6-qism. Murakkab topshiriqlar (bonus)

### 6.1. Buferlash jumbog'i

**Javob:** a) `AB` ikki marta; b) terminalda **6** satr (`i=0` — 2, `i=1` — 4), `| cat` bilan **8** satr (`i=0` — 4, `i=1` — 4). (Tekshirilgan.)

**Tushuntirish.**

a) `printf("A")` satrni tugatmaydi (`\n` yo'q), shuning uchun "A" `stdout` buferida qoladi. `fork()` jarayon xotirasini, demak buferni ham nusxalaydi. Endi ikkala jarayonning buferida "A" bor. Har biri "B\n" qo'shadi va bufer chiqariladi → `AB` ikki marta. Bu terminalda ham, kanalda ham shunday.

b) `stdout` ning buferlash rejimi qayerga yozilayotganiga bog'liq:
* **terminal** — satrli buferlash: har bir `\n` da bufer tozalanadi. `fork` paytida bufer bo'sh. i=0 da 2 ta jarayon → `i=0` 2 marta; i=1 da 4 ta jarayon → `i=1` 4 marta. Jami **6**.
* **kanal yoki fayl** — to'liq buferlash (bufer 4 KiB): jarayon tugaguncha hech narsa chiqmaydi.
  * 1-`fork` dan keyin 2 jarayon, har biri buferga `i=0\n` yozadi.
  * 2-`fork` buferlarni nusxalaydi → 4 jarayon, har birining buferida `i=0\n`, keyin `i=1\n` qo'shiladi.
  * Chiqishda 4 ta jarayon `i=0\ni=1\n` ni chiqaradi → `i=0` 4 marta, `i=1` 4 marta. Jami **8**.

c) Har bir `fork()` dan oldin `fflush(stdout)`:

```c
printf("A");
fflush(stdout);
fork();
printf("B\n");          // natija: A, B, B  (ya'ni "AB\nB\n")
```

```c
for (int i = 0; i < 2; i++) {
    fflush(stdout);
    fork();
    printf("i=%d\n", i);
}
fflush(stdout);         // har doim 6 satr
```

Yoki dastur boshida `setvbuf(stdout, NULL, _IONBF, 0)`, yoki `printf` o'rniga `write(1, ...)` (u buferlanmaydi).

### 6.2. Poygadagi eng kichik natija

**Javob:** a) **10**; b) **2**; c) quyidagi isbot.

a) Navbatlashuv amallarni yo'qotmasa (har bir LOAD oldingi STORE dan keyin), 2 · 5 = **10**.

b) Eng kichik qiymatga olib keladigan navbatlashuv (A va B — oqimlar):

| № | Harakat | counter |
|---|---|---|
| 1 | A: LOAD (R_A = 0) | 0 |
| 2 | B: 4 ta to'liq oshirish (LOAD/STORE × 4) | 4 |
| 3 | A: STORE R_A + 1 = 1 | **1** (B ning 4 ta oshirishi o'chirildi) |
| 4 | B: LOAD (R_B = 1) — B ning 5-oshirishi boshlandi | 1 |
| 5 | A: qolgan 4 ta to'liq oshirish | 5 |
| 6 | B: STORE R_B + 1 = 2 | **2** (A ning 4 ta oshirishi o'chirildi) |

A 5 marta (1+4), B 5 marta (4+1) oshirdi, natija — **2**. (Kichik `n` lar uchun barcha navbatlashuvlarni to'liq saralab chiqish bilan tekshirilgan: `n ≥ 2` da minimum har doim 2.)

c) **Isbot:**
1. Har qanday STORE `R + 1 ≥ 1` qiymatini yozadi; boshlang'ich 0 dan tashqari counter ga 0 hech qachon yozilmaydi.
2. Oxirgi STORE ni qaysidir oqim, masalan X, bajardi va u `v + 1` ni yozdi, bunda `v` — X ning **oxirgi** LOAD ida o'qilgan qiymat.
3. X 5 marta oshiradi, demak oxirgi LOAD idan **oldin** u kamida bitta STORE bajargan. O'sha paytdan boshlab counter ≥ 1 (1-band bo'yicha, 0 qayta yozilmaydi).
4. Demak `v ≥ 1` va yakuniy qiymat `v + 1 ≥ 2`.

Shu sababli natija 1 yoki 0 bo'la olmaydi. (`n = 1` da 3-band ishlamaydi va minimum 1 ga teng.)

**Baholash:** a) — 1 ball; b) to'g'ri qiymat va ishlaydigan navbatlashuv — 2 ball; c) isbot — 2 ball.

---

## Baholash mezonlari (umumiy)

| Qism | Maks. | To'liq ball | Qisman ball |
|---|---|---|---|
| 1. Test | 20 | Har bir to'g'ri javob — 2 ball | — |
| 2. Nazariy | 15 | Har savol 3 ball: barcha "asosiy fikrlar" bor, misollar to'g'ri | 1–2 ball: fikrlarning bir qismi bor yoki misol yo'q |
| 3. Hisoblash | 20 | To'g'ri javob **va** to'liq yechim (Gantt, jadvallar, qadamlar) | Usul to'g'ri, arifmetik xato — ballning yarmi; faqat javob, yechimsiz — 1 ball |
| 4. Amaliy | 30 | Har topshiriq 5 ball: ishlaydigan kod/buyruqlar (2), natija (1), to'g'ri tushuntirish (2) | Natija bor, lekin tushuntirish yo'q yoki noto'g'ri — 2–3 ball |
| 5. Keys | 15 | 5.1 — 8 ball, 5.2 — 7 ball (mezonlar yuqorida) | Mezonlar bo'yicha |
| 6. Bonus | +10 | 6.1 — 5 ball, 6.2 — 5 ball | Mezonlar bo'yicha |

**Baholash shkalasi (ixtiyoriy):** 86–100 — a'lo, 71–85 — yaxshi, 55–70 — qoniqarli, 55 dan kam — qoniqarsiz.
