การกลับเข้ามาใหม่ (คอมพิวเตอร์)

จากวิกิพีเดีย สารานุกรมเสรี
ข้ามไปที่การนำทาง ข้ามไปที่การค้นหา

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

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

คำจำกัดความของการกลับเข้าใหม่นี้แตกต่างจากความปลอดภัย ของ เธรดในสภาพแวดล้อมแบบมัลติเธรด รูทีนย่อย reentrant สามารถบรรลุความปลอดภัยของเธรด[1]แต่การรีเอนแรนต์เพียงอย่างเดียวอาจไม่เพียงพอต่อความปลอดภัยของเธรดในทุกสถานการณ์ ในทางกลับกัน โค้ดที่ปลอดภัยต่อเธรดไม่จำเป็นต้องมีการป้อนกลับเข้าไปใหม่ (ดูตัวอย่างด้านล่าง)

ข้อกำหนดอื่น ๆ ที่ใช้สำหรับโปรแกรม reentrant ได้แก่ "รหัสที่แชร์ได้" [2]รูทีนย่อย Reentrant บางครั้งถูกทำเครื่องหมายในวัสดุอ้างอิงว่า "สัญญาณปลอดภัย" [3]โปรแกรม Reentrant มักเป็น [a] "ขั้นตอนบริสุทธิ์"

ความเป็นมา

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

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

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

การกลับเข้ามาใหม่นั้นแตกต่างจาก แต่เกี่ยวข้องอย่างใกล้ชิดกับ ความปลอดภัย ของเธรด ฟังก์ชันสามารถป้องกันเธรดและยังไม่สามารถย้อนกลับได้ ตัวอย่างเช่น ฟังก์ชันสามารถห่อหุ้มฟังก์ชันด้วยmutex ได้ (ซึ่งหลีกเลี่ยงปัญหาในสภาพแวดล้อมแบบมัลติเธรด) แต่ถ้าฟังก์ชันนั้นถูกใช้ในรูทีนบริการขัดจังหวะ ฟังก์ชันนั้นอาจรอการเรียกใช้งานครั้งแรกเพื่อปล่อย mutex กุญแจสำคัญในการหลีกเลี่ยงความสับสนคือการที่ reentrant หมายถึงการดำเนินการเธรดเดียว เท่านั้น เป็นแนวคิดตั้งแต่สมัยที่ไม่มีระบบปฏิบัติการแบบมัลติทาสก์

กฎสำหรับการกลับเข้ามาใหม่

รหัส Reentrant ต้องไม่เก็บข้อมูลที่ไม่คงที่หรือทั่วโลกโดยไม่ซิงโครไนซ์
ฟังก์ชัน Reentrant สามารถทำงานกับข้อมูลส่วนกลางได้ ตัวอย่างเช่น รูทีนบริการขัดจังหวะการกลับเข้ามาใหม่อาจดึงสถานะของฮาร์ดแวร์มาใช้งานได้ (เช่น บัฟเฟอร์การอ่านพอร์ตอนุกรม) ซึ่งไม่เพียงแต่เป็นสากลเท่านั้น แต่ยังมีความผันผวนอีกด้วย อย่างไรก็ตาม ไม่แนะนำให้ใช้ตัวแปรสแตติกทั่วไปและข้อมูลส่วนกลาง ในแง่ที่ว่า ยกเว้นในส่วนของโค้ดที่ซิงโครไนซ์ ควรใช้ เฉพาะ คำสั่ง อ่าน-ปรับเปลี่ยน-เขียนแบบอะตอม มิก ในตัวแปรเหล่านี้ (ไม่ควรจะเป็นไปได้สำหรับ ขัดจังหวะหรือส่งสัญญาณมาในระหว่างการดำเนินการตามคำสั่งดังกล่าว) โปรดทราบว่าในภาษา C แม้แต่การอ่านหรือเขียนก็ไม่รับประกันว่าจะเป็นปรมาณู มันอาจจะแบ่งออกเป็นหลายอ่านหรือเขียน [4]มาตรฐาน C และ SUSv3 มีให้sig_atomic_tเพื่อจุดประสงค์นี้ แม้ว่าจะมีการรับประกันสำหรับการอ่านและเขียนอย่างง่ายเท่านั้น ไม่ใช่การเพิ่มหรือลด [5]ปฏิบัติการปรมาณูที่ซับซ้อนมากขึ้นมีอยู่ในC11ซึ่งให้stdatomic.h.
รหัส Reentrant ไม่สามารถแก้ไขตัวเองได้หากไม่มีการซิงโครไนซ์
ระบบปฏิบัติการอาจอนุญาตให้กระบวนการแก้ไขโค้ดได้ มีเหตุผลหลายประการสำหรับสิ่งนี้ (เช่นทำลายกราฟิกอย่างรวดเร็ว) แต่โดยทั่วไปจำเป็นต้องมีการซิงโครไนซ์เพื่อหลีกเลี่ยงปัญหากับการกลับเข้ามาใหม่

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

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

