ความแตกต่างระหว่าง fork และ exec


199

อะไรคือความแตกต่างระหว่างforkและexec?


3
ฟังก์ชั่นสรุปที่ดีโดยละเอียดของ fork, exec และกระบวนการควบคุมอื่น ๆ อยู่ที่yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland

9
@Justin เพราะเราต้องการเพื่อที่จะกลายเป็นสถานที่ที่จะไปสำหรับการเขียนโปรแกรมคำถาม
paxdiablo

4
@ Polaris878: โอ้ตอนนี้มันทำ! : D
Janusz Lenar

ดังนั้นการforkโคลนโดยทั่วไป: O
Sebastian Hojas

คำตอบ:


364

การใช้forkและexecยกตัวอย่างจิตวิญญาณของ UNIX ซึ่งเป็นวิธีที่ง่ายมากในการเริ่มต้นกระบวนการใหม่

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

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

การexecโทรเป็นวิธีที่จะแทนที่กระบวนการปัจจุบันทั้งหมดด้วยโปรแกรมใหม่ มันโหลดโปรแกรมลงในพื้นที่กระบวนการปัจจุบันและเรียกใช้จากจุดเริ่มต้น

ดังนั้นforkและexecมักจะใช้ตามลำดับเพื่อให้โปรแกรมใหม่ทำงานเป็นลูกของกระบวนการปัจจุบัน โดยทั่วไปแล้วfindเชลล์จะทำเช่นนี้เมื่อใดก็ตามที่คุณพยายามเรียกใช้โปรแกรมเช่น- เชลล์จะใช้งานจากนั้นเด็กจะโหลดfindโปรแกรมลงในหน่วยความจำตั้งค่าอาร์กิวเมนต์บรรทัดคำสั่งทั้งหมด I / O มาตรฐานและอื่น ๆ

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

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

การใช้งาน UNIX บางอย่างมีการปรับให้เหมาะสมforkซึ่งใช้สิ่งที่เรียกว่า copy-on-write นี่เป็นเคล็ดลับในการหน่วงเวลาการคัดลอกพื้นที่กระบวนการforkจนกว่าโปรแกรมจะพยายามเปลี่ยนบางสิ่งในพื้นที่นั้น สิ่งนี้มีประโยชน์สำหรับโปรแกรมที่ใช้อย่างเดียวforkและไม่ใช่execว่าพวกเขาไม่จำเป็นต้องคัดลอกพื้นที่กระบวนการทั้งหมด

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

ทราบว่ามีคนในครอบครัวของexecสาย ( execl, execle, execveและอื่น ๆ ) แต่execในบริบทที่นี่หมายถึงของพวกเขา

แผนภาพต่อไปนี้แสดงให้เห็นถึงการfork/execดำเนินงานทั่วไปที่bashเชลล์จะใช้ในการแสดงรายการไดเรกทอรีด้วยlsคำสั่ง:

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V

52

fork()แยกกระบวนการปัจจุบันออกเป็นสองกระบวนการ หรือกล่าวอีกอย่างหนึ่งว่าโปรแกรมเชิงเส้นที่ดีที่คุณนึกถึงนั้นกลายเป็นสองโปรแกรมแยกกันโดยใช้โค้ดหนึ่งชิ้น:

 int pid = fork();

 if (pid == 0)
 {
     printf("I'm the child");
 }
 else
 {
     printf("I'm the parent, my child is %i", pid);
     // here we can kill the child, but that's not very parently of us
 }

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

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


6
นอกจากนี้ยังมีเงื่อนไขว่าเมื่อใดpid < 0และการfork()โทรล้มเหลวหรือไม่
Jonathan Fingland

3
แต่นั่นไม่ได้ทำให้ใจของฉันเลย :-) โค้ดหนึ่งชิ้นที่ถูกดำเนินการโดยสองกระบวนการเกิดขึ้นทุกครั้งที่มีการใช้ไลบรารีหรือ DLL ที่ใช้ร่วมกัน
paxdiablo

31

