อะไรคือสิ่งที่exec()
ฟังก์ชั่นและครอบครัวของตนหรือไม่ เหตุใดจึงใช้ฟังก์ชันนี้และทำงานอย่างไร
กรุณาทุกคนอธิบายฟังก์ชันเหล่านี้
อะไรคือสิ่งที่exec()
ฟังก์ชั่นและครอบครัวของตนหรือไม่ เหตุใดจึงใช้ฟังก์ชันนี้และทำงานอย่างไร
กรุณาทุกคนอธิบายฟังก์ชันเหล่านี้
คำตอบ:
อย่างง่ายใน UNIX คุณมีแนวคิดเกี่ยวกับกระบวนการและโปรแกรม กระบวนการคือสภาพแวดล้อมที่โปรแกรมดำเนินการ
แนวคิดง่ายๆเบื้องหลัง "รูปแบบการดำเนินการ" ของ UNIX คือมีสองการดำเนินการที่คุณสามารถทำได้
ประการแรกคือการfork()
สร้างกระบวนการใหม่ที่มีรายการที่ซ้ำกัน (ส่วนใหญ่) ของโปรแกรมปัจจุบันรวมถึงสถานะของโปรแกรม มีความแตกต่างเล็กน้อยระหว่างกระบวนการทั้งสองซึ่งทำให้พวกเขาสามารถเข้าใจได้ว่าสิ่งใดคือผู้ปกครองและกระบวนการใดคือเด็ก
ประการที่สองคือการexec()
แทนที่โปรแกรมในกระบวนการปัจจุบันด้วยโปรแกรมใหม่ล่าสุด
จากการดำเนินการง่ายๆสองอย่างนั้นสามารถสร้างโมเดลการดำเนินการ UNIX ทั้งหมด
หากต้องการเพิ่มรายละเอียดเพิ่มเติมที่ด้านบน:
การใช้fork()
และexec()
เป็นตัวอย่างเจตนารมณ์ของ UNIX ซึ่งเป็นวิธีที่ง่ายมากในการเริ่มกระบวนการใหม่
การfork()
เรียกใช้ทำให้กระบวนการปัจจุบันใกล้เคียงกันซึ่งเหมือนกันในเกือบทุกด้าน (ไม่ใช่ทุกอย่างที่ถูกคัดลอกไปตัวอย่างเช่นขีด จำกัด ของทรัพยากรในการนำไปใช้งานบางอย่าง แต่แนวคิดคือการสร้างสำเนาให้ใกล้เคียงที่สุด) มีเพียงกระบวนการเดียวที่เรียก fork()
แต่กระบวนการสองกระบวนการกลับมาจากการโทรนั้นฟังดูแปลกประหลาด แต่ก็ค่อนข้างสง่างามจริงๆ
กระบวนการใหม่ (เรียกว่าชายด์) ได้รับ ID กระบวนการ (PID) ที่แตกต่างกันและมี PID ของกระบวนการเก่า (พาเรนต์) เป็น PID หลัก (PPID)
เนื่องจากกระบวนการทั้งสองกำลังรันโค้ดเดียวกันทุกประการพวกเขาจึงต้องสามารถบอกได้ว่าอันไหน - โค้ดส่งคืนของการfork()
ให้ข้อมูลนี้ - เด็กได้รับ 0 พาเรนต์ได้รับ PID ของเด็ก (หากfork()
ล้มเหลวไม่ เด็กถูกสร้างขึ้นและพาเรนต์ได้รับรหัสข้อผิดพลาด)
ด้วยวิธีนี้ผู้ปกครองจะรู้ PID ของเด็กและสามารถสื่อสารกับมันฆ่ามันรอมันและอื่น ๆ (เด็กสามารถค้นหากระบวนการหลักได้ตลอดเวลาด้วยการโทรไปหาgetppid()
)
การexec()
โทรจะแทนที่เนื้อหาปัจจุบันทั้งหมดของกระบวนการด้วยโปรแกรมใหม่ โหลดโปรแกรมลงในพื้นที่กระบวนการปัจจุบันและเรียกใช้จากจุดเริ่มต้น
ดังนั้นfork()
และexec()
มักใช้ตามลำดับเพื่อให้โปรแกรมใหม่ทำงานเป็นลูกของกระบวนการปัจจุบัน โดยทั่วไปfind
เชลล์จะทำสิ่งนี้ทุกครั้งที่คุณพยายามรันโปรแกรมเช่น- เชลล์ฟอร์กจากนั้นเด็กจะโหลดfind
โปรแกรมลงในหน่วยความจำตั้งค่าอาร์กิวเมนต์บรรทัดคำสั่งทั้งหมด I / O มาตรฐานและอื่น ๆ
แต่ไม่จำเป็นต้องใช้ร่วมกัน เป็นที่ยอมรับอย่างสมบูรณ์สำหรับโปรแกรมที่จะเรียกfork()
โดยไม่มีสิ่งต่อไปนี้exec()
ตัวอย่างเช่นโปรแกรมมีทั้งรหัสหลักและรหัสย่อย (คุณต้องระมัดระวังในสิ่งที่คุณทำการใช้งานแต่ละครั้งอาจมีข้อ จำกัด )
สิ่งนี้ถูกใช้ค่อนข้างมาก (และยังคงเป็น) สำหรับ daemons ซึ่งเพียงแค่ฟังบนพอร์ต TCP และแยกสำเนาของตัวเองเพื่อประมวลผลคำขอเฉพาะในขณะที่ผู้ปกครองกลับไปฟัง สำหรับสถานการณ์นี้โปรแกรมมีทั้งรหัสหลักและรหัสลูก
ในทำนองเดียวกันโปรแกรมที่รู้ว่าพวกเขาทำเสร็จแล้วและเพียงแค่ต้องการที่จะเรียกใช้โปรแกรมอื่นทำไม่จำเป็นต้องfork()
, exec()
และจากนั้นwait()/waitpid()
สำหรับเด็ก พวกเขาสามารถโหลดเด็กลงในพื้นที่กระบวนการปัจจุบันโดยตรงด้วยexec()
ไฟล์.
การใช้งาน UNIX บางอย่างมีการปรับให้เหมาะสมfork()
ซึ่งใช้สิ่งที่พวกเขาเรียกว่า copy-on-write นี่เป็นเคล็ดลับในการชะลอการคัดลอกพื้นที่ในกระบวนการfork()
จนกว่าโปรแกรมจะพยายามเปลี่ยนแปลงบางสิ่งในช่องว่างนั้น สิ่งนี้มีประโยชน์สำหรับโปรแกรมเหล่านั้นโดยใช้เพียงอย่างเดียวfork()
และexec()
ไม่จำเป็นต้องคัดลอกพื้นที่กระบวนการทั้งหมด ภายใต้ลินุกซ์fork()
สร้างสำเนาของตารางเพจและโครงสร้างงานใหม่เท่านั้นexec()
จะทำงานหนักในการ "แยก" หน่วยความจำของทั้งสองกระบวนการ
หากสิ่งexec
นี้เรียกว่าการติดตามfork
(และนี่คือสิ่งที่เกิดขึ้นเป็นส่วนใหญ่) นั่นจะทำให้เกิดการเขียนลงในพื้นที่กระบวนการและจากนั้นจะถูกคัดลอกสำหรับกระบวนการลูกก่อนที่จะอนุญาตให้แก้ไขได้
ลินุกซ์ยังมี a ที่vfork()
ได้รับการปรับให้เหมาะสมยิ่งขึ้นซึ่งแบ่งปันทุกอย่างระหว่างสองกระบวนการ ด้วยเหตุนี้จึงมีข้อ จำกัด บางประการในสิ่งที่เด็กสามารถทำได้และผู้ปกครองจะหยุดจนกว่าเด็กจะเรียกexec()
หรือ_exit()
หรือ
ต้องหยุดพาเรนต์ (และเด็กไม่ได้รับอนุญาตให้กลับจากฟังก์ชันปัจจุบัน) เนื่องจากกระบวนการทั้งสองใช้สแต็กเดียวกัน นี้เป็นเพียงเล็กน้อยที่มีประสิทธิภาพมากขึ้นสำหรับกรณีการใช้งานคลาสสิกของตามทันทีโดยfork()
exec()
ทราบว่ามีคนในครอบครัวของ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
exec
ยูทิลิตี้นี้จึงใช้สำหรับเปลี่ยนเส้นทาง IO ของกระบวนการปัจจุบัน กรณี "null" ที่เรียกใช้ exec โดยไม่มีอาร์กิวเมนต์ถูกนำมาใช้สำหรับอนุสัญญานี้อย่างไร
exec
เป็นวิธีการแทนที่โปรแกรมปัจจุบัน (เชลล์) ในกระบวนการนี้ด้วยโปรแกรมอื่นการไม่ระบุว่าโปรแกรมอื่นที่จะแทนที่อาจหมายความว่าคุณไม่ต้องการแทนที่
exec
การถูกเรียกโดยไม่มีโปรแกรม แต่มันค่อนข้างแปลกในสถานการณ์นี้เนื่องจากประโยชน์ดั้งเดิมของการเปลี่ยนเส้นทางสำหรับโปรแกรมใหม่ - โปรแกรมที่จะใช้งานได้จริงexec
- หายไปและคุณมีสิ่งประดิษฐ์ที่มีประโยชน์เปลี่ยนเส้นทางโปรแกรมปัจจุบัน - ซึ่งไม่ได้ใช้exec
หรือเริ่มต้น ขึ้น แต่อย่างใด - แทน
หน้าที่ในครอบครัว exec () มีพฤติกรรมที่แตกต่างกัน:
คุณสามารถผสมได้ดังนั้นคุณจึงมี:
สำหรับทั้งหมดอาร์กิวเมนต์เริ่มต้นคือชื่อของไฟล์ที่จะดำเนินการ
สำหรับข้อมูลเพิ่มเติมอ่านexec (3) man page :
man 3 exec # if you are running a UNIX system
execve()
รายการของคุณซึ่งกำหนดโดย POSIX และคุณได้เพิ่มexecvpe()
ซึ่ง POSIX ไม่ได้กำหนดไว้ (ส่วนใหญ่เป็นเหตุผลของแบบอย่างในอดีตมันทำให้ชุดของฟังก์ชันสมบูรณ์) มิฉะนั้นคำอธิบายที่เป็นประโยชน์เกี่ยวกับหลักการตั้งชื่อสำหรับครอบครัวซึ่งเป็นส่วนเสริมที่เป็นประโยชน์สำหรับคำตอบของpaxdiabloซึ่งจะอธิบายเพิ่มเติมเกี่ยวกับการทำงานของฟังก์ชัน
execvpe()
(et al,) ไม่ได้รายการexecve()
; มันมีหน้าคนแยกต่างหาก (อย่างน้อยใน Ubuntu 16.04 LTS) - ความแตกต่างคือexec()
ฟังก์ชั่นตระกูลอื่น ๆแสดงอยู่ในส่วนที่ 3 (ฟังก์ชัน) ในขณะที่execve()
แสดงอยู่ในส่วนที่ 2 (การเรียกระบบ) โดยทั่วไปทุกฟังก์ชั่นอื่น ๆ execve()
ในครอบครัวจะดำเนินการในแง่ของการเรียกไปยัง
exec
ครอบครัวของฟังก์ชั่นให้กระบวนการของคุณรันโปรแกรมที่แตกต่างกันเปลี่ยนโปรแกรมเก่ามันก็ทำงาน คือถ้าโทร
execl("/bin/ls", "ls", NULL);
จากนั้นls
โปรแกรมจะดำเนินการด้วยรหัสกระบวนการผู้ควบคุมการทำงานปัจจุบันและผู้ใช้ / กลุ่ม (สิทธิ์การเข้าถึง) ของกระบวนการที่เรียกว่าexecl
ของกระบวนการที่เรียกว่า หลังจากนั้นโปรแกรมเดิมจะไม่ทำงานอีกต่อไป
ในการเริ่มกระบวนการใหม่fork
ระบบจะใช้การเรียกระบบ การรันโปรแกรมโดยไม่ต้องแทนที่เดิมคุณต้องแล้วfork
exec
หน้าที่ผู้บริหารและครอบครัวคืออะไร
exec
ครอบครัวฟังก์ชั่นการทำงานทั้งหมดที่ใช้ในการเรียกใช้ไฟล์เช่นexecl
, execlp
, execle
, execv
และexecvp
.They เป็น frontends ทั้งหมดexecve
และให้วิธีการที่แตกต่างกันของการเรียกมันว่า
เหตุใดจึงใช้ฟังก์ชันนี้
ฟังก์ชัน Exec ใช้เมื่อคุณต้องการเรียกใช้งาน (เรียกใช้) ไฟล์ (โปรแกรม)
และมันทำงานอย่างไร
ซึ่งทำงานโดยการเขียนทับอิมเมจกระบวนการปัจจุบันด้วยภาพที่คุณเปิดตัว พวกเขาแทนที่ (โดยสิ้นสุด) กระบวนการที่กำลังทำงานอยู่ (กระบวนการที่เรียกว่าคำสั่ง exec) ด้วยกระบวนการใหม่ที่เปิดตัว
สำหรับรายละเอียดเพิ่มเติม: ดูลิงค์นี้
exec
มักใช้ร่วมกับ fork
ซึ่งฉันเห็นว่าคุณถามถึงด้วยดังนั้นฉันจะพูดถึงเรื่องนี้โดยคำนึงถึงสิ่งนี้
exec
เปลี่ยนกระบวนการปัจจุบันเป็นโปรแกรมอื่น ถ้าคุณเคยดู Doctor Who นี่ก็เหมือนกับตอนที่เขางอกใหม่ร่างกายเก่าของเขาจะถูกแทนที่ด้วยร่างใหม่
สิ่งนี้เกิดขึ้นกับโปรแกรมของคุณและexec
เป็นทรัพยากรจำนวนมากที่เคอร์เนลระบบปฏิบัติการตรวจสอบเพื่อดูว่าไฟล์ที่คุณส่งผ่านไปexec
เป็นอาร์กิวเมนต์ของโปรแกรม (อาร์กิวเมนต์แรก) สามารถเรียกใช้งานได้โดยผู้ใช้ปัจจุบัน (id ผู้ใช้ของกระบวนการ การexec
โทร) และหากเป็นเช่นนั้นจะแทนที่การแม็พหน่วยความจำเสมือนของกระบวนการปัจจุบันด้วยหน่วยความจำเสมือนให้กระบวนการใหม่และคัดลอกargv
และenvp
ข้อมูลที่ส่งผ่านในการexec
โทรไปยังพื้นที่ของการแม็พหน่วยความจำเสมือนใหม่นี้ สิ่งอื่น ๆ อาจเกิดขึ้นที่นี่เช่นกัน แต่ไฟล์ที่เปิดไว้สำหรับโปรแกรมที่เรียกexec
จะยังคงเปิดอยู่สำหรับโปรแกรมใหม่และจะแชร์ ID กระบวนการเดียวกัน แต่โปรแกรมที่เรียกexec
จะหยุดทำงาน (เว้นแต่ว่า exec จะล้มเหลว)
ด้วยเหตุผลที่ว่านี้จะทำวิธีนี้ก็คือว่าโดยการแยกการทำงาน ใหม่ โปรแกรมออกเป็นสองขั้นตอนเช่นนี้คุณสามารถทำบางสิ่งบางอย่างระหว่างคนทั้งสองขั้นตอน สิ่งที่ต้องทำบ่อยที่สุดคือตรวจสอบให้แน่ใจว่าโปรแกรมใหม่เปิดไฟล์บางไฟล์เป็นตัวอธิบายไฟล์บางไฟล์ (โปรดจำไว้ว่า file descriptors จะไม่เหมือนกับFILE *
แต่เป็นint
ค่าที่เคอร์เนลรู้) การทำเช่นนี้คุณสามารถ:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
สิ่งนี้ทำงานได้สำเร็จ:
/bin/echo "hello world" > ./output_file.txt
จากเชลล์คำสั่ง
เมื่อกระบวนการใช้ fork () มันจะสร้างสำเนาของตัวมันเองขึ้นมาและรายการที่ซ้ำกันนี้จะกลายเป็นลูกของกระบวนการ fork () ถูกนำไปใช้โดยใช้ clone () system call ใน linux ซึ่งส่งคืนสองครั้งจากเคอร์เนล
มาทำความเข้าใจกับตัวอย่าง:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
ในตัวอย่างเราสันนิษฐานว่า exec () ไม่ได้ใช้ในกระบวนการย่อย
แต่แม่และลูกแตกต่างกันในคุณลักษณะบางอย่างของ PCB (บล็อกควบคุมกระบวนการ) เหล่านี้คือ:
แต่ความจำตอนเด็กล่ะ? พื้นที่ที่อยู่ใหม่สร้างขึ้นสำหรับเด็กหรือไม่
คำตอบในไม่ หลังจากส้อม () ทั้งพาเรนต์และเด็กแชร์พื้นที่แอดเดรสหน่วยความจำของพาเรนต์ ใน linux พื้นที่แอดเดรสเหล่านี้จะแบ่งออกเป็นหลายเพจ เฉพาะเมื่อเด็กเขียนไปยังเพจหน่วยความจำพาเรนต์เท่านั้นการสร้างซ้ำของเพจนั้นจะถูกสร้างขึ้นสำหรับเด็ก สิ่งนี้เรียกอีกอย่างว่า copy on write (คัดลอกพาเรนต์เพจเฉพาะเมื่อเด็กเขียนเท่านั้น)
มาทำความเข้าใจกับตัวอย่างการ copy on write
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
แต่ทำไม copy on write จึงจำเป็น?
การสร้างกระบวนการโดยทั่วไปเกิดขึ้นผ่านการรวมกันของ fork () - exec () มาทำความเข้าใจกันก่อนว่า exec () ทำอะไร
กลุ่มฟังก์ชัน Exec () แทนที่พื้นที่ที่อยู่ของเด็กด้วยโปรแกรมใหม่ เมื่อ exec () ถูกเรียกภายในเด็กพื้นที่แอดเดรสแยกต่างหากจะถูกสร้างขึ้นสำหรับเด็กซึ่งแตกต่างจากของผู้ปกครองโดยสิ้นเชิง
หากไม่มีสำเนาบนกลไกการเขียนที่เกี่ยวข้องกับ fork () เพจที่ซ้ำกันจะถูกสร้างขึ้นสำหรับเด็กและข้อมูลทั้งหมดจะถูกคัดลอกไปยังเพจของเด็ก การจัดสรรหน่วยความจำใหม่และการคัดลอกข้อมูลเป็นกระบวนการที่มีราคาแพงมาก (ใช้เวลาของโปรเซสเซอร์และทรัพยากรระบบอื่น ๆ ) เรารู้ด้วยว่าในกรณีส่วนใหญ่เด็กจะเรียก exec () และนั่นจะแทนที่หน่วยความจำของเด็กด้วยโปรแกรมใหม่ ดังนั้นสำเนาแรกที่เราทำอาจจะเสียเปล่าถ้าไม่มี copy on write
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
เหตุใดผู้ปกครองจึงรอกระบวนการย่อย
เหตุใดการเรียกระบบ exec () จึงจำเป็น?
ไม่จำเป็นต้องใช้ exec () กับ fork () หากโค้ดที่เด็กจะเรียกใช้นั้นอยู่ในโปรแกรมที่เกี่ยวข้องกับพาเรนต์ไม่จำเป็นต้องใช้ exec ()
แต่ลองนึกถึงกรณีที่เด็กต้องเรียกใช้หลายโปรแกรม มาดูตัวอย่างโปรแกรมเชลล์กัน รองรับหลายคำสั่งเช่น find, mv, cp, date เป็นต้นจะรวมโค้ดโปรแกรมที่เกี่ยวข้องกับคำสั่งเหล่านี้ไว้ในโปรแกรมเดียวหรือให้ลูกโหลดโปรแกรมเหล่านี้ลงในหน่วยความจำเมื่อจำเป็น?
ทั้งหมดขึ้นอยู่กับกรณีการใช้งานของคุณ คุณมีเว็บเซิร์ฟเวอร์ที่ให้อินพุต x ที่ส่งคืน 2 ^ x ไปยังไคลเอนต์ สำหรับแต่ละคำขอเว็บเซิร์ฟเวอร์จะสร้างลูกใหม่และขอให้คำนวณ คุณจะเขียนโปรแกรมแยกต่างหากเพื่อคำนวณสิ่งนี้และใช้ exec () หรือไม่? หรือคุณจะเขียนโค้ดการคำนวณภายในโปรแกรมหลัก?
โดยปกติการสร้างกระบวนการจะเกี่ยวข้องกับการเรียกใช้ fork (), exec (), wait () และ exit () เข้าด้วยกัน
exec(3,3p)
ฟังก์ชั่นเปลี่ยนกระบวนการปัจจุบันอีกด้วย นั่นคือกระบวนการปัจจุบันหยุดลงและอีกกระบวนการหนึ่งทำงานแทนโดยยึดทรัพยากรบางส่วนที่โปรแกรมเดิมมีอยู่