ความแตกต่างระหว่าง fork (), vfork (), exec () และ clone ()


198

ฉันกำลังมองหาความแตกต่างระหว่างสิ่งทั้งสี่นี้ใน Google และฉันคาดหวังว่าจะมีข้อมูลจำนวนมากเกี่ยวกับเรื่องนี้ แต่จริงๆแล้วไม่มีการเปรียบเทียบที่ชัดเจนระหว่างการโทรสี่สาย

ฉันตั้งค่าเกี่ยวกับการพยายามรวบรวมพื้นฐานแบบคร่าว ๆ ดูความแตกต่างระหว่างการเรียกระบบเหล่านี้และนี่คือสิ่งที่ฉันได้รับ ข้อมูลทั้งหมดนี้ถูกต้อง / ฉันไม่มีข้อมูลสำคัญอะไร

Fork โดยทั่วไปแล้วการเรียก fork จะทำซ้ำกระบวนการปัจจุบันเหมือนกันเกือบทุกวิธี (ไม่ใช่ทุกสิ่งที่ถูกคัดลอกมาตัวอย่างเช่นข้อ จำกัด ทรัพยากรในการใช้งานบางอย่าง แต่แนวคิดก็คือการสร้างสำเนาให้ใกล้เคียงที่สุด

กระบวนการใหม่ (เด็ก) รับรหัสกระบวนการที่แตกต่าง (PID) และมี PID ของกระบวนการเก่า (พาเรนต์) เป็นพาเรนต์ PID (PPID) เนื่องจากทั้งสองกระบวนการกำลังทำงานในรหัสเดียวกันทุกประการพวกเขาสามารถบอกได้ว่าโค้ดใดที่โค้ดส่งคืนของ fork - เด็กได้รับ 0 ผู้ปกครองได้รับ PID ของเด็ก ทั้งหมดนี้เป็นเรื่องสมมติว่า fork call ใช้งานได้หากไม่ใช่จะไม่มีการสร้างลูกและผู้ปกครองจะได้รับรหัสข้อผิดพลาด

Vfork: ความแตกต่างพื้นฐานระหว่าง vfork และทางแยกคือเมื่อกระบวนการใหม่ถูกสร้างขึ้นด้วย vfork () กระบวนการหลักถูกระงับชั่วคราวและกระบวนการลูกอาจยืมพื้นที่ที่อยู่ของผู้ปกครอง สถานการณ์แปลก ๆ นี้ดำเนินต่อไปจนกระทั่งกระบวนการลูกออกจากกันหรือเรียกใช้ execve () ณ จุดที่กระบวนการหลักดำเนินต่อไป

ซึ่งหมายความว่ากระบวนการลูกของ vfork () จะต้องระมัดระวังเพื่อหลีกเลี่ยงการแก้ไขตัวแปรของกระบวนการหลักโดยไม่คาดคิด โดยเฉพาะอย่างยิ่งกระบวนการลูกจะต้องไม่ส่งคืนจากฟังก์ชันที่มีการเรียก vfork () และจะต้องไม่เรียก call exit () (หากต้องการออกก็ควรใช้ _exit () จริง ๆ แล้วนี่ก็เป็นจริงสำหรับเด็กด้วย ของส้อมปกติ ())

Exec :การเรียก exec เป็นวิธีการโดยทั่วไปแทนที่กระบวนการปัจจุบันทั้งหมดด้วยโปรแกรมใหม่ มันโหลดโปรแกรมลงในพื้นที่กระบวนการปัจจุบันและเรียกใช้จากจุดเริ่มต้น exec () แทนที่กระบวนการปัจจุบันด้วยการปฏิบัติการที่ชี้โดยฟังก์ชั่น การควบคุมจะไม่กลับสู่โปรแกรมดั้งเดิมเว้นแต่ว่าจะมีข้อผิดพลาด exec ()

Clone :โคลนในฐานะส้อมสร้างกระบวนการใหม่ ไม่เหมือนกับ fork การเรียกเหล่านี้อนุญาตให้กระบวนการ child แบ่งใช้ส่วนของบริบทการดำเนินการกับกระบวนการเรียกเช่นพื้นที่หน่วยความจำตารางไฟล์ descriptors และตารางตัวจัดการสัญญาณ

เมื่อกระบวนการลูกถูกสร้างขึ้นด้วยโคลนมันดำเนินการฟังก์ชั่นการใช้งาน fn (หาเรื่อง) (สิ่งนี้แตกต่างจาก fork ซึ่งการประมวลผลดำเนินการต่อในชายด์จากจุดที่เรียก fork ดั้งเดิม) อาร์กิวเมนต์ fn เป็นตัวชี้ไปยังฟังก์ชันที่เรียกว่าโดยกระบวนการ child ที่จุดเริ่มต้นของการดำเนินการ อาร์กิวเมนต์หาเรื่องถูกส่งผ่านไปยังฟังก์ชัน fn

เมื่อแอปพลิเคชันฟังก์ชัน fn (ARG) ส่งคืนกระบวนการลูกจะยุติ จำนวนเต็มที่ส่งคืนโดย fn คือรหัสออกสำหรับกระบวนการลูก กระบวนการลูกอาจยุติอย่างชัดเจนโดยการเรียก exit (2) หรือหลังจากได้รับสัญญาณที่ร้ายแรง

แบบฟอร์มข้อมูล:

ขอบคุณที่สละเวลาอ่าน! :)


