Hướng phát triển ngăn xếp trong hầu hết các hệ thống hiện đại là gì?


102

Tôi đang chuẩn bị một số tài liệu đào tạo về C và tôi muốn các ví dụ của mình phù hợp với mô hình ngăn xếp điển hình.

Ngăn xếp C phát triển theo hướng nào trong Linux, Windows, Mac OSX (PPC và x86), Solaris và các Unix gần đây nhất?


Câu trả lời:


147

Sự tăng trưởng của ngăn xếp thường không phụ thuộc vào bản thân hệ điều hành mà phụ thuộc vào bộ xử lý mà nó đang chạy. Ví dụ, Solaris chạy trên x86 và SPARC. Mac OSX (như bạn đã đề cập) chạy trên PPC và x86. Linux chạy trên mọi thứ, từ Hệ thống honkin 'z lớn của tôi tại nơi làm việc cho đến một chiếc đồng hồ đeo tay nhỏ bé .

Nếu CPU cung cấp bất kỳ loại lựa chọn nào, quy ước ABI / gọi được sử dụng bởi Hệ điều hành sẽ chỉ định lựa chọn nào bạn cần thực hiện nếu bạn muốn mã của mình gọi mã của người khác.

Các bộ xử lý và hướng của chúng là:

  • x86: xuống.
  • SPARC: có thể lựa chọn. ABI tiêu chuẩn sử dụng giảm.
  • PPC: Tôi nghĩ là xuống.
  • Hệ thống z: trong một danh sách liên kết, tôi không đùa bạn (nhưng vẫn không hoạt động, ít nhất là đối với zLinux).
  • ARM: có thể lựa chọn, nhưng Thumb2 chỉ có các mã hóa nhỏ gọn để giảm (LDMIA = tăng sau, STMDB = giảm trước).
  • 6502: xuống (nhưng chỉ 256 byte).
  • RCA 1802A: bất kỳ cách nào bạn muốn, tùy thuộc vào việc triển khai SCRT.
  • PDP11: giảm.
  • 8051: lên.

Hiển thị tuổi của tôi trên số cuối cùng đó, 1802 là con chip được sử dụng để điều khiển các tàu con thoi sớm (tôi nghi ngờ nếu cửa đã mở, dựa trên sức mạnh xử lý mà nó có :-) và máy tính thứ hai của tôi, COMX-35 ( sau ZX80 của tôi ).

Chi tiết PDP11 được thu thập từ đây , chi tiết 8051 từ đây .

Kiến trúc SPARC sử dụng mô hình thanh ghi cửa sổ trượt. Các chi tiết có thể nhìn thấy về mặt kiến ​​trúc cũng bao gồm một bộ đệm hình tròn gồm các cửa sổ đăng ký hợp lệ và được lưu vào bộ nhớ cache bên trong, với các bẫy khi dòng chảy quá mức / thiếu. Xem chi tiết tại đây . Như hướng dẫn sử dụng SPARCv8 giải thích , hướng dẫn LƯU và KHÔI PHỤC giống như hướng dẫn THÊM cộng với xoay cửa sổ thanh ghi. Sử dụng hằng số dương thay vì hằng số âm thông thường sẽ tạo ra một ngăn xếp tăng dần lên.

Kỹ thuật SCRT đã đề cập ở trên là một kỹ thuật khác - năm 1802 đã sử dụng một số hoặc đó là mười sáu thanh ghi 16 bit cho SCRT (kỹ thuật gọi và trả về tiêu chuẩn). Một là bộ đếm chương trình, bạn có thể sử dụng bất kỳ thanh ghi nào làm PC với SEP Rnhướng dẫn. Một là con trỏ ngăn xếp và hai là con trỏ luôn được đặt để trỏ đến địa chỉ mã SCRT, một để gọi, một để trả về. Không có sổ đăng ký nào được đối xử theo cách đặc biệt. Hãy nhớ rằng những chi tiết này là từ bộ nhớ, chúng có thể không hoàn toàn chính xác.

Ví dụ: nếu R3 là PC, R4 là địa chỉ cuộc gọi SCRT, R5 là địa chỉ trả về SCRT và R2 là "ngăn xếp" (dấu ngoặc kép khi nó được triển khai trong phần mềm), SEP R4sẽ đặt R4 là PC và bắt đầu chạy SCRT mã cuộc gọi.

Sau đó, nó sẽ lưu trữ R3 trên "ngăn xếp" R2 (tôi nghĩ R6 đã được sử dụng để lưu trữ tạm thời), điều chỉnh nó lên hoặc xuống, lấy hai byte theo sau R3, tải chúng vào R3, sau đó thực hiện SEP R3và chạy ở địa chỉ mới.

