อะไรคือสาเหตุของการทำ double fork เมื่อสร้าง daemon?


165

ฉันพยายามสร้างภูตในหลาม ฉันได้พบคำถามต่อไปนี้ซึ่งมีแหล่งข้อมูลที่ดีอยู่ในนั้นซึ่งฉันกำลังติดตามอยู่ แต่ฉันอยากรู้ว่าเหตุใดจึงจำเป็นต้องใช้ส้อมคู่ ฉันมีรอยขีดข่วนรอบ ๆ google และพบว่ามีทรัพยากรมากมายประกาศว่าจำเป็น แต่ไม่ใช่เหตุผล

บางคนพูดถึงว่ามันคือการป้องกันไม่ภูตจากการซื้อสถานีควบคุม ว่ามันจะทำเช่นนี้ได้โดยไม่ต้องแยกสอง? อะไรคือผลกระทบหรือไม่



2
ปัญหาอย่างหนึ่งของการทำส้อมคู่คือผู้ปกครองไม่สามารถรับ PID ของกระบวนการหลานได้อย่างง่ายดาย (การfork()โทรส่งคืน PID ของเด็กไปยังผู้ปกครองดังนั้นจึงง่ายที่จะได้รับ PID ของกระบวนการเด็ก แต่ไม่ใช่เรื่องง่ายที่จะ รับ PID ของกระบวนการหลาน )
Craig McQueen

คำตอบ:


105

ดูรหัสอ้างอิงในคำถามเหตุผลคือ:

แยกลูกคนที่สองและออกทันทีเพื่อป้องกันไม่ให้ซอมบี้ สิ่งนี้ทำให้กระบวนการลูกที่สองถูกกำพร้าทำให้กระบวนการเริ่มต้นมีหน้าที่รับผิดชอบในการล้างข้อมูล และเนื่องจากเด็กคนแรกที่เป็นผู้นำเซสชั่นโดยไม่ต้องควบคุมขั้วมันเป็นไปได้เพื่อให้ได้รับหนึ่งด้วยการเปิดสถานีในอนาคต (ระบบ V- ตามระบบ) ที่สองนี้การค้ำประกันส้อมว่าเด็กจะไม่เป็นผู้นำเซสชั่นการป้องกันภูตจากที่เคยซื้อสถานีควบคุม

ดังนั้นเพื่อให้แน่ใจว่า daemon ได้รับการจดสิทธิบัตรอีกครั้งบน init (ในกรณีที่กระบวนการเตะ daemon นั้นใช้เวลานาน) และลบโอกาสที่ daemon จะเรียกใช้ tty ควบคุมอีกครั้ง ดังนั้นหากไม่มีกรณีเหล่านี้มาใช้ก็ควรแยกให้เพียงพอ " Unix Network Programming - Stevens " มีส่วนที่ดีในเรื่องนี้


28
นี้ไม่ถูกต้องทั้งหมด p=fork(); if(p) exit(); setsid()วิธีมาตรฐานในการสร้างภูตก็คือการทำ ในกรณีนี้ผู้ปกครองก็จะออกและกระบวนการลูกคนแรกจะได้รับการซ่อมแซม มายากลดับเบิลส้อมเป็นเพียงที่จำเป็นในการป้องกันไม่ให้ภูตจากการแสวงหา TTY บริการ
parasietje

1
ดังนั้นที่ผมเข้าใจว่าถ้าฉันเริ่มต้นโปรแกรมและกระบวนการนี้กระบวนการเด็กแรกจะเป็นและจะสามารถที่จะเปิด terminal TTY แต่ถ้าฉันแยกจากเด็กคนนี้อีกครั้งและยุติเด็กคนแรกเด็กที่แยกสองคนนี้จะไม่เป็นและจะไม่สามารถเปิดเทอร์มินัล TTY ได้ คำสั่งนี้ถูกต้องหรือไม่ forkschildsession leadersession leader
tonix