2
ทำไม vfork ต้องไม่โทรออก () หรือจะไม่กลับมา? ไม่ออก () เพียงใช้ _exit () ใช่ไหม ฉันพยายามจะเข้าใจด้วย :)
LazerSharks

2
@Gnuey: เพราะมันอาจเป็นไปได้ (หากมีการใช้งานแตกต่างจากfork()ที่เป็นใน Linux และอาจ BSD ทั้งหมด) ยืมพื้นที่ที่อยู่ของผู้ปกครอง สิ่งที่มันทำนอกเหนือจากการโทรexecve()หรือ_exit()มีศักยภาพที่ดีในการทำให้ผู้ปกครองสับสน โดยเฉพาะอย่างยิ่งการexit()เรียกatexit()ตัวจัดการและ "finalizers" อื่น ๆ เช่นมันล้างกระแส stdio การกลับมาจากvfork()เด็กอาจเป็นไปได้ (เหมือนอย่างที่เคยเป็นมาก่อน) ทำให้กองซ้อนของผู้ปกครองสับสน
ninjalj

ฉันสงสัยว่าเกิดอะไรขึ้นกับเธรดกระบวนการของผู้ปกครอง พวกเขาทั้งหมดถูกโคลนหรือเฉพาะเธรดที่เรียกforksyscall หรือไม่
Mohammad Jafar Mashhadi

@LazerSharks vfork สร้างกระบวนการคล้ายเธรดที่ใช้หน่วยความจำร่วมกันโดยไม่มีการป้องกันการคัดลอกเมื่อเขียนดังนั้นการทำสิ่งต่าง ๆ อาจทำให้กระบวนการหลักขยะ
Jasen

คำตอบ:


159
  • vfork()เป็นการเพิ่มประสิทธิภาพที่ล้าสมัย ก่อนการจัดการหน่วยความจำที่ดีให้fork()ทำสำเนาหน่วยความจำของพ่อแม่แบบเต็มดังนั้นมันจึงค่อนข้างแพง เนื่องจากในหลายกรณี a fork()ถูกตามด้วยexec()ซึ่งจะทิ้งการแม็พหน่วยความจำปัจจุบันและสร้างขึ้นใหม่มันเป็นค่าใช้จ่ายที่ไม่จำเป็น ทุกวันนี้fork()ไม่ได้คัดลอกหน่วยความจำ มันตั้งง่าย ๆ ว่า "สำเนาบนเขียน" ดังนั้นfork()+ exec()เป็นเพียงเป็นที่มีประสิทธิภาพ+vfork()exec()

  • clone()จะ syscall fork()ที่ใช้โดย ด้วยพารามิเตอร์บางตัวมันจะสร้างกระบวนการใหม่กับคนอื่น ๆ มันจะสร้างเธรด ความแตกต่างระหว่างพวกเขาเป็นเพียงโครงสร้างข้อมูล (พื้นที่หน่วยความจำสถานะโปรเซสเซอร์, สแต็ค, PID, เปิดไฟล์ ฯลฯ ) ที่ใช้ร่วมกันหรือไม่



