จะเกิดอะไรขึ้นเมื่อโปรแกรมคอมพิวเตอร์ทำงาน


180

ฉันรู้ทฤษฎีทั่วไป แต่ไม่สามารถอธิบายรายละเอียดได้

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

ฉันยังรู้ว่าโปรแกรมคอมพิวเตอร์ใช้หน่วยความจำสองชนิด: stack และ heap ซึ่งเป็นส่วนหนึ่งของหน่วยความจำหลักของคอมพิวเตอร์ สแต็กใช้สำหรับหน่วยความจำที่ไม่ไดนามิกและฮีปสำหรับหน่วยความจำแบบไดนามิก (ตัวอย่างเช่นทุกอย่างที่เกี่ยวข้องกับnewโอเปอเรเตอร์ใน C ++)

สิ่งที่ฉันไม่เข้าใจคือสิ่งที่ทั้งสองเชื่อมต่อกัน สแต็คใช้ในการดำเนินการตามคำแนะนำ ณ จุดใด คำแนะนำจาก RAM ไปจนถึงสแต็คไปยังรีจิสเตอร์?


43
+1 สำหรับถามคำถามพื้นฐาน!
mkelley33

21
อืม ... คุณรู้พวกเขาเขียนหนังสือเกี่ยวกับเรื่องนั้น คุณต้องการศึกษาสถาปัตยกรรม OS นี้ด้วยความช่วยเหลือของ SO จริงๆหรือไม่?
Andrey

1
ฉันเพิ่มแท็กสองสามตัวตามลักษณะที่เกี่ยวข้องกับหน่วยความจำของคำถามและการอ้างอิงถึง C ++ แม้ว่าฉันคิดว่าคำตอบที่ดีอาจมาจากคนที่มีความรู้ใน Java หรือ C #!)
mkelley33

14
โหวตแล้วและสนับสนุน ฉันกลัวที่จะถามอยู่เสมอ ...
Maxpm

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

คำตอบ:


161

มันขึ้นอยู่กับระบบจริง ๆ แต่ระบบปฏิบัติการที่ทันสมัยพร้อมหน่วยความจำเสมือนมักจะโหลดอิมเมจกระบวนการและจัดสรรหน่วยความจำแบบนี้:

+---------+
|  stack  |  function-local variables, return addresses, return values, etc.
|         |  often grows downward, commonly accessed via "push" and "pop" (but can be
|         |  accessed randomly, as well; disassemble a program to see)
+---------+
| shared  |  mapped shared libraries (C libraries, math libs, etc.)
|  libs   |
+---------+
|  hole   |  unused memory allocated between the heap and stack "chunks", spans the
|         |  difference between your max and min memory, minus the other totals
+---------+
|  heap   |  dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
|   bss   |  Uninitialized global variables; must be in read-write memory area
+---------+
|  data   |  data segment, for globals and static variables that are initialized
|         |  (can further be split up into read-only and read-write areas, with
|         |  read-only areas being stored elsewhere in ROM on some systems)
+---------+
|  text   |  program code, this is the actual executable code that is running.
+---------+

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

โปรดทราบว่าตำแหน่งของเช่นกองซ้อนและกองอาจอยู่ในลำดับที่แตกต่างกันในบางระบบ (ดูคำตอบของ Billy O'Nealด้านล่างสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ Win32)

ระบบอื่นอาจแตกต่างกันมาก ตัวอย่างเช่น DOS ทำงานในโหมดจริงและการจัดสรรหน่วยความจำเมื่อเรียกใช้โปรแกรมดูแตกต่างกันมาก:

+-----------+ top of memory
| extended  | above the high memory area, and up to your total memory; needed drivers to
|           | be able to access it.
+-----------+ 0x110000
|  high     | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
|  upper    | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
|           | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+ 
|    DOS    | DOS permanent area, kept as small as possible, provided routines for display,
|  kernel   | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained 
|  vector   | the addresses of routines called when interrupts occurred.  e.g.
|  table    | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that 
|           | location to service the interrupt.
+-----------+ 0x0

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

ในพื้นที่ที่อยู่ของกระบวนการอย่างไรก็ตามโปรแกรมมีแนวโน้มที่จะคล้ายกันเฉพาะพวกเขาเท่านั้นที่ถูกอธิบายว่าเป็นส่วนรหัส, ส่วนข้อมูล, กอง, ส่วนกอง, ฯลฯ และมันถูกแมปแตกต่างกันเล็กน้อย แต่พื้นที่ทั่วไปส่วนใหญ่ยังคงอยู่ที่นั่น

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

ระบบที่แตกต่างกัน (แบบฝังอะไรก็ตาม) อาจมีสถาปัตยกรรมที่แตกต่างกันมากเช่นระบบแบบไม่มีระบบระบบสถาปัตยกรรมฮาร์วาร์ด (โดยมีรหัสและข้อมูลถูกเก็บไว้ในหน่วยความจำกายภาพแยกต่างหาก) ระบบที่เก็บ BSS ไว้ในหน่วยความจำแบบอ่านอย่างเดียว โปรแกรมเมอร์) ฯลฯ แต่นี่คือส่วนสำคัญทั่วไป