2
@tonix: การฟอร์กเพียงอย่างเดียวไม่ได้สร้างผู้นำเซสชัน setsid()ว่าจะกระทำโดย ดังนั้นกระบวนการที่แยกจากกันครั้งแรกจะกลายเป็นผู้นำเซสชันหลังจากการโทรsetsid()แล้วเราแยกอีกครั้งเพื่อให้กระบวนการสุดท้ายที่แยกกันสองครั้งไม่ได้เป็นผู้นำเซสชันอีกต่อไป นอกจากความต้องการsetsid()ที่จะเป็นผู้นำเซสชันคุณก็จะได้รับความสนใจ
dbmikus

169

ฉันพยายามที่จะเข้าใจส้อมคู่และสะดุดกับคำถามนี้ที่นี่ หลังจากการวิจัยมากมายนี่คือสิ่งที่ฉันคิดได้ หวังว่ามันจะช่วยชี้แจงสิ่งที่ดีกว่าสำหรับทุกคนที่มีคำถามเดียวกัน

ในระบบปฏิบัติการยูนิกซ์ทุกขั้นตอนอยู่ในกลุ่มซึ่งจะเป็นของเซสชั่นที่ นี่คือลำดับชั้น ...

เซสชัน (SID) →กลุ่มกระบวนการ (PGID) →กระบวนการ (PID)

กระบวนการแรกในกลุ่มกระบวนการกลายเป็นผู้นำกลุ่มกระบวนการและกระบวนการแรกในเซสชันกลายเป็นผู้นำเซสชัน ทุกครั้งสามารถมีหนึ่ง TTY ที่เกี่ยวข้องกับมัน เฉพาะผู้นำเซสชันเท่านั้นที่สามารถควบคุม TTY ได้ สำหรับกระบวนการที่จะ daemonized อย่างแท้จริง (วิ่งในพื้นหลัง) เราควรตรวจสอบให้แน่ใจว่าผู้นำเซสชันถูกฆ่าเพื่อไม่ให้มีความเป็นไปได้ที่เซสชันจะควบคุม TTY

ฉันรันโปรแกรม daemon ของ python เช่น Sander Marechal จากเว็บไซต์นี้บน Ubuntu ของฉัน นี่คือผลที่มีความคิดเห็นของฉัน

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

หมายเหตุว่ากระบวนการที่เป็นผู้นำในเซสชั่นหลังเพราะมันDecouple#1 PID = SIDมันยังคงสามารถควบคุม TTY ได้

โปรดทราบว่าไม่เป็นผู้นำเซสชั่นFork#2 PID != SIDกระบวนการนี้ไม่สามารถควบคุม TTY ได้ daemonized อย่างแท้จริง

โดยส่วนตัวแล้วฉันพบว่าคำศัพท์ทางแยกสองครั้งทำให้เกิดความสับสน สำนวนที่ดีกว่าอาจจะแยกส้อมส้อม

ลิงก์เพิ่มเติมที่น่าสนใจ:


การแยกสองครั้งยังป้องกันการสร้างซอมบี้เมื่อกระบวนการหลักรันเป็นเวลานานและด้วยเหตุผลบางประการลบตัวจัดการเริ่มต้นสำหรับสัญญาณที่แจ้งว่ากระบวนการนั้นเสียชีวิต
Trismegistos

แต่ข้อที่สองสำหรับยังสามารถเรียก decouple และกลายเป็นผู้นำเซสชันและรับเทอร์มินัล
Trismegistos

2
นี่ไม่เป็นความจริง. ครั้งแรกที่fork()ป้องกันการสร้างซอมบี้แล้วให้คุณปิดผู้ปกครอง
parasietje

1
ตัวอย่างที่น้อยที่สุดเพื่อให้ผลลัพธ์ที่ยกมาข้างต้นนี้gist.github.com/cannium/7aa58f13c834920bb32c
สามารถ

1
มันจะดีใด ๆ ที่จะเรียกsetsid() ก่อนเดียวfork()? จริงๆแล้วฉันเดาคำตอบของคำถามนี้ตอบว่า
Craig McQueen

118