22
vforkหลีกเลี่ยงความจำเป็นในการเพิ่มหน่วยความจำชั่วคราวเพื่อให้สามารถดำเนินการได้ชั่วคราวexecและยังคงมีประสิทธิภาพมากกว่าforkแม้ว่าจะไม่ได้รับปริญญาที่สูงก็ตาม ดังนั้นหนึ่งสามารถหลีกเลี่ยงการ overcommit หน่วยความจำเพียงเพื่อให้โปรแกรมใหญ่ hunking สามารถวางไข่กระบวนการเด็ก ดังนั้นไม่เพียงเพิ่มประสิทธิภาพ แต่อาจทำให้เป็นไปได้ทั้งหมด
Deduplicator

5
ที่จริงแล้วฉันได้เห็นโดยตรงว่า fork () นั้นไกลจากราคาถูกอย่างไรเมื่อ RSS ของคุณมีขนาดใหญ่ ฉันคิดว่านี่เป็นเพราะเคอร์เนลยังคงมีการคัดลอกตารางหน้าทั้งหมด
Martina Ferrari

4
แต่ก็มีการคัดลอกทุกตารางหน้าการตั้งค่าหน่วยความจำที่สามารถเขียนได้ทุกสำเนาเมื่อเขียนในกระบวนการทั้งสองล้าง TLB และจากนั้นก็มีการยกเลิกการเปลี่ยนแปลงทั้งหมดไปยังผู้ปกครอง (และล้างอีกครั้ง TLB) execบน
zwol

3
vfork ยังคงมีประโยชน์ใน cygwin (เคอร์เนลอีมูเลชัน dll ที่ทำงานบน Windows ของ Microsoft) cygwin ไม่สามารถใช้ส้อมที่มีประสิทธิภาพได้เนื่องจากระบบปฏิบัติการพื้นฐานไม่มี
ctrl-alt-delor

80
  • execve() แทนที่อิมเมจที่เรียกใช้งานปัจจุบันด้วยอิมเมจอื่นที่โหลดจากไฟล์เรียกทำงาน
  • fork() สร้างกระบวนการลูก
  • vfork()เป็นรุ่นที่ดีที่สุดในประวัติศาสตร์ของการfork()ตั้งใจที่จะใช้เมื่อเรียกว่าโดยตรงหลังจากexecve() fork()มันกลับกลายเป็นว่าทำงานได้ดีในระบบที่ไม่ใช่ MMU (ซึ่งfork()ไม่สามารถทำงานได้อย่างมีประสิทธิภาพ) และเมื่อfork()ประมวลผลด้วยหน่วยความจำขนาดใหญ่เพื่อเรียกใช้โปรแกรมขนาดเล็ก (คิดว่า Java Runtime.exec()) POSIX ได้ได้มาตรฐานเพื่อแทนที่สองหลังการใช้งานที่ทันสมัยมากขึ้นเหล่านี้posix_spawn()vfork()
  • posix_spawn()ทำเทียบเท่ากับ a fork()/execve()และยังช่วยให้บาง fd เล่นกลในระหว่าง มันควรจะแทนที่fork()/execve()ส่วนใหญ่สำหรับแพลตฟอร์มที่ไม่ใช่ MMU
  • pthread_create() สร้างเธรดใหม่
  • clone()เป็นสายลินุกซ์เฉพาะซึ่งสามารถนำมาใช้ในการดำเนินการอะไรจากการfork() pthread_create()มันให้การควบคุมมากมาย rfork()แรงบันดาลใจในการ
  • rfork()เป็นการโทรเฉพาะแผน -9 มันควรจะเป็นการเรียกทั่วไปที่อนุญาตให้มีการแบ่งปันหลายระดับระหว่างกระบวนการและเธรดแบบเต็ม

