Powered By Blogger

Thursday, November 28, 2013

Understanding the Stack and how MIPS optimizes usage of stack.

Stack

When a program starts executing, a certain contiguous section of memory is set aside for the program called the stack.
.





Here's a diagram of a push operation.







Here's a diagram of the pop operation.



Stacks and Functions


Let's now see how the stack is used to implement functions. For each function call, there's a section of the stack reserved for the function. This is usually called a stack frame.

Let's imagine we're starting in main() in a C program. The stack looks something like this:


We'll call this the stack frame for main(). It is also called the activation record. A stack frame exists whenever a function has started, but yet to complete.

Suppose, inside of body of main() there's a call to foo(). Suppose foo() takes two arguments. One way to pass the arguments to foo() is through the stack. Thus, there needs to be assembly language code in main() to "push" arguments for foo() onto the the stack. The result looks like:


As you can see, by placing the arguments on the stack, the stack frame for main() has increased in size. We also reserved some space for the return value. The return value is computed by foo(), so it will be filled out once foo() is done.

Once we get into code for foo (), the function foo() may need local variables, so foo() needs to push
some space on the stack, which looks like:


foo() can access the arguments passed to it from main() because the code in main() places the arguments just as foo() expects it.

We've added a new pointer called FP which stands for frame pointer. The frame pointer points to the location where the stack pointer was, just before foo() moved the stack pointer for foo()'s own local variables.

Having a frame pointer is convenient when a function is likely to move the stack pointer several times throughout the course of running the function. The idea is to keep the frame pointer fixed for the duration of foo()'s stack frame. The stack pointer, in the meanwhile, can change values.
Thus, we can use the frame pointer to compute the locations in memory for both arguments as well as local variables. Since it doesn't move, the computations for those locations should be some fixed offset from the frame pointer.

And, once it's time to exit foo(), you just have to set the stack pointer to where the frame pointer is, which effectively pops off foo()'s stack frame. It's quite handy to have a frame pointer.

We can imagine the stack growing if foo() calls another function, say, bar(). foo() would push arguments on the stack just as main() pushed arguments on the stack for foo().
So when we exit foo() the stack looks just as it did before we pushed on foo()'s stack frame, except this time the return value has been filled in


.


Once main() has the return value, it can pop that and the arguments to foo() off the stack.




Recursive Functions

Surprisingly enough, there's very little to say about recursive functions, because it behaves just as non-recursive functions do. To call a recursive function, push arguments and a return value on the stack, and call it like any other function.
It returns back the same way as well.

How MIPS Does It

In the previous discussion of function calls, we said that arguments are pushed on the stack and space for the return value is also pushed.
This is how CPUs used to do it. With the RISC revolution (admittedly, nearly 20 years old now) and large numbers of registers used in typical RISC machines, the goal is to (try and) avoid using the stack.
Why? The stack is in physical memory, which is RAM. Compared to accessing registers, accessing memory is much slower---probably on the order of 100 to 500 times as slow to access RAM than to access a register.
MIPS has many registers, so it does the following:
  • There are four registers used to pass arguments: $a0, $a1, $a2, $a3.
  • If a function has more than four arguments, or if any of the arguments is a large structure that's passed by value, then the stack is used.
  • There must be a set procedure for passing arguments that's known to everyone based on the types of the functions. That way, the caller of the function knows how to pass the arguments, and the function being called knows how to access them. Clearly, if this protocol is not established and followed, the function being called would not get its arguments properly, and would likely compute bogus values or, worse, crash.
  • The return value is placed in registers $v0, and if necessary, in $v1.
In general, this makes calling functions a little easier. In particular, the calling function usually does not need to place anything on the stack for the function being called.
However, this is clearly not a panacea. In particular, imagine main() calls foo(). Arguments are passed using $a0 and $a1, say.
What happens when foo() calls bar()? If foo() has to pass arguments too, then by convention, it's supposed to pass them using $a0 and $a1, etc. What if foo() needs the argument values from main() afterwards?

To prevent its own arguments from getting overwritten, foo() needs to save the arguments to the stack.Thus, we don't entirely avoid using the stack.

Leaf Procedures

In general, using registers to pass arguments and for return values doesn't prevent the use of the stack. Thus, it almost seems like we postpone the inevitable use of the stack. Why bother using registers for return values and arguments?
Eventually, we have to run code from a leaf procedure. This is a function that does not make any calls to any other functions. Clearly, if there were no leaf procedures, we wouldn't exit the program, at least, not in the usual way. (If this isn't obvious to you, try to think about why there must be leaf procedures).

In a leaf procedure, there are no calls to other functions, so there's no need to save arguments on the stack. There's also no need to save return values on the stack. You just use the argument values from the registers, and place the return value in the return value registers.

SKB Tinkering -Part1

Basic functions for sk_buff{}



skb_headroom(), skb_tailroom()


Prototype / Description
int skb_headroom(const struct sk_buff *skb);


bytes at buffer head


int skb_tailroom(const struct sk_buff *skb);


bytes at buffer