initพูดอย่างเคร่งครัดคู่ส้อมมีอะไรจะทำอย่างไรกับเรื่องการเลี้ยงดูภูตเป็นเด็กของ ทุกสิ่งที่มีความจำเป็นต้องอีกครั้งผู้ปกครองเด็กที่ผู้ปกครองต้องออก สามารถทำได้ด้วยส้อมเดียวเท่านั้น นอกจากนี้การทำดับเบิลส้อมด้วยตัวเองไม่ได้อีกแม่กระบวนการภูตไปinit; ผู้ปกครองภูตที่จะต้องออกจาก ในคำอื่น ๆ ผู้ปกครองมักจะออกเมื่อฟอร์กภูตที่เหมาะสมเพื่อให้กระบวนการภูตเป็นอีกครั้งที่ parented initไป

ดังนั้นทำไมส้อมคู่? POSIX.1-2008ส่วนที่ 11.1.3 " สถานีควบคุม " มีคำตอบ (เน้นเพิ่ม):

เทอร์มินัลการควบคุมสำหรับเซสชันถูกจัดสรรโดยผู้นำเซสชันในลักษณะที่กำหนดไว้ หากผู้นำเซสชั่นไม่มีสถานีควบคุมและเปิดแฟ้มอุปกรณ์ปลายทางที่ไม่ได้เชื่อมโยงกับเซสชั่นโดยไม่ต้องใช้O_NOCTTYตัวเลือก (ดูopen()) ก็คือการดำเนินการที่กำหนดไว้ไม่ว่าจะเป็นขั้วกลายเป็นขั้วควบคุมของผู้นำเซสชั่น หากกระบวนการที่เป็นไม่ได้เป็นผู้นำเซสชั่นเปิดแฟ้มขั้วหรือO_NOCTTYตัวเลือกที่ถูกนำมาใช้บนopen(), แล้วที่สถานีจะไม่กลายเป็นสถานีควบคุมของกระบวนการเรียก

สิ่งนี้บอกเราว่าหากกระบวนการภูตทำสิ่งนี้ ...

int fd = open("/dev/console", O_RDWR);

... แล้วกระบวนการภูตอาจได้รับ/dev/consoleการควบคุมของขั้วขึ้นอยู่กับว่ากระบวนการภูตเป็นผู้นำเซสชั่นและขึ้นอยู่กับการใช้งานระบบ โปรแกรมสามารถรับประกันได้ว่าการเรียกข้างต้นจะไม่ได้รับการควบคุมสถานีถ้าโปรแกรมแรกมั่นใจได้ว่ามันไม่ได้เป็นผู้นำเซสชัน

โดยปกติเมื่อเรียกใช้ daemon setsidจะถูกเรียก (จากกระบวนการลูกหลังจากการโทรfork) เพื่อแยก daemon ออกจากเทอร์มินัลการควบคุม อย่างไรก็ตามการเรียกsetsidก็หมายความว่ากระบวนการเรียกร้องจะเป็นผู้นำเซสชั่นของเซสชั่นใหม่ซึ่งใบเปิดเป็นไปได้ว่าอาจภูต reacquire สถานีควบคุม เทคนิค double-fork ทำให้แน่ใจได้ว่ากระบวนการ daemon ไม่ใช่ผู้นำเซสชันซึ่งรับประกันได้ว่าการเรียกไปยังopenดังตัวอย่างข้างต้นจะไม่ส่งผลให้กระบวนการ daemon เรียกคืนเทอร์มินัลการควบคุมอีกครั้ง

เทคนิค double-fork เป็นหวาดระแวงเล็กน้อย อาจไม่จำเป็นถ้าคุณรู้ว่าภูตจะไม่เปิดไฟล์อุปกรณ์ปลายทาง นอกจากนี้ในบางระบบอาจไม่จำเป็นแม้ว่า daemon จะเปิดไฟล์อุปกรณ์เทอร์มินัลเนื่องจากลักษณะการทำงานนั้นได้กำหนดไว้ในการนำไปใช้งาน แต่สิ่งหนึ่งที่ไม่ได้ดำเนินการตามที่กำหนดไว้ก็คือว่าเป็นผู้นำในเซสชั่นสามารถจัดสรรสถานีควบคุม หากกระบวนการไม่ใช่ผู้นำเซสชันก็ไม่สามารถจัดสรรเทอร์มินัลการควบคุมได้ ดังนั้นถ้าคุณต้องการที่จะหวาดระแวงและให้แน่ใจว่ากระบวนการภูตไม่สามารถได้รับโดยไม่ได้ตั้งใจสถานีควบคุมโดยไม่คำนึงถึงรายละเอียดการดำเนินงานที่กำหนดใด ๆ แล้วเทคนิคดับเบิลส้อมเป็นสิ่งจำเป็น