Để quay lại, nó sẽ SEP R5kéo địa chỉ cũ ra khỏi ngăn xếp R2, thêm hai vào đó (để bỏ qua byte địa chỉ của cuộc gọi), tải nó vào R3 và SEP R3bắt đầu chạy mã trước đó.

Rất khó để quấn lấy đầu bạn lúc đầu sau tất cả mã dựa trên ngăn xếp 6502/6809 / z80 nhưng vẫn trang nhã theo cách đập đầu vào tường. Ngoài ra, một trong những tính năng bán chạy nhất của chip là một bộ đầy đủ gồm 16 thanh ghi 16-bit, mặc dù thực tế là bạn đã ngay lập tức mất 7 trong số đó (5 cho SCRT, hai cho DMA và ngắt từ bộ nhớ). Ahh, thành công của tiếp thị so với thực tế :-)

Hệ thống z thực sự khá giống nhau, sử dụng các thanh ghi R14 và R15 của nó để gọi / trả.


3
Để thêm vào danh sách, ARM có thể phát triển theo một trong hai hướng, nhưng có thể được đặt thành này hoặc hướng khác bằng cách triển khai silicon cụ thể (hoặc có thể được chọn bằng phần mềm). Một số ít tôi đã xử lý luôn ở chế độ tăng trưởng.
Michael Burr

1
Trong phần nhỏ của thế giới ARM mà tôi đã thấy cho đến nay (ARM7TDMI), ngăn xếp hoàn toàn được xử lý trong phần mềm. Địa chỉ trả về được lưu trong một sổ đăng ký được lưu bằng phần mềm nếu cần, và các hướng dẫn tăng / giảm trước / sau cho phép đặt nó và những thứ khác trên ngăn xếp theo một trong hai hướng.
starblue

1
Một HPPA, ngăn xếp đã lớn lên! Khá hiếm trong số các kiến ​​trúc hiện đại hợp lý.
tml

2
Đối với người hiếu, đây là một nguồn lực tốt về cách ngăn xếp hoạt động trên z / OS: www-03.ibm.com/systems/resources/Stack+and+Heap.pdf
Dillon thu mình lại

1
Cảm ơn @paxdiablo cho sự hiểu biết của bạn. Đôi khi mọi người coi đó là một sự sỉ nhục cá nhân khi bạn đưa ra nhận xét như vậy, đặc biệt là khi đó là một người lớn tuổi hơn. Tôi chỉ biết có một sự khác biệt bởi vì tôi đã từng mắc phải sai lầm tương tự trong quá khứ. Bảo trọng.
CasaDeRobison

23

Trong C ++ (có thể thích ứng với C) stack.cc :

static int
find_stack_direction ()
{
    static char *addr = 0;
    auto char dummy;
    if (addr == 0)
    {
        addr = &dummy;
        return find_stack_direction ();
    }
    else
    {
        return ((&dummy > addr) ? 1 : -1);
    }
}

14
Wow, đã lâu rồi tôi không thấy từ khóa "auto".
paxdiablo

9
(& dummy> addr) không được xác định. Kết quả của việc cung cấp hai con trỏ cho một toán tử quan hệ chỉ được xác định nếu hai con trỏ trỏ trong cùng một mảng hoặc cấu trúc.
sigjuice

2
Việc cố gắng điều tra bố cục của ngăn xếp của riêng bạn - thứ mà C / C ++ hoàn toàn không xác định - là "không thể di chuyển" để bắt đầu, vì vậy tôi sẽ không thực sự quan tâm đến điều đó. Có vẻ như chức năng này sẽ chỉ hoạt động chính xác một lần.
ephemient

9
Không cần thiết phải sử dụng a staticcho việc này. Thay vào đó, bạn có thể chuyển địa chỉ dưới dạng đối số cho một cuộc gọi đệ quy.
R .. GitHub NGỪNG TRỢ GIÚP ICE

5
cộng, bằng cách sử dụng một static, nếu bạn gọi này nhiều hơn một lần, những cuộc gọi tiếp theo có thể thất bại ...
Chris Dodd

7

Lợi thế của việc giảm dần là trong các hệ thống cũ, ngăn xếp thường ở trên cùng của bộ nhớ. Các chương trình thường lấp đầy bộ nhớ bắt đầu từ dưới cùng, do đó, kiểu quản lý bộ nhớ này giảm thiểu nhu cầu đo lường và đặt dưới cùng của ngăn xếp ở nơi nào đó hợp lý.


3
Không phải là một 'lợi thế', một sự căng thẳng thực sự.
Marquis of Lorne

1
Không phải là một sự căng thẳng. Vấn đề là phải có hai vùng bộ nhớ đang phát triển không can thiệp vào nhau (trừ khi bộ nhớ đầy), như @valenok đã chỉ ra.
YvesgereY