ฉันคิดว่าแนวคิดบางอย่างจาก"Advanced Unix Programming" โดย Marc Rochkindมีประโยชน์ในการทำความเข้าใจกับบทบาทที่แตกต่างกันของfork()/ exec()โดยเฉพาะอย่างยิ่งสำหรับคนที่เคยใช้CreateProcess()รุ่นWindows :

โปรแกรมคือชุดของคำแนะนำและข้อมูลที่ถูกเก็บไว้ในแฟ้มปกติบนดิสก์ (จาก 1.1.2 โปรแกรมกระบวนการและเธรด)

.

ในการเรียกใช้โปรแกรมเคอร์เนลจะถูกถามก่อนเพื่อสร้างกระบวนการใหม่ซึ่งเป็นสภาพแวดล้อมที่โปรแกรมทำงาน (จาก 1.1.2 โปรแกรมกระบวนการและเธรด)

.

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

เมื่อคุณเข้าใจความแตกต่างระหว่างโปรแกรมและกระบวนการพฤติกรรมfork()และexec()ฟังก์ชั่นของสามารถสรุปได้ดังนี้:

  • fork() สร้างซ้ำของกระบวนการปัจจุบัน
  • exec() แทนที่โปรแกรมในกระบวนการปัจจุบันด้วยโปรแกรมอื่น

(นี่คือพื้นฐานที่ง่ายขึ้นสำหรับคำตอบที่ละเอียดยิ่งกว่าของpaxdiabloรุ่น)


29

Fork สร้างสำเนาของกระบวนการเรียก โดยทั่วไปตามโครงสร้าง ป้อนคำอธิบายรูปภาพที่นี่

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(สำหรับข้อความกระบวนการลูก (รหัส), ข้อมูล, สแต็คเหมือนกับกระบวนการเรียก) กระบวนการลูกเรียกใช้โค้ดใน if block

EXEC แทนที่กระบวนการปัจจุบันด้วยรหัสข้อมูลกระบวนการสแต็กใหม่ โดยทั่วไปตามโครงสร้าง ป้อนคำอธิบายรูปภาพที่นี่

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(หลังจากเรียก exec ยูนิกซ์เคอร์เนลจะล้างข้อความกระบวนการลูก, ข้อมูล, สแต็คและการเติมด้วยข้อความ / ข้อมูลที่เกี่ยวข้องกับกระบวนการ foo) ดังนั้นกระบวนการเด็กจะมีรหัสที่แตกต่างกัน (รหัสของ foo {ไม่เหมือนกับ parent})


1
เป็นบิตที่ไม่เกี่ยวข้องกับคำถาม แต่รหัสนี้ไม่ก่อให้เกิดสภาพการแข่งขันหากกระบวนการเด็กเกิดขึ้นเพื่อให้เสร็จเป็นรหัสแรกหรือไม่ ในกรณีนั้นกระบวนการของผู้ปกครองจะป้วนเปี้ยนรอให้เด็กยกเลิกตัวเองใช่ไหม?
stdout

7

พวกเขาใช้ร่วมกันเพื่อสร้างกระบวนการลูกใหม่ ขั้นแรกการโทรforkจะสร้างสำเนาของกระบวนการปัจจุบัน (กระบวนการลูก) จากนั้นexecจะถูกเรียกจากภายในกระบวนการลูกเพื่อ "แทนที่" สำเนาของกระบวนการหลักด้วยกระบวนการใหม่

กระบวนการดังกล่าวเป็นเช่นนี้:

child = fork();  //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail

