ก้าวข้าม `fork()` + `exec()`
(lwn.net)- spawn templates คือข้อเสนอการสร้างโปรเซสสำหรับเคอร์เนล Linux ที่มุ่งให้เคอร์เนลแคชข้อมูลของไฟล์ปฏิบัติการไว้ล่วงหน้า เพื่อเร่งการเริ่มโปรเซสในภายหลังสำหรับแอปพลิเคชันที่รันไฟล์ปฏิบัติการเดิมซ้ำ ๆ
- fork() ต้องคัดลอกสถานะทั้งหมดของโปรเซสรวมถึงหน่วยความจำเพื่อสร้างโปรเซสลูก และในหลายกรณี
exec()ที่ตามมาทันทีจะทิ้งหน่วยความจำนั้นไป ทำให้รูปแบบเดิมไม่มีประสิทธิภาพ - spawn_template_create() ระบุไฟล์ปฏิบัติการได้ด้วย
execfdหรือพาธสัมบูรณ์filenameอย่างใดอย่างหนึ่งเพื่อคืนค่า template file descriptor และเคอร์เนลจะเปิดไฟล์นั้นพร้อมแคชข้อมูลที่จำเป็นต่อการรันแบบรวดเร็ว - spawn_template_spawn() ทำงานในลักษณะใกล้เคียงกับเส้นทาง
fork()/exec()ปกติ โดยยังคงการตรวจสอบที่ใช้เมื่อรันไฟล์ใหม่ไว้ครบถ้วน และผลเบนช์มาร์กในจดหมายนำรายงานว่าดีขึ้นราว 2% {p:2} - การสร้างโปรเซสว่างเปล่าโดยอิง pidfd และการตั้งค่าผ่าน
pidfd_config()ถูกมองว่าเป็นแนวทางที่ดีกว่า โดยมีเป้าหมายเพื่อรองรับการอิมพลีเมนต์posix_spawn()ใน user space
ข้อจำกัดของโมเดลการสร้างโปรเซสแบบ Unix
- ตั้งแต่ยุคแรกของ Unix นั้น
fork()เป็น system call หลักแบบยึดโปรเซสเป็นศูนย์กลางที่ใช้สร้างโปรเซสลูกจากสำเนาของโปรเซสแม่ ส่วนexec()ใช้รันโปรแกรมใหม่แทนที่โปรเซสปัจจุบัน - ในเคอร์เนล Linux ฟังก์ชันหลักชุดเดียวกันนี้เป็นที่รู้จักมากกว่าในชื่อ clone() และ execve()
- โมเดลการสร้างโปรเซสนี้มีทั้งความงดงามและข้อเสีย และแม้ข้อเสนอ spawn templates ของ Li Chen จะไม่ได้ถูกรับเข้าเคอร์เนล Linux ในรูปแบบปัจจุบัน แต่ก็อาจนำไปสู่ primitive ใหม่สำหรับการสร้างโปรเซสในอนาคต
fork()เป็น system call ที่มีต้นทุนค่อนข้างสูง เพราะต้องคัดลอกสถานะทั้งหมดของโปรเซสรวมถึงหน่วยความจำเพื่อสร้างโปรเซสลูก- ตลอดหลายปีมีการปรับแต่งประสิทธิภาพหลายอย่าง แต่โดยพื้นฐานแล้ว
fork()ก็ยังเป็นงานที่มีต้นทุนสูง - บ่อยครั้งที่หลังเรียก
fork()จะตามด้วยexec()ทันที และexec()จะทิ้งหน่วยความจำทั้งหมดที่คัดลอกมาให้โปรเซสลูก - แม้จะมีความพยายามปรับแต่งอย่าง vfork() แต่รูปแบบ
fork()ตามด้วยexec()ก็ยังมีต้นทุนสูงกว่าที่ควรจะเป็น
Spawn templates
- ชุดแพตช์ของ Li Chen มุ่งเน้นแอปพลิเคชันที่รันไฟล์ปฏิบัติการเดิมซ้ำ ๆ เพื่อปรับแต่งรูปแบบ
fork()และexec()ให้มีประสิทธิภาพขึ้น - ตัวอย่างหนึ่งคือโปรแกรมที่ต้องเรียก Git ซ้ำ ๆ เพื่อดึงข้อมูลเกี่ยวกับเนื้อหาในรีโพซิทอรี
- ในกรณีเช่นนี้ โปรแกรมสามารถสร้างเทมเพลตเพื่อเฉลี่ยต้นทุนการตั้งค่าไปยังการรันหลายครั้ง และเร่งการเรียกใช้งานผ่านเทมเพลตนั้น
- การสร้างเทมเพลตใช้ system call
spawn_template_create()- มีซิกเนเจอร์ในรูปแบบ
int spawn_template_create(struct spawn_template_create_args *args, size_t args_size);
- มีซิกเนเจอร์ในรูปแบบ
- การเรียกนี้จะคืนค่า file descriptor ที่แทนเทมเพลตของไฟล์ปฏิบัติการ
- ต้องระบุไฟล์ปฏิบัติการด้วย file descriptor
execfdหรือพาธสัมบูรณ์filenameอย่างใดอย่างหนึ่ง และไม่สามารถใช้ทั้งสองพร้อมกันได้ - เคอร์เนลจะเปิดไฟล์ที่ระบุ และแคชข้อมูลหลายอย่างที่จำเป็นต่อการรันไฟล์นั้นให้เร็วขึ้นในครั้งถัดไป
- แต่ละครั้งของการรันสามารถมีอาร์กิวเมนต์ สภาพแวดล้อม การเปลี่ยนแปลง file descriptor และการจัดการสัญญาณที่แตกต่างกันได้
- รายละเอียดการรันจะถูกจัดวางไว้ในโครงสร้าง
spawn_template_spawn_argsargvเป็นพอยน์เตอร์ไปยังรายการอาร์กิวเมนต์ที่จะส่งให้โปรแกรมenvpเป็นพอยน์เตอร์ไปยังสภาพแวดล้อมของโปรแกรมactionsเป็นพอยน์เตอร์ไปยังอาร์เรย์spawn_template_actionสำหรับส่งการเปลี่ยนแปลง file descriptor และการจัดการสัญญาณ
spawn_template_actionประกอบด้วยฟิลด์type,flags,fd,newfd,arg- หากต้องปิด file descriptor 4 ในโปรเซสลูก ให้ตั้ง
typeเป็นSPAWN_TEMPLATE_ACTION_CLOSEและตั้งfdเป็น 4 - แอ็กชันอื่น ๆ รองรับการทำสำเนา file descriptor การเปิดไฟล์ การเปลี่ยน working directory และการเปลี่ยนการจัดการสัญญาณ
- หากต้องปิด file descriptor 4 ในโปรเซสลูก ให้ตั้ง
- เมื่อกรอกข้อมูลการรันครบแล้ว ก็ใช้
spawn_template_spawn()เพื่อรันโปรเซสใหม่- มีซิกเนเจอร์ในรูปแบบ
int spawn_template_spawn(int template_fd, struct spawn_template_spawn_args *args, int args_size);
- มีซิกเนเจอร์ในรูปแบบ
- การทำงานภายในใกล้เคียงกับเส้นทาง
fork()/exec()แบบปกติ - การตรวจสอบตามปกติทั้งหมดที่ใช้เมื่อรันไฟล์ใหม่ยังคงถูกเก็บไว้ครบถ้วน
- ข้อมูลที่แคชไว้ในเทมเพลตช่วยเร่งความเร็วของกระบวนการสร้างโดยรวม
- ผลเบนช์มาร์กในจดหมายนำอยู่ที่ดีขึ้นราว 2% ซึ่งอาจมีความหมายสำหรับแอปพลิเคชันที่ตรงกับรูปแบบการใช้งานที่คาดไว้ {p:2}
มุ่งสู่ posix_spawn()
- Mateusz Guzik ประเมินว่า “สำนวนแบบ
fork+execทั้งหมดนั้นแย่มากและควรถูกกำจัดออกไป” - จุดที่ดูแปลกของชุดแพตช์คือยังคงส่วน
fork()ไว้ทั้งที่มองว่าต้นทุนส่วนใหญ่อยู่ตรงนั้น - การปรับแต่งควรเป็นการตัดการคัดลอกโปรเซสปัจจุบันออก แล้วสร้าง “โปรเซสสะอาด (pristine process)” แทน
- Christian Brauner มองว่าแนวคิด builder API สำหรับ
exec“ไม่ได้แปลกขนาดนั้น” - แต่เขาอยากใช้แนวทางที่สร้าง API ใหม่บน abstraction ของ pidfd ที่มีอยู่แล้วมากกว่า
- แม้ยังไม่มีรายละเอียดเชิงรูปธรรม แต่การเพิ่มออปชันให้ pidfd_open() เพื่อสร้างโปรเซสว่างเปล่าน่าจะเป็นแนวทางที่ถูกต้อง
- จากนั้นค่อยเรียก system call ใหม่
pidfd_config()หลายครั้ง เพื่อใส่ค่าที่ต้องการให้โปรเซสใหม่ เช่น สภาพแวดล้อมและอิมเมจที่จะรัน pidfd_config()จะมีบทบาทคล้ายกับ fsconfig()- เป้าหมายสำคัญของอินเทอร์เฟซใหม่นี้คือการรองรับการอิมพลีเมนต์ posix_spawn() ใน user space
posix_spawn()เหมาะจะเป็นทางเลือกแทนรูปแบบfork()/exec()- อิมพลีเมนต์ปัจจุบันซ่อน
fork()และexec()ไว้ภายใน ขณะที่อิมพลีเมนต์แบบเนทีฟจะมีโครงสร้างต่างออกไป - Li Chen เห็นด้วยว่า API ที่ Brauner วาดภาพไว้กว้าง ๆ นั้นดูดีกว่า และวางแผนจะเดินงานต่อไปในทิศทางนั้น
- แม้
spawn templatesจะไม่เข้าสู่เคอร์เนล Linux แต่หากงานในอนาคตออกผล Linux ก็อาจมีการอิมพลีเมนต์posix_spawn()ที่เหมาะสมได้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
มีงานเขียนที่เกี่ยวข้องคือบทความ A fork() in the road: https://www.microsoft.com/en-us/research/wp-content/uploads/...
ในบทคัดย่อ ผู้เขียนโต้แย้งว่าตรงข้ามกับความเชื่อทั่วไปที่ว่า Unix
fork()+exec()เป็นการออกแบบอันชาญฉลาด มันอาจเป็นแฮ็กที่ฉลาดสำหรับเครื่องและโปรแกรมในยุค 1970 แต่ตอนนี้เป็นนามธรรมที่ไม่ดีสำหรับโปรแกรมเมอร์ยุคใหม่ และยังจำกัดการติดตั้งใช้งานระบบปฏิบัติการด้วยมุมมองคือ แทนที่จะคงไว้เป็นฟีเจอร์พื้นฐานระดับปฐมภูมิของระบบปฏิบัติการ ควรสอนมันในฐานะโบราณวัตถุทางประวัติศาสตร์ และไม่ควรให้เป็นวิธีสร้างโปรเซสแบบแรกที่นักศึกษาได้เรียนรู้
fork()+exec()กลายมาเป็นแบบนั้น ก็เพื่อให้สามารถรัน โปรแกรมที่ใหญ่เกินกว่าจะอยู่ในหน่วยความจำร่วมกับโปรแกรมแม่ได้การติดตั้งใช้งานดั้งเดิมจะสว็อปโปรแกรมที่เรียก
fork()ออกไปยังดิสก์ จากนั้นก่อนคืนการควบคุมก็จะคัดลอกและปรับรายการในตารางโปรเซส ทำให้มีทั้งโปรเซสที่อยู่ในหน่วยความจำและโปรเซสที่ถูกสว็อปออกไป แล้วฝั่งที่อยู่ในหน่วยความจำจะได้รับการควบคุมเพื่อเรียกexec()ได้วิธีนี้ทำให้สามารถรันโปรแกรมขนาดใหญ่ได้แม้บนเครื่อง PDP-11 ขนาดเล็ก และเป็นสิ่งจำเป็นในยุคที่หน่วยความจำมีราคาแพงมาก
ที่น่าสนใจคือ QNX ไม่มีตัวโหลดโปรแกรมอยู่ในระบบปฏิบัติการ แต่อยู่ในไลบรารี มันจะอ่าน header ของไฟล์ปฏิบัติการ จัดสรรหน่วยความจำ โหลดโปรแกรมและเตรียมพร้อมสำหรับการรัน จากนั้นลิงก์กับ
.soที่ใช้เริ่มต้นโปรแกรม โดยตัวโหลดโปรแกรมรันอยู่ใน user space ที่ไม่มีสิทธิพิเศษ แบบนี้น่าจะใกล้เคียงกับวิธีที่ถูกต้องมากกว่าfork()นั้นช้ามากเห็นด้วยว่าควรมีฟีเจอร์พื้นฐานที่ไม่ใช่
fork()แต่ก็ไม่แน่ใจว่าสมรรถนะเป็นเหตุผลที่ดีที่สุดหรือไม่fork(): The Scalable Commutativity Rule: Designing Scalable Software for Multicore Processors https://people.csail.mit.edu/nickolai/papers/clements-sc.pdffork()เหมาะมากกับ แพตเทิร์น zygoteนึกวิธีเพิ่มประสิทธิภาพที่ทั้งมีประสิทธิผลและสง่างามได้ยากพอ ๆ กัน
เมื่อไม่นานมานี้ฉันเจอบั๊กแปลก ๆ เพราะต้องปิด file descriptor เพิ่มเติมในโปรเซสที่ถูก fork
จากประสบการณ์ของฉัน สิ่งที่เจอบ่อยกว่ามากคือ “ต้องการโปรเซสใหม่เอี่ยมทั้งหมด” มากกว่า “ต้องการสำเนาของโปรเซสปัจจุบัน” แต่กลับไม่มีวิธีแสดงอย่างตรงไปตรงมาสำหรับแบบแรก และทำได้แค่ประมาณด้วยการคัดลอกก่อนแล้วค่อยแก้ทีหลัง ซึ่งรู้สึกแปลกมาก
O_CLOEXECไม่ใช่หรือ?posix_spawnหรอกหรือ?การพูดว่า “
fork()เป็น system call ที่ค่อนข้างมีต้นทุนสูง และต้องคัดลอกสถานะทั้งหมดของโปรเซสรวมถึงหน่วยความจำสำหรับโปรเซสลูก แม้จะมีการปรับแต่งมาหลายปี แต่นี่ก็ยังเป็นงานที่มีต้นทุนสูงโดยพื้นฐาน ที่แย่กว่านั้นคือหลังเรียกfork()ก็มักจะตามด้วยexec()ทันที ทำให้หน่วยความจำที่คัดลอกอย่างตั้งใจเพื่อโปรเซสลูกถูกทิ้งทั้งหมด” โดยไม่พูดถึง copy-on-write เลย ถือว่าแปลกมันคือการปรับแต่งที่ทำให้ไม่ต้องคัดลอกหน่วยความจำทั้งหมดจริง ๆ แต่กลับถูกละไว้
ถึงหน่วยความจำที่ page จริงชี้อยู่จะถูกแชร์กันได้ แต่ก็ยังต้องจัดสรร page ใหม่เพื่อเก็บสำเนาของโครงสร้างเหล่านี้ และการไล่วนคัดลอกโครงสร้างทั้งหมดก็ยังมีต้นทุนสูงอยู่ดี
fork()จะไม่ได้คัดลอกตัวหน่วยความจำเอง แต่ก็ยังต้องคัดลอก page tableถ้าเป็นโปรเซสที่ถือ RAM หลายสิบ GB การ
fork()อาจใช้เวลานาน และสิ่งนี้เกิดขึ้นทุกครั้งที่ Redis dump ไฟล์.rdbหรือเขียน AOF แบบ binary log ใหม่ตั้งแต่ปี 2012 ก็มีบทความที่แสดงต้นทุนสูงของงานนี้แล้ว: https://redis.io/blog/testing-fork-time-on-awsxen-infrastruc...
บน
m2.xlargeที่ใช้ RAM ราว 25GB,fork()ใช้เวลา 5.67 วินาที เมื่อคิดว่าโดยปกติไคลเอนต์ Redis มักเจอ latency ระดับมิลลิวินาทีเลขหลักเดียวสำหรับงานส่วนใหญ่ นี่ถือเป็นช่วงหยุดชะงักที่ยาวมาก และนี่เป็นแค่เวลาคัดลอก page table เท่านั้นน่าแปลกที่ไม่มีการพูดถึง huge page เลย ซึ่งดูเหมือนจะเป็นประเด็นสำคัญมากในที่นี้ ผ่านไป 14 ปี ฮาร์ดแวร์คงเร็วขึ้น แต่ Redis instance ก็น่าจะใช้ RAM มากขึ้นด้วย ดังนั้นคงน่าสนใจถ้าเอา benchmark นี้มารันใหม่
fork()ก็ยังต้องจ่ายต้นทุนในการตั้งค่ามันอยู่ดี ถ้าโปรเซสแม่มีเธรดที่ทำงานหนักจำนวนมาก เช่นใน Java ก็อาจเกิด copy-on-write ที่ไม่จำเป็นขึ้นมากก่อนที่exec()จะทำงานการ fork โปรแกรมที่มีขนาดหน่วยความจำเสมือนใหญ่จึงเป็นปัญหาความช้าที่เป็นที่รู้กันดี
ความงามของโมเดล
fork()+exec()อยู่ที่หลังfork()แล้ว คุณยังใช้ API ปกติชุดเดิมเพื่อตั้งค่าได้ทุกแบบทางเลือกแทนแบบเรียกควบที่เห็นมาจนถึงตอนนี้ดูอ่อนเกินไปในระดับพื้นฐาน เพราะต้องเพิ่มตัวเลือกการตั้งค่าทั้งหมดเข้าไปเป็นพารามิเตอร์ของการเรียก และยังต้องทำให้ขยายต่อในอนาคตได้โดยไม่เละเทะ
fork()/exec()จะมีประโยชน์ในบางกรณี แต่ถ้า API ต่างๆ รับอาร์กิวเมนต์pidfdก็น่าจะโอเคมากทีเดียว โดย 0 อาจหมายถึงโปรเซสปัจจุบันได้ปัญหาน่าจะมีแค่พวกไบนารี
setuid/setgidซึ่งในกรณีนั้นอาจจัดการเป็นกรณีพิเศษในexecจะดีกว่าเช่น อาจสร้างโปรเซสที่ถูกหยุดไว้ด้วย
pidfd_t ps = spawn();แล้วค่อยตั้งค่าด้วยsetuid(ps, 33);,capset(ps, ...);,socket(ps, ...);,mmap(ps, ...);,process_vm_writev(ps, ...);,exec(ps, ...);,signal(ps, SIGCONT);นี่ก็เป็นคำวิจารณ์ด้วยว่า API ของ system call ปกติไม่ได้คิดเผื่อคำถามว่า “ถ้าฉันอยากทำสิ่งนี้กับโปรเซสอื่นที่ฉันมีสิทธิ์เข้าถึงล่ะ?” มากพอ ถ้าทำแบบนี้ ความปลอดภัยต่อเธรดของ
fork()ก็อาจพอเป็นไปได้ระดับหนึ่งด้วยแต่ก็เห็นด้วยว่าวิธีแบบ
CreateProcessที่รับพารามิเตอร์จำนวนมหาศาลนั้นไม่ใช่ API ฝั่งผู้ใช้ที่ดีนักตัวอย่างเช่น มี API ที่ทำให้อ็อบเจ็กต์บางตัวกลายเป็น file descriptor หมายเลข 4 ได้ แล้วก็สามารถรันโปรแกรมเพื่อให้โปรแกรมนั้นไปหาอ็อบเจ็กต์นั้นที่ descriptor หมายเลข 4 ได้ แบบนี้แปลกมาก
Windows แม้จะมีข้อเสียมากมาย ก็ไม่ได้ใช้
fork()+exec()แต่ให้ตัวเลือกเกี่ยวกับวิธีสร้างโปรเซสเป็นหลัก ถึงจะไม่สง่างาม แต่ทิศทางถูกต้องแล้วfork()+exec()ถ้าเป็นอีกโลกหนึ่งที่ไม่มี
fork()+exec()ตั้งแต่แรก “API ปกติ” จำนวนมากก็คงมีอาร์กิวเมนต์pidแบบชัดเจนเพื่อให้เปลี่ยนการตั้งค่าของอีกโปรเซสได้ Fuchsia ก็ประมาณนั้นโลกแบบนั้นมีข้อดีหลายอย่าง อย่างที่ชัดที่สุดคือไม่ต้องสร้างระบบ IPC แยกขึ้นมาแบบกึ่งมหัศจรรย์เพื่อรายงานข้อผิดพลาดของการตั้งค่า และยังสามารถมีโปรเซสผู้จัดการที่คอยปรับคุณสมบัติของลูกได้ด้วย ซึ่งมีประโยชน์มาก โดยเฉพาะดีบักเกอร์น่าจะชอบ
fork()คือทำให้ API ปกติที่เปลี่ยนสถานะโปรเซสรับ process handle แบบชัดเจนแบบนั้นก็ใช้ API เดียวกันตั้งค่าโปรเซสว่างๆ ได้ และยังเอาไปประกอบกับอย่างอื่น เช่น IPC หรือการดีบัก ได้ด้วย
ถ้าโปรเซสเริ่มมาในสถานะเชื่อมกับ
ptraceและไม่มีเธรด ก็อาจบังคับให้ system call เกิดขึ้นระหว่างขั้นตั้งค่าได้ Linux เองยังไม่มีแนวคิดเรื่อง “โปรเซสที่ไม่มีเธรด” ด้วยซ้ำ ดังนั้นอาจต้องมีเธรดหลอกความเข้าใจผิดว่า
fork()ราคาถูกนั้นแพร่หลายจนน่าแปลก ทั้งที่มันเป็น O(N) ตามขนาดโปรเซส และก็เป็นแบบนั้นมาตลอดใช่ มันเป็น copy-on-write แต่ก็มีความสัมพันธ์เชิงเส้นระหว่างขนาดโปรเซสกับจำนวน page table entries ที่ต้องใช้เพื่อแทนมัน
ไม่น่าแปลกใจที่แพตช์ของ Chen ถูกปฏิเสธ มันเป็นกรณีใช้งานเฉพาะทางเกินไปจนไม่คุ้มจะรองรับ
จากมุมมองของนักพัฒนาเชลล์ เห็นด้วยกับข้อสรุปที่ว่า “มีโอกาสสูงที่นักพัฒนาจะต้อนรับ การทำแบบ native ที่ไม่ซ่อน
fork()และexec()ไว้ข้างในเหมือนอิมพลีเมนเทชันปัจจุบัน”fork()ดูน่ากลัวในเชิงแนวคิดมาตั้งแต่ตอนที่ผมเรียนมันครั้งแรก ถ้าอยากทำงานอย่างหนึ่งคือเริ่มโปรเซส ก็ไม่ควรต้องผ่าน คาถาปริศนา ที่เป็นอีกงานหนึ่งซึ่งไม่เกี่ยวกันเลยอย่างการ fork โปรเซสปัจจุบันอย่างในตัวอย่างของบทความ ผมสงสัยว่าวิธีที่ดีที่สุดในการจัดการสถานการณ์ที่โปรเซสหนึ่งต้องเปิดโปรเซสลูก
gitจำนวนมากคืออะไร การเริ่มgitใหม่ซ้ำๆ จากศูนย์ระหว่างงานของโปรเซสแม่ที่รันยาวๆ ดูไม่สมเหตุสมผลเลย แล้ว abstraction ต้นทุนต่ำที่ให้ผลเหมือนกันคืออะไร?fork()นั้นเรียบง่ายในเชิงแนวคิด ถ้าไม่ดึงเลเยอร์อื่นเข้ามา การเริ่มโปรเซสก็เริ่มจากสิ่งเดียวที่คุณมั่นใจว่ามีอยู่แน่ๆ นั่นคือ ตัวเองไม่อย่างนั้นก็ต้องมีหลายขั้นตอน ทั้งสร้างโปรเซส เติมสิ่งที่จะให้มันรันเข้าไป แล้วจัดให้มันเริ่มทำงาน หรือไม่ก็ต้องเอาไปผูกติดรวมกับเลเยอร์อื่นอย่างถาวรแบบ Win32 เช่นไฟล์ซิสเต็ม, object loader, linker
fork()+exec()นั้นไม่สมเหตุสมผลเลย ทุกวันนี้ก็รู้แล้วว่ามันเป็นเพียง ความพิลึกทางประวัติศาสตร์ แต่ก็ยังมีคนทำเหมือนfork()+exec()เป็นสิ่งที่ดีจริงๆlibgit2อยู่แล้ว จะจินตนาการเป็นการคุยกับgitdบางตัวผ่าน pipe หรือ socket ก็ได้ แต่ไม่รู้ว่าทำไมถึงจะเป็นไอเดียที่ดี ถ้าไม่ทำแบบนั้นก็ต้องเปิดโปรเซสเหตุผลที่แทน
exec/forkได้ยากก็เพราะปกติแล้วต้องตั้งค่าโปรเซสใหม่ก่อน เช่น ตั้งค่า signal handler, ปิดหรือเปิด file descriptor, สลับ namespace, ตั้งค่าseccomp, ปรับสิทธิ์แต่ system call สำหรับสิ่งเหล่านี้ตอนนี้ใช้ได้กับโปรเซสปัจจุบันเท่านั้น จึงต้องมีวิธีทดแทน ข้อเสนอในบทความคือสร้าง API ใหม่สำหรับสิ่งนี้
ผมคิดว่า system call ใหม่อย่าง
spawnอาจสร้างโปรเซสว่างๆ ขึ้นมา แล้วโหลด loader เบาๆ เข้าไป พร้อมส่งข้อมูลการตั้งค่าแบบใดก็ได้ให้มัน จากนั้น loader ก็ไปตั้งค่าโปรเซสและexec()โปรแกรมหลักแบบนี้จะคง API เดิมไว้ได้โดยไม่ต้อง fork หน่วยความจำ แต่ file descriptor กับอย่างอื่นก็ยังต้องทำสำเนาอยู่ดี
ถ้าไม่ได้ล้อเล่นก็ขออภัย แต่
posix_spawn()มีอยู่แล้ว และใน glibcforkก็เป็นแค่นามแฝงของclone()ถึงจะไม่เหมือนข้อเสนอเดิมทุกอย่างเป๊ะๆ แต่
fork()/exec()ก็ใกล้เคียงกับของ legacy จริงๆถ้า
forkและexecสามารถแสดงพฤติกรรมที่ต่อเนื่องและ เป็นพีชคณิต ได้มากกว่าแค่คุณสมบัติ copy-on-write มันก็น่าจะมีประโยชน์ขึ้นและน่าใช้งานขึ้นด้วย เช่น เอาไปใช้กับการประเมินค่าแบบขี้เกียจได้มีการถกเถียงเกี่ยวกับ API เก่าแก่นี้บน Hacker News อยู่มาก และตัวอย่างหนึ่งคือ https://news.ycombinator.com/item?id=31739794