6

Ngăn xếp tăng dần trên x86 (được định nghĩa bởi kiến ​​trúc, con trỏ ngăn xếp gia tăng pop, giảm dần đẩy.)


5

Trong MIPS và nhiều kiến trúc RISC hiện đại (như PowerPC, RISC-V, SPARC ...) không có pushpophướng dẫn. Những hoạt động đó được thực hiện một cách rõ ràng bằng cách điều chỉnh thủ công con trỏ ngăn xếp, sau đó tải / lưu trữ giá trị tương đối vào con trỏ đã điều chỉnh. Tất cả các thanh ghi (ngoại trừ thanh ghi 0) đều có mục đích chung vì vậy về lý thuyết bất kỳ thanh ghi nào cũng có thể là con trỏ ngăn xếp và ngăn xếp có thể phát triển theo bất kỳ hướng nào mà người lập trình muốn

Điều đó nói rằng, ngăn xếp thường phát triển xuống trên hầu hết các kiến ​​trúc, có thể để tránh trường hợp khi dữ liệu ngăn xếp và chương trình hoặc dữ liệu đống lớn lên và xung đột với nhau. Ngoài ra còn có những lý do giải quyết tuyệt vời được đề cập đến câu trả lời của sh- . Một số ví dụ: MIPS ABIs phát triển xuống dưới và sử dụng $29(AKA $sp) làm con trỏ ngăn xếp, RISC-V ABI cũng phát triển xuống dưới và sử dụng x2 làm con trỏ ngăn xếp

Trong Intel 8051, ngăn xếp tăng lên, có thể là do không gian bộ nhớ quá nhỏ (128 byte trong phiên bản gốc) nên không có heap và bạn không cần đặt ngăn xếp lên trên để nó được tách ra khỏi heap ngày càng tăng. từ đáy

Bạn có thể tìm thêm thông tin về việc sử dụng ngăn xếp trong các kiến ​​trúc khác nhau tại https://en.wikipedia.org/wiki/Calling_convention

Xem thêm


2

Chỉ là một bổ sung nhỏ cho các câu trả lời khác, theo như tôi thấy thì chưa chạm đến điểm này:

Việc ngăn xếp phát triển xuống dưới làm cho tất cả các địa chỉ trong ngăn xếp có độ lệch dương so với con trỏ ngăn xếp. Không cần hiệu số âm, vì chúng chỉ trỏ đến không gian ngăn xếp không sử dụng. Điều này giúp đơn giản hóa việc truy cập các vị trí ngăn xếp khi bộ xử lý hỗ trợ định địa chỉ tương đối điểm xếp chồng.

Nhiều bộ xử lý có các hướng dẫn cho phép truy cập có giá trị bù chỉ dương so với một số thanh ghi. Chúng bao gồm nhiều kiến ​​trúc hiện đại, cũng như một số kiến ​​trúc cũ. Ví dụ: ARM Thumb ABI cung cấp cho các truy cập tương đối với điểm xếp chồng với độ lệch dương được mã hóa trong một từ lệnh 16 bit.

Nếu ngăn xếp lớn dần lên, tất cả các hiệu số hữu ích liên quan đến điểm xếp chồng sẽ là số âm, điều này kém trực quan và kém thuận tiện hơn. Nó cũng mâu thuẫn với các ứng dụng khác của địa chỉ liên quan đến thanh ghi, ví dụ để truy cập các trường của một cấu trúc.


2

Trên hầu hết các hệ thống, ngăn xếp tăng dần và bài viết của tôi tại https://gist.github.com/cpq/8598782 giải thích TẠI SAO nó lại giảm. Nó rất đơn giản: làm thế nào để bố trí hai khối bộ nhớ đang phát triển (heap và stack) trong một đoạn bộ nhớ cố định? Giải pháp tốt nhất là đặt chúng ở hai đầu đối diện và để chúng phát triển về phía nhau.


ý chính đó dường như đã chết :(
Ven

@Ven - Tôi có thể đến được
Brett Holman

1

Nó phát triển xuống vì bộ nhớ được cấp cho chương trình có "dữ liệu vĩnh viễn" tức là mã cho chính chương trình ở dưới cùng, sau đó là đống ở giữa. Bạn cần một điểm cố định khác mà từ đó tham chiếu ngăn xếp, để bạn đứng đầu. Điều này có nghĩa là ngăn xếp tăng dần xuống, cho đến khi nó có khả năng tiếp giáp với các đối tượng trên đống.


0

Macro này sẽ phát hiện nó trong thời gian chạy mà không có UB:

#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);

__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) { 
    return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.