
บทนำ
ในบทความนี้ เราจะอธิบายว่า System Calls (SysCalls) คืออะไร ใช้ทำอะไร และแตกต่างจากฟังก์ชันในไลบรารีมาตรฐาน C อย่างไร เราจะแสดงบทบาทของพวกมันต่อเคอร์เนลในระบบ และกระบวนการใช้บริการที่พวกมันให้มา พร้อมรายการ System Calls ที่ใช้บ่อยที่สุด พร้อมคำอธิบายสั้นๆ และไฟล์หัวข้อที่ประกาศ รวมถึงวิธีติดตามการทำงานของ System Calls ผ่านคอนโซลคำสั่ง
แนวคิดชั้นของระบบ (System Layers)
ทุกระบบประกอบด้วยชั้นต่างๆ ที่แต่ละชั้นมีหน้าที่และสิทธิพิเศษของตัวเอง เริ่มจากระดับต่ำสุดคือ ฮาร์ดแวร์ และสิ้นสุดที่ชั้นบนสุดที่เรียกว่า User Mode
โปรแกรมทั้งหมดที่เรารันบนคอมพิวเตอร์ และโค้ดทั้งหมดที่เราเขียนและคอมไพล์ จะทำงานเป็นกระบวนการใน User Mode ของระบบ ในโหมดนี้ ผู้ใช้ทำงาน ใช้แอปพลิเคชัน จัดการไฟล์ ฯลฯ ด้านความปลอดภัย นี่คือชั้นที่ตื้นที่สุดของระบบ จึงถูกจำกัดมากที่สุด และโดยค่าเริ่มต้นถูกจำกัดให้ไม่สามารถจัดการทรัพยากรระบบโดยตรงได้
ชั้นฮาร์ดแวร์เป็นระดับต่ำสุด ขึ้นไปด้านบนคือ Linux Kernel เคอร์เนลคือหัวใจของระบบปฏิบัติการ ที่ผสานรวมกับฮาร์ดแวร์และจัดการอย่างเคร่งครัด เป็นผู้เดียวที่มีสิทธิ์ทำได้ ตัวอย่างเช่น การจัดการกระบวนการและหน่วยความจำ การเข้าถึงข้อมูลจากฮาร์ดดิสก์หรือ SSD การควบคุมอุปกรณ์เครือข่าย ทุกอย่างที่ OS และกระบวนการทำกับฮาร์ดแวร์ ผ่านเคอร์เนลเสมอ
เนื่องจากข้อจำกัดในการเข้าถึงและจัดการทรัพยากรที่ OS กำหนดให้กับ User Mode และการเข้าถึงทรัพยากรนี้เป็นไปได้เฉพาะผ่านเคอร์เนล จึงจำเป็นต้องมีกลไกที่ช่วยให้กระบวนการโต้ตอบกับทรัพยากรที่ต้องการเพื่อทำงานได้
System Calls คืออะไร?
System Calls (SysCalls) เป็นชุดฟังก์ชันที่ระบบปฏิบัติการจัดเตรียไว้ ทำหน้าที่เป็น อินเทอร์เฟซระหว่างกระบวนการผู้ใช้ (user process) และเคอร์เนล (kernel) ฟังก์ชันเหล่านี้ช่วยให้เราโต้ตอบกับ OS และร้องขอบริการที่ต้องใช้ทรัพยากรระบบผ่านเคอร์เนล SysCalls ไม่ได้ให้สิทธิ์เข้าถึงทรัพยากรโดยตรง แต่เคอร์เนลเป็นผู้จัดการและทำให้ทรัพยากรพร้อมใช้งานให้กระบวนการที่เรียก ภายใต้การจัดการที่เคร่งครัดของเคอร์เนล
ในทางปฏิบัติ ฟังก์ชัน System Call ดูเหมือนฟังก์ชันทั่วไป และถูก implement แบบนั้น มีอาร์กิวเมนต์นำเข้าสำหรับส่งข้อมูลที่จะประมวลผล และอาร์กิวเมนต์ส่งออกสำหรับรับผลลัพธ์จาก OS วิธีการเรียกใช้เหมือนฟังก์ชันทั่วไปในไลบรารี C ดังนั้นคำถามว่าเป็นฟังก์ชันเรียกหรือไม่ ในทางปฏิบัติเป็นคำถามเชิงแนวคิดเพียงอย่างเดียวสำหรับนักเขียนโปรแกรม
อาร์กิวเมนต์ของฟังก์ชันเหล่านี้ใช้ชนิดข้อมูลมาตรฐานของภาษา C แม้ว่าจะมักเป็นพอยน์เตอร์ที่เก็บที่อยู่หน่วยความจำ descriptors paths ฯลฯ ตามลักษณะงานของมัน
ใน Linux ปัจจุบันมี System Calls ประมาณ 350 ตัว นอกจากนี้ยังมีฟังก์ชันจำนวนมากที่ไม่ใช่ System Calls แต่จัดการทรัพยากรผ่าน SysCalls เช่น malloc() free() calloc() realloc() ที่จัดการหน่วยความจำแบบไดนามิกโดยใช้ syscalls ในโค้ดของพวกมัน
การติดตาม System Calls ด้วยคำสั่ง strace
คำสั่ง strace ใช้ติดตาม system calls แบบ real-time จากคอนโซล Linux ค่อนข้างยืดหยุ่น มีตัวเลือกมากมาย ดูได้ด้วยคำสั่ง:
strace -h

