อะไรคือความแตกต่างระหว่างforkและexec?
forkโคลนโดยทั่วไป: O
                อะไรคือความแตกต่างระหว่างforkและexec?
forkโคลนโดยทั่วไป: O
                คำตอบ:
การใช้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
              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ดำเนินการในกระบวนการปัจจุบันที่สามารถดำเนินการเป้าหมาย
pid < 0และการfork()โทรล้มเหลวหรือไม่
                    ฉันคิดว่าแนวคิดบางอย่างจาก"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รุ่น)
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})
พวกเขาใช้ร่วมกันเพื่อสร้างกระบวนการลูกใหม่ ขั้นแรกการโทร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
}
              fork () สร้างสำเนาของกระบวนการปัจจุบันโดยดำเนินการใน child ใหม่โดยเริ่มจากหลังการเรียก fork () หลังจาก fork () มันเหมือนกันยกเว้นค่าส่งคืนของฟังก์ชัน fork () (RTFM สำหรับรายละเอียดเพิ่มเติม) จากนั้นกระบวนการทั้งสองสามารถแยกออกจากกันได้อีกโดยที่หนึ่งไม่สามารถเข้าไปยุ่งกับกระบวนการอื่นได้ยกเว้นอาจผ่านการจัดการไฟล์ที่ใช้ร่วมกัน
exec () แทนที่กระบวนการปัจจุบันด้วยกระบวนการใหม่ มันไม่มีส่วนเกี่ยวข้องกับ fork () ยกเว้นว่า exec () มักจะติดตาม fork () เมื่อสิ่งที่ต้องการคือการเปิดใช้กระบวนการลูกที่แตกต่างกันแทนที่จะแทนที่อันที่ปัจจุบัน
ความแตกต่างที่สำคัญระหว่าง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.‖
มันสร้างสำเนาของกระบวนการทำงาน ขั้นตอนการทำงานที่เรียกว่าการปกครองและกระบวนการสร้างขึ้นใหม่ที่เรียกว่ากระบวนการเด็ก วิธีแยกความแตกต่างระหว่างสองวิธีนี้คือการดูค่าที่ส่งคืน:
fork() ส่งกลับตัวระบุกระบวนการ (pid) ของกระบวนการเด็กในผู้ปกครอง
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;
}
              ตัวอย่างที่สำคัญในการทำความเข้าใจfork()และexec()แนวคิดคือเชลล์โปรแกรมตัวแปลคำสั่งที่ผู้ใช้ดำเนินการตามปกติหลังจากเข้าสู่ระบบเชลล์จะตีความคำแรกของบรรทัดคำสั่งเป็นชื่อคำสั่ง
สำหรับคำสั่งหลายเปลือก ส้อมและกระบวนการเด็กผู้บริหารคำสั่งที่เกี่ยวข้องกับชื่อของการรักษาคำพูดที่เหลืออยู่ในบรรทัดคำสั่งเป็นพารามิเตอร์คำสั่ง
เปลือกช่วยให้สามประเภทของคำสั่ง อันดับแรกคำสั่งสามารถเป็น ไฟล์เรียกทำงานที่มีรหัสวัตถุที่สร้างขึ้นโดยการรวบรวมซอร์สโค้ด (ตัวอย่างเช่นโปรแกรม C) ประการที่สองคำสั่งสามารถเป็นไฟล์ปฏิบัติการที่มีลำดับของบรรทัดคำสั่งเชลล์ ในที่สุดคำสั่งสามารถเป็นคำสั่งเชลล์ภายใน (แทนไฟล์ที่เรียกใช้งานได้เช่นcd- , lsเป็นต้น)