เจาะลึก Apple Darwin OS และเคอร์เนล XNU
(tansanrao.com)- Darwin ของ Apple คือรากฐานตระกูล Unix ของ macOS, iOS และ OS สมัยใหม่ของ Apple ส่วน XNU เป็นเคอร์เนลแบบไฮบริดที่ผสาน Mach และ BSD ไว้ในเคอร์เนลเดียว
- XNU คงไว้ซึ่ง task, thread, virtual memory และ IPC แบบอิง port ของ Mach ขณะเดียวกันก็วางบริการ BSD ไว้ใน address space เดียวกันของเคอร์เนล เพื่อลดต้นทุนการส่งข้อความแบบไมโครเคอร์เนลล้วน
- สายวิวัฒนาการของ NeXTSTEP ที่มาจาก Mach 2.5+4.3BSD นำไปสู่ Mac OS X และ Darwin หลัง Apple ซื้อ NeXT ในปี 1996 จากนั้นจึงค่อย ๆ เพิ่มโค้ด FreeBSD, I/O Kit, 64-bit, ARM และการรองรับ Apple Silicon เข้ามาเป็นลำดับ
- macOS และ iOS ขยายความสามารถต่าง ๆ เช่น sandbox, code signing, SIP, APFS, DriverKit, QoS scheduling, Jetsam และ compressed memory ผ่านการทำงานร่วมกันระหว่างเคอร์เนลกับ user space
- วิวัฒนาการของ XNU ใกล้เคียงกับแนวทางที่รักษาฐาน Mach/BSD ไว้แทนการเขียนเคอร์เนลใหม่ โดยรวมส่วนที่ต้องการประสิทธิภาพไว้ในเคอร์เนล และแยกส่วนที่ต้องการ isolation ออกไปผ่าน IPC และ user space
จุดเริ่มต้นของ Darwin และ XNU
- Darwin คือระบบปฏิบัติการแกนหลักตระกูล Unix ที่รองรับ macOS, iOS และแพลตฟอร์ม OS สมัยใหม่ของ Apple
- ที่ศูนย์กลางมีเคอร์เนล XNU ซึ่งย่อมาจาก “X is Not Unix” โดยผสานแกนไมโครเคอร์เนล Mach เข้ากับองค์ประกอบ BSD Unix
- โครงสร้างนี้มุ่งหาสมดุลระหว่างความเป็นโมดูลาร์กับประสิทธิภาพ โดยใช้ทั้งการออกแบบแบบส่งข้อความของ Mach และความเสถียรกับความเข้ากันได้กับ POSIX ของ BSD
ประวัติที่ต่อเนื่องจาก Mach, NeXTSTEP สู่ Mac OS X
- Mach เริ่มต้นในปี 1985 เป็นโครงการที่ Carnegie Mellon University นำโดย Richard Rashid และ Avie Tevanian
- เป็นการออกแบบไมโครเคอร์เนลที่ตั้งใจให้มีเฉพาะฟังก์ชันระดับล่าง เช่น การจัดการหน่วยความจำ, CPU scheduling และ IPC อยู่ในเคอร์เนล ส่วน file system, networking และ driver อยู่เป็นเซิร์ฟเวอร์ใน user space
- แนวคิดอย่าง task, thread, Mach port, copy-on-write และ memory object กลายเป็นออบเจกต์หลักของเคอร์เนล
- NeXTSTEP เปิดตัวในปี 1989 โดยวางซับซิสเต็ม 4.3BSD Unix บนเคอร์เนล Mach 2.5
- NeXT เลือกให้ความสำคัญกับประสิทธิภาพ โดยผสานโค้ด BSD เข้าไปใน address space ของเคอร์เนล แทนแนวทางไมโครเคอร์เนลล้วน
- ยังรวม DriverKit ที่อิง Objective-C ไว้ด้วย และภายหลังสืบทอดมาสู่สาย XNU ของ Apple
- Apple ซื้อ NeXT ในปี 1996 และเลือก NeXTSTEP เป็นฐานของ Mac OS X ใหม่
- โครงการ Rhapsody เริ่มขึ้น และเคอร์เนลไฮบริด Mach/BSD ของ NeXT จึงเข้าสู่ Apple
- ต่อมา XNU รับโค้ดสาย Mach 3.0 ที่อิง OSFMK 7.3 รวมถึงโค้ด 4.4BSD และ FreeBSD เข้ามา
การพัฒนาระยะแรกของ Darwin และ Mac OS X
- Apple ออก Mac OS X Developer Preview ในปี 1999 และเปิดตัว Darwin 1.0 ในปี 2000 โดยเปิดเคอร์เนล XNU และ user space Unix พื้นฐานให้นักพัฒนา
- Mac OS X 10.0 Cheetah เปิดตัวเชิงพาณิชย์ในปี 2001 โดยอิง Darwin 1.3.1
- จุดเน้นของการเปลี่ยนแปลงช่วงแรกคือการเสริมความแข็งแกร่งให้เลเยอร์ BSD, networking, file system และประสิทธิภาพของ threading
- Mac OS X 10.1 Puma ปรับปรุงประสิทธิภาพการจัดการ thread และการรองรับ real-time thread
- Mac OS X 10.2 Jaguar รวม IPv6, IPSec,
mDNSResponderและ HFS+ journaling - Mac OS X 10.3 Panther ผสานการปรับปรุงเคอร์เนล FreeBSD 5 และ kernel lock ที่ละเอียดขึ้น เพื่อเพิ่มการใช้ประโยชน์จาก multiprocessor
- Mac OS X 10.4 Tiger ได้รับการรับรอง UNIX 03 นำ
kqueue/keventของ FreeBSD มาใช้ และรักษาฐานข้ามแพลตฟอร์มสำหรับการเปลี่ยนผ่านไปยัง Intel Mac
ความต้องการบนมือถือที่ 64-bit และ iPhone OS นำเข้ามา
- Mac OS X 10.5 Leopard อิง Darwin 9 และนำการรันเคอร์เนล 64-bit, driver 64-bit, ASLR, sandbox และ DTrace เข้ามา
- iPhone OS รุ่นแรกในปี 2007 ก็เปิดตัวโดยอิง Darwin 9 ทำให้ XNU ขยายไปถึงอุปกรณ์มือถือ ARM
- iPhone รุ่นแรกมี RAM จำกัดและใช้ swap ไม่ได้ จึงใช้กลไก Jetsam เพื่อปิดแอปเบื้องหลังเมื่อหน่วยความจำเหลือน้อย
- iPhone OS รันแอป third-party ภายใน sandbox และกำหนดให้ binary ต้องผ่าน code signing อย่างเข้มงวด
- Mac OS X 10.6 Snow Leopard ยุติการรองรับ PowerPC และเสริมการปรับแต่งสำหรับ 64-bit และ multicore โดยเน้น Intel
- Grand Central Dispatch และ
libdispatchเป็นไลบรารีใน user space แต่ใช้ประโยชน์จาก thread pool และการรองรับ scheduling ของเคอร์เนล - OpenCL ก็ต้องอาศัยการผสานอย่างใกล้ชิดระหว่าง user framework กับ kernel driver สำหรับ GPU computing
- Grand Central Dispatch และ
- iOS 4 ปรับ scheduler ให้สอดคล้องกับการแยกลำดับความสำคัญของแอปเบื้องหลังและการรองรับ multicore ARM SoC
การขยายฟีเจอร์เคอร์เนลของ macOS และ iOS สมัยใหม่
- OS X 10.9 Mavericks เพิ่ม compressed memory และ timer coalescing
- compressed memory บีบอัดหน้า memory ที่ไม่ active ไว้ใน RAM เพื่อลด disk swap
- timer coalescing เป็นวิธีลดการใช้พลังงานโดยจัดเวลาให้ CPU ตื่นพร้อมกัน
- OS X 10.11 El Capitan นำ System Integrity Protection หรือ SIP เข้ามา
- SIP ถูกบังคับใช้โดยเคอร์เนลผ่านเฟรมเวิร์ก Mandatory Access Control ของเลเยอร์ BSD ทำให้แม้แต่ process ที่เป็น root ก็แก้ไขไฟล์ระบบและ process สำคัญไม่ได้
- macOS 10.13 High Sierra นำ APFS มาเป็น file system เริ่มต้น
- เลเยอร์ VFS ของ XNU ถูกขยายเพื่อรองรับ snapshot, cloning และการเข้ารหัสระดับ container ของ APFS
- ในช่วงเวลาเดียวกัน การโหลด kext จาก third-party เริ่มต้องได้รับอนุมัติจากผู้ใช้
- macOS 10.15 Catalina นำ DriverKit สมัยใหม่เข้ามา
- DriverKit ย้าย driver จำนวนมากออกจากเคอร์เนลไปเป็น Driver Extension ใน user space
- เคอร์เนลให้การเข้าถึงฮาร์ดแวร์แบบจำกัดแก่ driver ใน user space ผ่าน IPC และ shared memory
- Catalina ยังนำ read-only system volume เข้ามาเพื่อเสริมการป้องกันของ SIP
XNU ในยุค Apple Silicon
- macOS 11 Big Sur และ Darwin 20 ในปี 2020 เป็นรุ่นแรกที่รองรับ Apple Silicon Mac
- XNU รองรับ ARM อยู่แล้วผ่าน iOS แต่บน Apple Silicon Mac ต้องคำนึงถึงโครงสร้าง CPU แบบ heterogeneous big.LITTLE ด้วย
- scheduler รับรู้ heterogeneous core เพื่อจัด thread ที่มีลำดับความสำคัญสูงและงานหนักไปยัง performance core และจัด thread ที่มี QoS ต่ำหรือเป็นงานเบื้องหลังไปยัง efficiency core
- QoS class ถูกใช้เป็น scheduling hint ที่อาจส่งผลต่อการเลือกประเภท core บน Apple Silicon ด้วย
- ในสถาปัตยกรรม unified memory ของ Apple Silicon ตัวจัดการหน่วยความจำของเคอร์เนลและ GPU driver จะจัดการการแชร์ buffer
- abstraction ของ Mach VM เหมาะกับการแชร์ memory object ระหว่าง user space กับ GPU ผ่านการ remap VM แทนการ copy
- backend ARM64 รองรับ Pointer Authentication โดยใช้ PAC key กับ exception frame และ system pointer และช่วยลดผลกระทบจากการโจมตีแบบ ROP
- XNU ยังคงเป็นฐานร่วมของหลายแพลตฟอร์มของ Apple เช่น macOS, iOS, watchOS, tvOS, bridgeOS และ visionOS
โครงสร้างเคอร์เนลไฮบริดของ XNU
- องค์ประกอบ Mach และ BSD ของ XNU ถูกลิงก์เป็น kernel binary เดียวและแชร์ address space เดียวกัน
- ไม่มีขอบเขตการป้องกันระหว่าง Mach กับ BSD และภายในเคอร์เนลทั้งสองโต้ตอบกันด้วยการเรียกฟังก์ชันปกติ ไม่ใช่ข้อความ IPC
- Unix system call อย่าง
read()ไม่ได้ส่งข้อความไปยังเซิร์ฟเวอร์ BSD แยกต่างหาก แต่เข้าถึงโค้ด file system ของ BSD ภายในเคอร์เนลโดยตรง
- Mach รับผิดชอบ infrastructure หลักของเคอร์เนล
- จัดการการสร้างและยุติ task กับ thread, context switching, scheduling ระดับล่าง, lock, timer และ scheduling queue
- แต่ละ BSD process สอดคล้องกับ Mach task และแต่ละ thread สอดคล้องกับ Mach thread
- Mach VM ให้บริการ virtual address map, memory object, copy-on-write และการแชร์หน่วยความจำแบบอิง IPC
- BSD ให้ลักษณะและบริการแบบ Unix
- จัดการ PID, user ID, signal, POSIX thread, file system, networking, Unix IPC, device I/O, permission และ security framework
- VFS จัดการ file system เช่น HFS+, APFS, NFS และในกรณีไฟล์ที่ map เข้าหน่วยความจำ จะเชื่อมกับ Mach VM ผ่าน vnode pager
- sandbox และ SIP ทำงานผ่านความร่วมมือระหว่างโมดูลความปลอดภัยของ BSD กับการจำกัด Mach task port
- I/O Kit เป็นแกนที่สามของ XNU เป็นเฟรมเวิร์ก driver แบบ object-oriented ที่เขียนด้วย C++ รูปแบบจำกัด
- แสดงอุปกรณ์และ driver เป็นลำดับชั้นของ class และ driver รันเป็นออบเจกต์ C++ ภายในเคอร์เนล
- เปิดการเข้าถึงแบบจำกัดแก่ user space ผ่านคุณสมบัติ I/O Registry และอินเทอร์เฟซ user client
- ก่อน DriverKit ใน macOS สมัยใหม่ driver ส่วนใหญ่ทำงานในเคอร์เนลในรูปแบบ kext
Mach IPC และบริการระบบ
- XNU ไม่ใช้ข้อความ Mach ในเส้นทาง Unix system call แต่ใช้ Mach IPC อย่างกว้างขวางสำหรับบริการใน user space และการสื่อสารระหว่างเคอร์เนลกับ process
- Mach port ใช้เป็น handle ฝั่ง user space ของ kernel object หลายชนิด
- แต่ละ task มี task port และ process ที่มีสิทธิ์สามารถใช้มันเพื่อตรวจสอบหรือควบคุม task อื่นได้
- event และ notification ก็ส่งผ่าน Mach message
- WindowServer รับ event อินพุตผู้ใช้จากเคอร์เนลเป็น Mach message
- Grand Central Dispatch ใช้ Mach port ภายในเพื่อทำให้ thread ที่รอ event หลับ
kqueue/keventสามารถรอทั้งข้อความ Mach port และ file descriptor พร้อมกันได้
- เฟรมเวิร์ก XPC ของ Apple สร้างอยู่บน Mach message
- การเชื่อมต่อ XPC ภายในอิง Mach port
- โมเดลสิทธิ์ของ Mach port ถูกใช้ในบริการอย่าง
securitydของ Keychain เพื่อตรวจสอบสิทธิ์ผู้เรียก - Mach message ส่ง out-of-line memory และสิทธิ์ port ได้ จึงใช้สร้าง RPC ระดับสูง
- MIG หรือ Mach Interface Generator ใช้สร้างนิยามอินเทอร์เฟซและโค้ดส่งรับข้อความระหว่างเคอร์เนลกับ user space
Scheduler และการจัดการ Thread
- XNU scheduler เริ่มจาก scheduler แบบ round-robin อิง priority ของ Mach แต่ถูกปรับแก้อย่างมากให้เหมาะกับความต้องการของเดสก์ท็อปและมือถือ
- Mach ในอดีตกำหนด priority ของ thread ในช่วง 0–127 และ XNU ใช้ค่าต่าง ๆ เช่น
sched_priและbase_pri- thread แบบ time-sharing อาจมี priority เปลี่ยนตามการใช้งาน
- real-time thread ใช้ priority คงที่
- XNU จัดการประสิทธิภาพและ load balancing ด้วย run queue ต่อ CPU และ scheduler interrupt
- sandbox ของแอป iOS และการรันเบื้องหลังสะท้อนแนวคิดเรื่องบทบาทงานหรือกลุ่ม priority เข้าไปใน scheduler
- QoS class ถูกผสานเข้ากับ scheduling ตั้งแต่ iOS 8 และ OS X 10.10 เป็นต้นมา
- class เช่น user-interactive, user-initiated, default, utility, background ส่งผลต่อช่วง priority และ scheduling
- thread ที่สร้างด้วย Grand Central Dispatch หรือ NSThread จะสืบทอด QoS
- บน Apple Silicon thread ที่มี QoS แบบ background อาจถูกจัดไปยัง efficiency core
- รองรับ real-time queue และ deadline-based scheduling สำหรับ real-time audio และงานสำคัญด้วย
การจัดการหน่วยความจำและ Mach VM
- การจัดการหน่วยความจำของ XNU มีซับซิสเต็ม Mach VM เป็นศูนย์กลาง
- แต่ละ Mach task มี virtual address space ที่แสดงเป็น VM map และ VM region
fork()ใช้ copy-on-write แทนการคัดลอกหน่วยความจำทั้งหมดทันที- parent และ child แชร์หน้าเดียวกันจนกว่าจะมีการเขียน
- Mach ใช้แนวคิด memory object และ pager
- default pager ของ anonymous memory รับผิดชอบโดย daemon
dynamic_pagerใน user space ซึ่งจัดการ swap file เมื่อจำเป็น - file memory ใช้ vnode pager ในเลเยอร์ BSD ภายในเคอร์เนล เพื่อโต้ตอบกับโค้ด file system
- default pager ของ anonymous memory รับผิดชอบโดย daemon
- compressed memory ของ Mavericks ถูกทำโดยเพิ่ม compression pager ภายในเคอร์เนล
- เมื่อ memory pressure สูง หน้า inactive จะไม่ถูกส่งลงดิสก์ทันที แต่ถูกบีบอัดเก็บไว้ใน compressor pool ภายใน RAM
- หากการบีบอัดยังไม่พอ จึงใช้ disk swap
- การจัดการ physical memory รับผิดชอบโดย pmap ซึ่งเป็นเลเยอร์ที่ขึ้นกับสถาปัตยกรรม
- pmap จัดการ page table หรือโครงสร้างเทียบเท่าของสถาปัตยกรรมนั้น
- บน ARM64 ฟีเจอร์ความปลอดภัยและประเด็นเกี่ยวกับ cache ก็เชื่อมโยงกับ pmap
- shared cache ของ
dyldmap physical page เดียวกันแบบ read-only ไปยังหลาย process เพื่อใช้งานอย่างมีประสิทธิภาพ
การรองรับ Virtualization
- บน Intel Mac มี Hypervisor.framework ตั้งแต่ OS X 10.10 เพื่อรองรับ virtualization ใน user space
- ใช้ Intel VT-x เพื่อให้ process ใน user space ทำงานเหมือน virtual machine monitor ได้
- เครื่องมืออย่าง
xhyveและแอป virtualization บางส่วนใช้ฟีเจอร์นี้
- บน Apple Silicon Virtualization.framework ของ macOS 11 ทำงานบนไฮเปอร์ไวเซอร์ในเคอร์เนลสำหรับ ARM64
- นักพัฒนาสามารถรัน Linux หรือ macOS VM จาก user space ได้
- ใช้แนวทางการเข้าถึงผ่านเฟรมเวิร์กและสิทธิ์ของ Apple แทนการอนุญาตให้ไฮเปอร์ไวเซอร์ third-party ใด ๆ อยู่ในเคอร์เนล
- จากมุมมองเคอร์เนล ฟังก์ชันไฮเปอร์ไวเซอร์รวมถึงการจัดการ guest physical memory, trap-and-emulate สำหรับคำสั่งที่ละเอียดอ่อน และการเปิดเผยอินเทอร์เฟซ vCPU
- Mach scheduler จัด schedule vCPU ซึ่งเป็น thread ในมุมมองของ host และซับซิสเต็มหน่วยความจำถูกใช้สำหรับการ map หน่วยความจำของ guest
- บน iOS ก็สามารถใช้ฟังก์ชัน virtualization ได้ภายใต้เงื่อนไขและสิทธิ์บางอย่าง และเคยมีกรณีเปิดใช้งานไฮเปอร์ไวเซอร์บนอุปกรณ์ A14 ที่ jailbreak แล้วเพื่อรัน Linux VM
Secure Enclave และ Exclaves
- macOS ใช้กลไก isolation สองแบบคือ Secure Enclave และ exclaves เพื่อปกป้องงานและข้อมูลที่อ่อนไหว
- Secure Enclave เป็นซับซิสเต็มเฉพาะที่ถูก harden และรวมอยู่ใน Apple SoC
- มีอยู่ใน iPhone, iPad, T2 หรือ Apple Silicon Mac เป็นต้น
- รันระบบปฏิบัติการที่อิงไมโครเคอร์เนลของตัวเอง และจัดการข้อมูลอ่อนไหว เช่น คีย์เข้ารหัสและข้อมูลชีวมิติ
- มีเป้าหมายเพื่อแยกข้อมูลสำคัญออก แม้ main application processor หรือเคอร์เนลจะถูกเจาะ
- Exclaves เป็นโครงสร้างความปลอดภัยที่ใหม่กว่า ซึ่งปรากฏใน macOS 14.4 และ iOS 17
- แทนที่จะวางงานอ่อนไหวไว้ใน privilege domain เดียวกับเคอร์เนล XNU หลัก จะย้าย resource สำคัญบางส่วนไปยังโดเมน “externally located” แยกต่างหาก
- resource ที่อยู่ในขอบเขตนี้รวมถึงบริการ Apple ID, audio buffer, sensor data และองค์ประกอบจัดการไฟแสดงสถานะ
- kext พิเศษและ private framework เช่น ExclaveKextClient.kext, ExclaveSEPManagerProxy.kext, ExclavesAudioKext.kext มีส่วนเกี่ยวข้องกับการจัดการ
- การแยกนี้ให้ชั้นป้องกันเพิ่มเติม โดย isolation งานภายใน exclave แม้เคอร์เนลหลักจะถูกเจาะ
ทิศทางการออกแบบระยะยาว
- Darwin และ XNU เป็นการออกแบบแบบผสม ไม่ใช่ทั้งไมโครเคอร์เนลเต็มรูปแบบหรือโมโนลิทิกเคอร์เนลเต็มรูปแบบ
- แกนที่อิง Mach ช่วยให้ปรับตัวเข้ากับสถาปัตยกรรมและฟีเจอร์ระบบใหม่ได้ ส่วนเลเยอร์ BSD ให้สภาพแวดล้อมที่เข้ากันได้กับ POSIX รวมถึงเครื่องมือและ API แบบ Unix
- Apple รองรับการเปลี่ยน CPU จาก PowerPC ไป Intel และ ARM รวมถึงหมวดอุปกรณ์ใหม่อย่าง iPhone, Apple Watch และ Apple Vision Pro บนฐาน XNU
- การเปลี่ยนแปลงเคอร์เนลส่วนใหญ่ดำเนินไปในสามแนวทาง
- ฟีเจอร์ใหม่ถูก ขยาย บนเคอร์เนลเดิม
- องค์ประกอบที่สำคัญต่อประสิทธิภาพถูก ผสาน ไว้ในเคอร์เนล
- องค์ประกอบที่ต้องการ isolation ถูก แยก ผ่าน Mach IPC และ user space
- การปล่อยซอร์สโค้ดสาธารณะของ Darwin เป็นหน้าต่างให้นักวิจัยได้ตรวจดูเคอร์เนลไฮบริดเชิงพาณิชย์ แต่ขอบเขตที่เปิดเผยก็มีข้อจำกัด
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ระบบหน่วยความจำเสมือนของ Mach ถูกนำไปใช้ไม่เพียงใน 4.4BSD และ FreeBSD แต่ยังรวมถึง NetBSD[0], OpenBSD[1] ด้วย แต่ดูเหมือนไม่ใช่ใน DragonFly BSD[2]
[0] https://netbsd.org/docs/kernel/uvm.html
[1] https://man.openbsd.org/OpenBSD-3.0/uvm.9
[2] https://www.dragonflybsd.org/mailarchive/kernel/2011-04/msg0...
พอถึง FreeBSD 4 ก็ไม่มีโค้ด Mach ดั้งเดิมเหลืออยู่ใน codebase ของเคอร์เนลแล้ว และเรื่องนี้จบไปตั้งแต่ปลายทศวรรษ 1990 ดังนั้นสิ่งที่เชื่อม FreeBSD กับ Mach ได้ก็มีแค่ช่วงแตกแขนง/ฐานรากแรก ๆ เท่านั้น
NetBSD และ OpenBSD ก็ใช้ต่ออยู่พักหนึ่ง แต่ชนกับข้อจำกัดด้านประสิทธิภาพ, SMP/ความสามารถในการขยาย และเครือข่ายของการออกแบบ Mach จึงเขียนใหม่ทั้งหมดเป็น UVM (Unified Virtual Memory) ที่ Chuck Cranor ออกแบบและนำการพัฒนา ส่วน OpenBSD ภายหลังยืม implementation นี้มาใช้และยังใช้อยู่จนถึงปัจจุบัน
ในบรรดา BSD ที่ยังมีชีวิตอยู่[1] ตัวที่ยังใช้ Mach ต่อเนื่องมีแค่ XNU/Darwin และนั่นก็ไม่ใช่ Mach 2.5 แต่เป็น Mach 3 เคยมี Mach 2.5, 3, 4 (GNU/Hurd คือ Mach 4) แต่ความเข้ากันได้ต่ำ และส่วนใหญ่แชร์อิทธิพลกันในระดับสถาปัตยกรรมโดยรวมเท่านั้น ดังนั้นมองว่าเป็นการออกแบบแยกกันที่ได้รับอิทธิพลร่วมกันจะเหมาะกว่า
[0] ตั้งแต่แรกก็ไม่ได้มีร่องรอยมากนักอยู่แล้ว
[1] ผมก็ไม่แน่ใจเหมือนกันว่า DragonBSD ตอนนี้ตายไปแล้วหรือยังมีชีวิตอยู่
Darwin น่าสนใจตรงที่องค์ประกอบหลัก ๆ เปลี่ยนแปลงอย่างสุดขั้วด้วยความเร็วสูง ตั้งแต่การยอมทิ้งความเข้ากันได้ย้อนหลังของ system call, การบังคับ code signing, ไปจนถึง dyld_shared_cache ที่เลิกใช้ไฟล์ system library แยกชิ้นเพื่อเพิ่มความเร็วในการโหลดไฟล์ executable แบบไดนามิก เป็นแนวทางออกแบบที่เน้นผลลัพธ์โดยไม่มีความอาลัยหรือพื้นที่ศักดิ์สิทธิ์
ดูเป็นแนวทางที่มีแต่บริษัทฮาร์ดแวร์รายใหญ่อย่าง Apple เท่านั้นที่ทำได้
[1] https://www.theregister.com/2025/03/08/kernel_sanders_apple_...
บทความบอกว่า pager daemon ที่จัดการไฟล์ swap รันอยู่ใน user space และหน่วยความจำเคอร์เนลก็สามารถถูก swap ได้ แต่ไม่ได้อธิบายว่า daemon ใน user space จะ swap หน่วยความจำเคอร์เนลได้อย่างไร
สงสัยว่ามีข้อยกเว้นที่ hardcode ไว้สำหรับ daemon พิเศษหรือไม่ หรือใช้ system call พิเศษกันแน่ จะอ่านรายละเอียดเฉพาะของการจัดการหน่วยความจำใน user space เพิ่มได้ที่ไหน?
https://web.mit.edu/darwin/src/modules/xnu/osfmk/man/memory_...
อย่างไรก็ตาม ไม่แน่ชัดว่า Darwin เคยใช้ฟีเจอร์นี้จริงหรือไม่ และอย่างน้อยในช่วงราว 20 ปีที่ผ่านมาไม่ได้ใช้ dynamic_pager ไม่เคยใช้ interface นี้ แต่ใช้ Mach interface ที่จำกัดกว่ามาก โดยเมื่อ XNU แจ้งว่า swap ไม่พอ ก็สร้างไฟล์ swap แล้วส่งให้เคอร์เนลผ่าน system call
macx_swapon,macx_swapoffการ swap จริง ๆ ทำโดยเคอร์เนล ส่วนโค้ด dynamic_pager เก่าอยู่ที่นี่:https://github.com/apple-oss-distributions/system_cmds/blob/...
ฟีเจอร์นั้นตอนนี้ก็ย้ายเข้าเคอร์เนลแล้ว ทำให้ dynamic_pager ปัจจุบันแทบไม่ได้ทำอะไรเลยจริง ๆ:
https://github.com/apple-oss-distributions/system_cmds/blob/...
หน่วยความจำเคอร์เนลส่วนใหญ่ถูกตรึงไว้ (wired) จึงไม่สามารถถูก paging ได้ แต่เคอร์เนลสามารถร้องขอหน่วยความจำที่ paging ได้อย่างชัดเจนด้วยวิธีอย่าง
IOMallocPageableและหน่วยความจำนั้นสามารถ swap ลงดิสก์ได้ เพียงแต่แทบไม่ค่อยใช้ และโค้ดแบบนี้ต้องระวังเพื่อหลีกเลี่ยง deadlock แม้ user space จะไม่ได้เกี่ยวข้องกับ “paging” เองอีกต่อไป แต่ก็พบได้บ่อยที่ user space เข้ามาเกี่ยวข้องในชั้นล่างลงไปหนึ่งหรือสองชั้น เช่น ระบบไฟล์ใน user space แบบ FSKit หรือ FUSE, ระบบไฟล์บน disk image, หรือ NFS/SMB ที่ผ่านส่วนขยายเครือข่ายใน user space อย่างไรก็ตาม ส่วนสุดท้ายนี้อาจผิดได้ ระบบไฟล์ที่ติดอยู่กับ user space นั้นทำได้แน่นอน แต่การวาง swap บนระบบไฟล์แบบนั้นอาจไม่ได้รับการรองรับทุกครั้งที่เห็นเรื่องราวของเคอร์เนล Darwin ก็อดสงสัยไม่ได้ว่าถ้า Apple แค่ ฟอร์ก Linux แล้ววางบริการของ OS ไว้บนมัน ทุกอย่างจะแตกต่างไปแค่ไหน
โดยเฉพาะเมื่อดูว่า Apple ยึดติดกับ Darwin มากเพียงใด ก็รู้สึกไม่ค่อยดี เพราะสิ่งที่โอเพนซอร์สสูญเสียไปดูไม่สมดุลกับผลตอบแทนเมื่อเทียบกับเวลาและค่าใช้จ่ายที่ Apple ต้องทุ่มลงไป
Linux ในช่วงปลายทศวรรษ 1990 ก็ไม่ได้เป็นตัวเลือกที่ดีกว่าอย่างชัดเจน และหลังจาก OS X ผ่านไปหลายเวอร์ชันจนกลายเป็น OS ตระกูล UNIX ที่ประสบความสำเร็จที่สุดบนพีซีผู้บริโภคแล้ว การเปลี่ยนไปใช้ฐาน Linux ก็แทบไม่มีผลประโยชน์ระยะสั้น มีแต่ต้นทุนและความเสี่ยงสูง
ถ้า Apple ยื้อ classic MacOS ต่อไปอีก 5 ปี หรือ Linux เติบโตเต็มที่เร็วกว่านี้ 5 ปี การเปลี่ยนผ่านสู่ OS X อาจแตกต่างไปมาก แต่การทิ้ง XNU เพื่อไปใช้เคอร์เนล Linux ก่อนยุค 2.6 นั้นไม่สมเหตุสมผลเลย
ถ้ามองตามมาตรฐานปัจจุบัน FreeBSD มีทั้งข้อดีของ Darwin และลักษณะโอเพนซอร์สแบบ Linux อยู่มาก หากต้องการสภาพแวดล้อมที่ปลอดภัยกว่าโดยไม่ต้องพึ่งพา Apple มากขึ้นเรื่อย ๆ FreeBSD และ BSD อื่น ๆ ก็น่าพิจารณาเป็นเป้าหมายสำหรับการเผยแพร่ซอฟต์แวร์
https://en.m.wikipedia.org/wiki/MkLinux
ดูเหมือนจะไม่มีงานที่นำ GUI และระบบนิเวศแอปพลิเคชันของ Macintosh ไปไว้บน Linux อย่างไรก็ตาม แม้ก่อนซื้อ NeXT Apple ก็เคยรันสภาพแวดล้อม Macintosh บน Unix ผ่าน A/UX สำหรับ Mac 68k และต่อมาผ่าน Macintosh Application Environment สำหรับ Solaris และ HP-UX โดยอย่างหลังรัน Mac OS เป็นโปรเซสของ Unix ถ้าจำไม่ผิด งานของ Macintosh Application Environment กลายเป็นพื้นฐานของ Blue Box ใน Rhapsody และต่อมาคือสภาพแวดล้อม Mac OS X Classic ในเชิงทฤษฎี การพอร์ต Macintosh Application Environment ไปยัง MkLinux ก็เป็นสิ่งที่จินตนาการได้ ในปี 1996 นั้น หลังการยุติคดีความที่เกี่ยวข้องกับ BSD ก็มี BSD แบบเสรีและโอเพนซอร์สสมัยใหม่อยู่แล้ว
แน่นอนว่าในช่วงกลางทศวรรษ 1990 การรัน classic Mac OS เป็นโปรเซสบน OS สมัยใหม่อย่าง Linux, FreeBSD, BeOS หรือ Windows NT นั้นไม่สมจริงในฐานะยุทธศาสตร์เดสก์ท็อปสำหรับผู้บริโภค เพราะต้องใช้ทรัพยากรระดับเวิร์กสเตชัน ขณะที่ Apple ยังรองรับ Mac 68k อยู่ และ Mac OS 8 ก็ยังรันบนเครื่อง 68030/68040 บางรุ่นได้ ถ้าเป็นยุค G3/G4 ก็คงสมจริงขึ้น และในทศวรรษ 2000 การรันโปรแกรม classic Macintosh แต่ละตัวเป็นโปรเซส Mac OS แยกต่างหากบน OS สมัยใหม่ก็น่าจะเป็นไปได้ แต่ถ้า Jobs ไม่กลับมา Apple คงอยู่ไม่ถึงปี 1998 ด้วยซ้ำ อีกทั้งการซื้อ NeXT ยังนำ Cocoa, IOKit, Quartz (ผู้สืบทอดของ Display PostScript) และเทคโนโลยีหลักอื่น ๆ ในปัจจุบันมาสู่ Mac ด้วย
มองอีกมุมก็รู้สึกคล้ายกับข้อเสนอว่า Apple ควรย้าย Safari ไปอยู่บน Chromium
ถ้าฟอร์ก Linux ตามกฎหมายแล้วอาจต้องเปิดซอร์สเคอร์เนลโมดูลทั้งหมดก็ได้ ซึ่งมีแนวโน้มจะเป็นผลดีต่อมนุษยชาติ แต่คงไม่ใช่ทิศทางที่ Apple ต้องการ
บทความนี้ใส่ทั้งความรักและแรงงานลงไปมาก ผมผ่านประวัติศาสตร์ส่วนใหญ่นี้มาเอง เคยพอร์ตโค้ด NeXTSTEP ไปยัง Windows เคยขุดคุ้ยความพยายามสร้างซ้ำของ GNUStep จำ YellowBox และ OpenStep ได้ อ่านหนังสือเจาะโครงสร้างภายใน และติดตามคอนเทนต์ WWDC มาโดยตลอด จากมุมมองนั้น สิ่งที่เขียนไว้แทบจะตรงกับความทรงจำของผมเกี่ยวกับวิวัฒนาการของระบบต่าง ๆ
Jobs เคยพยายามดึง Torvalds มาทำงานบน Mac OS X และ Linus ปฏิเสธ: https://www.macrumors.com/2012/03/22/steve-jobs-tried-to-hir...
ไม่แน่ใจว่า I/O Kit ถูกเขียนด้วยซับเซตของ C++ นี้เพียงเพราะเรื่องความเร็วหรือไม่ ตอนนั้นมีข้อถกเถียงอยู่ เพราะเมื่อ Apple ประกาศ MacOS X บริษัทบอกว่ามันจะไม่เข้ากันกับซอฟต์แวร์เดิม และพาร์ตเนอร์ทั้งหมดต้องเขียนใหม่ด้วย Objective-C
เมื่อกระแสตอบรับไม่ดี Apple จึงถอยและนำ Carbon ซึ่งเป็นชั้น API สำหรับแอปพลิเคชัน C++ และ Core Foundation ซึ่งเป็นรากฐานใต้ Foundation ที่อิง Objective-C เข้ามาใช้ นี่ก็เป็นเหตุผลที่มี Obj-C++ อยู่ด้วย จุดที่น่าสนใจคือพวกเขาทำให้การจัดการหน่วยความจำเป็นแบบ toll-free bridging กล่าวคืออ็อบเจ็กต์ที่ถูกจัดสรรในโลก C/C++ สามารถส่งต่อไปยัง Obj-C ได้โดยไม่มีโอเวอร์เฮดเพิ่มเติม
Apple ควรสร้าง ชุมชนซอฟต์แวร์เสรีและโอเพนซอร์ส ที่ดีกว่านี้รอบ ๆ XNU แม้ตอนนี้จะย้ายไป ARM แล้ว ก็ควรมีดิสทริบิวชันที่รันบน x64 ได้
ผมอยากเข้าใจ Darwin ให้ลึกระดับนี้อยู่พอดี เป็นบทความที่ดีมาก
เห็นถูกอ้างถึงท้ายบทความนี้ด้วย เป็นแหล่งข้อมูลที่จะคงอยู่ยาวนานในประวัติศาสตร์ macOS
เคอร์เนล NT ที่เป็นฐานนั้นยืดหยุ่นพอให้รองรับการปฏิบัติตาม POSIX ได้ ดังนั้นถ้ามีบทความที่เล่าเรื่องเหล่านี้ก็น่าจะน่าสนใจ
เป็นการสรุปประวัติที่ดี แต่ข้าม งานด้านความปลอดภัย ชั้นยอดหลายอย่างที่ทำให้ระบบปฏิบัติการของ Apple แตกต่างจาก Linux หรือ Windows ไปมาก ดูเหมือนว่ายังไม่มีการประเมินอย่างเหมาะสมว่า Apple นำหน้าไปไกลแค่ไหนในด้านความปลอดภัย บางทีวันหนึ่งการรับรู้นี้อาจแพร่หลายขึ้น จนคนที่ทำงานในสภาพแวดล้อมอ่อนไหวอาจถูก CISO กำหนดให้ใช้ Mac ก็ได้
แกนหลักคือ ระบบ code signing มันสามารถให้สิทธิ์แก่แอปหรือกักไว้ใน sandbox ได้ และทำให้การบังคับใช้นั้นคงอยู่จริง Apple ไม่ได้ใช้ ELF เหมือน UNIX ส่วนใหญ่ แต่ใช้ฟอร์แมตที่เรียกว่า Mach-O ความแตกต่างระหว่าง ELF กับ Mach-O ส่วนใหญ่ไม่สำคัญ แต่สิ่งสำคัญคือ Mach-O รองรับ section เพิ่มเติมที่เก็บ code directory ที่มีการลงลายเซ็นไว้ code directory เก็บ hash ของ code page ต่าง ๆ และเคอร์เนลเข้าใจโครงสร้างข้อมูลนี้ในระดับหนึ่ง ส่วน dyld สามารถเชื่อมโยงมันเมื่อโหลดไบนารีหรือไลบรารี XNU ตรวจสอบลายเซ็นของ code directory และ subsystem VMM จะคำนวณ hash เมื่อโหลด code page ตามต้องการ แล้วตรวจว่าตรงกับ hash ที่ลงลายเซ็นไว้ใน directory หรือไม่ ดังนั้น hash ของ code directory จึงทำหน้าที่เหมือนตัวระบุเฉพาะของโปรแกรมใด ๆ ใน ecosystem ของ Apple ได้ เรื่องนี้มีบั๊กอยู่อย่างหนึ่ง คือการเชื่อมโยงนี้แขวนอยู่กับโครงสร้าง Mach vnode ทำให้ถ้าเขียนทับไบนารีที่ลงลายเซ็นแล้วค่อยรัน แม้ลายเซ็นของไฟล์ใหม่จะถูกต้อง เคอร์เนลก็จะไม่พอใจและฆ่าโปรเซส ต้องแทนที่ไฟล์ทั้งไฟล์จริง ๆ จึงจะทำให้ระบบรับรู้สถานการณ์ใหม่ได้
บนรากฐานนี้ Apple วาง code requirements ทับลงไป นี่คือโปรแกรมที่เขียนด้วยภาษา expression ขนาดเล็กสำหรับแสดง constraint ต่อคุณสมบัติต่าง ๆ ของ code signature สามารถเขียน requirement ได้ เช่น “ไบนารีนี้ต้องลงลายเซ็นโดย Apple”, “อนุญาตไบนารีเวอร์ชันใดก็ได้ที่ลงลายเซ็นโดย subject ที่มี identity X ตาม certificate authority Y”, “ไบนารีนี้ต้องมี cdhash Z” หรือก็คือต้องเป็นไบนารีนั้นเป๊ะ ๆ ไบนารียังสามารถเปิดเผย designated requirement เพื่อบอก entity อื่นว่าต้องการให้ระบุตัวตนของตนด้วย requirement แบบใด ตอนแรกอาจดูเกินจำเป็น แต่ช่วยให้โปรแกรมคงตัวตนที่เสถียรและปลอมแปลงไม่ได้ แม้โปรแกรมจะพัฒนาเปลี่ยนแปลงไป
เคอร์เนลเปิดเผยตัวตนจากลายเซ็นของ task ให้ task อื่นผ่าน port ไลบรารีใน user space สามารถตีความภาษา constraint แล้วนำ requirement ไปผูกกับ port นั้นได้ ตัวอย่างเช่น เมื่อโปรแกรมหนึ่งบันทึกคีย์ไว้ใน system keychain เดมอน keychain ที่ implement ใน user space จะตรวจ designated requirement ของโปรแกรมที่ส่ง RPC มา แล้วตรวจว่าตรงกับคำขอใช้คีย์ในภายหลังหรือไม่
ระบบนี้ถูกทำให้เป็น abstraction ด้วยสิทธิ์ (entitlements) สิทธิ์คือคู่ key=value ที่แสดงการอนุญาต เนื่องจากเป็นระบบเปิด แอปจึงสามารถนิยามสิทธิ์ของตนเองได้ด้วย แต่ส่วนใหญ่ Apple เป็นผู้กำหนด บางอย่างเป็นแบบ opt-in ล้วน ๆ แค่ขอมา OS ก็อนุญาตให้อัตโนมัติและเงียบ ๆ ตอนแรกอาจดูไร้ประโยชน์ แต่ช่วยให้ App Store อธิบายล่วงหน้าได้ว่าแอปจะทำอะไร และโดยทั่วไปมากกว่านั้น ช่วยให้มีแนวทาง least privilege ที่ไม่ให้แอปเข้าถึงสิ่งที่ไม่จำเป็นต้องใช้ บางอย่างต้องมีหลักฐานเพิ่มเติม เช่น provisioning profile นี่คือโครงสร้างข้อมูล CMS ที่ลงลายเซ็นโดย Apple ซึ่งโดยคร่าว ๆ หมายความว่า “แอปที่มี designated requirement X สามารถใช้สิทธิ์จำกัด Y ได้” ดังนั้นหากจะใช้ต้องได้รับอนุญาตจาก Apple และบางอย่างก็ถูกใช้อย่างผิดวัตถุประสงค์จนแทบเป็นระบบ flag สำหรับการเซ็นทั่วไป และไม่เกี่ยวกับความปลอดภัย
ระบบขยายต่อไปได้อีกด้วยความร่วมมือระหว่าง user space กับ XNU การเซ็นไบนารีเป็นเพียงจุดเริ่มต้น เพราะหลายโปรแกรมมีไฟล์ข้อมูลด้วย ตรงนี้ระบบความปลอดภัยของ Apple ดูเหมือนงานปะผุอยู่บ้าง เคอร์เนลไม่ได้เกี่ยวข้องกับการตรวจ integrity ของไฟล์ข้อมูล แต่จะมี plist อยู่ในตำแหน่งพิเศษของโครงสร้างไดเรกทอรี bundle ที่ค่อนข้างกำหนดขึ้นตามอำเภอใจ และใน plist นั้นมี hash แยกตามไฟล์ของไฟล์ข้อมูลทั้งหมดใน bundle จากนั้น hash ของ plist จะเข้าไปอยู่ใน code signature และสุดท้าย Gatekeeper จะตรวจทั้งหมดเมื่อรันครั้งแรก เคอร์เนลถาม Gatekeeper ว่าจะอนุญาตให้รันโปรแกรมหรือไม่ และ Gatekeeper ตัดสินจากการมีอยู่ของ extended attribute ที่ติดมากับไฟล์ ซึ่ง GUI tool อย่างเว็บเบราว์เซอร์หรือเครื่องมือแตกไฟล์บีบอัดเป็นผู้เผยแพร่ต่อ โค้ด OS ใน user space อย่าง Finder จะเรียก Gatekeeper ให้ตรวจเมื่อดาวน์โหลดโปรแกรมมาครั้งแรก และ Gatekeeper จะ hash ทุกไฟล์ใน bundle เพื่อตรวจว่าตรงกับเนื้อหาที่ลงลายเซ็นไว้ในไบนารีหรือไม่ ดังนั้นบน macOS จึงมี dialog “Verifying app” ที่ช้าเวลารันครั้งแรก ดูเหมือนเป็นวิธีป้องกันไม่ให้แอปที่เปิดไฟล์ข้อมูลขนาดใหญ่โดยไม่ใช้ mmap ค้าง แต่ก็น่าเสียดายที่บนเครือข่ายเร็ว ๆ การตรวจของ Gatekeeper ที่ไม่ได้ optimize อาจช้ากว่าการดาวน์โหลดเอง Apple ดูไม่ค่อยใส่ใจ เพราะมองการแจกจ่ายนอก Store เป็นเทคโนโลยี legacy
สุดท้ายคือ Seatbelt ซึ่งเป็นภาษาโปรแกรมมิงแบบ Lisp สำหรับแสดงกฎ sandbox ไฟล์เหล่านี้ถูก compile ใน user space เป็น bytecode บางอย่าง แล้วเคอร์เนลเป็นผู้ประเมิน ภาษาค่อนข้างซับซ้อน ทำให้สามารถแสดงกฎตามอำเภอใจได้ว่า component ต่าง ๆ ของระบบโต้ตอบกันอย่างไรและทำอะไรได้บ้าง ทั้งหมดอิงกับตัวตนจาก code signing
ในโครงสร้างข้างต้นเคยมีช่องโหว่ชัดเจนที่เพิ่งถูกปิดใน release ล่าสุด ๆ คือไฟล์ข้อมูลสามารถมีโค้ดได้ แต่ถูกตรวจเพียงครั้งเดียว แอป Electron หรือ JVM มีโค้ดในฟอร์แมตพกพาได้ จึงเป็นเช่นนั้นจริง ดังนั้นแอปหนึ่งเคยสามารถแก้ไขไฟล์ข้อมูลเพื่อ inject โค้ดเข้าไปในอีกแอปและ bypass code signing ได้ ใน macOS รุ่นใหม่ Seatbelt sandbox แอปทั้งหมดที่กำลังรันเพื่อป้องกันสิ่งนี้ เท่าที่ผมรู้ macOS ยุคใหม่ไม่มีโค้ดที่อยู่นอก sandbox หนึ่งใน policy ของ sandbox คือไม่ให้แอปแก้ไขไฟล์ข้อมูลของแอปอื่นโดยไม่ได้รับอนุญาต policy ค่อนข้างซับซ้อน แอปที่ลงลายเซ็นโดยนิติบุคคลเดียวกันที่ Apple ตรวจสอบแล้วสามารถแก้ไขกันและกันได้ แอปยังสามารถอนุญาตการแก้ไขจากแอปอื่นที่ตรงตาม code requirement ได้ และผู้ใช้ก็สามารถให้สิทธิ์เมื่อจำเป็นได้ หากต้องการตรวจสอบ ให้ไปที่ Settings -> Privacy & Security -> App Management ปิดสิทธิ์ของ Terminal.app แล้วรีสตาร์ต จากนั้นลองรันคำสั่งอย่าง
vim /Applications/Google Chrome.app/Contents/Info.plistแม้ permission ของไฟล์จะเป็น rw แต่ vim จะมองเป็น read-onlyจากตรงนี้ไปผมไม่ได้ทำงานที่ Apple ดังนั้นความเข้าใจของผมก็สุดแค่นี้ เท่าที่ทราบ เคอร์เนลไม่เข้าใจ app bundle และผมก็ไม่แน่ใจว่ามันตัดสินอย่างไรว่าจะเปลี่ยน system call
open()ให้เป็น read-only หรือไม่ เดาว่า policy เริ่มต้นของ Seatbelt ทำให้เคอร์เนล upcall ไปยัง security daemon ที่อ่านฟอร์แมต bundle และฐานข้อมูลสิทธิ์ SQLite ได้ แล้ว daemon นั้นจึงเปรียบเทียบ designated requirement ของฝั่งที่เปิดไฟล์กับ policy ที่ bundle และ sandbox แสดงออกมาเพื่อตัดสินผมไม่คิดว่าการเรียกฟีเจอร์แบบนั้นว่า ความปลอดภัย จะเหมาะสม
ในความเห็นของผม ความปลอดภัยควรหมายถึงความปลอดภัยของเจ้าของหรือผู้ใช้คอมพิวเตอร์เสมอ
ฟีเจอร์ลักษณะนี้ของ Apple อาจนำไปใช้เพื่อเพิ่มความปลอดภัยได้ก็จริง แต่เป้าหมายหลักในการออกแบบคือให้ผู้ขายที่ขายคอมพิวเตอร์ไปแล้วสามารถควบคุมได้เข้มงวดยิ่งขึ้นว่าเจ้าของโดยหลักการแล้วใช้อุปกรณ์ที่ไม่ควรเป็นของผู้ขายอีกต่อไปอย่างไร กล่าวคือ เป็นแนวทางที่ทำให้ Apple ตัดสินใจได้ว่าผู้ใช้ปลายทางจะรันโปรแกรมใด