คุณพูดว่า:

ฉันยังรู้ว่าโปรแกรมคอมพิวเตอร์ใช้หน่วยความจำสองชนิด: stack และ heap ซึ่งเป็นส่วนหนึ่งของหน่วยความจำหลักของคอมพิวเตอร์

"Stack" และ "heap" เป็นเพียงแนวคิดที่เป็นนามธรรมมากกว่า "จำ" ที่แตกต่างทางร่างกายของหน่วยความจำ

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

"กอง"เป็นเพียงชื่อเล่นก้อนของหน่วยความจำที่สามารถปันส่วนให้กับความต้องการและเป็น addressed สุ่ม (หมายถึงคุณสามารถเข้าถึงสถานที่ใด ๆ ในนั้นโดยตรง) โดยทั่วไปจะใช้สำหรับโครงสร้างข้อมูลที่คุณจัดสรร ณ รันไทม์ (ใน C ++, การใช้newและdelete, และmallocและเพื่อน ๆ ใน C, ฯลฯ )

สแต็กและฮีปบนสถาปัตยกรรม x86 ทั้งทางกายภาพอยู่ในหน่วยความจำระบบ (RAM) ของคุณและถูกแม็พผ่านการจัดสรรหน่วยความจำเสมือนลงในพื้นที่แอดเดรสกระบวนการตามที่อธิบายไว้ข้างต้น

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

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


คำถามของคุณ:

สแต็คใช้ในการดำเนินการตามคำแนะนำ ณ จุดใด คำแนะนำจาก RAM ไปจนถึงสแต็คไปยังรีจิสเตอร์?

สแต็ก (ในระบบ / ภาษาที่มีและใช้งาน) มักใช้ในลักษณะนี้:

int mul( int x, int y ) {
    return x * y;       // this stores the result of MULtiplying the two variables 
                        // from the stack into the return value address previously 
                        // allocated, then issues a RET, which resets the stack frame
                        // based on the arg list, and returns to the address set by
                        // the CALLer.
}

int main() {
    int x = 2, y = 3;   // these variables are stored on the stack
    mul( x, y );        // this pushes y onto the stack, then x, then a return address,
                        // allocates space on the stack for a return value, 
                        // then issues an assembly CALL instruction.
}

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

f( g( h( i ) ) ); 

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

FYI ข้างต้นคือการเรียกการประชุม C ใช้โดย C ++ ภาษา / ระบบอื่น ๆ อาจส่งอาร์กิวเมนต์ลงบนสแต็กในลำดับที่แตกต่างกันและบางภาษา / แพลตฟอร์มไม่ได้ใช้สแต็คและไปในรูปแบบที่แตกต่างกัน

โปรดทราบว่ารหัสเหล่านี้ไม่ใช่บรรทัดที่ใช้งานจริงของรหัส C คอมไพเลอร์ได้แปลงให้เป็นคำสั่งภาษาเครื่องในการปฏิบัติการ จากนั้นจะถูกคัดลอกจากพื้นที่ TEXT ไปยังไปป์ไลน์ CPU จากนั้นลงใน CPU register และดำเนินการจากที่นั่น [สิ่งนี้ไม่ถูกต้อง ดูการแก้ไขของ Ben Voigtด้านล่าง]


4
ขออภัยคำแนะนำหนังสือที่ดีอาจเป็นคำตอบที่ดีกว่า IMO
Andrey

13
ใช่ "RTFM" ดีกว่าเสมอ
Sdaz MacSkibbons