การกลับเข้ามาใหม่ของรูทีนย่อยที่ทำงานบนทรัพยากรของระบบปฏิบัติการหรือข้อมูลที่ไม่ใช่ในเครื่องขึ้นอยู่กับอะตอมมิกของการดำเนินการที่เกี่ยวข้อง ตัวอย่างเช่น หากรูทีนย่อยแก้ไขตัวแปรโกลบอล 64 บิตบนเครื่อง 32 บิต การดำเนินการอาจถูกแบ่งออกเป็นสองการดำเนินการ 32 บิต ดังนั้นหากรูทีนย่อยถูกขัดจังหวะขณะดำเนินการ และเรียกอีกครั้งจากตัวจัดการการขัดจังหวะ ตัวแปรโกลบอลอาจอยู่ในสถานะที่มีการอัปเดตเพียง 32 บิตเท่านั้น ภาษาโปรแกรมอาจให้การรับประกัน atomicity สำหรับการหยุดชะงักที่เกิดจากการกระทำภายในเช่นการกระโดดหรือการเรียก จากนั้นฟังก์ชันfในนิพจน์เช่น(global:=1) + (f())โดยที่ลำดับการประเมินของนิพจน์ย่อยอาจใช้ภาษาโปรแกรมโดยอำเภอใจ จะเห็นตัวแปรส่วนกลางตั้งค่าเป็น 1 หรือเป็นค่าก่อนหน้า แต่จะไม่อยู่ในสถานะระดับกลางที่มีการอัปเดตเพียงบางส่วนเท่านั้น (อย่างหลังสามารถเกิดขึ้นได้ในCเนื่องจากนิพจน์ไม่มีจุดลำดับ ) ระบบปฏิบัติการอาจให้การรับประกันอะตอมมิกสำหรับสัญญาณเช่น การเรียกของระบบถูกขัดจังหวะโดยสัญญาณที่ไม่มีผลกระทบบางส่วน ฮาร์ดแวร์โปรเซสเซอร์อาจรับประกันอะตอมมิกสำหรับการขัดจังหวะเช่น คำแนะนำของโปรเซสเซอร์ที่ถูกขัดจังหวะซึ่งไม่มีผลกระทบบางส่วน

ตัวอย่าง

เพื่อแสดงให้เห็นถึงการกลับเข้ามาใหม่ บทความนี้ใช้เป็นตัวอย่างฟังก์ชันยูทิลิตี้Cswap()ซึ่งใช้พอยน์เตอร์สองตัวและเปลี่ยนค่าของพวกมัน และรูทีนการจัดการการขัดจังหวะที่เรียกใช้ฟังก์ชันสลับเช่นกัน

ไม่ reentrant หรือ thread-safe

นี่คือตัวอย่างฟังก์ชันการสลับที่ล้มเหลวในการกลับเข้ามาใหม่หรือปลอดภัยต่อเธรด เนื่องจากtmpตัวแปรมีการแบ่งปันกันทั่วโลก โดยไม่มีการซิงโครไนซ์ระหว่างอินสแตนซ์ของฟังก์ชันที่ทำงานพร้อมกัน อินสแตนซ์หนึ่งอาจรบกวนข้อมูลที่อาศัยโดยอีกอินสแตนซ์หนึ่ง ดังนั้น จึงไม่ควรใช้ในรูทีนบริการอินเตอร์รัปต์isr():

int tmp ; 

การ สลับเป็นโมฆะ( int * x , int * y )    
{
    tmp = * x ;  
    * x = * y ;  
    /* ฮาร์ดแวร์ขัดจังหวะอาจเรียกใช้ isr() ที่นี่ */
    * y = tmp ; }      


เป็นโมฆะisr () 
{
    int x = 1 , y = 2 ;      
    สลับ( & x , & y ); 
}

เธรดปลอดภัยแต่ไม่ reentrant