if (child < 0) {
    std::cout << "Failed to fork GUI process...Exiting" << std::endl;
    exit (-1);
} else if (child == 0) {       // This is the Child Process
    // Call one of the "exec" functions to create the child process
    execvp (argv[0], const_cast<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}

2
ในบรรทัดที่ 7 มันถูกกล่าวถึงว่า exec () ฟังก์ชั่นสร้างกระบวนการเด็ก .. มันเป็นเช่นนั้นเพราะ fork () ได้สร้างกระบวนการเด็กและการเรียก exec () เพียงแค่แทนที่โปรแกรมของกระบวนการใหม่ที่เพิ่งสร้าง
cbinder

4

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

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


3

ความแตกต่างที่สำคัญระหว่างfork()และexec()คือ

การfork()เรียกระบบสร้างโคลนของโปรแกรมที่กำลังทำงานอยู่ โปรแกรมต้นฉบับจะดำเนินการต่อโดยใช้รหัสบรรทัดถัดไปหลังจากการเรียกฟังก์ชัน fork () โคลนยังเริ่มทำการประมวลผลที่บรรทัดถัดไปของรหัส ดูรหัสต่อไปนี้ที่ฉันได้รับจากhttp://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

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

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

exec()ครอบครัวของสายระบบแทนรหัสในขณะนี้การดำเนินการของกระบวนการที่มีชิ้นส่วนของรหัสอื่น กระบวนการยังคง PID ไว้ แต่จะกลายเป็นโปรแกรมใหม่ ตัวอย่างเช่นพิจารณารหัสต่อไปนี้:

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

โปรแกรมนี้เรียกใช้execvp()ฟังก์ชันเพื่อแทนที่รหัสด้วยโปรแกรมวันที่ หากรหัสถูกเก็บไว้ในไฟล์ชื่อ exec1.c ดังนั้นการเรียกใช้งานจะสร้างเอาต์พุตต่อไปนี้:

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

โปรแกรมแสดงบรรทัด ― พร้อมที่จะ exec () . . ‖และหลังจากเรียกใช้ฟังก์ชัน execvp () ให้แทนที่รหัสด้วยโปรแกรมวันที่ โปรดสังเกตว่าบรรทัด - . . ใช้งานไม่ได้‖ไม่แสดงเพราะ ณ จุดนั้นมีการเปลี่ยนรหัส แต่เราเห็นผลลัพธ์ของการดำเนินการ ―date -u.‖


1

ป้อนคำอธิบายรูปภาพที่นี่fork():

มันสร้างสำเนาของกระบวนการทำงาน ขั้นตอนการทำงานที่เรียกว่าการปกครองและกระบวนการสร้างขึ้นใหม่ที่เรียกว่ากระบวนการเด็ก วิธีแยกความแตกต่างระหว่างสองวิธีนี้คือการดูค่าที่ส่งคืน:

  1. fork() ส่งกลับตัวระบุกระบวนการ (pid) ของกระบวนการเด็กในผู้ปกครอง

  2. fork() ผลตอบแทน 0 ในเด็ก

exec():

มันเริ่มต้นกระบวนการใหม่ภายในกระบวนการ มันโหลดโปรแกรมใหม่เข้าสู่กระบวนการปัจจุบันแทนที่โปรแกรมที่มีอยู่

fork()+ exec():

เมื่อเริ่มต้นโปรแกรมใหม่คือแรกfork()สร้างกระบวนการใหม่แล้วexec()(เช่นโหลดลงในหน่วยความจำและดำเนินการ) โปรแกรมไบนารีมันควรจะทำงาน

int main( void ) 
{
    int pid = fork();
    if ( pid == 0 ) 
    {
        execvp( "find", argv );
    }

    //Put the parent to sleep for 2 sec,let the child finished executing 
    wait( 2 );

    return 0;
}

0

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

สำหรับคำสั่งหลายเปลือก ส้อมและกระบวนการเด็กผู้บริหารคำสั่งที่เกี่ยวข้องกับชื่อของการรักษาคำพูดที่เหลืออยู่ในบรรทัดคำสั่งเป็นพารามิเตอร์คำสั่ง

เปลือกช่วยให้สามประเภทของคำสั่ง อันดับแรกคำสั่งสามารถเป็น ไฟล์เรียกทำงานที่มีรหัสวัตถุที่สร้างขึ้นโดยการรวบรวมซอร์สโค้ด (ตัวอย่างเช่นโปรแกรม C) ประการที่สองคำสั่งสามารถเป็นไฟล์ปฏิบัติการที่มีลำดับของบรรทัดคำสั่งเชลล์ ในที่สุดคำสั่งสามารถเป็นคำสั่งเชลล์ภายใน (แทนไฟล์ที่เรียกใช้งานได้เช่นcd- , lsเป็นต้น)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.