การแก้ไขไบนารีระหว่างการประมวลผล


10

ฉันมักจะเจอสถานการณ์เมื่อพัฒนาซึ่งฉันกำลังทำงานกับไฟล์ไบนารีพูดa.outในพื้นหลังเพราะมันทำงานได้ยาวมาก ในขณะที่ทำเช่นนั้นฉันทำการเปลี่ยนแปลงรหัส C ซึ่งผลิตa.outและรวบรวมa.outอีกครั้ง จนถึงตอนนี้ฉันไม่ได้มีปัญหาใด ๆ กับเรื่องนี้ กระบวนการที่ทำงานอยู่a.outดำเนินต่อไปตามปกติไม่เคยล้มเหลวและจะเรียกใช้รหัสเดิมที่เริ่มต้น

อย่างไรก็ตามการพูดa.outเป็นไฟล์ขนาดใหญ่อาจเทียบได้กับขนาดของ RAM จะเกิดอะไรขึ้นในกรณีนี้ และบอกว่ามันเชื่อมโยงกับไฟล์วัตถุที่ใช้ร่วมกันจะเกิดlibblas.soอะไรขึ้นถ้าฉันแก้ไขlibblas.soในช่วงรันไทม์? อะไรจะเกิดขึ้น?

คำถามหลักของฉันคือ - ไม่รับประกัน OS ที่ว่าเมื่อผมทำงานa.outแล้วรหัสเดิมมักจะทำงานได้ตามปกติตามเดิมไบนารีโดยไม่คำนึงถึงขนาดของไบนารีหรือ.soไฟล์มันเชื่อมโยงไปแม้ในขณะที่เหล่านั้น.oและ.soไฟล์ modfied ระหว่าง Runtime?

ฉันรู้ว่ามีคำถามเหล่านี้ที่จัดการปัญหาที่คล้ายกัน: /programming/8506865/when-a-binary-file-runs-does-itoes-copy-its-entire-binary-data-into-memory -At ครั้งเดียว จะเกิดอะไรขึ้นถ้าคุณแก้ไขสคริปต์ในระหว่างการดำเนินการ? เป็นไปได้อย่างไรที่จะทำการอัพเดทสดขณะที่โปรแกรมกำลังทำงานอยู่?

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


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

@JohnWHSmith บน Stackoverflow คำตอบยอดนิยมบอกว่าif they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their sourceดังนั้นฉันได้รับความประทับใจว่าถ้าไบนารีของคุณมีขนาดใหญ่มากแล้วถ้าส่วนหนึ่งของไบนารีของคุณออกจาก RAM แต่จำเป็นต้องใช้อีกครั้งก็คือ "โหลดจากแหล่งที่มา" - ดังนั้นการเปลี่ยนแปลงใด ๆ.(s)oไฟล์จะสะท้อนให้เห็นในระหว่างการดำเนิน แต่แน่นอนฉันอาจเข้าใจผิด - ซึ่งเป็นเหตุผลที่ฉันถามคำถามที่เฉพาะเจาะจงมากขึ้นนี้
texasflood

@JohnWHSmith คำตอบที่สองก็บอกว่าNo, it only loads the necessary pages into memory. This is demand paging.ดังนั้นฉันก็เลยรู้สึกว่าสิ่งที่ฉันขอไม่สามารถรับประกันได้
texasflood

คำตอบ:


11

ในขณะที่คำถาม Stack Overflow ดูเหมือนจะเพียงพอในตอนแรกฉันเข้าใจจากความคิดเห็นของคุณว่าทำไมคุณยังคงมีข้อสงสัยเกี่ยวกับเรื่องนี้ สำหรับฉันนี่เป็นสถานการณ์ที่สำคัญอย่างยิ่งที่เกี่ยวข้องเมื่อระบบย่อย UNIX (กระบวนการและไฟล์) สื่อสารกัน

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

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

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

หากคุณดูที่การแทนที่ส่วนที่ปฏิบัติการได้ในคำตอบของ Gilles คุณจะเห็นว่าขึ้นอยู่กับวิธีที่คุณแก้ไข / ลบไฟล์เคอร์เนลจะตอบสนอง / ปรับตัวแตกต่างกันตลอดเวลาผ่านกลไกที่ใช้ในระบบย่อยของไฟล์

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

กลยุทธ์ที่ 2 และ 3 มีความปลอดภัยสำหรับไฟล์ที่เรียกใช้งานเช่นกัน: แม้ว่าการเรียกใช้ไฟล์ปฏิบัติการ (และไลบรารีที่โหลดแบบไดนามิก) จะไม่เปิดไฟล์ในกรณีที่มีตัวให้คำอธิบายไฟล์ แต่จะทำงานในลักษณะเดียวกัน ตราบใดที่บางโปรแกรมกำลังรันโค้ดไฟล์จะยังคงอยู่บนดิสก์แม้ไม่มีรายการไดเรกทอรี

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

ด้วยกลยุทธ์ 3 ขั้นตอนในการย้ายไฟล์ใหม่ไปยังชื่อที่มีอยู่จะลบรายการไดเรกทอรีที่นำไปสู่เนื้อหาเก่าและสร้างรายการไดเรกทอรีที่นำไปสู่เนื้อหาใหม่ สิ่งนี้ทำในการปฏิบัติการแบบอะตอมเดียวดังนั้นกลยุทธ์นี้มีข้อได้เปรียบที่สำคัญ: หากกระบวนการเปิดไฟล์ได้ตลอดเวลาก็จะเห็นเนื้อหาเก่าหรือเนื้อหาใหม่ - ไม่มีความเสี่ยงในการผสมเนื้อหาหรือไฟล์ที่ไม่ได้ ที่มีอยู่.