Image
skb_reserve()
Prototype
void skb_reserve(struct sk_buff *skb, unsigned int len);
Description
adjust headroom
Image
skb_push()
Prototype
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
Description
add data to the start of a buffer
Image
skb_pull()
Prototype
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
Description
remove data from the start of a buffer
Image
skb_put()
Prototype
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
Description
add data to a buffer
Image
skb_trim()
Prototype
void skb_trim(struct sk_buff *skb, unsigned int len);
Description
remove end from a buffer
Image

Memory Layout of C Programs

Memory Layout of C Programs


A typical memory representation of C program consists of following sections.
1. Text segment
2. Initialized data segment
3. Uninitialized data segment
4. Stack
5. Heap
A typical memory layout of a running process
1. Text Segment:
A text segment , also known as a code segment or simply as text, is one of the sections of a program in an object file or in memory, which contains executable instructions.
As a memory region, a text segment may be placed below the heap or stack in order to prevent heaps and stack overflows from overwriting it.
Usually, the text segment is sharable so that only a single copy needs to be in memory for frequently executed programs, such as text editors, the C compiler, the shells, and so on. Also, the text segment is often read-only, to prevent a program from accidentally modifying its instructions.
2. Initialized Data Segment:
Initialized data segment, usually called simply the Data Segment. A data segment is a portion of virtual address space of a program, which contains the global variables and static variables that are initialized by the programmer.
Note that, data segment is not read-only, since the values of the variables can be altered at run time.
This segment can be further classified into initialized read-only area and initialized read-write area.
For instance the global string defined by char s[] = “hello world” in C and a C statement like int debug=1 outside the main (i.e. global) would be stored in initialized read-write area. And a global C statement like const char* string = “hello world” makes the string literal “hello world” to be stored in initialized read-only area and the character pointer variable string in initialized read-write area.
Ex: static int i = 10 will be stored in data segment and global int i = 10 will also be stored in data segment
3. Uninitialized Data Segment:
Uninitialized data segment, often called the “bss” segment, named after an ancient assembler operator that stood for “block started by symbol.” Data in this segment is initialized by the kernel to arithmetic 0 before the program starts executing
uninitialized data starts at the end of the data segment and contains all global variables and static variables that are initialized to zero or do not have explicit initialization in source code.
For instance a variable declared static int i; would be contained in the BSS segment.
For instance a global variable declared int j; would be contained in the BSS segment.
4. Stack:
The stack area traditionally adjoined the heap area and grew the opposite direction; when the stack pointer met the heap pointer, free memory was exhausted. (With modern large address spaces and virtual memory techniques they may be placed almost anywhere, but they still typically grow opposite directions.)
The stack area contains the program stack, a LIFO structure, typically located in the higher parts of memory. On the standard PC x86 computer architecture it grows toward address zero; on some other architectures it grows the opposite direction. A “stack pointer” register tracks the top of the stack; it is adjusted each time a value is “pushed” onto the stack. The set of values pushed for one function call is termed a “stack frame”; A stack frame consists at minimum of a return address.
Stack, where automatic variables are stored, along with information that is saved each time a function is called. Each time a function is called, the address of where to return to and certain information about the caller’s environment, such as some of the machine registers, are saved on the stack. The newly called function then allocates room on the stack for its automatic and temporary variables. This is how recursive functions in C can work. Each time a recursive function calls itself, a new stack frame is used, so one set of variables doesn’t interfere with the variables from another instance of the function.
5. Heap:
Heap is the segment where dynamic memory allocation usually takes place.

The heap area begins at the end of the BSS segment and grows to larger addresses from there.The Heap area is managed by malloc, realloc, and free, which may use the brk and sbrk system calls to adjust its size (note that the use of brk/sbrk and a single “heap area” is not required to fulfill the contract of malloc/realloc/free; they may also be implemented using mmap to reserve potentially non-contiguous regions of virtual memory into the process’ virtual address space). The Heap area is shared by all shared libraries and dynamically loaded modules in a process.

Kernel Locking Matrix


IRQ Handler A
IRQ Handler B
Softirq A
Softirq B
Tasklet A
Tasklet B
Timer A
Timer B
User Context A
User Context B
IRQ Handler A
None
IRQ Handler B
SLIS
None
Softirq A
SLI
SLI
SL
Softirq B
SLI
SLI
SL
SL
Tasklet A
SLI
SLI
SL
SL
None
Tasklet B
SLI
SLI
SL
SL
SL
None
Timer A
SLI
SLI
SL
SL
SL
SL
None
Timer B
SLI
SLI
SL
SL
SL
SL
SL
None
User Context A
SLI
SLI
SLBH
SLBH
SLBH
SLBH
SLBH
SLBH
None
User Context B
SLI
SLI
SLBH
SLBH
SLBH
SLBH
SLBH
SLBH
MLI
None

Table 5.2. Legend for Locking Requirements Table

SLIS
spin_lock_irqsave
SLI
spin_lock_irq
SL
spin_lock
SLBH
spin_lock_bh
MLI
mutex_lock_interruptible