3
+1 คำตอบนี้แย่เกินไป ~ สี่ปีหลังจากถามคำถาม
Tim Seguine

12
แต่ที่ยังไม่ได้อธิบายว่าทำไมมันจึงสำคัญชะมัดว่าภูตไม่สามารถ reacquire ควบคุมขั้ว
UloPe

7
คำหลักคือ "ตั้งใจ" ได้รับเครื่องควบคุม หากกระบวนการเกิดขึ้นเพื่อเปิดเทอร์มินัลและมันกลายเป็นกระบวนการควบคุมเทอร์มินัลมากกว่าถ้ามีคนออก ^ C จากเทอร์มินัลนั้นก็สามารถยุติกระบวนการได้ ดังนั้นอาจเป็นการดีที่จะป้องกันกระบวนการที่เกิดขึ้นโดยไม่ได้ตั้งใจ โดยส่วนตัวแล้วฉันจะติดกับ fork เดี่ยวและ setsid () สำหรับโค้ดที่ฉันเขียนซึ่งฉันรู้ว่าจะไม่เปิดเทอร์มินัล
BobDoolittle

1
@BobDoolittle สิ่งนี้เกิดขึ้นได้อย่างไร "โดยไม่ตั้งใจ"? กระบวนการจะไม่เพียง แต่จะจบลงด้วยการเปิดเทอร์มินัลหากยังไม่ได้ทำการเขียน บางทีการตีสองครั้งอาจมีประโยชน์หากโปรแกรมเมอร์ไม่รู้จักรหัสและไม่ทราบว่าอาจเปิดไฟล์ tty ได้หรือไม่
Marius

10
@Marius LogFile=/dev/consoleลองจินตนาการถึงสิ่งที่อาจเกิดขึ้นถ้าคุณเพิ่มบรรทัดเช่นนี้ไปยังแฟ้มการกำหนดค่าภูตของคุณ: โปรแกรมไม่สามารถรวบรวมเวลาที่จะเปิดไฟล์ได้)
Dan Molding

11

นำมาจากBad CTK :

"ในรสชาติของ Unix บางท่านจะถูกบังคับให้ทำสองครั้งส้อมในการเริ่มต้นในการที่จะเข้าสู่โหมดภูต. นี้เป็นเพราะฟอร์กเดียวไม่รับประกันว่าจะแยกออกจากสถานีควบคุม."


3
วิธีการสามารถส้อมเดียวไม่แยกออกจากการควบคุมส้อมขั้ว แต่คู่ทำเพื่อ? สิ่งนี้เกิดขึ้นในระบบยูนิกซ์
bdonlan

12
daemon ต้องปิดเป็น descriptors อินพุตและเอาต์พุตไฟล์ (fds) มิฉะนั้นจะยังคงเชื่อมต่อกับเทอร์มินัลที่เริ่มทำงานกระบวนการ fork ที่สืบทอดสืบทอดมาจากพาเรนต์ เห็นได้ชัดว่าเด็กคนแรกปิด fds แต่นั่นไม่ได้ล้างทุกอย่าง บนทางแยกที่สอง FDS ไม่อยู่เพื่อให้เด็กที่สองไม่สามารถเชื่อมต่อกับอะไรอีกต่อไป
Aaron Digulla

4
@Aaron: ไม่ daemon ทำการ "แยก" ตัวเองออกจากเทอร์มินัลการควบคุมอย่างถูกต้องโดยการเรียกsetsidหลังจาก fork เริ่มต้น จากนั้นตรวจสอบให้แน่ใจว่าอยู่ห่างจากเทอร์มินัลการควบคุมโดยการฟอร์กอีกครั้งและออกจากเซสชันผู้นำ (กระบวนการที่เรียกว่าsetsid)
แดน Molding

