OS · บทที่ 3 Mode Switch

หัวข้อ 8 · 12 นาที

UNIX I/O, IPC และโครงสร้าง kernel

นึกภาพก่อน

ปลั๊กไฟที่บ้านใช้ได้กับทุกอย่าง: พัดลม ทีวี ที่ชาร์จ ไม่ต้องมีเต้ารับแยกสำหรับแต่ละเครื่อง UNIX ออกแบบ I/O แบบเดียวกัน: ไฟล์ คีย์บอร์ด จอ เครือข่าย ใช้ system call ชุดเดียวกัน โปรแกรมที่อ่านจากไฟล์ได้ จึงอ่านจากคีย์บอร์ดหรือจาก output ของโปรแกรมอื่นได้โดยไม่ต้องแก้โค้ด

หลักห้าข้อของ UNIX I/O

หลักความหมาย
Uniformityทุก operation กับทุกไฟล์และทุก device ใช้ system call ชุดเดียว: open, close, read, write
Open before useopen คืน handle (file descriptor) สำหรับใช้ใน call ถัด ๆ ไปกับไฟล์นั้น
Byte-orientedทุกอย่างเป็นลำดับของ byte
Kernel-buffered read/writekernel พักข้อมูลไว้ใน buffer ทั้งตอนอ่านและตอนเขียน
Explicit closeต้องปิดเองเพื่อ garbage collect file descriptor ที่เปิดไว้

ทำไมต้อง open ก่อน: kernel ตรวจสิทธิ์และหาไฟล์ ครั้งเดียว ตอน open แล้วให้ file descriptor (ตัวเลขเล็ก ๆ) กลับมา read/write ครั้งถัดไปไม่ต้องตรวจชื่อและสิทธิ์ซ้ำ

ทำไมต้อง kernel-buffered: อุปกรณ์ทำงานเป็นก้อนและเร็วช้าไม่เท่าโปรแกรม เช่น โปรแกรมขอ 1 byte แต่ disk อ่านทีละ block หรือ packet มาถึงก่อนที่โปรแกรมจะเรียก read buffer ใน kernel เป็นตัวคั่นกลาง

open เป็น "Swiss Army knife"

open ของ UNIX ทำได้หลายอย่างตาม option:

  • ถ้าไฟล์ไม่มี → คืน error
  • ถ้าไฟล์ไม่มี → สร้างแล้วเปิด
  • ถ้าไฟล์มีอยู่ → คืน error
  • ถ้าไฟล์มีอยู่ → เปิด
  • ถ้าไฟล์มีอยู่และไม่ว่าง → ล้างแล้วเปิด
  • ถ้าไฟล์มีอยู่และไม่ว่าง → คืน error

ทำไมไม่แยกเป็น exists / create / open

c
if (!exists(name))
    create(name);   // can create fail?
fd = open(name);    // does the file exist?

ปัญหาคือสามบรรทัดนี้ ไม่ atomic ระหว่างแต่ละบรรทัด process อื่นแทรกเข้ามาได้:

  1. เราเรียก exists(name) → ได้ว่าไม่มี
  2. process อื่นสร้างไฟล์ชื่อเดียวกัน
  3. เราเรียก create(name) → ล้มเหลว หรือทับไฟล์ของคนอื่น
  4. หรือ process อื่นลบไฟล์หลัง create → open ของเราไม่เจอไฟล์

ผลของ exists ล้าสมัยทันทีที่ได้มา การรวมทุกอย่างไว้ใน open call เดียวให้ kernel ทำ "ตรวจและทำ" เป็นขั้นเดียวที่ไม่มีใครแทรกได้ ปัญหาแบบนี้คือ race condition ซึ่งเป็นหัวใจของบท Synchronization

ใน UNIX

  • โปรแกรมเป็นไฟล์ของคำสั่งได้ (shell script)
  • โปรแกรมส่ง output ไปที่ไฟล์ได้
  • โปรแกรมอ่าน input จากไฟล์ได้
  • output ของโปรแกรมหนึ่งเป็น input ของอีกโปรแกรมได้

ทั้งหมดนี้ทำได้เพราะ uniformity: โปรแกรมไม่รู้และไม่ต้องรู้ว่าปลายทางเป็นจอ ไฟล์ หรืออีกโปรแกรม

Interprocess Communication (IPC)

รูปแบบทิศทางลักษณะ
Producer–consumerทางเดียวoutput ของโปรแกรมหนึ่งเป็น input ของอีกโปรแกรม ใช้ pipe
Client–serverสองทางserver ทำงานเฉพาะทาง เช่น print server
File systemผ่านไฟล์เขียนข้อมูลลงไฟล์ แล้วอีกฝ่ายอ่านไฟล์เป็น input — ผู้อ่านและผู้เขียน ไม่ต้องทำงานพร้อมกัน

โครงสร้างของ OS

Monolithic kernelMicrokernel
บริการของ OS (file system, driver, network)อยู่ ใน kernel ทั้งหมด ทำงานใน kernel modeส่วนใหญ่ย้ายออกไปเป็น process ใน user mode
kernel มีทุกอย่างเฉพาะส่วนจำเป็น: IPC, scheduling พื้นฐาน, memory พื้นฐาน
การเรียกบริการเรียกฟังก์ชันภายใน kernelส่ง message ระหว่าง process ผ่าน kernel
ข้อดีเร็ว (ไม่ต้องสลับ mode ระหว่างบริการ)บริการหนึ่งพังไม่ลาก kernel พัง, kernel เล็ก ตรวจสอบง่าย
ข้อเสียบั๊กในส่วนใดก็ทำทั้ง kernel พังได้, kernel ใหญ่ช้ากว่าเพราะต้องสลับ mode และส่ง message หลายรอบ

ตัวอย่างไล่ทีละขั้น

โจทย์: โปรแกรมอ่านไฟล์ 100 byte แล้วพิมพ์ออกจอ ใช้ system call อะไรบ้าง ตามลำดับ

  1. fd = open("data.txt", ...) — kernel หาไฟล์ ตรวจสิทธิ์ คืน file descriptor (open before use)
  2. n = read(fd, buf, 100) — kernel copy ข้อมูลจาก buffer ของ kernel เข้า buf (byte-oriented, kernel-buffered)
  3. write(1, buf, n) — เขียนไปที่ file descriptor ของจอ ใช้ write ตัวเดียวกับที่ใช้เขียนไฟล์ (uniformity)
  4. close(fd) — คืน file descriptor (explicit close)

ถ้าผู้ใช้รันด้วย program > out.txt ขั้นที่ 3 จะเขียนลงไฟล์แทนจอ โดยโปรแกรมไม่ต้องแก้อะไรเลย

จุดที่มักพลาด

1. คิดว่า device ต่างชนิดใช้ system call ต่างชุด

uniformity: open, close, read, write ใช้กับทุกไฟล์และทุก device

2. คิดว่า pipe สื่อสารสองทาง

pipe เป็น producer–consumer ทางเดียว สองทางคือ client–server

3. คิดว่า IPC ผ่านไฟล์ต้องให้สองฝ่ายรันพร้อมกัน

ไม่ต้อง นี่คือจุดต่างของแบบ file system

4. คิดว่าแยก exists/create/open แล้วปลอดภัยกว่า

ตรงกันข้าม ยิ่งแยกยิ่งมีช่องให้ process อื่นแทรก

5. สลับ monolithic กับ microkernel

monolithic = ทุกบริการอยู่ใน kernel · microkernel = kernel เล็ก บริการอยู่ใน user mode

ที่มา: 03-ProcessAndContextSwitch_Part-II_v2.pdf หน้า 24, 33–35, 37–40