2
ขอบคุณที่เพิ่มข้อมูลมากกว่าที่ถามจริงมันช่วยฉันประหยัดเวลาของฉัน
Neeraj

5
แผน 9 เป็นสิ่งที่น่าหยอกล้อ
JJ

1
สำหรับผู้ที่จำไม่ได้ว่า MMU หมายถึงอะไร: "หน่วยจัดการหน่วยความจำ" - อ่านเพิ่มเติมเกี่ยวกับ Wikipedia
mgarey

43
  1. fork()- สร้างกระบวนการลูกใหม่ซึ่งเป็นสำเนาที่สมบูรณ์ของกระบวนการหลัก กระบวนการลูกและผู้ปกครองใช้พื้นที่ที่อยู่เสมือนที่แตกต่างกันซึ่งมีการเริ่มต้นด้วยหน้าหน่วยความจำเดียวกัน จากนั้นเมื่อดำเนินการทั้งสองกระบวนการพื้นที่ที่อยู่เสมือนจะเริ่มแตกต่างกันมากขึ้นเนื่องจากระบบปฏิบัติการทำการคัดลอกหน้าหน่วยความจำที่ขี้เกียจซึ่งถูกเขียนโดยทั้งสองกระบวนการนี้และกำหนดสำเนาอิสระของหน้าที่แก้ไขของ หน่วยความจำสำหรับแต่ละกระบวนการ เทคนิคนี้เรียกว่า Copy-On-Write (COW)
  2. vfork()- สร้างกระบวนการลูกใหม่ซึ่งเป็นสำเนา "อย่างรวดเร็ว" ของกระบวนการหลัก ในทางตรงกันข้ามกับการเรียกของระบบfork()กระบวนการลูกและผู้ปกครองแบ่งปันพื้นที่ที่อยู่เสมือนเดียวกัน บันทึก! การใช้พื้นที่ที่อยู่เสมือนเดียวกันทั้งผู้ปกครองและเด็กใช้สแต็คเดียวกันตัวชี้สแต็คและตัวชี้คำสั่งเช่นเดียวกับในกรณีของคลาสสิกfork()! เพื่อป้องกันการรบกวนที่ไม่พึงประสงค์ระหว่างผู้ปกครองและเด็กซึ่งใช้กองเดียวกันการดำเนินการของการปกครองถูกแช่แข็งจนกว่าเด็กจะเรียกอย่างใดอย่างหนึ่งexec()(สร้างพื้นที่เสมือนใหม่อยู่และการเปลี่ยนไปใช้สแต็คที่แตกต่างกัน) หรือ_exit()(การสิ้นสุดของการดำเนินการกระบวนการ ) vfork()เป็นการเพิ่มประสิทธิภาพของfork()โมเดล "fork-and-exec" มันสามารถทำได้เร็วกว่า 4-5 เท่าfork()เพราะต่างจากfork()(แม้จะมี COW เก็บไว้ในใจ) การดำเนินการvfork()โทรระบบไม่รวมถึงการสร้างพื้นที่ที่อยู่ใหม่ (การจัดสรรและการตั้งค่าของไดเรกทอรีหน้าใหม่)
  3. clone()- สร้างกระบวนการลูกใหม่ พารามิเตอร์ต่างๆของการเรียกของระบบนี้ระบุว่าส่วนใดของกระบวนการหลักที่จะต้องคัดลอกลงในกระบวนการลูกและส่วนใดที่จะถูกใช้ร่วมกันระหว่างพวกเขา เป็นผลให้การเรียกระบบนี้สามารถใช้เพื่อสร้างเอนทิตีการเรียกใช้งานทุกชนิดเริ่มต้นจากเธรดและการตกแต่งโดยกระบวนการอิสระอย่างสมบูรณ์ ในความเป็นจริงการclone()เรียกของระบบเป็นฐานที่ใช้สำหรับการดำเนินการpthread_create()และทุกตระกูลของการfork()เรียกระบบ
  4. exec()- รีเซ็ตหน่วยความจำทั้งหมดของกระบวนการโหลดและแยกวิเคราะห์ไบนารีที่ระบุได้ตั้งค่าสแต็กใหม่และผ่านการควบคุมไปยังจุดเริ่มต้นของปฏิบัติการที่โหลด การเรียกระบบนี้จะไม่ส่งคืนการควบคุมไปยังผู้เรียกและทำหน้าที่โหลดโปรแกรมใหม่ไปยังกระบวนการที่มีอยู่แล้ว การเรียกระบบนี้พร้อมการเรียกfork()ระบบรวมกันเป็นรูปแบบการจัดการกระบวนการ UNIX แบบคลาสสิกที่เรียกว่า "fork-and-exec"