2
@bdonlan: มันไม่ได้เป็นforkที่แยกจากสถานีควบคุม มันเป็นอย่างsetsidนั้น แต่setsidจะล้มเหลวหากถูกเรียกจากหัวหน้ากลุ่มกระบวนการ ดังนั้นเริ่มต้นforkจะต้องทำก่อนsetsidเพื่อให้แน่ใจว่าsetsidจะเรียกว่าจากกระบวนการที่ไม่ได้เป็นหัวหน้ากลุ่มกระบวนการ กระบวนการที่สองforkทำให้มั่นใจได้ว่ากระบวนการสุดท้าย (กระบวนการที่จะเป็น daemon) ไม่ใช่ผู้นำเซสชัน เฉพาะผู้นำเซสชันเท่านั้นที่สามารถรับเทอร์มินัลการควบคุมได้ดังนั้นทางแยกที่สองนี้รับรองว่า daemon จะไม่ได้รับการควบคุมเทอร์มินัลอีกครั้งโดยไม่ตั้งใจ นี่เป็นความจริงของ POSIX OS ใด ๆ
Dan Molding

@DanMoulding สิ่งนี้ไม่รับประกันว่าลูกคนที่สองจะไม่ได้รับเครื่องควบคุมเพราะมันสามารถเรียก setsid และกลายเป็นผู้นำเซสชันแล้วได้รับเครื่องควบคุม
Trismegistos

7

ตาม "การเขียนโปรแกรมขั้นสูงใน Unix Environment" โดย Stephens และ Rago ทางแยกที่สองเป็นคำแนะนำที่มากกว่าและจะทำเพื่อรับประกันว่า daemon ไม่ได้รับเทอร์มินัลการควบคุมบนระบบที่ใช้ระบบ V


3

สาเหตุหนึ่งคือกระบวนการหลักสามารถรอเด็ก ๆ ทันทีและลืมมันได้ เมื่อหลานสาวตายแล้วพ่อแม่ก็จะเป็นผู้เริ่มต้นและมันจะรอ () มัน - และนำมันออกจากสถานะซอมบี้

ผลลัพธ์คือกระบวนการพาเรนต์ไม่จำเป็นต้องรับรู้ถึง forked children และยังทำให้สามารถ fork กระบวนการที่รันเป็นเวลานานจาก libs เป็นต้น


2

ภูตโทร () มี _exit โทรปกครอง () ถ้ามันประสบความสำเร็จ แรงจูงใจที่เดิมอาจจะได้รับการอนุญาตให้ผู้ปกครองที่จะทำบางทำงานพิเศษในขณะที่เด็กเป็น daemonizing

อาจเป็นไปตามความเชื่อที่ผิดที่จำเป็นเพื่อให้มั่นใจว่า daemon ไม่มีกระบวนการหลักและได้รับการชดใช้ให้เริ่มต้น - แต่สิ่งนี้จะเกิดขึ้นเมื่อแม่ตายในกรณี fork เดี่ยว

ดังนั้นฉันคิดว่าทุกอย่างจะเดือดพล่านในท้ายที่สุด - ส้อมเดียวก็เพียงพอแล้วตราบใดที่พ่อแม่เสียชีวิตในเวลาอันสั้น


2

การสนทนาที่ดีของมันปรากฏอยู่ที่http://www.developerweb.net/forum/showthread.php?t=3025

อ้างอิง mlampkin จากที่นั่น:

... คิดว่าการเรียก setsid () เป็นวิธี "ใหม่" ในการทำสิ่งต่าง ๆ (ยกเลิกการเชื่อมโยงจากเทอร์มินัล) และ [สอง] fork () โทรหลังจากที่มันซ้ำซ้อนเพื่อจัดการกับ SVr4 ...


-1

มันอาจจะง่ายกว่าที่จะเข้าใจในวิธีนี้:

  • fork และ setsid แรกจะสร้างเซสชันใหม่ (แต่ ID กระบวนการ == เซสชัน ID)
  • ทางแยกที่สองทำให้แน่ใจว่า ID กระบวนการ! = เซสชัน ID
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.