56
@Andrey: บางทีคุณควรเปลี่ยนความคิดเห็นนั้นเป็น "คุณอาจต้องการอ่านคำแนะนำเกี่ยวกับหนังสือของคุณ " ฉันเข้าใจว่าคำถามประเภทนี้มีประโยชน์ในการตรวจสอบมากขึ้น แต่เมื่อใดก็ตามที่คุณต้องเริ่มแสดงความคิดเห็นด้วย "ขอโทษ แต่ .. "บางทีคุณควรพิจารณาตั้งค่าสถานะโพสต์เพื่อให้ผู้ดูแลสนใจหรืออย่างน้อยก็ให้คำอธิบายว่าทำไมความคิดเห็นของคุณควรมีความสำคัญกับใคร
mkelley33

2
คำตอบที่ยอดเยี่ยม แน่นอนมันล้างบางสิ่งสำหรับฉัน!
Maxpm

2
@Mikael: คุณอาจมีแคชที่จำเป็นซึ่งในกรณีใดก็ตามที่มีการอ่านข้อมูลจากหน่วยความจำแคชทั้งบรรทัดจะถูกอ่านและแคชจะถูกบรรจุ หรืออาจเป็นไปได้ที่จะให้คำแนะนำแก่ผู้จัดการแคชว่าจะต้องใช้ข้อมูลเพียงครั้งเดียวดังนั้นการคัดลอกลงในแคชจึงไม่มีประโยชน์ นั่นสำหรับอ่าน สำหรับการเขียนมีการเขียนกลับและแคชการเขียนผ่านซึ่งส่งผลต่อเมื่อตัวควบคุม DMA สามารถอ่านข้อมูลได้และจากนั้นมีโฮสต์ทั้งหมดของโปรโตคอลการเชื่อมโยงกันของแคชสำหรับจัดการกับโปรเซสเซอร์หลายตัวแต่ละตัวมีแคชของตัวเอง สิ่งนี้สมควรได้รับ Q.
Ben Voigt

61

Sdaz มีจำนวน upvotes ที่น่าทึ่งในช่วงเวลาสั้น ๆ แต่น่าเศร้าที่ทำให้เกิดความเข้าใจผิดเกี่ยวกับวิธีการที่คำสั่งต่าง ๆ เคลื่อนผ่านซีพียู

คำถามที่ถาม:

คำแนะนำจาก RAM ไปจนถึงสแต็คไปยังรีจิสเตอร์?

Sdaz กล่าวว่า:

โปรดทราบว่ารหัสเหล่านี้ไม่ใช่บรรทัดที่ใช้งานจริงของรหัส C คอมไพเลอร์ได้แปลงให้เป็นคำสั่งภาษาเครื่องในการปฏิบัติการของคุณ จากนั้นจะถูกคัดลอกจากพื้นที่ TEXT ไปยังไปป์ไลน์ CPU จากนั้นลงใน CPU register และดำเนินการจากที่นั่น

แต่นี่เป็นสิ่งที่ผิด ยกเว้นกรณีพิเศษของรหัสแก้ไขตัวเองคำแนะนำไม่เคยใส่ดาต้าพา ธ และไม่สามารถดำเนินการจากดาต้าพา ธ ไม่ได้

การลงทะเบียน CPU x86คือ:

  • การลงทะเบียนทั่วไป EAX EBX ECX EDX

  • แบ่งกลุ่มลงทะเบียน CS DS ES FS GS SS

  • ดัชนีและพอยน์เตอร์ ESI EDI EBP EIP ESP

  • ตัวบ่งชี้ EFLAGS

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

ไม่มีการลงทะเบียนเหล่านี้ใช้สำหรับรหัสที่ปฏิบัติการได้ EIPมีที่อยู่ของคำสั่งดำเนินการไม่ใช่คำสั่งนั้น

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

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

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

คำศัพท์จะสับสนเล็กน้อยเนื่องจากregisterเป็นคำศัพท์ทางวิศวกรรมไฟฟ้าสำหรับกลุ่มของ D-flipflops และคำแนะนำ (หรือโดยเฉพาะอย่างยิ่ง microinstructions) อาจถูกเก็บไว้ชั่วคราวในชุดของ D-flipflops แต่ตอนนี้ไม่ได้เป็นสิ่งที่มีความหมายเมื่อนักวิทยาศาสตร์คอมพิวเตอร์หรือวิศวกรซอฟต์แวร์หรือทำงานของโรงสีนักพัฒนาใช้ระยะทะเบียน มันหมายถึงการลงทะเบียน datapath ตามที่ระบุไว้ข้างต้นและสิ่งเหล่านี้ไม่ได้ใช้สำหรับการขนส่งรหัส

