อะไรคือความแตกต่างระหว่าง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เป็นต้น)