This blog post is going to be the first one in my OS series. Without any second thought, I decided to write on the topic process, as it's the fundamental concept around which the other elements of OS is built.
Without any further introduction, let's dive in.
What is a Process?
First, let me provide an academic definition of Process followed by practical illustrations. Process is a running instance of a program/code. Several processes can run the same program, but each Process has its own state. Example: You can open multiple instances of some applications like Firefox, Microsoft Word, etc. Each one maintains its process state. A process executes the instructions sequentially (one at a time).
Let's see how the execution of a program looks in the memory. Let's take the following program an example.
In the context of the Process, the above program will look like the following diagram.
Note: The text segment will have assembly code or executable code. Just for the sake of illustration, I've used the high-level code.
As shown in the diagram, the program has to be organized in the following segments for the execution.
The data segment generally holds the global variables and the static variables of the program.
The dynamic allocation of memory is achieved by reserving a desired space in the heap memory. Example: In C++, when we do new() / malloc(), the Process reserves the requested size from the heap segment. And, when we deallocate the memory by free(), the reserved space is given back to the heap memory marking it as free memory.
The stack segment holds the local variables, function parameters of each function. You can think of it as any state that needs to be maintained to execute a function, will be placed in that function's stack segment.
The text segment holds the program or code in an executable format like in Assembly code. As it was already pointed out that a process executes the program sequentially, we have a pointer called Program Counter (PC) that points what's the next instruction to be executed.
Process Execution State
Every Process has a state attached to it, indicating what it is doing. There are five states in the process lifecycle. Each state is listed below.
- New: OS creates a new process
- Running: executing instructions on the CPU
- Ready: ready to run, but waiting for the CPU
- Waiting: waiting for an event to complete (most I/O operation(s) of the Process).
- Terminated: OS destroys the Process.
As the program executes, it moves from state to state as a result of the program executions (e.g., system call), OS actions (scheduling), and external actions (interrupts).
When a process moves from running state to waiting state, OS will give the CPU time to another process in the ready state in the meantime. The Process moves to the waiting state when the OS schedules another process to execute on CPU. Also, the Process will go to the waiting state when it waits for the system call it made to complete.
The data structure of a Process
The data structure used to maintain execution state, program counter, stack pointer, etc. is Process Control Block (PCB). OS allocates a new PCB on the creation of each Process and places it on a state queue. The OS deallocates the PCB when the Process terminates. The PCB holds the following for the execution of the Process.
- Process State (running, waiting, etc.)
- Process ID (pid)
- Program Counter (PC)
- Stack pointer
- Register values
- Memory management info
- List of open files
- Queue pointers for state queues
- Scheduling info (e.g., priority)
- I/O status
Activity Monitor/Task Manager applications read the PCB table and show the process information to the users.
Process State Queues
OS maintains the PCBs of all the processes in state queues. It places the PCBs of all the processes in the same execution state in the appropriate state queue. When the OS changes the state of a process, the PCB is unlinked from its current state queue and moved to its new state queue. Each I/O device has its own wait queue. Example: If a group of processes is waiting for data to be fetched from a disk block, all those processes will be put in the disk waiting queue.
The PCBs of the same state queue are linked together using a doubly-linked list.
Note: The size of the running queue will be equal to the number of CPU cores. The size of the other queues is unbounded.
This marks the end of Process Part-I. In Part-II, we will further discuss the process context switch, parent, and child process.
Thanks for reading :)