Q:- What is a process?
A:- A program in execution on a von Neumann computer.
Von Neumann Machine: contains the following hardware
Note: Process is a program running at a particular time.
Main user abstraction in modern OS:
Kernel<-------------->Process
OS designer (MS,Linux etc)<------------>Application designer
One Kernel<------------->Many processes
Some Definitions:
• Abstract machine interface is the interface between a process and the kernel.
• System call is a call through which a process makes a request to the kernel.
• Classic Process is a process running on a von Neumann computer.
Note: Abstract Machine is single processor machine.
Accounting
Abstract Machine:
Kernel Resources & Kernel State:
Von Neumann Computer ---------- Process ---------- C Program Interface
| Turing Machine | Process State |
|---|---|
| Tape | Read-Write Data (Global Variables) |
| Transition Function | Instructions (Program text) which is read only |
| Current Turing Machine State | Instruction Pointer (IP) |
| Head Position | Register |
Linux Process address space (x86):
Let’s see how we got the addresses above:
#include<stdio.h>
#include<stdlib.h>
int x; //creating a globalm variable
int main (int c, char **v)
{
void *p = malloc (1); //dynamically allocating a memory space
printf ("Prog %p\n Data %p\n //print on the screen: Prog, Data, Heap, Stack
Heap %p\n Stack %p\n", //now get their addresses in your computer:
&main, &x, p, &c); //Program address is where your main begins -> 0x8000000
} //Data is where the global variable being created
//Heap starts at your dynamically allocated memory place ->0x8660000
//Stack begins where you push your first local variable ->0xc00000
The stack is an area of memory reserved for function arguments and local variables. The stack is allocated a function at a time: when we call a function, we push more space onto the stack to hold that function's local variables; and when the function returns, we pop its local variable space off the stack. The architecture has a special register, called the stack pointer (%esp on x86), that points to the current stack location. Stacks generally grow from the top down, so when a function is called, the stack pointer is set to a smaller value.
We need to keep local variables that are used during the running program somewhere in memory. Where can we store Local Variables in memory? To place Local Variables where Global Variables are kept is a bad idea. Here is why:
Let's look at a simple recursive function: "Factorial"
int factorial(int x)
{
if (x == 0)
return 1;
else
return x * factorial(x - 1);
}
pseudo-assembly code:
EX: 'factorial(5)'
0x02: pushl $5
0x05: call _factorial
0x06: popl %eax // pop the argument we pushed
0x09: ... continue ...
factorial: // The 'factorial' function definition
0x10: cmpl 4(%esp), $0 // Is x == 0?
0x12: jne 0x1D // If not, jump ahead
0x14: movl $1, %eax // Return 1
0x17: ret
0x1D: movl 4(%esp), %eax // %eax = f;
0x20: subl %eax, $1 // %eax--;
0x22: pushl %eax // push 'x - 1' as argument
0x24: call _factorial // call 'factorial' recursively
// result is returned in '%eax'
0x26: mull 4(%esp), %eax // %eax *= x;
// now %eax == x * factorial(x - 1)
0x29: ret // return!
When the function is called:
These steps are repeated for each function call.
Stack grows downward, because it is much more natural and convenient to refer to arguments and local variables with positive offsets, such as 4(%esp).
Operating System needs to know only how to grow the stack (user processes can arrange stack other ways.).
The stack assumes that function never returns more than once. It is a feature that almost every programming language has. The reason is: When a function returns all the local variables and also the state that the function was in it will be deleted. Therefore that functions state and variables will not exist any more.
Memory Layout (4 GB)
Another section of memory is heap. In the heap we store dynamically allocated memories. Dynamic memory is being used to limit the process resource usage dynamically based on availability. In edition we also have to store the register state and the program counter. The main reason is that we must be able to run more than one process at a time.
We use the following commands to allocate and free memory:
* Malloc( ) -----> Memory allocation
* Free( ) -----> Free Memory
The Process Control Block is located in the kernel memory and has the following structure:
Kernel Memory (Process Control Block)
Process blocks when it needs a resource that’s not available now. It doesn't return until finished (forces process to wait until done). Kernel puts process to sleep until it’s ready. It avoids busy waiting and useless work. A blocking system call might put a process on a wait queue, if the operation can't complete right away.
How to create a process?
int x;
pid_tp = fork();
x = p;
printf (“%d\n”, x)
Output: 2, 0
int execve(const char *program_filename , const char *argv[] ) . Some other functions that handle processes:
void exit(int status) -- terminates the process which calls this function and returns the exit status value. int waitpid(pid, statusp, options) -- waits for process termination. The function suspends execution of the current process until a child as specified by the pid argument has exited. ps -- lists all the processes that you own. ps aux -- lists all the processes on the machine. kill() -- terminates a process by using the kill command. We can find a process’s pid (by using ps), and then type kill [pid] Note: Zombie is a process that is exited, but it’s parent haven’t called waitpid( ) on it.