06 - Process attributes and context switching

Class: CSCE-313


Notes:

Outline:

The story so far

Pasted image 20260213134847.png|500

Notes:

Program execution flow

  1. Load code and data segments from executable file into memory
  2. Create stack and heap
  3. Transfer control to program's entry point
  4. Provide services to it (network, file connections IO, etc.)

Notes:

Pasted image 20260213134952.png|500

Overview of memory regions

Stack This region contains temporary data such as method/function parameters, return address and local variables.
Heap This region is dynamically allocated memory to during a process's run time.
Text This section includes the current activity represented by the value of Program Counter and the contents of the processor's registers.
Data This section contains global and static variables.

The lifecycle of a process

State Description
Start This is the initial state when a process is first started/created.
Ready - The process is waiting to be assigned to a processor.
- Ready processes are waiting to have the processor allocated to them by the operating system so that they can run.
- Process may come into this state after the start state or while running and being interrupted by the scheduler to assign CPU to some other process.
Running Once the process has been assigned to a processor by the OS scheduler, the process state is set to running and the processor executes its instructions.
Waiting Process moves into the waiting state if it needs to wait for a resource, such as waiting for user input, or waiting for a file to become available.
Terminated or exited Once the process finishes its execution, or it is terminated by the operating system, it is moved to the terminated state where it waits to be removed from main memory.

Notes:

State of a Process in Action

Pasted image 20260213135511.png|500

Notes:

How does the OS represent a process in the kernel?

Notes:

Process Control Block (PCB)

Process State The current state of the process i.e., whether it is ready, running, waiting, or whatever.
Process privileges This is required to allow/disallow access to system resources.
Process ID Unique identification for each of the process in the operating system.
Pointer A pointer to parent process.
Program Counter Program Counter is a pointer to the address of the next instruction to be executed for this process.
CPU registers Various CPU registers where process need to be stored for execution for running state.
CPU Scheduling Information Process priority and other scheduling information which is required to schedule the process.
Memory management information This includes information about the page table, memory limits, etc. Segment table depending on memory used by the operating system.
Accounting information This includes the amount of CPU used for process execution, time limits, execution ID etc.
IO status information This includes a list of I/O devices allocated to the process.
Notes:

Pasted image 20260213141109.png|500

Notes:

OS's Internal Tables

An OS keeps a lot of information in main memory, much of this info is about:

The PCB is a process's metadata. It's not the same as program data.

Program's data (variables, allocated memory) and code are kept in the process image (i.e., address space), which is separate and usually bigger in footprint.

Notes:


Some questions

How many processes can be in the running state simultaneously?

What state do you think a process is in most of the time?

How many processes can a Linux system theoretically support?


Context Switch: PCB and Hardware States

  1. When a process is running, its hardware state ( PC,SP, registers, etc.) is in the CPU. The hardware registers contain the current values.
  2. When the OS stops running a process, it saves the current values of the registers into the process's PCB .
  3. When the OS is ready to start executing a new process, it loads the hardware registers from the values stored in that process's PCB.

The process of changing the CPU hardware state from one process to another is called a context switch.

This can happen 100 or 1000 or more times a second!

The Execution Trace of Processes

Pasted image 20260213142020.png|600

Notes:

Examples: tracing processes execution

Description Command
Trace the system calls of the command ls $ strace ls -la top3.txt
Display only the write system call of the ls command $ strace -e write ls -la top3.txt
Trace the system calls of the command mkdir $ strace mkdir books
Trace network-related system calls $ strace -e network nc -v -n 127.0.0.1 80
Trace a process using its PID $ strace -p 1908
Print the time spent on system call $ strace -r mkdir books
Get a statistical report for an execution trace $ strace -c ping google.com
Notes:

Scheduling and the Process Control Block (PCB)

Mechanism of a context switch:

Pasted image 20260213143028.png|450

Pasted image 20260216140531.png|600

ctxsw(char *from_sp, char *to_sp) X

Pasted image 20260213143135.png|600

States of a Process

User view: A process is executing continuously
In reality: Several processes compete for the CPU and other resources
A process may be

Pasted image 20260216140827.png|500

Lab 3 context switching

Pasted image 20260213143227.png|600

Notes:

Overall steps:

  1. Find another thread eligible to execute
  2. Save your own context (current thread)
  3. Restore the context from that other thread to pick up

Example:

Representing Threads

Pasted image 20260216140959.png|400

Notes:

Switching between Threads - I

Pasted image 20260216141041.png|400

Notes:

Switching between Threads - II

int t_yield() {
    // the caller uses the slot at index "current" in the proc table
    next = find_next_valid();
    if(next == curr) return ERROR; // only main thread remains
    atomically:
        save caller's context in current; - swapcontext
        restore context from next;
    return SUCCESS;
}

When a thread invokes t_yield(), the "other" thread returns from swapcontext!

Notes:

Test example

void dosomething (int32_t x, int32_t y) {
    for (int32_t i = x; i < y; i++) {
        // Perform some computation
        printf ("Hil (%d -> %d): Running: %d\n", x, v, i);
        t_yield (); // Yield the control to other workers
    }
    
    printf ("Hil (%d -> %d): Done!\n", x, y); // Perform some computation
    t_finish (); // All the work is done!
}
int main () {
    t_init (); // Initialize the runtime
    
    if (t_create (dosomething, 0, 10) != 0) return -1;
    if (t_create (dosomething, 10, 20) !=0) return -1;
    if (t_create (dosomething, 20, 30) !=0) return -1;
    
    while (t_yield () >=1); // Wait for the workers to finish their tasks
    return 0;
}

Notes:

A simple example - I

static ucontext_t uctx_main, uctx_func1, uctx_func2;

static void func1 (void) ,{
    printf ("%s: started\n" func__);
    printf ("%s: swapcontext̀(&uctx funć1, &uctx_func2)\n", __func__);
    Swapcontext (&uctx func1, &uctx func2);
    prihtf ("%s: returning\n", __func__);
}

static void func2 (void) {
    printf "%s: started\n" func );
    printf ("%s: swapcontext̀(&uctx func2) &uctx_func1)\n", __func__);
    Swapcontext (&uctx func2, &uctx func1);
    prihtf ("%s: returning\n", __func__);
}

Notes:

A simple example - II

int main (int argc, char *argv[]) {
	char func1_stack[16384];
	char func2_stack[16384];
	
	getcontext (&uctx_func1); uctx_func1.uc_stack.ss_sp = func1_stack;
	uctx_func1.uc_stack.ss_size = sizeof (func1_stack);
	uctx_func1.uc_link = &uctx_main;
	makecontext (&uctx_func1, func1, 0);
	
	getcontext (&uctx_func2); uctx_func2.uc_stack.ss_sp = func2_stack;
	uctx_func2.uc_stack.ss_size = sizeof (func2_stack); /* Successor context is f1(), unless argc > 1 */ 
	uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1; 
	makecontext (&uctx_func2, func2, 0);
	
	printf ("%s: swapcontext(&uctx_main, &uctx_func2)\n", __func__);
	swapcontext (&uctx_main, &uctx_func2);
	printf ("%s: exiting\n", __func__);
	exit (EXIT_SUCCESS);
}