ฟังก์ชันswap()ในตัวอย่างก่อนหน้านี้สามารถทำให้เธรดปลอดภัยโดยการสร้างthread tmp -local ยังคงล้มเหลวในการกลับเข้ามาใหม่ และสิ่งนี้จะทำให้เกิดปัญหาต่อไปหากisr()มีการเรียกในบริบทเดียวกันกับเธรดที่ดำเนินการแล้วswap():

_Thread_local int tmp ;  

การ สลับเป็นโมฆะ( int * x , int * y )    
{
    tmp = * x ;  
    * x = * y ;  
    /* ฮาร์ดแวร์ขัดจังหวะอาจเรียกใช้ isr() ที่นี่ */
    * y = tmp ; }      


เป็นโมฆะisr () 
{
    int x = 1 , y = 2 ;      
    สลับ( & x , & y ); 
}

Reentrant แต่ไม่ปลอดภัยสำหรับเธรด

การแก้ไข (ค่อนข้างวางแผน) ต่อไปนี้ของฟังก์ชัน swap ซึ่งระมัดระวังในการปล่อยให้ข้อมูลส่วนกลางอยู่ในสถานะที่สอดคล้องกัน[ โต้แย้ง ]ในเวลาที่ออก เป็นการกลับเข้ามาใหม่ อย่างไรก็ตาม มันไม่ปลอดภัยสำหรับเธรด เนื่องจากไม่มีการล็อก จึงสามารถขัดจังหวะได้ตลอดเวลา:

int tmp ; 

การ สลับเป็นโมฆะ( int * x , int * y )    
{
    /* บันทึกตัวแปรส่วนกลาง */
    int s ; 
    s = tmp ;  

    tmp = * x ;  
    * x = * y ;  
    * y = tmp ; /* ฮาร์ดแวร์ขัดจังหวะอาจเรียกใช้ isr() ที่นี่ */       

    /* คืนค่าตัวแปรส่วนกลาง */
    tmp = s ;  
}

เป็นโมฆะisr () 
{
    int x = 1 , y = 2 ;      
    สลับ( & x , & y ); 
}

Reentrant และเธรดปลอดภัย

การใช้งานswap()ที่จัดสรรtmpบนสแต็ ก แทนที่จะเป็นแบบโกลบอลและที่ถูกเรียกด้วยตัวแปรที่ไม่แบ่งใช้เท่านั้นเนื่องจากพารามิเตอร์[b]เป็นทั้งเธรดที่ปลอดภัยและ reentrant เธรดปลอดภัยเนื่องจากสแต็กอยู่ในเธรดและฟังก์ชันที่ดำเนินการกับข้อมูลในเครื่องจะให้ผลลัพธ์ที่คาดหวังเสมอ ไม่มีการเข้าถึงข้อมูลที่ใช้ร่วมกันดังนั้นจึงไม่มีการแข่งขันข้อมูล

การ สลับเป็นโมฆะ( int * x , int * y )    
{
    int tmp ; 
    tmp = * x ;  
    * x = * y ;  
    * y = tmp ; /* ฮาร์ดแวร์ขัดจังหวะอาจเรียกใช้ isr() ที่นี่ */      
}

เป็นโมฆะisr () 
{
    int x = 1 , y = 2 ;      
    สลับ( & x , & y ); 
}

ตัวจัดการขัดจังหวะ Reentrant

ตัวจัดการการขัดจังหวะที่กลับเข้ามาใหม่คือตัวจัดการการขัดจังหวะที่เปิดใช้งานการขัดจังหวะอีกครั้งในช่วงต้นของตัวจัดการการขัดจังหวะ ซึ่งอาจลด เวลาแฝง ของการขัดจังหวะ [6]โดยทั่วไป ในขณะที่ตั้งโปรแกรมรูทีนบริการขัดจังหวะ ขอแนะนำให้เปิดใช้งานการขัดจังหวะอีกครั้งโดยเร็วที่สุดในตัวจัดการการขัดจังหวะ แนวปฏิบัตินี้ช่วยหลีกเลี่ยงการสูญเสียการขัดจังหวะ [7]

ตัวอย่างเพิ่มเติม

ในโค้ดต่อไปนี้ ทั้ง ฟังก์ชัน fและgฟังก์ชันต่างๆ จะไม่ถูกส่งกลับ

int v = 1 ;   

int () 
{
    วี+= 2 ;  
    กลับv ; 
}