2
โปรดทราบว่าข้อกำหนดของ BSD และ POSIX vforkนั้นอ่อนแอดังนั้นจึงเป็นสิ่งที่ถูกกฎหมายที่จะvforkใช้คำพ้องความหมายของfork(และ POSIX.1-2008 ลบออกvforkจากข้อมูลจำเพาะทั้งหมด) หากคุณบังเอิญทดสอบโค้ดของคุณในระบบที่มีความหมายเหมือนกัน (เช่นโพสต์ 4.4 4.4 ส่วนใหญ่นอกเหนือจาก NetBSD, ลีนุกซ์ลีนุกซ์ล่วงหน้า 2.2.0-pre6 เป็นต้น) อาจทำงานได้แม้ว่าคุณจะละเมิดvforkสัญญาก็ตาม ถ้าคุณเรียกใช้ที่อื่น บางคนที่จำลองด้วยfork(เช่น OpenBSD) ยังคงรับประกันว่าผู้ปกครองจะไม่กลับมาทำงานจนกว่าลูกexecหรือ_exits มันไม่ใช่แบบพกพาที่น่าขัน
ShadowRanger

2
เกี่ยวกับประโยคสุดท้ายของจุดที่ 3 ของคุณ: ฉันสังเกตเห็นบน Linux โดยใช้ strace ซึ่งในขณะที่ตัวห่อหุ้ม glibc สำหรับ fork () เรียก clone syscall, wrapper สำหรับ vfork () เรียก vfork syscall
ขณะที่

7

fork (), vfork () และ clone () ทั้งหมดเรียก do_fork () เพื่อทำงานจริง แต่มีพารามิเตอร์ต่างกัน

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

สำหรับ fork เด็กและพ่อมีตารางหน้า VM อิสระ แต่เนื่องจากประสิทธิภาพ fork จะไม่คัดลอกหน้าใด ๆ จริง ๆ เพียงตั้งหน้าทั้งหมดที่เขียนได้เพื่ออ่านอย่างเดียวสำหรับกระบวนการลูก ดังนั้นเมื่อกระบวนการลูกต้องการเขียนอะไรบางอย่างบนหน้านั้นจะมีข้อยกเว้นหน้าเกิดขึ้นและเคอร์เนลจะจัดสรรหน้าใหม่ที่โคลนจากหน้าเก่าที่มีสิทธิ์เขียน เรียกว่า "copy on write"

สำหรับ vfork หน่วยความจำเสมือนนั้นถูกต้องทั้งกับเด็กและพ่อเพราะเหตุนี้พ่อและลูกจึงไม่สามารถตื่นขึ้นพร้อมกันเพราะพวกเขาจะมีอิทธิพลต่อกัน ดังนั้นพ่อจะนอนตอนท้ายของ "do_fork ()" และตื่นขึ้นมาเมื่อเด็กโทรออก () หรือ execve () ตั้งแต่นั้นมาจะเป็นเจ้าของตารางหน้าใหม่ นี่คือรหัส (ใน do_fork ()) ที่พ่อนอน

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

นี่คือรหัส (ใน mm_release () เรียกโดย exit () และ execve ()) ซึ่งปลุกพ่อให้ตื่น