ชื่อและจำนวนของดาต้าพา ธ ที่ลงทะเบียนแตกต่างกันไปสำหรับสถาปัตยกรรมซีพียูอื่น ๆ เช่น ARM, MIPS, Alpha, PowerPC แต่ทั้งหมดนั้นดำเนินการคำสั่งโดยไม่ต้องผ่าน ALU


ขอขอบคุณสำหรับการชี้แจง. ฉันลังเลที่จะเพิ่มสิ่งนั้นเนื่องจากฉันไม่คุ้นเคย แต่ก็ทำตามคำขอของคนอื่น
Sdaz MacSkibbons

s / ARM / RAM / in "หมายถึงข้อมูลและรหัสจะถูกรวมเข้าด้วยกันใน ARM" ขวา?
Bjarke Freund-Hansen

@bjarkef: ครั้งแรกที่ใช่ แต่ไม่ใช่ครั้งที่สอง ฉันจะแก้ไข
Ben Voigt

17

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

#include <stdlib.h>
#include <stdio.h>

int main()
{
    int stackValue = 0;
    int *addressOnStack = &stackValue;
    int *addressOnHeap = malloc(sizeof(int));
    if (addressOnStack > addressOnHeap)
    {
        puts("The stack is above the heap.");
    }
    else
    {
        puts("The heap is above the stack.");
    }
}

ใน Windows NT (และเป็นลูก ๆ ) โปรแกรมนี้จะผลิตโดยทั่วไป:

ฮีปอยู่เหนือสแต็ก

ในกล่อง POSIX มันจะบอกว่า:

สแต็กอยู่เหนือกอง

รูปแบบหน่วยความจำ UNIX ค่อนข้างอธิบายได้ดีที่นี่โดย @Sdaz MacSkibbons ดังนั้นฉันจะไม่ย้ำอีกครั้งที่นี่ แต่นั่นไม่ใช่รุ่นหน่วยความจำเท่านั้น เหตุผลที่ POSIX ต้องการรุ่นนี้คือการเรียกระบบsbrk โดยทั่วไปบนกล่อง POSIX เพื่อให้ได้หน่วยความจำมากขึ้นกระบวนการเพียงบอกเคอร์เนลให้เลื่อนตัวแบ่งระหว่าง "รู" และ "ฮีป" ต่อไปในพื้นที่ "รู" ไม่มีทางที่จะส่งคืนหน่วยความจำไปยังระบบปฏิบัติการและระบบปฏิบัติการเองไม่จัดการฮีปของคุณ ไลบรารีรันไทม์ C ของคุณต้องระบุ (ผ่าน malloc)

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

รุ่นหน่วยความจำของ Windows นั้นแตกต่างกันเนื่องจากรหัสที่ใช้แตกต่างกัน Windows ใช้รูปแบบไฟล์ PE ซึ่งจะปล่อยให้รหัสอยู่ในรูปแบบขึ้นอยู่กับตำแหน่ง นั่นคือรหัสขึ้นอยู่กับที่ในหน่วยความจำเสมือนที่โหลดรหัส มีการตั้งค่าสถานะในข้อมูลจำเพาะ PE ซึ่งบอกกับระบบปฏิบัติการว่าในหน่วยความจำไลบรารีหรือไฟล์เรียกทำงานที่ต้องการถูกแมปเมื่อโปรแกรมของคุณรัน หากไม่สามารถโหลดโปรแกรมหรือไลบรารีตามที่อยู่ที่ต้องการได้ตัวโหลด Windows จะต้องทำการรีบูทไลบรารี่ / executable - โดยพื้นฐานแล้วมันจะย้ายโค้ดขึ้นอยู่กับตำแหน่งเพื่อชี้ไปที่ตำแหน่งใหม่ - ซึ่งไม่ต้องการตารางการค้นหาและไม่สามารถใช้ประโยชน์ได้เนื่องจากไม่มีตารางการค้นหาเพื่อเขียนทับ น่าเสียดายที่ต้องมีการใช้งานที่ซับซ้อนมากในตัวโหลดของ Windows และมีเวลาเริ่มต้นที่สูงหากต้องทำการรีบูตอิมเมจ แพคเกจซอฟต์แวร์เชิงพาณิชย์ขนาดใหญ่มักจะปรับเปลี่ยนไลบรารีของตนเองเพื่อเริ่มต้นตามที่อยู่ที่แตกต่างกันเพื่อหลีกเลี่ยงการรีบูต windows เองทำเช่นนี้กับไลบรารีของตัวเอง (เช่น ntdll.dll, kernel32.dll, psapi.dll ฯลฯ - ทั้งหมดมีที่อยู่เริ่มต้นที่แตกต่างกันตามค่าเริ่มต้น)

