Well, let's start with a quick review of basic data structures (if you understand *what* stacks are, you can skip this part). Stacks, if you haven't learned by now, are a basic data structure in which items are 'stacked' or 'pushed' onto a conceptual pile or stack, and removed ('popped') in the exact order they were pushed in. In the simplest case, there is a block of memory already assigned for the stack to grow into, and the top of the stack is simply represented by a pointer to the most recently added item. For example, if you have an empty stack with room for four items:
| |
| |
| |
| | < - top = 0
---
if you push two items, a and b, it would then look like
| | | | | |
| | | | | |
| | | | |b|<- top = 1
| | |a|<- top = 0 |a|
---<- top = base --- ---
if you then pushed c, popped c and b back off, and pushed d, e and f, it would be
| | | | | | | | | | |f|<-top=3
|c|<- top = 2 | | | | | | |e|<-top=2 |e|
|b| |b|<-top=1 | | |d|<-top=1 |d| |d|
|a| |a| |a|<-top=0 |a| |a| |a|
--- --- --- --- --- ---
Notice that the stack is full; if you tries to push another item onto it, you get what is called a stack overflow. Some types f stacks will grow automatically to fit whatever size they need, but this simple stack - which can be implemented in hardware as two registers, one for the base of the stack and one for the top of the stack - is limited to the space allocated for it.
Stacks are an extremely useful data structure for two particular reasons, both having to do with function calls. First, if when you jump to a function or subroutine, you first push the address of the instruction following the jump onto the stack, the function then can pop that address off when it is done, and return to the place where it was invoked from without having the address hardwired into it - thus, you can call the same function from anywhere in the code, even recursively, and it will return correctly. This is the main reason that most CPUs implement a stack in hardware, and virtually all of them have a pair of special instructions for performing the function call and the function return as a single action; on the x86, these are CALL and RET, respectively (there is also a RETF, which is used to return from a 'far' call between two different code segments, but that's getting ahead of things).
Second, if the arguments and data for a function are stored on the stack, rather than at a fixed location in general data, it allows you to call functions recursively; each new call has it's own 'frame' on the stack, and the individual items are referenced using a special pointer call the frame pointer, or base pointer. When the function returns, it pops all of it's data and arguments back off, clearing the space away for reuse. See
http://www.delorie.com/djgpp/doc/ug/asm/calling.html and
http://www.osdev.org/developers/guide01/ for more details, if you want to.
But wait, there's more... To understand the x86 stack segment, you have to first understand the segmented memory architecture of the x86 CPUs. There are several explanations of segmentation on this board and in the various tutorials. My own best attempt at doing so (if you can ignore the typoes) is at
http://204.215.248.28/board.jsp?message=1417 . A better introduction can be found in my favorite assembly language book, _Assembly Language Step By Step_ by Jeff Duntemann. The best source of detailed information is Intel's own documentation, which can be downloaded from their web site,
http://www.intel.com/ .
Assuming you've read one of those now and undertsand about segmentation... a big assumption, I know... the purpose of the stack segment should be a bit clearer. It simply acts as a way of assigning an area of memory for the stack to operate in. The segmentation both protects the stack from memory overruns by the rest of the program, and also protects the rest of the program from stack overflows or underflows. Or that, at least, seems to have been the idea.
Comments and Corrections Welcome.