up(tsk->p_opptr->vfork_sem);

สำหรับ sys_clone () มันมีความยืดหยุ่นมากกว่าเนื่องจากคุณสามารถป้อน clone_flags ใด ๆ ดังนั้น pthread_create () เรียกการเรียกระบบนี้ด้วย clone_flags มากมาย:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

สรุป: fork (), vfork () และ clone () จะสร้างกระบวนการลูกที่มีการแบ่งใช้ทรัพยากรที่แตกต่างกันกับกระบวนการพ่อ นอกจากนี้เรายังสามารถพูดได้ว่า vfork () และ clone () สามารถสร้างเธรด (อันที่จริงพวกเขาเป็นกระบวนการเนื่องจากพวกเขามีอิสระ task_struct) เนื่องจากพวกเขาแบ่งปันตารางหน้า VM กับกระบวนการพ่อ


-4

in fork (), กระบวนการ child หรือ parent จะดำเนินการตามการเลือก cpu .. แต่ใน vfork (), child จะดำเนินการก่อน หลังจากที่เด็กยกเลิกผู้ปกครองจะดำเนินการ


3
ไม่ถูกต้อง. ก็สามารถจะนำมาใช้เป็นvfork() fork()
ninjalj

หลังจาก AnyFork () ไม่ได้กำหนดว่าใครเป็นผู้ปกครอง / ลูกคนแรก
AjayKumarBasuthkar

5
@Raj: คุณมีความเข้าใจผิดเกี่ยวกับแนวคิดบางอย่างถ้าคุณคิดว่าหลังจากการฟอร์กแล้วมีความคิดโดยนัยของลำดับอนุกรม Forking สร้างกระบวนการใหม่แล้วส่งคืนการควบคุมไปยังทั้งสองกระบวนการ (แต่ละอันจะส่งกลับที่แตกต่างกันpid) - ระบบปฏิบัติการสามารถกำหนดเวลากระบวนการใหม่ให้ทำงานแบบขนานหากสิ่งนั้นเหมาะสม (เช่นโปรเซสเซอร์หลายตัว) หากด้วยเหตุผลบางอย่างที่คุณต้องการให้กระบวนการเหล่านี้ดำเนินการตามลำดับเฉพาะคุณต้องมีการซิงโครไนซ์เพิ่มเติมที่การฟอร์กไม่มีให้ ตรงไปตรงมาคุณอาจไม่ต้องการแม้แต่ส้อมในตอนแรก
Andon M. Coleman

จริงๆแล้ว @AjayKumarBasuthkar และ @ninjalj คุณผิดทั้งคู่ ด้วยvfork()เด็กวิ่งก่อน มันอยู่ในหน้าคน; การดำเนินการของผู้ปกครองถูกระงับจนกว่าเด็กจะตายหรืออย่างใดอย่างหนึ่งexecของ และ ninjalj ค้นหาซอร์สโค้ดเคอร์เนล ไม่มีทางที่จะดำเนินการไม่vfork()เป็นfork()เพราะพวกเขาผ่านการขัดแย้งที่แตกต่างกันไปdo_fork()ภายในเคอร์เนล อย่างไรก็ตามคุณสามารถใช้งานvforkกับclonesyscall ได้
Zac Wimer

@ZacWimer: ดูความคิดเห็นของ ShadowRanger กับคำตอบอื่น ๆstackoverflow.com/questions/4856255/… Linux เก่าได้ทำการซิงโครไนซ์มันเหมือน BSDs อื่นที่ไม่ใช่ NetBSD (ซึ่งมักจะถูกนำไปใช้กับระบบที่ไม่ใช่ MMU จำนวนมาก) จาก manpage Linux: ใน 4.4BSD มันถูกทำให้ตรงกัน (2) แต่ NetBSD แนะนำอีกครั้ง; ดู⟨ netbsd.org/Documentation/kernel/vfork.html ⟩ ใน Linux มันเทียบเท่ากับ fork (2) จนถึง 2.2.0-pre6 หรือมากกว่านั้น
ninjalj
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.