int ก. () 
{
    กลับf () + 2 ;   
}

ข้างต้นf()ขึ้นอยู่กับตัวแปรโกลบอลที่ไม่คงที่v; ดังนั้น หากf()ถูกขัดจังหวะระหว่างการดำเนินการโดย ISR ซึ่งแก้ไขvการกลับเข้าไปใหม่f()จะส่งกลับค่าที่ไม่ถูกต้องvของ ค่าของvและ ดังนั้น มูลค่าที่ส่งกลับของfจึงไม่สามารถคาดการณ์ได้อย่างมั่นใจ: ค่าเหล่านี้จะแตกต่างกันไปขึ้นอยู่กับว่าอินเทอร์รัปต์ถูกแก้ไขvระหว่างfการดำเนินการของ หรือไม่ จึงfไม่รีรอ ไม่เป็นgเพราะมันเรียกfซึ่งไม่ได้กลับเข้ามาใหม่

เวอร์ชันที่มีการเปลี่ยนแปลงเล็กน้อยเหล่านี้เป็น reentrant:

int ( int ฉัน)  
{
    กลับi + 2 ;   
}

int g ( int ผม)  
{
    คืนค่าf ( ผม) + 2 ;   
}

ต่อไปนี้ ฟังก์ชันนี้ปลอดภัยต่อเธรด แต่ไม่ใช่ (จำเป็น) ที่กลับเข้ามาใหม่:

ฟังก์ชันint () 
{
    mutex_lock ();

    // ... 
// ตัวฟังก์ชัน// ...    
    

    mutex_unlock ();
}

ในข้างต้นfunction()สามารถเรียกตามเธรดต่าง ๆ ได้โดยไม่มีปัญหาใด ๆ แต่ถ้าฟังก์ชันนี้ถูกใช้ในตัวจัดการการขัดจังหวะแบบย้อนกลับและการขัดจังหวะครั้งที่สองเกิดขึ้นภายในฟังก์ชัน รูทีนที่สองจะหยุดทำงานตลอดไป เนื่องจากบริการอินเตอร์รัปต์สามารถปิดการใช้งานอินเตอร์รัปต์อื่นๆ ทั้งระบบอาจประสบปัญหา

หมายเหตุ

  1. ^ โปรแกรมที่ทำให้การปรับเปลี่ยนตัวเองเป็นอนุกรมอาจเป็นการกลับเข้ามาใหม่ และขั้นตอนบริสุทธิ์ที่อัปเดตข้อมูลทั่วโลกโดยไม่มีการทำให้เป็นอนุกรมถูกต้องอาจล้มเหลวในการกลับเข้ามาใหม่
  2. ^ ถ้า isr() เรียกว่า swap() โดยมีตัวแปรโกลบอลหนึ่งหรือสองตัวเป็นพารามิเตอร์ ดังนั้น swap() จะไม่กลับเข้ามาใหม่

ดูเพิ่มเติม

อ้างอิง

  1. ^ Kerrisk 2010 , หน้า. 657 .
  2. ^ รัลสตัน 2000 , p. 1514–1515.
  3. ^ "pthread_cond_init()--เริ่มต้นตัวแปรเงื่อนไข " ศูนย์ ความรู้ไอบีเอ็ม สืบค้นเมื่อ2019-10-05 .
  4. ↑ Preshing , เจฟฟ์ (2013-06-18). "ปฏิบัติการปรมาณูกับปฏิบัติการที่ไม่ใช่ปรมาณู" . Preshing ในการเขียนโปรแกรม เก็บถาวรจากต้นฉบับเมื่อ 2014-12-03 . สืบค้นเมื่อ2018-04-24 .
  5. ^ Kerrisk 2010 , หน้า. 428 .
  6. ^ Sloss และคณะ 2547 , น. 342 .
  7. ^ เรเกร์, จอห์น (2006). "การใช้การขัดจังหวะอย่างปลอดภัยและมีโครงสร้างในซอฟต์แวร์แบบเรียลไทม์และแบบฝังตัว" (PDF ) คู่มือระบบเรียลไทม์และระบบสมองกลฝังตัว ซีอาร์ซี เพรส . เก็บถาวร(PDF)จากต้นฉบับเมื่อ 2007-08-24 – ผ่านทางเว็บไซต์ของผู้เขียนที่ University of Utah School of Computing

ผลงานที่อ้างถึง

อ่านเพิ่มเติม