// สำหรับ SYS_write
int main(void) {
const char msg[] = "Hello POSIX!\n";
// syscall(SYS_write, fd, buffer, length)
syscall(SYS_write, STDOUT_FILENO, msg, sizeof(msg) - 1);
syscall(SYS_exit, 0);
return 0;
}
```
---
## ผลลัพธ์การรันด้วย strace
```bash
$ gcc syscall_direct.c -o syscall_direct
$ ./syscall_direct
Hello POSIX!
$ strace -e write,exit_group ./syscall_direct
write(1, "Hello POSIX!\n", 13Hello POSIX!
) = 13
exit_group(0) = ?
+++ exited with 0 +++
```
`strace` ช่วยให้เห็น system call ที่โปรแกรมเรียกจริง ๆ ในระหว่างการทำงาน
---
## 1.1.7 Boot Process (UEFI Linux)
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart TB
A["Power On"] --> B["UEFI Firmware
POST + Init HW"]
B --> C["EFI System Partition"]
C --> D["Bootloader: GRUB / systemd-boot"]
D --> E["Load Kernel + initramfs"]
E --> F["Kernel Init: mount rootfs"]
F --> G["pid=1: systemd / OpenRC"]
G --> H["User Space: Services + Login"]
---
## ขั้นตอน Boot โดยละเอียด
1. **POST (Power-On Self-Test)** — UEFI/BIOS ตรวจสอบฮาร์ดแวร์พื้นฐาน CPU, RAM, GPU
2. **Bootloader** — UEFI อ่าน `.efi` จาก EFI System Partition (ESP) แล้วโหลด GRUB หรือ systemd-boot
3. **Kernel Loading** — Bootloader โหลด kernel image (`vmlinuz-*`) และ initial ramdisk (`initramfs-*`)
4. **Kernel Initialization** — Kernel unpacks initramfs, probe driver, mount root filesystem จริง
5. **Init System** — Process แรก (pid=1) เริ่มต้น service, mount filesystem เพิ่มเติม, สร้าง getty/login
---
## Init System ที่พบบ่อย
- **systemd** — ใช้ในเกือบทุก distro หลัก (Ubuntu, Fedora, Arch, Debian) มี unit file (`.service`, `.timer`, `.socket`, `.target`)
- **OpenRC** — ใช้ใน Gentoo, Alpine Linux รูปแบบ shell script
- **runit** — ใช้ใน Void Linux เน้นความเรียบง่ายและเร็ว
- **s6** — เน้น process supervision และ correctness
- **SysVinit** — แบบเดิม ใช้ runlevel และ `/etc/rc.d/` (ปัจจุบันแทบไม่ใช้แล้ว)
---
## ตัวอย่าง systemd service file
```ini
# /etc/systemd/system/hello.service
[Unit]
Description=Hello World Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/hello.sh
Restart=on-failure
User=nobody
[Install]
WantedBy=multi-user.target
```
---
## คำสั่งจัดการ systemd service
```bash
# เปิดใช้งานและเริ่ม service
sudo systemctl daemon-reload
sudo systemctl enable hello.service
sudo systemctl start hello.service
# ตรวจสอบสถานะ
systemctl status hello.service
journalctl -u hello.service -f
```
`systemctl` ใช้ควบคุม service ส่วน `journalctl` ใช้ดู log
---
# 1.2 การจัดการโพรเซส
## Process Management
---
## 1.2.1 Process คืออะไร
**Process (โพรเซส)** คือโปรแกรมที่กำลังทำงาน (program in execution)
แตกต่างจาก **program** ที่เป็นเพียงไฟล์บน disk
โพรเซสประกอบด้วย:
- **Text Section:** โค้ดโปรแกรม (instruction)
- **Data Section:** global และ static variable
- **Heap:** หน่วยความจำที่จองแบบ dynamic (`malloc()`, `new`)
- **Stack:** local variable, function argument, return address
- **Program Counter (PC)** และ **CPU Register**
---
## Process Memory Layout
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart TB
S["Stack (เติบโตลง ↓)"]
F["... Free Space ..."]
H["Heap (เติบโตขึ้น ↑)"]
B["BSS (uninitialized data)"]
D["Data (initialized global)"]
T["Text (โค้ด)"]
S --> F --> H --> B --> D --> T
---
## Process Control Block (PCB)
**PCB** คือโครงสร้างข้อมูลใน kernel ที่เก็บข้อมูลทุกอย่างที่ต้องใช้ในการจัดการ process แต่ละตัว
บน Linux เรียกว่า `struct task_struct` (ในไฟล์ `include/linux/sched.h`)
ข้อมูลสำคัญใน PCB:
- **Process ID (PID)** และ **Parent PID (PPID)**
- **Process State** (New, Ready, Running, Waiting, Terminated)
- **Program Counter** และ **CPU Register**
- **Memory Management Info** (page table)
- **Scheduling Info** (priority, policy, nice value)
- **I/O Status** (open file descriptor)
---
## 1.2.2 สถานะของ Process
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
stateDiagram-v2
[*] --> New: fork()
New --> Ready: admit
Ready --> Running: scheduler dispatch
Running --> Ready: timer interrupt
Running --> Waiting: I/O request
Waiting --> Ready: I/O done
Running --> Terminated: exit()
Terminated --> [*]
---
## คำอธิบายแต่ละสถานะ
- **New:** กำลังถูกสร้าง ยังไม่เข้า ready queue
- **Ready:** พร้อมทำงาน แต่รอ CPU ว่าง
- **Running:** กำลังถูก CPU ประมวลผล
- **Waiting (Blocked):** รอเหตุการณ์ภายนอก เช่น I/O, signal, mutex
- **Terminated (Zombie):** ทำงานเสร็จแล้ว แต่ยังไม่ถูก parent เรียก `wait()`
บน Linux ดูได้ด้วย `ps` ตัวอักษรที่ปรากฏ:
`R`=Running, `S`=Sleep, `D`=Uninterruptible Sleep, `Z`=Zombie, `T`=Stopped
---
## 1.2.3 Process Scheduling Queues
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart LR
RQ["Ready Queue"] -->|"dispatch"| CPU["CPU"]
CPU -->|"time slice expired"| RQ
CPU -->|"I/O request"| DQ1["Disk Queue"]
CPU -->|"I/O request"| DQ2["Network Queue"]
CPU -->|"fork()"| RQ
DQ1 -->|"I/O done"| RQ
DQ2 -->|"I/O done"| RQ
CPU -->|"exit"| END["Terminate"]
---
## ประเภทของ Queue
**Job Queue:** รวมทุกโพรเซสในระบบทั้งหมด
**Ready Queue:** โพรเซสที่อยู่ในหน่วยความจำหลักและพร้อมทำงาน รอ CPU
**Device Queue:** แต่ละ I/O device มี queue ของตนเอง โพรเซสที่รอ I/O จะเข้า queue นี้
OS ใช้ queue เหล่านี้เพื่อจัดลำดับการเข้าถึง CPU และอุปกรณ์ต่าง ๆ อย่างเป็นระบบ ลด overhead และเพิ่มประสิทธิภาพการใช้ทรัพยากร
---
## 1.2.4 fork(), exec(), wait()
บน UNIX/Linux การสร้าง process ใหม่ใช้ **`fork()`**
- **copy** ทุกอย่างจาก parent สร้างเป็น child
- ในทางปฏิบัติใช้ Copy-on-Write (CoW)
มักตามด้วย **`exec*()`**
- เพื่อโหลดโปรแกรมใหม่ทับ memory image
- ตระกูลฟังก์ชัน: `execl`, `execlp`, `execle`, `execv`, `execvp`, `execve`
- ต่างกันที่การส่ง argument, การค้นหาใน PATH, environment
**`wait()`** ใช้รอ child process จบ ป้องกัน zombie
---
## Fork Sequence Diagram
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
sequenceDiagram
participant P as Parent
participant C as Child
P->>P: fork()
Note over P,C: Kernel สร้าง PCB ใหม่ copy address space (CoW)
P->>P: pid = child_pid
C->>C: pid = 0
C->>C: execve("/bin/ls", ...)
P->>P: waitpid(child_pid, ...)
C-->>P: SIGCHLD + exit status
---
## ตัวอย่างโค้ด fork + exec (1)
```c
// fork_exec.c - แสดงตัวอย่าง fork() + execvp() + waitpid()
#include
#include
#include
#include
int main(void) {
printf("[Parent] pid=%d กำลังจะ fork()\n", getpid());
pid_t pid = fork(); // สร้าง child process
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
// (ส่วน child/parent ต่อในหน้าถัดไป)
}
```
---
## ตัวอย่างโค้ด fork + exec (2)
```c
if (pid == 0) {
// --- Child Process ---
printf("[Child] pid=%d ppid=%d กำลังจะ exec ls\n",
getpid(), getppid());
char *args[] = {"ls", "-l", "/tmp", NULL};
execvp("ls", args); // แทนที่ memory image ด้วย ls
perror("execvp");
exit(EXIT_FAILURE);
} else {
// --- Parent Process ---
int status;
waitpid(pid, &status, 0); // รอ child จบ
if (WIFEXITED(status))
printf("[Parent] child exit code=%d\n", WEXITSTATUS(status));
}
```
---
## ผลลัพธ์การรัน
```bash
$ gcc fork_exec.c -o fork_exec && ./fork_exec
[Parent] pid=12345 กำลังจะ fork()
[Parent] fork สำเร็จ child pid=12346
[Child] pid=12346 ppid=12345 กำลังจะ exec ls
total 12
drwxrwxrwt 10 root root 4096 Apr 20 14:30 .
-rw-r--r-- 1 user user 123 Apr 20 14:29 test.txt
[Parent] child จบด้วย exit code 0
```
---
## 1.2.5 Thread และ Multithreading
**Thread** คือหน่วยการทำงาน (unit of execution) ภายใน process เดียวกัน
แชร์: address space, open file, signal handler
แต่มี: stack, register, program counter ของตัวเอง
**ประโยชน์ของ Multithreading:**
- **Responsiveness:** UI thread ไม่บล็อกเมื่อ background task ทำงาน
- **Resource Sharing:** แชร์ data ได้โดยไม่ต้อง IPC
- **Economy:** สร้าง thread เร็วกว่า process มาก
- **Scalability:** ใช้ประโยชน์ multi-core CPU
---
## User-level vs Kernel-level Thread
**User-level Thread (ULT):**
- OS ไม่รู้จัก thread; library ใน user space เป็นคนจัดการ
- ตัวอย่าง: green thread
- **ข้อดี:** เร็ว (ไม่ต้อง syscall)
- **ข้อเสีย:** ถ้า thread หนึ่งเรียก blocking syscall ทั้ง process จะ block
**Kernel-level Thread (KLT):**
- Kernel เป็นคนจัดการและ schedule
- **ข้อดี:** ถ้า thread หนึ่ง block อันอื่นยังทำงานได้, ใช้ multi-core ได้
- **ข้อเสีย:** switching ช้ากว่า ULT
---
## 1.2.6 Thread Model
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828',
'clusterBkg':'#3c3836',
'clusterBorder':'#fe8019'
}}}%%
flowchart TB
subgraph M21["Many-to-One"]
U1A["ULT"] --> K1A["KLT"]
U1B["ULT"] --> K1A
end
subgraph O2O["One-to-One"]
U2A["ULT"] --> K2A["KLT"]
U2B["ULT"] --> K2B["KLT"]
end
subgraph M2M["Many-to-Many"]
U3A["ULT"] --> K3A["KLT"]
U3B["ULT"] --> K3B["KLT"]
end
---
## เปรียบเทียบ Thread Model
| Model | ข้อดี | ข้อเสีย | ตัวอย่าง |
| --- | --- | --- | --- |
| **Many-to-One** | เร็ว ไม่ต้อง syscall | block ทั้ง process | Green Thread |
| **One-to-One** | true parallelism | overhead สูง | Linux NPTL, Windows |
| **Many-to-Many** | ยืดหยุ่น ประสิทธิภาพดี | implement ซับซ้อน | Solaris เดิม |
**Linux** ใช้ One-to-One ผ่าน **NPTL (Native POSIX Thread Library)**
pthread แต่ละตัวถูก map เป็น kernel task ผ่าน `clone()` syscall
---
## 1.2.7 IPC: Inter-Process Communication
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart LR
P1["Process A"]
P2["Process B"]
SM["Shared Memory"]
MQ["Message Queue"]
PI["Pipe / FIFO"]
SK["Socket"]
P1 <--> SM
P2 <--> SM
P1 -->|send| MQ -->|recv| P2
P1 -->|write| PI -->|read| P2
P1 <-->|bi-dir| SK
SK <--> P2
---
## ประเภท IPC (1)
**Shared Memory (หน่วยความจำร่วม):**
- สอง process map หน่วยความจำส่วนเดียวกัน
- เร็วมาก (ไม่มี copy) แต่ต้องจัดการ synchronization
- API: POSIX (`shm_open`, `mmap`), SysV (`shmget`, `shmat`)
**Message Passing:**
- ส่งข้อความผ่าน kernel ง่ายกว่า shared memory แต่ช้ากว่า
- API: POSIX mqueue, SysV msgget/msgsnd/msgrcv
**Pipe (`|`):**
- unidirectional byte stream ระหว่าง parent-child
- ใช้มากบน shell เช่น `ls | grep txt`
---
## ประเภท IPC (2)
**FIFO (Named Pipe):**
- เหมือน pipe แต่มีชื่อใน filesystem
- ใช้ระหว่าง process ที่ไม่มีความสัมพันธ์กันได้
- สร้างด้วย `mkfifo`
**Socket:**
- bidirectional
- ใช้ได้ทั้งในเครื่อง (UNIX domain socket) และข้ามเครื่อง (TCP/UDP)
- พื้นฐานของการสื่อสารเครือข่ายและไมโครเซอร์วิส
**Signal:**
- ส่งเหตุการณ์แบบ asynchronous
- เช่น `SIGINT` (Ctrl+C), `SIGTERM`, `SIGKILL`, `SIGCHLD`
---
## ตัวอย่าง pipe (Child เขียน)
```c
// pipe_demo.c - ตัวอย่าง anonymous pipe
#include
#include
#include
#include
int main(void) {
int fd[2]; // fd[0] = read, fd[1] = write
char buffer[128];
if (pipe(fd) < 0) { perror("pipe"); return 1; }
pid_t pid = fork();
if (pid == 0) {
close(fd[0]); // child ปิด read end
const char *msg = "สวัสดีจาก child process!\n";
write(fd[1], msg, strlen(msg));
close(fd[1]);
return 0;
}
```
---
## ตัวอย่าง pipe (Parent อ่าน)
```c
// Parent: เป็นฝั่งอ่าน
close(fd[1]); // ปิด write end ที่ไม่ใช้
ssize_t n = read(fd[0], buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0';
printf("[Parent] รับข้อความ: %s", buffer);
}
close(fd[0]);
wait(NULL); // รอ child จบ
return 0;
}
```
**ผลลัพธ์:**
```
[Parent] รับข้อความ: สวัสดีจาก child process!
```
---
# 1.3 อัลกอริทึมการจัดตารางเวลา CPU
## CPU Scheduling Algorithm
---
## 1.3.1 เกณฑ์การวัดประสิทธิภาพ
เมื่อออกแบบ CPU scheduler ต้องพิจารณาเป้าหมายหลายอย่างพร้อมกัน ซึ่งอาจขัดแย้งกัน
- **CPU Utilization** — เปอร์เซ็นต์เวลาที่ CPU ไม่ว่าง (สูงสุด 40%-90%)
- **Throughput** — จำนวน process ที่เสร็จต่อหน่วยเวลา (สูงสุด)
- **Turnaround Time (T)** — เวลาตั้งแต่ process เข้าสู่ระบบจนเสร็จ (ต่ำสุด)
- **Waiting Time (W)** — เวลาที่ process ใช้อยู่ใน ready queue (ต่ำสุด)
- **Response Time (R)** — เวลาจาก submit จนได้ response แรก (ต่ำสุด สำคัญสำหรับ interactive)
---
## สมการ Turnaround Time
โดย **T** คือ turnaround time
**t_completion** คือเวลาที่ process จบ
**t_arrival** คือเวลาที่ process เข้าระบบ
---
## สมการ Waiting Time
โดย **W** คือ waiting time
**T** คือ turnaround time
**t_burst** คือเวลาที่ process ต้องการ CPU (CPU burst time)
---
## สมการ Average Waiting Time
โดย **W̄** คือ average waiting time, **W_i** คือ waiting time ของ process i, **n** คือจำนวน process ทั้งหมด
---
## 1.3.2 Preemptive vs Non-preemptive
**Non-preemptive (Cooperative):**
- Process ที่ได้ CPU จะทำงานจนเสร็จหรือ block เอง
- Scheduler ไม่แย่งกลับได้
- ง่ายต่อการ implement แต่ process สำคัญอาจรอนาน
**Preemptive:**
- Scheduler แย่ง CPU กลับคืนได้ตลอดเวลา
- เช่น เมื่อ timer หมด หรือ process priority สูงกว่าเข้ามา
- ยืดหยุ่นกว่า แต่ต้องระวัง race condition
---
## 1.3.3 First-Come First-Served (FCFS)
**FCFS** เป็นอัลกอริทึมที่ง่ายที่สุด
- Process เข้าก่อนได้ CPU ก่อน
- เป็น non-preemptive
- ใช้ FIFO queue
**ตัวอย่าง:** P1=24, P2=3, P3=3 (มาพร้อมกัน)
ลำดับ P1 → P2 → P3:
```
| P1 |P2|P3|
0 24 27 30
```
Average Waiting = (0+24+27)/3 = **17 ms**
---
## FCFS — Convoy Effect
หากสลับลำดับเป็น P2 → P3 → P1:
```
|P2|P3|P1 |
0 3 6 30
```
- Waiting Time: P1=6, P2=0, P3=3
- **เฉลี่ย = 3 ms** (ดีขึ้นมาก!)
**ปัญหา Convoy Effect:** process สั้น ๆ ต้องรอ process ยาวตัวหน้า ทำให้ throughput และ response time แย่ลง
---
## 1.3.4 SJF (Shortest-Job-First)
**SJF** เลือก process ที่มี burst time สั้นที่สุดก่อน
- พิสูจน์ได้ว่าให้ average waiting time **ต่ำสุด (optimal)**
- ปัญหา: เราไม่รู้ burst time ล่วงหน้า
- ต้อง **predict** จากประวัติด้วย exponential averaging
ใช้สมการคาดการณ์ burst time ครั้งถัดไป (ดูหน้าถัดไป)
---
## สมการ Exponential Averaging (SJF)
**τ_(n+1)** = ค่าคาดการณ์ burst time ครั้งถัดไป
**t_n** = burst time จริงครั้งล่าสุด
**τ_n** = ค่าคาดการณ์ครั้งก่อน
**α** (0 ≤ α ≤ 1) = น้ำหนักถ่วง
---
## SRTF (Shortest-Remaining-Time-First)
**SRTF** = เวอร์ชัน preemptive ของ SJF
ถ้ามี process ใหม่มาและ remaining time น้อยกว่า process ที่กำลังทำงาน จะ preempt ทันที
**ตัวอย่าง:**
| Process | Arrival | Burst |
| --- | --- | --- |
| P1 | 0 | 8 |
| P2 | 1 | 4 |
| P3 | 2 | 9 |
| P4 | 3 | 5 |
Average Waiting = (9+0+15+2)/4 = **6.5 ms**
---
## 1.3.5 Priority Scheduling
กำหนด **priority number** ให้แต่ละ process
- ตัวเลขเล็ก = priority สูง (เช่น Linux nice -20 ถึง +19)
- Process priority สูงสุดได้ CPU ก่อน
**ปัญหา Starvation (Indefinite Blocking):**
- Process priority ต่ำอาจไม่ได้ทำงานเลย
- หาก process priority สูงมาเรื่อย ๆ
**แก้ด้วย Aging:**
- เพิ่ม priority ของ process ที่รอนานขึ้นเรื่อย ๆ
- เช่น ทุก 1 วินาที เพิ่ม priority +1
---
## 1.3.6 Round Robin (RR)
**Round Robin** กำหนด **time quantum** เท่า ๆ กัน (10-100 ms)
- แต่ละ process ได้ quantum หนึ่งชุด
- เมื่อหมด preempt ไปต่อท้าย ready queue
**การเลือก Time Quantum:**
- **ใหญ่มาก:** กลายเป็น FCFS, response time แย่
- **เล็กมาก:** context switch overhead สูง
- **ค่าที่เหมาะ:** 80% ของ CPU burst ควรสั้นกว่า quantum
---
## ตัวอย่าง RR (quantum=4)
| Process | Burst |
| --- | --- |
| P1 | 24 |
| P2 | 3 |
| P3 | 3 |
```
|P1 |P2 |P3|P1 |P1 |P1 |P1 |P1 |
0 4 7 10 14 18 22 26 30
```
- Waiting: P1 = 30-24 = 6, P2 = 4, P3 = 7
- **Average = 5.67 ms**
---
## 1.3.7 Multilevel Queue Scheduling
แบ่ง ready queue เป็นหลายระดับตามประเภทของ process
1. System processes (priority สูงสุด)
2. Interactive processes (foreground)
3. Interactive editing
4. Batch processes
5. Student processes (ต่ำสุด)
แต่ละ queue ใช้อัลกอริทึมของตัวเอง เช่น foreground = RR, background = FCFS
มีนโยบายระหว่าง queue: fixed priority หรือ time slicing
---
## 1.3.8 Multilevel Feedback Queue
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart TB
NEW["New Process"] --> Q0["Queue 0 (RR q=8ms)
Highest Priority"]
Q0 -->|"quantum หมด"| Q1["Queue 1 (RR q=16ms)"]
Q1 -->|"quantum หมด"| Q2["Queue 2 (FCFS)
Lowest"]
Q0 -->|"done/block"| EXIT["Exit/Wait"]
Q1 -->|"done/block"| EXIT
Q2 -->|"done/block"| EXIT
Q2 -.->|"aging"| Q1
Q1 -.->|"aging"| Q0
---
## MLFQ — แนวคิดสำคัญ
พัฒนาจาก Multilevel Queue ตรงที่ process สามารถ **ย้ายระหว่าง queue** ได้ตามพฤติกรรม
**Process I/O bound** (ใช้ CPU สั้น ๆ):
- จะอยู่ Q ระดับสูง
- ได้ priority สูง
**Process CPU bound** (กิน CPU ยาว ๆ):
- ค่อย ๆ ถูก demote ลง queue ล่าง
- ใช้ aging ป้องกัน starvation
---
## 1.3.9 Multi-processor Scheduling
บนระบบ multi-core มีประเด็นเพิ่มเติม
- **Asymmetric Multiprocessing:** มี master CPU เดียวจัดการ scheduling + I/O
- **Symmetric Multiprocessing (SMP):** ทุก CPU รัน scheduler เอง
- **Processor Affinity:** ให้ process เดิมกลับมารันบน CPU เดิม เพื่อใช้ cache warmth (`taskset`, `sched_setaffinity()`)
- **Load Balancing:** กระจาย process ให้ CPU มีภาระใกล้เคียงกัน (Push vs Pull)
- **NUMA:** schedule คำนึงถึงตำแหน่งของ memory node เพื่อลด latency
---
## 1.3.10 Linux CFS
**Linux CFS (Completely Fair Scheduler)** ใช้ตั้งแต่ kernel 2.6.23 (2007)
- ใช้ **red-black tree** เก็บ process ตาม **virtual runtime (vruntime)**
- Process ที่ vruntime น้อยที่สุดจะได้ CPU ก่อน
- ให้ "ความยุติธรรม" ในแง่ของ proportional share
ใช้ nice value เพื่อกำหนดน้ำหนัก (weight) ของแต่ละ process
---
## สมการ vruntime ใน CFS
**Δt** = เวลาที่ process เพิ่งใช้ CPU
**W_nice0** = น้ำหนักของ nice=0 (1024)
**W_process** = น้ำหนักของ process ตาม nice value
---
## Scheduler อื่น ๆ
**O(1) Scheduler** (kernel 2.6.0-2.6.22):
- ใช้ 140 priority bitmap
- การเลือก process เป็น O(1) ไม่ขึ้นกับจำนวน process
**EEVDF (Earliest Eligible Virtual Deadline First):**
- ตั้งแต่ Linux 6.6 (2023)
- ได้ทยอยแทนที่ CFS
**Windows Scheduler:**
- priority-based preemptive scheduling
- มี 32 priority level: real-time (16-31), variable (0-15)
- มี priority boost สำหรับ foreground window และ I/O completion
---
# 1.4 การซิงโครไนซ์โพรเซส
## Process Synchronization
---
## 1.4.1 Critical Section Problem
**Critical Section** คือส่วนของโค้ดที่เข้าถึง shared resource
เช่น ตัวแปรร่วม, ไฟล์, database
การ execute พร้อมกันหลาย thread ใน critical section อาจทำให้ข้อมูลผิดพลาด
โครงสร้างทั่วไป:
```
while (true) {
// entry section (ขอเข้า)
// --- critical section ---
// exit section (ออก)
// remainder section (งานอื่น ๆ)
}
```
---
## เงื่อนไข 3 ข้อของโซลูชัน
โซลูชันที่ถูกต้องต้องมีครบ 3 ข้อ
1. **Mutual Exclusion:** ณ เวลาหนึ่ง ต้องมี process ไม่เกิน 1 ตัวใน critical section
2. **Progress:** ถ้าไม่มีใครอยู่ใน CS และมีหลาย process อยาก enter การตัดสินใจต้องไม่ถูกเลื่อนอย่างไม่มีที่สิ้นสุด
3. **Bounded Waiting:** ต้องมีขอบเขตบนของจำนวนครั้งที่ process อื่นเข้าก่อนได้ หลังจากที่ process หนึ่งขอเข้า CS
---
## 1.4.2 Race Condition
**Race Condition** เกิดเมื่อผลลัพธ์ขึ้นกับลำดับการ execute ของ thread ที่ไม่คาดเดาได้
**ตัวอย่างคลาสสิก:** `counter++` ดูเหมือนเป็นคำสั่งเดียว แต่จริง ๆ เป็น 3 คำสั่ง
```asm
LOAD R1, [counter]
ADD R1, R1, 1
STORE [counter], R1
```
---
## ตัวอย่าง Race Condition
ถ้า 2 thread interleave กัน ค่าอาจผิดได้
```
Thread A: LOAD R1 = 5
Thread B: LOAD R1 = 5
Thread A: ADD R1 = 6
Thread B: ADD R1 = 6
Thread A: STORE counter = 6
Thread B: STORE counter = 6 ← ควรเป็น 7!
```
ค่าหายไป 1 ครั้ง เพราะทั้งสอง thread อ่านค่าเดิม (5) ก่อนที่อีกฝ่ายจะเขียน
นี่คือเหตุผลที่ต้องการ synchronization mechanism
---
## 1.4.3 Peterson's Algorithm
```c
// Peterson's algorithm สำหรับ 2 process
bool flag[2] = {false, false};
int turn = 0;
void enter_cs(int i) {
int j = 1 - i;
flag[i] = true; // ประกาศว่าต้องการเข้า
turn = j; // ให้สิทธิ์อีกฝ่ายก่อน
while (flag[j] && turn == j)
; // busy-wait
}
void exit_cs(int i) {
flag[i] = false;
}
```
---
## Peterson + Bakery
**Peterson's Algorithm:**
- ใช้ตัวแปรร่วม `flag[2]` และ `turn`
- รองรับเฉพาะ 2 process
- ต้องการ memory ordering ที่ถูกต้อง
- ในฮาร์ดแวร์สมัยใหม่ต้องใช้ `atomic` หรือ memory barrier
**Bakery Algorithm (Lamport, 1974):**
- ขยาย Peterson ให้รองรับ N process
- ใช้ "บัตรคิว" (number) แบบร้านเบเกอรี่
- คนที่ได้เลขน้อยสุดได้เข้าก่อน
---
## 1.4.4 Hardware Solution
CPU สมัยใหม่มี **atomic instruction** ที่อ่าน-แก้-เขียนในคำสั่งเดียวโดยไม่ถูกขัด
**Test-and-Set (TAS):**
```c
bool TestAndSet(bool *target) {
bool old = *target;
*target = true;
return old;
}
```
บน x86 คำสั่งคือ `LOCK CMPXCHG` บน ARM ใช้ `LDXR`/`STXR`
---
## Compare-and-Swap (CAS)
**Compare-and-Swap** เป็นพื้นฐานของ lock-free data structure
```c
int CompareAndSwap(int *value, int expected, int new_value) {
int old = *value;
if (old == expected)
*value = new_value;
return old;
}
```
ใช้กันแพร่หลายใน:
- C/C++ atomic operations
- Java `AtomicInteger`
- Database transaction (optimistic locking)
- Lock-free queue, stack
---
## 1.4.5 Semaphore
**Semaphore** (Dijkstra, 1965) คือจำนวนเต็ม S ที่เข้าถึงได้ด้วยสอง atomic operation
- **wait(S)** (หรือ P, down) — รอจนกว่า S > 0 แล้วลด
- **signal(S)** (หรือ V, up) — เพิ่ม S แล้วปลุก process ที่รอ
```c
wait(S):
while (S <= 0) ; // รอ (จริง ๆ block ไม่ใช่ busy-wait)
S--;
signal(S):
S++;
```
---
## Binary vs Counting Semaphore
**Binary Semaphore** (ค่า 0 หรือ 1):
- ใช้แทน mutex
- คุมการเข้าถึง critical section
**Counting Semaphore** (ค่าจำนวนเต็มใด ๆ):
- ใช้ควบคุมจำนวน resource ที่มีจำกัด
- ตัวอย่าง: connection pool ขนาด 10
- เริ่มต้น S = 10 → ทุก connection ใช้งานพร้อมกันได้ 10 ตัว
---
## 1.4.6 Mutex, Spin Lock, RWLock
**Mutex (Mutual Exclusion):**
- เหมือน binary semaphore แต่มี **ownership**
- ผู้ที่ lock ต้องเป็นผู้ unlock (pthread_mutex)
**Spin Lock:**
- แทนที่จะ sleep ตอน wait จะ busy-wait (loop)
- เหมาะเมื่อ critical section สั้นมาก และ context switch แพงกว่าการรอ
**Read-Write Lock (RWLock):**
- แยก lock สำหรับอ่าน (shared) และเขียน (exclusive)
- เหมาะกับข้อมูลที่อ่านบ่อย เขียนน้อย
---
## 1.4.7 Monitor และ Condition Variable
**Monitor** (Hoare, 1974) เป็น high-level abstraction
- รวม shared data + procedure เข้าไว้ด้วยกัน
- รับประกันว่ามี process เดียวเท่านั้นที่อยู่ใน monitor ในเวลาหนึ่ง
**Condition Variable** ใช้สำหรับรอเงื่อนไขใน monitor
- `wait(c)` — ปล่อย monitor แล้วรอ
- `signal(c)` — ปลุก process หนึ่งที่รอ c
- `broadcast(c)` — ปลุกทุก process ที่รอ
ตัวอย่าง: Java `synchronized` + `wait()`/`notify()`, Python `threading.Condition`
---
## 1.4.8 Producer-Consumer (1)
ปัญหา **Bounded Buffer**: producer ใส่ข้อมูล consumer นำออก buffer ขนาดจำกัด
```c
semaphore mutex = 1; // ป้องกัน buffer
semaphore empty = N; // จำนวนช่องว่าง
semaphore full = 0; // จำนวน item
// Producer
while (true) {
item = produce();
wait(empty); // รอช่องว่าง
wait(mutex);
put_into_buffer(item);
signal(mutex);
signal(full); // แจ้งว่ามี item
}
```
---
## Producer-Consumer (2)
```c
// Consumer
while (true) {
wait(full); // รอ item
wait(mutex);
item = remove_from_buffer();
signal(mutex);
signal(empty); // แจ้งว่ามีช่องว่าง
consume(item);
}
```
**ใช้ semaphore 3 ตัว:**
- `mutex` (binary) — ป้องกัน buffer
- `empty` (counting) — นับช่องว่าง
- `full` (counting) — นับ item ที่มี
---
## ปัญหาคลาสสิกอื่น ๆ
**Readers-Writers:**
- หลาย reader อ่านพร้อมกันได้ แต่ writer ต้อง exclusive
- มี First/Second/Third problem ตามการจัดลำดับความสำคัญ
**Dining Philosophers:**
- 5 นักปราชญ์นั่งล้อมโต๊ะ ระหว่างกันมีตะเกียบ 1 คู่
- ต้องการ 2 อัน (ซ้าย+ขวา) เพื่อกิน
- คลาสสิกของ deadlock ถ้าทุกคนหยิบตะเกียบซ้ายพร้อมกัน
**Sleeping Barber:**
- ช่างตัดผมและห้องรอที่มีเก้าอี้จำกัด
- synchronize ลูกค้า ↔ ช่าง
---
## 1.4.9 Deadlock — 4 Conditions
**Deadlock** เกิดเมื่อกลุ่ม process ติดค้างกันโดยแต่ละตัวถือ resource และรอ resource ที่อีกตัวหนึ่งถืออยู่
ต้องเกิดเงื่อนไข 4 ข้อพร้อมกัน (**Coffman conditions**)
1. **Mutual Exclusion:** resource ใช้ได้ทีละคนเท่านั้น
2. **Hold and Wait:** process ถือ resource ขณะรอ resource อื่น
3. **No Preemption:** resource ถูกแย่งคืนไม่ได้ ต้องปล่อยเอง
4. **Circular Wait:** มีวงกลมของ process ที่รอซึ่งกันและกัน
---
## Deadlock Diagram
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fb4934',
'lineColor':'#fb4934',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart LR
P1["Process P1
ถือ R1, รอ R2"]
P2["Process P2
ถือ R2, รอ R1"]
R1["Resource R1"]
R2["Resource R2"]
P1 -->|hold| R1
P2 -->|hold| R2
P1 -.->|request| R2
P2 -.->|request| R1
---
## 1.4.10 การจัดการ Deadlock (1)
**1) Prevention:** ทำลายเงื่อนไข 1 ใน 4 ข้อ
- ทำลาย Mutual Exclusion — ทำได้ยาก (บาง resource ต้อง exclusive)
- ทำลาย Hold and Wait — ขอ resource ทีเดียวก่อนเริ่มทำงาน
- ทำลาย No Preemption — อนุญาตให้แย่ง resource
- ทำลาย Circular Wait — request resource ตามลำดับ ID เสมอ
**2) Avoidance (Banker's Algorithm):**
- ตัดสินใจจัดสรรโดยรับประกัน safe state เสมอ
- ต้องรู้ maximum resource need ล่วงหน้า
---
## สมการ Banker's Algorithm
**Need[i,j]** = จำนวน resource j ที่ process i ยังต้องการเพิ่ม
**Max[i,j]** = ความต้องการสูงสุด
**Allocation[i,j]** = จำนวนที่จัดสรรแล้ว
---
## การจัดการ Deadlock (2)
**3) Detection & Recovery:**
- ปล่อยให้ deadlock เกิด
- ตรวจจับด้วย resource allocation graph cycle
- แก้ไขโดย kill process หรือ rollback
**4) Ignore (Ostrich Algorithm):**
- ทำเป็นไม่รู้ไม่เห็น เหมือนนกกระจอกเทศซุกหัวในทราย!
- UNIX/Linux/Windows ใช้วิธีนี้สำหรับ application-level
- เพราะ deadlock เกิดไม่บ่อย และการ prevent/avoid มี cost สูง
---
# 1.5 การจัดการหน่วยความจำ
## Memory Management
---
## 1.5.1 Address Binding
**Address Binding** คือกระบวนการแมป "logical address" ในโปรแกรมไปเป็น "physical address" ในหน่วยความจำจริง
เกิดได้ 3 เวลา:
1. **Compile-time Binding:** รู้ physical address ตั้งแต่คอมไพล์ (absolute code) — ไม่ยืดหยุ่น ใช้ใน embedded
2. **Load-time Binding:** คอมไพล์เป็น relocatable code รู้ตอน load — ต้อง load ตำแหน่งเดิมทุกครั้ง
3. **Execution-time Binding (Runtime):** แมประหว่างรัน ต้องมี hardware ช่วย (MMU) — **OS สมัยใหม่ใช้**
---
## 1.5.2 MMU และ Address Types
**Logical (Virtual) Address:** address ที่ process เห็น (CPU สร้าง)
**Physical Address:** address จริงใน RAM
**MMU (Memory Management Unit):** ฮาร์ดแวร์ที่แปลง logical → physical address ใน runtime โดยใช้ page table
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart LR
CPU["CPU
VA 0x1234"] -->|VA| MMU["MMU
+ Page Table"]
MMU -->|PA 0xA234| RAM["Physical RAM"]
TLB["TLB Cache"] <-->|lookup| MMU
---
## 1.5.3 Contiguous Allocation
จัดสรรหน่วยความจำให้ process อยู่ในบริเวณ memory ต่อเนื่องกัน
**Strategy การเลือก hole สำหรับ process ขนาด n:**
- **First Fit:** ใช้ hole แรกที่ขนาดพอ
- เร็ว แต่เกิด fragmentation
- **Best Fit:** ใช้ hole ที่ใหญ่พอและเล็กสุด
- ใช้พื้นที่คุ้ม แต่เหลือ hole เล็กมาก
- **Worst Fit:** ใช้ hole ใหญ่สุด
- ตรงข้าม best fit hole ที่เหลือยังใช้งานได้
ผลทดลอง: First Fit และ Best Fit ดีกว่า Worst Fit
---
## 1.5.4 Fragmentation
**Internal Fragmentation:**
- พื้นที่เหลือภายในที่จัดสรรให้ process แล้ว แต่ไม่ได้ใช้
- ตัวอย่าง: request 18 KB แต่ได้ block 20 KB → เสีย 2 KB
**External Fragmentation:**
- พื้นที่ว่างรวมเพียงพอ แต่กระจายเป็น hole เล็ก ๆ
- ไม่ต่อเนื่อง ใช้ไม่ได้
**Compaction:**
- ย้าย process เข้ามาชิดกันเพื่อรวม hole เป็นก้อนใหญ่
- Cost สูง (ต้อง dynamic relocation)
---
## 1.5.5 Paging
**Paging** แบ่งหน่วยความจำออกเป็นชิ้นเท่า ๆ กัน
- **Page:** ชิ้นของ logical address (มักเป็น 4 KB)
- **Frame:** ชิ้นของ physical address (ขนาดเดียวกับ page)
- **Page Table:** โครงสร้างที่แมป page number → frame number
ข้อดี: ไม่มี external fragmentation, ทำ virtual memory ได้
ข้อเสีย: มี internal fragmentation เล็กน้อย, ต้อง lookup page table
---
## สมการ Paging Address Translation
**p** = page number
**d** = offset ภายใน page
**f** = PageTable[p] = frame number
**offset d** คงเดิม
---
## TLB และ Effective Access Time
**TLB (Translation Lookaside Buffer)** เป็น cache ของ page table ใน MMU
- เก็บ (page → frame) ที่ใช้บ่อย
- หลีกเลี่ยงการอ่าน page table จาก memory ทุกครั้ง
**h** = TLB hit rate, **t** = TLB lookup, **m** = memory access
---
## 1.5.6 Page Table Variants
สำหรับ 64-bit address ถ้าใช้ single-level page table จะมีขนาดมหาศาล จึงใช้โครงสร้างอื่น
- **Hierarchical (Multi-level) Page Table:**
- แบ่ง page table เป็นหลายชั้น
- x86_64 ใช้ 4-5 level (PML4/5, PDPT, PD, PT)
- **Hashed Page Table:**
- ใช้ hash function บน page number
- เหมาะกับ address space 64-bit
- **Inverted Page Table:**
- 1 entry ต่อ 1 frame (physical) แทน 1 entry ต่อ 1 page
- ประหยัด memory แต่ค้นหาช้ากว่า
---
## 1.5.7 Segmentation
**Segmentation** แบ่งโปรแกรมตามหน่วยตรรกะ (segment) ขนาดไม่เท่ากัน
ตัวอย่าง segment: code, data, stack, heap
Address เป็น (segment_id, offset)
**Segmentation with Paging:**
- ใช้ segment กำหนดขอบเขตตรรกะ
- แต่ละ segment paging ย่อยภายใน
- x86 แบบเดิมใช้วิธีนี้
- x86_64 ยกเลิก segmentation แล้ว (ใช้ flat paging)
---
## 1.5.8 Virtual Memory
Virtual Memory ให้ process เห็น address space ใหญ่กว่า RAM จริงได้
**Demand Paging:**
- โหลด page เข้า RAM เฉพาะตอนที่ใช้ (ครั้งแรกที่ถูก access)
**Page Fault:**
- เมื่อ access page ที่ไม่อยู่ใน RAM
- OS handle: หา frame ว่าง (อาจ swap out), โหลด page, resume process
**Copy-on-Write (CoW):**
- หลัง `fork()` ไม่ copy page จริง
- Parent/child share pages แบบ read-only
- เมื่อใครเขียน เกิด page fault แล้วค่อย copy
---
## EAT สำหรับ Demand Paging
**p** = probability ของ page fault
**t_ma** = memory access time (เช่น 100 ns)
**t_pf** = page fault service time (เช่น 8 ms = 8,000,000 ns)
แม้ page fault rate น้อยมาก ก็ทำให้ระบบช้าลงอย่างมีนัยสำคัญ
---
## 1.5.9 Page Replacement Algorithms
เมื่อ RAM เต็ม ต้องเลือก page ใดที่จะถูก swap ออก
**FIFO:** ไล่ page ที่อยู่มานานสุด
- ง่ายแต่มี **Belady's Anomaly** (เพิ่ม frame กลับทำให้ fault มากขึ้น)
**Optimal (OPT):** ไล่ page ที่จะไม่ถูกใช้นานที่สุดในอนาคต
- ทำไม่ได้จริง แต่ใช้เป็น benchmark
**LRU (Least Recently Used):** ไล่ page ที่ไม่ได้ใช้นานที่สุดในอดีต
- ใกล้เคียง OPT
**LFU (Least Frequently Used):** ไล่ page ที่ใช้น้อยครั้งสุด
**Clock (Second Chance):** ใช้ reference bit วนดูแบบ circular
---
## เปรียบเทียบ Page Replacement
ตัวอย่าง reference string: `7 0 1 2 0 3 0 4 2 3 0 3 2` มี 3 frames
| Algorithm | Page Faults | ลักษณะ |
| --- | --- | --- |
| FIFO | 15 | ง่ายสุด มี Belady's Anomaly |
| Optimal | 9 | Benchmark ที่ทำไม่ได้ |
| LRU | 12 | ใกล้ optimal, overhead สูง |
| Clock | 12-13 | ใกล้ LRU, overhead ต่ำ |
**Clock** เป็นที่นิยมในระบบจริงเพราะ implement ง่ายและประสิทธิภาพใกล้ LRU
---
## 1.5.10 Thrashing
**Thrashing** เกิดเมื่อ process เกิด page fault บ่อยมาก
- CPU ใช้เวลาส่วนใหญ่ไป swap pages แทน execute จริง
- CPU utilization ต่ำ
- Disk I/O สูงผิดปกติ
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fb4934',
'lineColor':'#fb4934',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart LR
A["CPU Util ต่ำ"] --> B["เพิ่ม multiprog."]
B --> C["โหลด process เพิ่ม"]
C --> D["Page Fault ทะลัก"]
D --> A
---
## Working Set Model
**Working Set Model** (Denning, 1968)
- ในหน้าต่างเวลา Δ
- page ที่ถูก access คือ working set ของ process นั้น
- OS ควรเก็บทั้ง working set ไว้ใน RAM
ถ้าผลรวมของ working set > RAM → จะ thrashing
**Page Fault Frequency (PFF):**
- ตรวจ fault rate
- ถ้าสูงเกินเพดานบน → เพิ่ม frame ให้ process
- ถ้าต่ำเกินเพดานล่าง → ลด frame
---
# 1.6 การจัดการระบบไฟล์
## File System Management
---
## 1.6.1 File Concept
**File** คือชุดข้อมูลต่อเนื่องที่มีชื่อและถูกเก็บบน secondary storage
**Attributes:**
- name, identifier (inode number), type
- location (pointer to data), size
- protection (permission), time & date, user/group
**Operations:** create, write, read, reposition (seek), delete, truncate, open, close
**Type:** ระบุด้วย extension (.txt, .exe) หรือ magic number ต้นไฟล์
- ELF = `0x7F 45 4C 46`, PNG = `89 50 4E 47`
---
## 1.6.2 Access Methods
**Sequential Access:**
- อ่าน/เขียนตามลำดับจากต้นถึงท้าย
- เหมาะกับ tape, log file
**Direct (Random) Access:**
- ข้ามไปอ่านตำแหน่งใดก็ได้ด้วย `seek()`
- เหมาะกับ database
**Indexed Access:**
- มี index file ที่ชี้ไปยัง position ใน data file
- ใช้ใน ISAM, B-tree index
---
## 1.6.3 Directory Structure
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
flowchart TB
R["/"] --> H["home"]
R --> E["etc"]
R --> V["var"]
H --> U1["user1"]
H --> U2["user2"]
U1 --> D1["docs"]
U1 --> P1["pics"]
D1 --> F1["report.pdf"]
U2 --> L1["link"] -.->|symlink| F1
---
## ประเภท Directory Structure
- **Single-level:** 1 directory ทั้งระบบ ไฟล์ต้องมีชื่อไม่ซ้ำ
- ตัวอย่าง: CP/M, MS-DOS 1.0
- **Two-level:** แต่ละ user มี directory ของตน
- MS-DOS เวอร์ชันหลัง
- **Tree:** hierarchy ไม่จำกัดระดับ
- Unix, Windows
- **Acyclic Graph:** อนุญาต link (symbolic/hard link) แต่ไม่มี cycle
- **General Graph:** มี cycle ได้ — ต้องระวังใน traversal, garbage collection
---
## 1.6.4 Mounting
**Mount** คือการแปะ filesystem บน device เข้าไปเป็นส่วนหนึ่งของ directory tree
```bash
# Mount disk partition /dev/sdb1 เข้าที่ /mnt/data
sudo mount -t ext4 /dev/sdb1 /mnt/data
# ดูทุก filesystem ที่ mount อยู่
mount | column -t
findmnt
# Unmount
sudo umount /mnt/data
```
---
## /etc/fstab
ไฟล์ `/etc/fstab` กำหนด mount อัตโนมัติตอนบูต
```fstab
#
UUID=xxxx-xxxx / ext4 defaults 0 1
UUID=yyyy-yyyy /home ext4 defaults 0 2
/dev/sda2 swap swap defaults 0 0
tmpfs /tmp tmpfs defaults,size=2G 0 0
```
ใช้ UUID จะปลอดภัยกว่า device name เพราะไม่เปลี่ยนเมื่อย้าย disk
---
## 1.6.5 File Allocation Methods
| Method | ข้อดี | ข้อเสีย |
| --- | --- | --- |
| **Contiguous** | random access เร็ว | external frag, ขยายยาก |
| **Linked** | ไม่เกิด external frag | random access ช้า |
| **Indexed** | random access เร็ว, ไม่ frag | overhead ของ index block |
**FAT** ใช้ linked allocation โดยเก็บ pointer รวมไว้ใน File Allocation Table
**ext2/3/4** ใช้ indexed allocation ด้วยโครงสร้าง inode
- 12 direct pointer + indirect + double indirect + triple indirect
---
## 1.6.6 Free Space Management
- **Bit Vector (Bitmap):**
- 1 bit ต่อ 1 block (0=free, 1=used)
- หา block ว่างเร็ว ใช้พื้นที่น้อย
- **Linked List:**
- free block แต่ละตัว point ไปหาตัวถัดไป
- ใช้ได้แต่ traverse ช้า
- **Grouping:**
- block แรกของกลุ่มเก็บ address ของ free block ถัด n-1 ตัว
- **Counting:**
- เก็บ (address, count) ของ contiguous free blocks
---
## 1.6.7 ext2/3/4 Structure
โครงสร้างของ ext2/3/4 บน Linux ประกอบด้วย:
- **Superblock:** metadata ของ filesystem ทั้งหมด (block size, total blocks, free blocks, magic number)
- **Group Descriptors:** อธิบาย block group แต่ละกลุ่ม
- **Block Bitmap / Inode Bitmap:** บอกว่า block/inode ไหนว่าง
- **Inode Table:** array ของ inode แต่ละ inode เก็บ metadata + pointer ของ 1 ไฟล์
- **Data Blocks:** เก็บเนื้อไฟล์
---
## Inode
**Inode** ไม่มีชื่อไฟล์ (ชื่อเก็บใน directory entry) มีเฉพาะ:
- Type (regular, dir, link, device)
- Permission, Owner, Group
- Size, Timestamps (atime, mtime, ctime)
- Link count
- Block pointers (12 direct + 1 indirect + 1 double indirect + 1 triple indirect)
---
## ดู Inode ด้วย stat
```bash
# ดู inode ของไฟล์
$ stat /etc/passwd
File: /etc/passwd
Size: 2856 Blocks: 8 IO Block: 4096 regular file
Device: 259,2 Inode: 131073 Links: 1
Access: (0644/-rw-r--r--) Uid: (0/root) Gid: (0/root)
# ดูรายละเอียด inode เต็ม (ext4)
$ sudo debugfs -R "stat <131073>" /dev/nvme0n1p2
```
ค่า inode number เป็น unique identifier ภายใน filesystem นั้น ๆ
---
## 1.6.8 Journaling vs CoW
**Journaling:**
- เขียน "สมุดบันทึก (journal/log)" ไว้ก่อนว่าจะทำอะไร
- เมื่อ crash ระหว่างเขียน สามารถ replay หรือ rollback ได้
- ตัวอย่าง: ext3/4, NTFS, XFS
**Copy-on-Write (CoW):**
- ไม่ overwrite ข้อมูลเดิม
- เขียนลง block ใหม่ แล้วอัปเดต pointer atomically
- ตัวอย่าง: Btrfs, ZFS, APFS
**Log-structured File System:**
- เขียนทั้ง filesystem เป็น log ต่อเนื่อง
- ตัวอย่าง: NILFS, F2FS (Flash-friendly)
---
## 1.6.9 เปรียบเทียบ FS (1)
| FS | Max File | Max Volume | ใช้กับ |
| --- | --- | --- | --- |
| **FAT32** | 4 GB | 2 TB | USB, SD Card |
| **exFAT** | 16 EB | 128 PB | SD Card ใหญ่ |
| **NTFS** | 16 EB | 256 TB | Windows |
| **ext4** | 16 TB | 1 EB | Linux ทั่วไป |
| **XFS** | 8 EB | 8 EB | RHEL, SUSE |
---
## 1.6.9 เปรียบเทียบ FS (2)
| FS | ประเภท | Feature เด่น | ใช้กับ |
| --- | --- | --- | --- |
| **Btrfs** | CoW | snapshot, RAID | Fedora, openSUSE |
| **ZFS** | CoW | pool, RAID-Z, dedup | FreeBSD, TrueNAS |
| **APFS** | CoW | snapshot, encryption | macOS, iOS |
| **F2FS** | Log-struct | flash-optimized | Android |
ระบบไฟล์ CoW (Btrfs, ZFS, APFS) มีจุดเด่นที่ snapshot และ data integrity ผ่าน checksum
---
# 1.7 ระบบ I/O และอุปกรณ์ต่อพ่วง
## I/O System
---
## 1.7.1 I/O Hardware
**Port:** จุดเชื่อมต่อระหว่าง device กับ computer
**Bus:** สายส่งข้อมูลร่วมระหว่าง device
- ตัวอย่าง: PCI Express, USB, SATA
**Device Controller:** ชิปที่ควบคุม device
- มี register ภายใน (data, status, control)
- สื่อสารกับ CPU ผ่าน memory-mapped I/O หรือ port-mapped I/O
CPU ไม่ได้คุยกับ device โดยตรง แต่คุยผ่าน controller เสมอ
---
## 1.7.2 Polling
**Polling:** CPU วนถาม device ว่าพร้อมหรือยัง
```c
while (!(device_status & READY))
; // busy-wait
data = device_data;
```
**ข้อดี:**
- latency ต่ำ
- เหมาะกับ device ที่ตอบสนองเร็วมาก
**ข้อเสีย:**
- เปลือง CPU มาก
- CPU ไม่ได้ทำงานอื่นระหว่างรอ
---
## Interrupt-driven I/O
**Interrupt-driven I/O:** CPU ทำงานอื่น เมื่อ device พร้อม จะส่ง **interrupt** CPU จึง handle
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#fabd2f',
'lineColor':'#83a598',
'secondaryColor':'#504945',
'tertiaryColor':'#282828'
}}}%%
sequenceDiagram
participant CPU
participant Dev as Device Ctrl
participant ISR as Interrupt Handler
CPU->>Dev: start I/O
Note over CPU: ทำงานอื่นต่อ
Dev->>CPU: INT (interrupt)
CPU->>ISR: jump to ISR vector
ISR->>Dev: read status + data
ISR-->>CPU: iret (return)
---
## DMA (Direct Memory Access)
**DMA** สำหรับถ่ายข้อมูลปริมาณมาก
- DMA controller ย้ายข้อมูล RAM ↔ device
- ไม่รบกวน CPU ระหว่างการถ่ายข้อมูล
- เสร็จแล้วค่อย interrupt CPU
**ใช้ใน:**
- Disk I/O
- Network adapter
- GPU
- Audio device
DMA ช่วยลด CPU overhead อย่างมากสำหรับ bulk transfer
---
## 1.7.3 Device Driver
**Device Driver** คือโมดูลใน kernel (หรือ user space สำหรับ microkernel)
- รู้จักรายละเอียดของ device แต่ละรุ่น
- ทำหน้าที่แปลง generic I/O request ของ kernel ให้เป็นคำสั่งเฉพาะของ device
**Linux แบ่ง device เป็น 3 ประเภท:**
- **Character Device:** อ่าน/เขียนทีละ byte เช่น keyboard, serial, `/dev/tty0`
- **Block Device:** อ่าน/เขียนเป็น block เช่น disk, `/dev/sda`
- **Network Device:** พิเศษ เข้าถึงผ่าน socket API แทน file system
---
## Kernel Module Hello World
```c
// hello_mod.c - ตัวอย่าง kernel module ง่าย ๆ
#include
#include
#include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Moo");
MODULE_DESCRIPTION("ตัวอย่าง Hello World kernel module");
static int __init hello_init(void) {
printk(KERN_INFO "[hello_mod] โหลดโมดูลเรียบร้อย\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "[hello_mod] ถอนโมดูลเรียบร้อย\n");
}
module_init(hello_init);
module_exit(hello_exit);
```
---
## Build และใช้งาน Kernel Module
**Makefile:**
```makefile
obj-m += hello_mod.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
**คำสั่ง:**
```bash
make
sudo insmod hello_mod.ko # load
sudo dmesg | tail -5
lsmod | grep hello_mod
sudo rmmod hello_mod # unload
```
---
## 1.7.4 Buffering, Caching, Spooling
**Buffering:**
- เก็บข้อมูลระหว่างผู้ผลิตและผู้บริโภคเพื่อ smooth rate ที่ต่างกัน
- ตัวอย่าง: double buffering ใน video
**Caching:**
- เก็บสำเนาของข้อมูลที่ใช้บ่อยในที่เร็ว
- ตัวอย่าง: page cache (OS เก็บ disk block ใน RAM)
**Spooling (Simultaneous Peripheral Operations On-Line):**
- buffer พิเศษสำหรับ device ที่ไม่ interleave ได้ (เช่น printer)
- เก็บงานใน queue แล้ว serve ทีละงาน
---
## 1.7.5 Disk Scheduling
ดิสก์แบบจานหมุนมี **seek time** (เวลาเลื่อนหัวอ่าน) เป็น bottleneck
การจัดลำดับ request สำคัญมาก
อัลกอริทึมหลัก:
- **FCFS** — ตามลำดับมาก่อน
- **SSTF (Shortest Seek Time First)** — ใกล้ head สุด
- **SCAN (Elevator)** — ไปทางเดียวจนสุดแล้วกลับ
- **C-SCAN** — เหมือน SCAN แต่ jump กลับต้น
- **LOOK / C-LOOK** — ไม่ต้องไปสุดจริง ๆ
---
## ตัวอย่าง Disk Scheduling
**Queue:** `98, 183, 37, 122, 14, 124, 65, 67`, head = 53
| Algorithm | Head Movement |
| --- | --- |
| FCFS | 640 |
| SSTF | 236 |
| SCAN | 236 |
| C-SCAN | 386 |
| LOOK | 208 |
| C-LOOK | 322 |
SSTF อาจเกิด starvation; SCAN/LOOK สมดุลกว่า
---
## I/O Scheduler บน SSD/NVMe
สำหรับ SSD ที่ไม่มี seek time อัลกอริทึมเหล่านี้ไม่สำคัญเท่า HDD
แต่ OS ยังใช้ scheduler เพื่อ merge request และควบคุม fairness ระหว่าง process
```bash
# ดู I/O scheduler ปัจจุบัน
$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
# เปลี่ยนชั่วคราว
$ echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
```
Scheduler ทันสมัย: `mq-deadline`, `kyber`, `bfq`, `none`
---
## เปลี่ยน Scheduler ถาวรด้วย udev
```bash
$ sudo tee /etc/udev/rules.d/60-ioschedulers.rules <