เพื่อติดตาม syscalls ของกระบวนการ ให้รันด้วย:
strace ./process
ตัวอย่างการติดตามจริง
เราใช้โค้ดตัวอย่างจากบทความ "Connect Function in C language" ซึ่งเชื่อมต่อกับ Google ผ่าน HTTP ส่งคำสั่งและรับการตอบสนอง
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
int main() {
// ... โค้ดการเชื่อมต่อ socket ...
}
คอมไพล์แล้วรันด้วย strace:
strace ./connect

จากภาพ แม้โค้ดเราจะใช้แค่ socket() connect() send() recv() แต่การรันจริงต้องการ system calls อื่นๆ มากมายที่เกิดจากโค้ดภายในฟังก์ชันที่เราใช้
คำสั่งแสดงสรุป system calls เมื่อกระบวนการสิ้นสุด:
strace -c ./connect

รายการ System Calls ที่ใช้บ่อย
System Calls สำหรับจัดการกระบวนการ
| Function | Description | Header |
|---|---|---|
| execl(), execlp(), execle(), execv(), execvp(), execvpe() | กลุ่มฟังก์ชันแทนที่กระบวนการหนึ่งด้วยอีกตัวหนึ่ง โดยสืบทอด PID และทรัพยากรจากกระบวนการแม่ | unistd.h |
| fork() | สร้างกระบวนการลูกโดยโคลนกระบวนการแม่ | unistd.h |
| getpid() | ดึง PID ของกระบวนการที่เรียก | unistd.h |
| getppid() | ดึง PID ของกระบวนการแม่ | unistd.h |
| exit() | แจ้งสถานะการออกจากกระบวนการ | stdlib.h |
| getcwd(), getwd() | คืนสตริงไดเรกทอรีทำงานปัจจุบัน | unistd.h |
| pause() | หยุดกระบวนการจนกว่าจะได้รับสัญญาณ | unistd.h |
| wait() | รอผลลัพธ์จากกระบวนการลูก | wait.h |
System Calls สำหรับจัดการไฟล์
| Function | Description | Header |
|---|---|---|
| open(), openat() | เปิดไฟล์ | fcntl.h |
| close() | ปิดไฟล์ | unistd.h |
| read() | อ่านไฟล์ | unistd.h |
| poll() | ตรวจสอบความพร้อมของไฟล์สำหรับการดำเนินการเฉพาะ | poll.h |
| write() | เขียนไฟล์ | unistd.h |
| fcntl() | จัดการ file descriptors | fcntl.h |
| truncate(), ftruncate() | ตัดไฟล์ให้มีความยาวตามที่กำหนด | unistd.h |
| creat() | สร้างไฟล์ | fcntl.h |
| chmod(), fchmod() | ตั้งค่าสิทธิ์ไฟล์ | sys/stat.h |
| unlink() | ลบไฟล์ | unistd.h |
System Calls สำหรับจัดการสัญญาณ (Signals)
| Function | Description | Header |
|---|---|---|
| signal() | เชื่อมโยงสัญญาณกับ handler | signal.h |
| kill() | ส่งสัญญาณไปยังกระบวนการ | signal.h |
| killpg() | ส่งสัญญาณไปยังกลุ่มกระบวนการ | signal.h |
| raise() | ดำเนินการของสัญญาณในกระบวนการที่เรียก | signal.h |
| sigaction() | ตั้งค่าการกระทำของสัญญาณ | signal.h |
| sigwait() | ระงับการทำงานจนกว่าจะได้รับสัญญาณที่ระบุ | signal.h |
| sighold() | เพิ่มสัญญาณเข้า signal mask | signal.h |
| sigignore() | เพิกเฉยสัญญาณ | signal.h |
| sigblock() | บล็อกสัญญาณ | signal.h |
| psignal(), psiginfo() | พิมพ์ข้อความอธิบายสัญญาณ | signal.h |
System Calls สำหรับจัดการ Socket
| Function | Description | Header |
|---|---|---|
| socket() | สร้าง socket | socket.h |
| bind() | ผูกชื่อกับ socket | socket.h |
| connect() | เชื่อมต่อ socket | socket.h |
| listen() | ฟังการเชื่อมต่อบน socket | socket.h |
| accept() | ยอมรับการเชื่อมต่อบน socket | socket.h |
| recv(), recvfrom(), recvmsg() | รับข้อความจาก socket | socket.h |
| send(), sendto(), sendmsg() | ส่งข้อความผ่าน socket | socket.h |
| getsockname() | คืนที่อยู่ของ socket จาก descriptor | socket.h |
| getsockopt() | ตั้งค่าหรือดึงค่าตัวเลือก socket | socket.h |
สรุป
บทความนี้ได้อธิบายว่าฟังก์ชัน System Call คืออะไร และใช้ร้องขอบริการเคอร์เนลที่เกี่ยวข้องกับทรัพยากรระบบอย่างไร แสดงการโต้ตอบระหว่างกระบวนการผู้ใช้และเคอร์เนลผ่าน System Calls พร้อมรายการ System Calls ที่ใช้บ่อยที่สุด คำอธิบายสั้นๆ และไฟล์หัวข้อที่ต้อง include รวมถึงวิธีติดตาม syscalls ของกระบวนการผ่านคอนโซลคำสั่ง