การgccคอมไพล์ไฟล์อีกครั้ง : เมื่อใช้(และพฤติกรรมอาจคล้ายกันสำหรับคอมไพเลอร์อื่น ๆ ) คุณกำลังใช้กลยุทธ์ 2 คุณจะเห็นว่าโดยการเรียกใช้straceกระบวนการของคอมไพเลอร์ของคุณ:

stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
  • คอมไพเลอร์ตรวจพบว่าไฟล์มีอยู่แล้วผ่านการเรียกstatและlstatระบบ
  • ไฟล์จะถูกยกเลิกการเชื่อมโยง ที่นี่แม้ว่าจะไม่สามารถเข้าถึงได้ผ่านชื่ออีกต่อไปa.outไอโหนดและเนื้อหาจะยังคงอยู่บนดิสก์ตราบใดที่มีการใช้งานโดยกระบวนการที่รันอยู่แล้ว
  • a.outไฟล์ใหม่จะถูกสร้างและทำให้ปฏิบัติการภายใต้ชื่อ นี่เป็นไอโหนดใหม่ล่าสุดและเนื้อหาใหม่ล่าสุดซึ่งกระบวนการที่รันอยู่แล้วไม่สนใจ

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


ยอดเยี่ยมคำตอบรายละเอียด นั่นอธิบายความสับสนของฉัน ดังนั้นฉันถูกต้องสมมติว่าเนื่องจาก inode ยังคงมีอยู่ข้อมูลจากไฟล์ไบนารีดั้งเดิมยังคงอยู่บนดิสก์ดังนั้นการใช้dfจำนวนไบต์ฟรีบนดิสก์จึงไม่ถูกต้องเนื่องจากไม่ใช้ inodes มีการลบลิงก์ระบบไฟล์ทั้งหมดที่นำมาพิจารณาหรือไม่ ดังนั้นผมจึงควรใช้df -i? (นี่เป็นเพียงความอยากรู้ทางเทคนิคฉันไม่จำเป็นต้องรู้การใช้งานดิสก์แน่นอน!)
texasflood

1
เพื่อชี้แจงสำหรับผู้อ่านในอนาคต - ความสับสนของฉันคือฉันคิดว่าในการดำเนินการไบนารีทั้งหมดจะถูกโหลดลงใน RAM ดังนั้นถ้า RAM มีขนาดเล็กส่วนหนึ่งของไบนารีจะออกจาก RAM และต้องโหลดใหม่จากดิสก์ - ซึ่งจะ ทำให้เกิดปัญหาหากคุณเปลี่ยนไฟล์ แต่คำตอบได้ทำให้ชัดเจนว่าไบนารีไม่เคยถูกลบออกจากดิสก์แม้ว่าคุณrmหรือmvเป็น inode ไปยังไฟล์ต้นฉบับจะไม่ถูกลบจนกว่ากระบวนการทั้งหมดจะลบลิงก์ของพวกเขาไปยัง inode นั้น
texasflood

@texasflood แน่นอน เมื่อลบเส้นทางทั้งหมดแล้วจะไม่มีกระบวนการใหม่ ( dfรวมอยู่) สามารถรับข้อมูลเกี่ยวกับไอโหนด ข้อมูลใหม่ใดก็ตามที่คุณพบจะเกี่ยวข้องกับไฟล์ใหม่และไอโหนดใหม่ ประเด็นหลักที่นี่คือระบบย่อยของกระบวนการไม่สนใจปัญหานี้ดังนั้นแนวคิดของการจัดการหน่วยความจำ (ความต้องการการเพจ, การแลกเปลี่ยนกระบวนการ, ความผิดพลาดของหน้า, ... ) นั้นไม่เกี่ยวข้องอย่างสมบูรณ์ นี่เป็นปัญหาระบบย่อยไฟล์และได้รับการดูแลโดยระบบย่อยไฟล์ ระบบย่อยของกระบวนการไม่รบกวนสิ่งนั้นไม่ใช่สิ่งที่อยู่ในนั้น
John WH Smith

@texasflood หมายเหตุเกี่ยวกับdf -i: เครื่องมือนี้อาจดึงข้อมูลจาก superblock fs หรือแคชของหมายความว่ามันอาจรวม inode ของไบนารีเก่า (ซึ่งลิงก์ทั้งหมดถูกลบ) นี่ไม่ได้หมายความว่ากระบวนการใหม่นั้นมีอิสระที่จะใช้ข้อมูลเก่านั้นได้
John WH Smith

2

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

แน่นอนนี้จะต้องได้รับการยืนยัน


2

กรณีนี้ไม่ได้เป็นทุกครั้งเมื่อแทนที่ไฟล์. jar ทรัพยากร Jar และตัวสะท้อนคลาส runtime runtime บางตัวจะไม่อ่านจากดิสก์จนกว่าโปรแกรมจะร้องขอข้อมูลอย่างชัดเจน

นี่เป็นเพียงปัญหาเนื่องจาก jar เป็นเพียงไฟล์เก็บถาวรแทนที่จะเป็นไฟล์ปฏิบัติการเดี่ยวที่ถูกแมปเข้ากับหน่วยความจำ นี่เป็นคำถามที่ไม่หยุดนิ่ง แต่ยังคงเป็นคำถามของคุณและสิ่งที่ฉันยิงด้วยตัวเอง

ดังนั้นสำหรับไฟล์ปฏิบัติการ: ใช่ สำหรับไฟล์ jar: อาจจะ (ขึ้นอยู่กับการใช้งาน)

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