มันขึ้นอยู่กับระบบจริง ๆ แต่ระบบปฏิบัติการที่ทันสมัยพร้อมหน่วยความจำเสมือนมักจะโหลดอิมเมจกระบวนการและจัดสรรหน่วยความจำแบบนี้:
+---------+
| 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ด้านล่าง]