TL; DR : เพราะนี่เป็นวิธีที่ดีที่สุดสำหรับการสร้างกระบวนการใหม่และการควบคุมในเชลล์แบบโต้ตอบ
fork () เป็นสิ่งที่จำเป็นสำหรับกระบวนการและท่อ
ที่จะตอบเฉพาะส่วนของคำถามนี้ถ้าgrep blabla fooจะถูกเรียกว่าผ่านexec()โดยตรงในพ่อแม่ผู้ปกครองจะยึดอยู่และ PID grep blabla fooกับทรัพยากรทั้งหมดจะถูกนำตัวไปจาก
อย่างไรก็ตามการพูดคุยให้ของทั่วไปเกี่ยวกับและexec() fork()เหตุผลสำคัญสำหรับพฤติกรรมดังกล่าวเป็นเพราะfork()/exec()เป็นวิธีมาตรฐานในการสร้างกระบวนการใหม่บน Unix / Linux และนี่ไม่ใช่สิ่งที่ผิดพลาด วิธีนี้มีมาตั้งแต่เริ่มต้นและได้รับอิทธิพลจากวิธีเดียวกันนี้จากระบบปฏิบัติการที่มีอยู่แล้วของเวลา เพื่อให้ได้คำตอบของคำถามที่เกี่ยวข้องfork()การสร้างกระบวนการใหม่นั้นง่ายกว่าเนื่องจากเคอร์เนลมีงานที่ต้องทำน้อยกว่าการจัดสรรทรัพยากรไปและคุณสมบัติมากมาย (เช่นตัวอธิบายไฟล์สภาพแวดล้อม ฯลฯ ) - ทั้งหมดสามารถ ถูกสืบทอดมาจากกระบวนการพาเรนต์ (ในกรณีนี้จากbash)
ประการที่สองตราบใดที่เชลล์อินเทอร์แอคทีฟไปคุณไม่สามารถเรียกใช้คำสั่งภายนอกโดยไม่ต้องฟอร์ก ในการเรียกใช้งาน executable ที่อาศัยอยู่บนดิสก์ (ตัวอย่างเช่น/bin/df -h) คุณจะต้องเรียกexec()ฟังก์ชันหนึ่งในตระกูลเช่นexecve()ซึ่งจะแทนที่ parent ด้วยกระบวนการใหม่ใช้ PID และตัวอธิบายไฟล์ที่มีอยู่เป็นต้น สำหรับเชลล์เชิงโต้ตอบคุณต้องการให้คอนโทรลกลับไปยังผู้ใช้และปล่อยให้เชลล์โต้ตอบหลักดำเนินต่อไป ดังนั้นวิธีที่ดีที่สุดคือการสร้างกระบวนการย่อยผ่านทางและปล่อยให้กระบวนการที่จะนำไปผ่านfork() execve()ดังนั้นเชลล์แบบโต้ตอบ PID 1156 จะวางไข่เด็กfork()ด้วย PID 1157 จากนั้นเรียกexecve("/bin/df",["df","-h"],&environment)ซึ่งทำให้/bin/df -hทำงานด้วย PID 1157 ตอนนี้เชลล์จะต้องรอให้กระบวนการออกจากระบบและกลับมาควบคุมอีกครั้ง
ในกรณีที่คุณต้องสร้างไพพ์ระหว่างคำสั่งตั้งแต่สองคำสั่งขึ้นไปdf | grepคุณจำเป็นต้องมีวิธีในการสร้างไฟล์ descriptors สองไฟล์ (นั่นคือการอ่านและเขียนปลายpipe()ไพพ์ซึ่งมาจากsyscall) จากนั้นให้กระบวนการใหม่สองอันสืบทอดกระบวนการเหล่านั้น นั่นคือการทำกระบวนการใหม่แล้วทำการคัดลอกส่วนการเขียนของไพพ์ผ่านการdup2()เรียกไปยังstdoutaka fd 1 ของมัน(ดังนั้นถ้าการเขียน end เป็น fd 4 เราก็ทำได้dup2(4,1)) เมื่อใดที่exec()จะวางไข่dfเกิดขึ้นกระบวนการของเด็กจะไม่คิดอะไรเลยstdoutและเขียนลงไปโดยไม่รู้ตัว กระบวนการเดียวกันเกิดขึ้นgrepยกเว้นเราfork()จะอ่านส่วนปลายของท่อด้วย fd 3 และdup(3,0)ก่อนที่จะวางไข่grepด้วยexec(). ทุกครั้งที่กระบวนการพาเรนต์ยังคงอยู่ที่นั่นรอการควบคุมอีกครั้งเมื่อไปป์ไลน์เสร็จสมบูรณ์
ในกรณีของคำสั่งในตัวโดยทั่วไปเชลล์จะไม่fork()ยกเว้นsourceคำสั่ง subshells fork()ต้อง
ในระยะสั้นนี่เป็นกลไกที่จำเป็นและมีประโยชน์
ข้อเสียของการฟอร์กและการปรับให้เหมาะสม
ตอนนี้เป็นที่แตกต่างกันสำหรับเปลือกหอยที่ไม่ใช่แบบโต้ตอบbash -c '<simple command>'เช่น แม้ว่าจะfork()/exec()เป็นวิธีการที่ดีที่สุดที่คุณต้องประมวลผลคำสั่งจำนวนมาก แต่ก็เป็นการสิ้นเปลืองทรัพยากรเมื่อคุณมีเพียงคำสั่งเดียว หากต้องการอ้างอิงStéphane Chazelasจากโพสต์นี้ :
Forking นั้นแพงในเวลา CPU, หน่วยความจำ, ตัวอธิบายไฟล์ที่จัดสรร ... การมีกระบวนการเชลล์โกหกเพียงแค่รอกระบวนการอื่นก่อนที่จะออกจากนั้นเป็นทรัพยากรที่สิ้นเปลือง นอกจากนี้มันทำให้ยากที่จะรายงานสถานะการออกของกระบวนการแยกต่างหากที่จะดำเนินการคำสั่งได้อย่างถูกต้อง (ตัวอย่างเช่นเมื่อกระบวนการถูกฆ่า)
ดังนั้นเชลล์จำนวนมาก (ไม่ใช่แค่bash) ใช้exec()เพื่ออนุญาตให้ใช้bash -c ''คำสั่งง่าย ๆ นั้น และด้วยเหตุผลที่กล่าวมาข้างต้นการย่อเล็กสุดของท่อในเชลล์สคริปต์นั้นดีกว่า บ่อยครั้งที่คุณเห็นผู้เริ่มต้นทำสิ่งนี้:
cat /etc/passwd | cut -d ':' -f 6 | grep '/home'
แน่นอนว่านี่จะเป็นfork()3 กระบวนการ นี่เป็นตัวอย่างง่ายๆ แต่ให้พิจารณาไฟล์ขนาดใหญ่ในช่วงกิกะไบต์ มันจะมีประสิทธิภาพมากขึ้นด้วยกระบวนการเดียว:
awk -F':' '$6~"/home"{print $6}' /etc/passwd
ความสิ้นเปลืองทรัพยากรจริง ๆ แล้วอาจเป็นรูปแบบหนึ่งของการโจมตีแบบปฏิเสธการให้บริการและโดยเฉพาะอย่างยิ่งการทิ้งระเบิดถูกสร้างขึ้นผ่านฟังก์ชั่นเชลล์ที่เรียกตัวเองว่าไปป์ไลน์ ทุกวันนี้สิ่งนี้ได้รับการลดลงโดย จำกัด จำนวนกระบวนการสูงสุดในกลุ่มcg บน systemdซึ่ง Ubuntu ก็ใช้ตั้งแต่รุ่น 15.04
แน่นอนว่าไม่ได้หมายความว่าการตีจะไม่ดี มันยังคงเป็นกลไกที่มีประโยชน์ตามที่กล่าวไว้ก่อนหน้านี้ แต่ในกรณีที่คุณสามารถหลีกเลี่ยงกระบวนการที่น้อยลงและใช้ทรัพยากรน้อยลงอย่างต่อเนื่องและมีประสิทธิภาพที่ดีขึ้นคุณควรหลีกเลี่ยงfork()ถ้าเป็นไปได้
ดูสิ่งนี้ด้วย