ในหลายโปรแกรมและหน้าคนของ Linux ฉันเคยเห็นโค้ดที่ใช้fork()
. ทำไมเราต้องใช้fork()
และจุดประสงค์คืออะไร?
ในหลายโปรแกรมและหน้าคนของ Linux ฉันเคยเห็นโค้ดที่ใช้fork()
. ทำไมเราต้องใช้fork()
และจุดประสงค์คืออะไร?
คำตอบ:
fork()
คือวิธีสร้างกระบวนการใหม่ใน Unix เมื่อคุณเรียกfork
คุณกำลังสร้างสำเนาของกระบวนการของคุณเองที่มีของตัวเองในพื้นที่ที่อยู่ สิ่งนี้ช่วยให้งานหลายอย่างทำงานได้โดยอิสระจากกันราวกับว่าแต่ละงานมีหน่วยความจำของเครื่องเต็ม
นี่คือตัวอย่างการใช้งานบางส่วนของfork
:
fork
เพื่อรันโปรแกรมที่คุณเรียกใช้จากบรรทัดคำสั่งfork
เพื่อสร้างกระบวนการของเซิร์ฟเวอร์หลายรายการซึ่งแต่ละกระบวนการจะจัดการคำขอในพื้นที่ที่อยู่ของตัวเอง หากหน่วยความจำเสียหรือรั่วหน่วยความจำอื่น ๆ จะไม่ได้รับผลกระทบดังนั้นจึงทำหน้าที่เป็นกลไกในการยอมรับข้อผิดพลาดfork
เพื่อจัดการแต่ละหน้าภายในกระบวนการแยกกัน วิธีนี้จะป้องกันไม่ให้โค้ดฝั่งไคลเอ็นต์ในหน้าเดียวนำเบราว์เซอร์ทั้งหมดของคุณลงfork
ใช้เพื่อวางกระบวนการในโปรแกรมคู่ขนานบางโปรแกรม (เช่นเดียวกับที่เขียนโดยใช้MPI ) โปรดทราบว่าสิ่งนี้แตกต่างจากการใช้เธรดซึ่งไม่มีพื้นที่แอดเดรสของตัวเองและมีอยู่ภายในกระบวนการfork
ทางอ้อมเพื่อเริ่มกระบวนการย่อย ตัวอย่างเช่นทุกครั้งที่คุณใช้คำสั่งเช่นsubprocess.Popen
ใน Python คุณจะfork
ต้องประมวลผลลูกและอ่านผลลัพธ์ สิ่งนี้ทำให้โปรแกรมสามารถทำงานร่วมกันได้การใช้งานfork
ในเชลล์โดยทั่วไปอาจมีลักษณะดังนี้:
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
เชลล์สร้างกระบวนการย่อยโดยใช้exec
และรอให้มันเสร็จสิ้นจากนั้นดำเนินการต่อด้วยการดำเนินการของตัวเอง สังเกตว่าคุณไม่จำเป็นต้องใช้ส้อมด้วยวิธีนี้ คุณสามารถสร้างกระบวนการย่อยจำนวนมากได้เสมอเนื่องจากโปรแกรมคู่ขนานอาจทำและแต่ละโปรแกรมอาจรันโปรแกรมพร้อมกัน โดยทั่วไปเวลาที่คุณกำลังสร้างกระบวนการใหม่ในระบบ Unix, fork()
คุณกำลังใช้ สำหรับเทียบเท่าของ Windows CreateProcess
ดูที่
หากคุณต้องการตัวอย่างเพิ่มเติมและคำอธิบายที่ยาวขึ้นWikipediaมีบทสรุปที่ดี และนี่คือสไลด์บางส่วนเกี่ยวกับวิธีการทำงานของกระบวนการเธรดและการทำงานพร้อมกันในระบบปฏิบัติการสมัยใหม่
fork()
เป็นวิธีที่จะสร้างกระบวนการใหม่ในยูนิกซ์ แต่จะอวดความรู้ที่มีอยู่อย่างน้อยหนึ่งอื่น ๆposix_spawn()
:
fork () เป็นวิธีที่ Unix สร้างกระบวนการใหม่ เมื่อถึงจุดที่คุณเรียกว่า fork () กระบวนการของคุณจะถูกโคลนและกระบวนการที่แตกต่างกันสองกระบวนการจะดำเนินการต่อจากที่นั่น หนึ่งในนั้นคือเด็กจะมี fork () ส่งคืน 0 อีกอันซึ่งเป็นพาเรนต์จะมี fork () ส่งคืน PID (ID กระบวนการ) ของเด็ก
ตัวอย่างเช่นหากคุณพิมพ์สิ่งต่อไปนี้ในเชลล์โปรแกรมเชลล์จะเรียกใช้ fork () จากนั้นดำเนินการคำสั่งที่คุณส่งผ่าน (telnetd ในกรณีนี้) ในลูกในขณะที่พาเรนต์จะแสดงพรอมต์อีกครั้งเช่นกัน เป็นข้อความที่ระบุ PID ของกระบวนการเบื้องหลัง
$ telnetd &
สำหรับเหตุผลที่คุณสร้างกระบวนการใหม่นั่นคือวิธีที่ระบบปฏิบัติการของคุณสามารถทำหลายอย่างในเวลาเดียวกันได้ นั่นเป็นเหตุผลที่คุณสามารถเรียกใช้โปรแกรมและในขณะที่กำลังทำงานอยู่ให้เปลี่ยนไปใช้หน้าต่างอื่นและทำอย่างอื่น
fork () ใช้เพื่อสร้างกระบวนการย่อย เมื่อมีการเรียกใช้ฟังก์ชัน fork () กระบวนการใหม่จะถูกสร้างขึ้นและการเรียกฟังก์ชัน fork () จะส่งคืนค่าที่แตกต่างกันสำหรับลูกและพาเรนต์
หากค่าที่ส่งคืนเป็น 0 คุณจะรู้ว่าคุณเป็นกระบวนการย่อยและหากค่าที่ส่งคืนเป็นตัวเลข (ซึ่งเป็นรหัสกระบวนการย่อย) แสดงว่าคุณเป็นผู้ปกครอง (และถ้าเป็นจำนวนลบแสดงว่าส้อมล้มเหลวและไม่มีการสร้างกระบวนการย่อย)
fork () โดยทั่วไปจะใช้เพื่อสร้างกระบวนการลูกสำหรับกระบวนการที่คุณเรียกใช้ฟังก์ชันนี้ เมื่อใดก็ตามที่คุณเรียก fork () มันจะส่งกลับศูนย์สำหรับรหัสลูก
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
ด้วยวิธีนี้คุณสามารถจัดเตรียมการดำเนินการที่แตกต่างกันสำหรับผู้ปกครองและเด็กและใช้ประโยชน์จากคุณสมบัติมัลติเธรด
fork () จะสร้างกระบวนการย่อยใหม่ที่เหมือนกับพาเรนต์ ดังนั้นทุกสิ่งที่คุณเรียกใช้ในโค้ดหลังจากนั้นจะถูกเรียกใช้โดยกระบวนการทั้งสองซึ่งมีประโยชน์มากหากคุณมีเซิร์ฟเวอร์และคุณต้องการจัดการคำขอหลายรายการ
คุณอาจไม่จำเป็นต้องใช้ส้อมในการเขียนโปรแกรมแบบวันต่อวันหากคุณกำลังเขียนแอปพลิเคชัน
แม้ว่าคุณจะต้องการให้โปรแกรมของคุณเริ่มโปรแกรมอื่นเพื่อทำงานบางอย่าง แต่ก็มีอินเทอร์เฟซอื่น ๆ ที่ใช้ส้อมอยู่เบื้องหลังเช่น "system" ใน C และ perl
ตัวอย่างเช่นหากคุณต้องการให้แอปพลิเคชันของคุณเปิดโปรแกรมอื่นเช่น bc เพื่อทำการคำนวณให้คุณคุณอาจใช้ 'ระบบ' เพื่อเรียกใช้งาน ระบบทำการ 'แยก' เพื่อสร้างกระบวนการใหม่จากนั้น 'exec' เพื่อเปลี่ยนกระบวนการนั้นให้เป็น bc เมื่อ bc เสร็จสิ้นระบบจะส่งคืนการควบคุมไปยังโปรแกรมของคุณ
คุณสามารถเรียกใช้โปรแกรมอื่นแบบอะซิงโครนัสได้ด้วย แต่ฉันจำวิธีไม่ได้
หากคุณกำลังเขียนเซิร์ฟเวอร์เชลล์ไวรัสหรือระบบปฏิบัติการคุณมีแนวโน้มที่จะต้องการใช้ fork
system()
. ฉันอ่านเกี่ยวกับfork()
เพราะฉันต้องการให้รหัส C ของฉันเรียกใช้สคริปต์ python
ส้อมสร้างกระบวนการใหม่ หากไม่มีทางแยกคุณจะมีระบบยูนิกซ์ที่สามารถรันได้เฉพาะ init
System call fork () ใช้เพื่อสร้างโปรเซส ไม่มีอาร์กิวเมนต์และส่งคืน ID กระบวนการ จุดประสงค์ของ fork () คือการสร้างกระบวนการใหม่ซึ่งกลายเป็นกระบวนการลูกของผู้เรียก หลังจากสร้างกระบวนการย่อยใหม่แล้วกระบวนการทั้งสองจะดำเนินการคำสั่งถัดไปหลังจากการเรียกระบบ fork () ดังนั้นเราต้องแยกแยะผู้ปกครองออกจากเด็ก สามารถทำได้โดยการทดสอบค่าที่ส่งคืนของ fork ():
ถ้า fork () ส่งกลับค่าลบแสดงว่าการสร้างโปรเซสลูกไม่สำเร็จ fork () คืนค่าศูนย์ให้กับกระบวนการลูกที่สร้างขึ้นใหม่ fork () ส่งคืนค่าบวก ID กระบวนการของกระบวนการย่อยไปยังพาเรนต์ ID กระบวนการที่ส่งคืนเป็นประเภท pid_t ที่กำหนดใน sys / types.h โดยปกติ ID กระบวนการเป็นจำนวนเต็ม ยิ่งไปกว่านั้นกระบวนการสามารถใช้ฟังก์ชัน getpid () เพื่อดึง ID กระบวนการที่กำหนดให้กับกระบวนการนี้ ดังนั้นหลังจากระบบเรียกใช้ fork () การทดสอบอย่างง่ายสามารถบอกได้ว่ากระบวนการใดเป็นลูก โปรดทราบว่า Unix จะทำสำเนาที่ถูกต้องของพื้นที่ที่อยู่ของผู้ปกครองและมอบให้กับเด็ก ดังนั้นกระบวนการแม่และลูกจึงมีช่องว่างที่อยู่แยกกัน
ให้เราทำความเข้าใจพร้อมตัวอย่างเพื่อให้ประเด็นข้างต้นชัดเจน ตัวอย่างนี้ไม่แยกความแตกต่างของกระบวนการหลักและกระบวนการย่อย
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
สมมติว่าโปรแกรมข้างต้นรันจนถึงจุดที่เรียกเพื่อ fork ()
หากดำเนินการ call to fork () สำเร็จ Unix จะสร้างสำเนาของช่องว่างที่อยู่ที่เหมือนกันสองชุดชุดหนึ่งสำหรับพาเรนต์และอีกอันสำหรับชายด์ กระบวนการทั้งสองจะเริ่มดำเนินการในคำสั่งถัดไปหลังจากการเรียก fork () ในกรณีนี้กระบวนการทั้งสองจะเริ่มดำเนินการที่งานมอบหมาย
pid = .....;
กระบวนการทั้งสองเริ่มต้นการดำเนินการทันทีหลังจากที่ระบบเรียก fork () เนื่องจากกระบวนการทั้งสองมีช่องว่างแอดเดรสที่เหมือนกัน แต่แยกกันตัวแปรเหล่านั้นจะเริ่มต้นก่อนการเรียก fork () จึงมีค่าเหมือนกันในช่องแอดเดรสทั้งสอง เนื่องจากทุกกระบวนการมีพื้นที่แอดเดรสของตัวเองการปรับเปลี่ยนใด ๆ จึงไม่ขึ้นกับกระบวนการอื่น ๆ กล่าวอีกนัยหนึ่งคือหากพาเรนต์เปลี่ยนค่าของตัวแปรการแก้ไขจะมีผลกับตัวแปรในพื้นที่แอดเดรสของกระบวนการหลักเท่านั้น ช่องว่างที่อยู่อื่น ๆ ที่สร้างโดยการเรียก fork () จะไม่ได้รับผลกระทบแม้ว่าจะมีชื่อตัวแปรเหมือนกันก็ตาม
เหตุผลของการใช้ write มากกว่า printf คืออะไร? เป็นเพราะ printf () เป็น "buffered" หมายถึง printf () จะจัดกลุ่มผลลัพธ์ของกระบวนการเข้าด้วยกัน ในขณะที่บัฟเฟอร์เอาต์พุตสำหรับกระบวนการพาเรนต์เด็กอาจใช้ printf เพื่อพิมพ์ข้อมูลบางอย่างซึ่งจะถูกบัฟเฟอร์ด้วย ด้วยเหตุนี้เนื่องจากผลลัพธ์จะไม่ถูกส่งไปยังหน้าจอในทันทีคุณอาจไม่ได้ลำดับที่ถูกต้องของผลลัพธ์ที่คาดหวัง ที่แย่กว่านั้นผลลัพธ์จากทั้งสองกระบวนการอาจผสมกันในรูปแบบแปลก ๆ เพื่อแก้ไขปัญหานี้คุณอาจพิจารณาใช้การเขียนแบบ "ไม่มีบัฟเฟอร์"
หากคุณเรียกใช้โปรแกรมนี้คุณอาจเห็นสิ่งต่อไปนี้บนหน้าจอ:
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
รหัสกระบวนการ 3456 อาจเป็นรหัสที่กำหนดให้กับแม่หรือลูก เนื่องจากความจริงที่ว่ากระบวนการเหล่านี้ทำงานพร้อมกันบรรทัดผลลัพธ์ของพวกเขาจึงถูกผสมกันในลักษณะที่ไม่สามารถคาดเดาได้ ยิ่งไปกว่านั้นลำดับของบรรทัดเหล่านี้จะถูกกำหนดโดยตัวกำหนดตารางเวลาของ CPU ดังนั้นหากคุณเรียกใช้โปรแกรมนี้อีกครั้งคุณอาจได้รับผลลัพธ์ที่แตกต่างไปจากเดิมโดยสิ้นเชิง
การประมวลผลหลายขั้นตอนเป็นศูนย์กลางของการประมวลผล ตัวอย่างเช่น IE หรือ Firefox ของคุณสามารถสร้างกระบวนการดาวน์โหลดไฟล์ให้คุณได้ในขณะที่คุณยังท่องอินเทอร์เน็ตอยู่ หรือในขณะที่คุณกำลังพิมพ์เอกสารในโปรแกรมประมวลผลคำคุณยังสามารถดูหน้าต่างๆและยังคงทำการแก้ไขบางอย่างได้
Fork () ใช้เพื่อสร้างกระบวนการใหม่ตามที่ทุกคนเขียนไว้
นี่คือรหัสของฉันที่สร้างกระบวนการในรูปแบบของต้นไม้ไบนารี ....... มันจะขอให้สแกนจำนวนระดับที่คุณต้องการสร้างกระบวนการในต้นไม้ไบนารี
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
เอาท์พุท
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
อันดับแรกต้องเข้าใจว่าการเรียกระบบ fork () คืออะไร ให้ฉันอธิบาย
การเรียกระบบ fork () สร้างกระบวนการหลักที่ซ้ำกันทำให้ซ้ำกันของสแต็กหลักฮีปข้อมูลที่เตรียมใช้งานข้อมูลที่ไม่ได้กำหนดค่าเริ่มต้นและแบ่งปันรหัสในโหมดอ่านอย่างเดียวกับกระบวนการหลัก
การเรียกระบบ Fork จะคัดลอกหน่วยความจำบนพื้นฐานการคัดลอกเมื่อเขียนหมายถึงเด็กสร้างในหน้าหน่วยความจำเสมือนเมื่อมีความต้องการในการคัดลอก
ตอนนี้วัตถุประสงค์ของส้อม ():
fork()
ใช้เพื่อสร้างกระบวนการย่อย โดยปกติจะใช้ในสถานการณ์ประเภทเดียวกันเช่นเธรด แต่มีความแตกต่าง แตกต่างจากเธรดfork()
สร้างกระบวนการแยกทั้งหมดซึ่งหมายความว่าเด็กและผู้ปกครองในขณะที่พวกเขาเป็นสำเนาโดยตรงของกันและกัน ณ จุดที่fork()
เรียกว่าพวกเขาแยกกันอย่างสมบูรณ์และไม่สามารถเข้าถึงพื้นที่หน่วยความจำของอีกฝ่ายได้ (โดยไม่ต้องไปที่ปัญหาปกติ คุณไปเพื่อเข้าถึงหน่วยความจำของโปรแกรมอื่น)
fork()
ยังคงถูกใช้โดยแอปพลิเคชันเซิร์ฟเวอร์บางตัวซึ่งส่วนใหญ่เป็นแอปพลิเคชันที่รันเป็นรูทบนเครื่อง * NIX ที่ยกเลิกสิทธิ์ก่อนประมวลผลคำขอของผู้ใช้ ยังมี usecases อื่น ๆ อยู่บ้าง แต่ตอนนี้คนส่วนใหญ่ย้ายไปใช้ multithreading แล้ว
เหตุผลที่อยู่เบื้องหลัง fork () เทียบกับเพียงแค่มีฟังก์ชัน exec () เพื่อเริ่มต้นกระบวนการใหม่อธิบายไว้ในคำตอบสำหรับคำถามที่คล้ายกันในการแลกเปลี่ยนสแต็กยูนิกซ์คำตอบของคำถามที่คล้ายกันในการแลกเปลี่ยนยูนิกซ์สแต็ค
โดยพื้นฐานแล้วเนื่องจาก fork คัดลอกกระบวนการปัจจุบันตัวเลือกที่เป็นไปได้ทั้งหมดสำหรับกระบวนการจะถูกกำหนดขึ้นโดยค่าเริ่มต้นดังนั้นโปรแกรมเมอร์จึงไม่ได้จัดหาให้
ในระบบปฏิบัติการ Windows ตรงกันข้ามโปรแกรมเมอร์ต้องใช้ฟังก์ชัน CreateProcess ซึ่งซับซ้อนกว่ามากและต้องมีการเติมโครงสร้างที่หลากหลายเพื่อกำหนดพารามิเตอร์ของกระบวนการใหม่
ดังนั้นเพื่อสรุปเหตุผลของการฟอร์ก (เทียบกับการดำเนินการ) คือความเรียบง่ายในการสร้างกระบวนการใหม่
การเรียกระบบ Fork () ใช้เพื่อสร้างกระบวนการย่อย มันซ้ำกันแน่นอนกับกระบวนการหลัก Fork คัดลอกส่วนสแต็กส่วนฮีปส่วนข้อมูลตัวแปรสภาพแวดล้อมอาร์กิวเมนต์บรรทัดคำสั่งจากพาเรนต์
ฟังก์ชันfork ()ถูกใช้เพื่อสร้างกระบวนการใหม่โดยทำซ้ำกระบวนการที่มีอยู่ซึ่งเรียกว่า กระบวนการที่มีอยู่ซึ่งเรียกใช้ฟังก์ชันนี้จะกลายเป็นกระบวนการหลักและกระบวนการที่สร้างขึ้นใหม่จะกลายเป็นกระบวนการลูก ตามที่ระบุไว้แล้วว่าเด็กเป็นสำเนาของผู้ปกครอง แต่มีข้อยกเว้นบางประการ
เด็กมี PID ที่ไม่ซ้ำกันเหมือนกับกระบวนการอื่น ๆ ที่ทำงานในระบบปฏิบัติการ
ชายด์มี ID กระบวนการหลักซึ่งเหมือนกับ PID ของ
กระบวนการที่สร้างขึ้น
การใช้ทรัพยากรและตัวนับเวลาของ CPU ถูกรีเซ็ตเป็นศูนย์ในกระบวนการย่อย
ชุดสัญญาณที่รอดำเนินการในชุดย่อยว่างเปล่า
เด็กไม่ได้รับช่วงเวลาใด ๆ จากผู้ปกครอง
ตัวอย่าง:
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
ตอนนี้เมื่อรวบรวมและเรียกใช้โค้ดด้านบน:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]