บน Windows หน่วยความจำเสมือนได้มาจากระบบผ่านการเรียกไปยังVirtualAllocและจะถูกส่งคืนไปยังระบบผ่านทางVirtualFree (โอเคเทคนิค VirtualAlloc ฟาร์มออกไปที่ NtAllocateVirtualMemory แต่นั่นเป็นรายละเอียดการนำไปใช้) (ตรงกันข้ามกับ POSIX ซึ่งหน่วยความจำไม่สามารถ ถูกเรียกคืน) กระบวนการนี้ช้า (และ IIRC ต้องการให้คุณจัดสรรเป็นชิ้นขนาดหน้าจริงโดยทั่วไปคือ 4kb หรือมากกว่า) Windows ยังมีฟังก์ชั่น heap ของตัวเอง (HeapAlloc, HeapFree และอื่น ๆ ) ซึ่งเป็นส่วนหนึ่งของห้องสมุดที่รู้จักกันในชื่อ RtlHeap ซึ่งรวมอยู่ในส่วนของ Windows เองซึ่งmallocโดยปกติแล้วC runtime (นั่นคือและเพื่อน) จะถูกนำมาใช้

Windows ยังมี API การจัดสรรหน่วยความจำดั้งเดิมค่อนข้างน้อยนับจากวันที่ต้องจัดการกับรุ่นเก่า 80386 และตอนนี้ฟังก์ชั่นเหล่านี้ถูกสร้างขึ้นบน RtlHeap สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ APIs ต่างๆที่ควบคุมจัดการหน่วยความจำใน Windows เห็นนี้บทความ MSDN: http://msdn.microsoft.com/en-us/library/ms810627

โปรดทราบด้วยว่านี่หมายถึงกระบวนการ Windows หนึ่งกระบวนการ (และโดยปกติ) มีมากกว่าหนึ่งฮีป (โดยทั่วไปแต่ละไลบรารีที่แชร์จะสร้างฮีปของตัวเองขึ้นมา)

(ข้อมูลส่วนใหญ่มาจาก "การเข้ารหัสที่ปลอดภัยใน C และ C ++" โดย Robert Seacord)


ข้อมูลดีมากขอบคุณ! หวังว่า "user487117" ในที่สุดก็กลับมาจริง ๆ :-)
Sdaz MacSkibbons

5

สแต็ค

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

ตัวอย่างเช่นการคูณจำนวนเต็ม:

MUL BX

เพิ่มจำนวนการลงทะเบียน AX ด้วยการลงทะเบียน BX (ผลลัพธ์จะเป็น DX และ AX, DX ที่มีบิตสูงกว่า)

เครื่องที่ใช้กองซ้อน (เช่น JAVA VM) ใช้กองซ้อนสำหรับการดำเนินงานขั้นพื้นฐาน การคูณข้างต้น:

DMUL

สิ่งนี้จะปรากฏค่าสองค่าจากด้านบนของสแต็กและทวีคูณ tem จากนั้นส่งผลลัพธ์กลับไปที่สแต็ก Stack เป็นสิ่งจำเป็นสำหรับเครื่องจักรชนิดนี้

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

กอง

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

การเข้าถึงทรัพยากรระบบ

ระบบปฏิบัติการมีส่วนต่อประสานสาธารณะว่าคุณสามารถเข้าถึงฟังก์ชั่นได้อย่างไร ในพารามิเตอร์ DOS ถูกส่งผ่านในการลงทะเบียนของ CPU Windows ใช้สแต็กสำหรับส่งพารามิเตอร์สำหรับฟังก์ชั่นระบบปฏิบัติการ (Windows API)

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