Skip to main content
Operating Systems

Processes in Operating Systems

Published: 2026-08-15
Level: undergraduate
Audience: Undergraduate students in Operating Systems

3.1 The Concept of a Process

3.1.1 Programs and Processes: Passive versus Active

The earlier sessions covered what operating systems exist and how they are structured. The next level of the subject is the process — the thing that runs inside the system. A process is a task or a job in action. The same idea was introduced in the previous session, so it deserves a careful restatement.

Hook: If you have ever asked "what is actually running right now on my computer?", you were asking about processes. One process or another is always in execution at any given moment — that is why the process is called an active entity.

A program is a set of instructions. The catch is that a program is not active all the time: it sits on disk (or in memory) as a plain collection of instructions until something runs it. For that reason a program is called a passive entity. A process is the program when it is actually doing work — one or the other process is always in execution at any given moment, which is why the process is called an active entity. The pair passive/active is the cleanest way to keep the two ideas apart: the program is the static description, the process is the dynamic execution of that description.

The same distinction is the reason a program file can be copied freely without anything "happening": the file on disk is a recipe, and the running process is the cooking. The recipe lists every step, but nothing cooks until someone reads it and starts following it. In the same way, an executable file on disk does nothing by itself; the moment the operating system loads it into memory and starts executing its instructions, a process exists. Two users can even run the same program at the same time — the text section is identical, but they are two separate processes with their own data, stack, and heap.

A subtle point worth locking in: a process is more than the program code. It also carries the current activity — where execution is right now, what the registers hold, what temporary data the running functions have. A program, in contrast, is only the code. When a program becomes a process, an executable file is loaded into memory; the two common ways to trigger this are double-clicking an icon for the executable file and typing its name on the command line.

Recap: Program = passive entity = the static instructions. Process = active entity = the program in execution. When you read "process", think "program + its current activity, right now".

3.1.2 What a Program Holds in Memory

When a program becomes a process, it occupies a specific area of memory, and that area has well-defined parts. Looking at the memory layout from lowest to highest address, a program in memory consists of:

  • Text section — the actual machine instructions of the program. Every copy of the same program shares the same text, so this is also called the code section.
  • Data — a portion of memory that is allocated in advance, before execution. Because the allocation is decided up front, the data section is static. This is where global variables live: their space is reserved when the process starts and stays fixed while it runs.
  • Stack — holds temporary data. When a process runs, the values of its variables are pushed onto the stack, and when they are needed for execution they are popped off. Function parameters, return addresses, and local variables all live here. Nothing in the stack is meant to be permanent; it exists only while the process lives, and it is emptied as functions return.
  • Heap — the area of memory that gets allocated during runtime, that is, during execution. The heap is dynamic: it is not fixed in advance the way the data section is. Every time a program asks the system for memory while it runs (for example, with a malloc call in C or a new call in Java), the space comes from the heap.
  • Program counter (PC) — not a data region but a register that always holds the address of the next instruction to be executed. It is listed alongside the memory parts because it is one of the pieces every running program carries. If you freeze a process at any instant, the PC tells you exactly which instruction comes next.

Two growth rules matter here, and they are worth picturing: the heap and the stack grow toward each other. The heap grows up — as memory is allocated at runtime, the allocated region keeps increasing, so the heap edge moves upward. The stack grows down. This block-diagram picture of a process in memory is the standard one, and the visual of "heap goes up, stack goes down" is the easiest way to remember which is which.

A concrete trace makes the two regions feel different. Suppose a program declares a global counter g and then calls a function that declares a local variable n and allocates a block of memory at runtime. The global g sits in the static data section, n is pushed onto the stack when the function starts and popped when the function returns, and the runtime block is carved out of the heap — the heap edge moves up a little, while the stack pointer moves down. Both regions are bounded; if a program pushes too much onto the stack (unbounded recursion), the stack collides downward, and if it allocates without freeing, the heap climbs until memory is exhausted.

Pitfalls:

  • Confusing "stack" with "the program's data". The stack only holds temporary per-call data; permanent data lives in the data section.
  • Thinking the heap size is fixed at start. The heap is dynamic by definition — that is exactly its purpose.
  • Treating the program counter as a memory section. The PC is a register (a piece of hardware state), not a region of the process's memory.
  • Assuming the text section differs between copies. Two processes running the same program share identical text; what differs is data, heap, and stack.

3.1.3 Jobs, Tasks, and Real-Time Systems

The same thing gets called by different names depending on the era and the context. From the batch systems to the time-sharing systems there have been jobs, programs, and tasks — the words are interchangeable, and a job, a program, or a task can all become a process. Even user programs that run in a time-shared manner fit this picture. The textbook terminology agrees: the terms job and process are used almost interchangeably, because much of operating-system theory was built during the era of batch job processing.

One special category stands apart: real-time systems. A real-time system has a time constraint — it must start at a particular time and must end at a particular time. The operating system can execute such constrained systems as well; the process concept still applies to them, but the deadline makes their scheduling requirements stricter than those of an ordinary time-shared program.

The practical difference is one of stakes. A time-shared program that finishes late is merely slow; a real-time process that finishes late may have missed its purpose entirely — an anti-lock brake controller that computes a response after the wheel has already locked is worse than useless. So when you study scheduling later in the course, remember that every process carries the same basic anatomy (text, data, stack, heap, PC), but real-time processes add a deadline on top of that anatomy.

Bridge: You now know what a process is and what it holds in memory. The next question is the one every operating system must answer: in which state is a process at any instant — and how does it move between states?

3.2 Process States

3.2.1 The Five States

A process is never stuck in a single state. It moves through a standard set of states, and many processes exist at once, each sitting in some state (and, as we will see, in some queue). The states are:

  • New — the process is being created; it has just come into existence. The operating system has built its bookkeeping (its identifier and control tables) but has not yet let it compete for the CPU.
  • Ready — the process is prepared to run, waiting for its turn at the CPU. Everything the process needs is in place; only the CPU itself is missing.
  • Running — the instructions of the process are being executed on the CPU.
  • Waiting — the process is waiting for an event to occur: waiting for an input to be taken, or for an output to be displayed. This state is also called the blocked state in other textbooks. The two names are interchangeable: waiting and blocked describe the same thing — the process cannot run until some event it depends on happens.
  • Terminated — the process has finished executing. The operating system still holds its records briefly so that accounting and parent bookkeeping can be done, then the process is removed from the system.

Intuition: a process is not one thing that sits still — it is a stream of activity. Every process alive in the system is in exactly one of these states at any instant, and most processes are not running: they are waiting in line, or blocked on an event. In a room full of students, at most one person speaks at a time (running); everyone else is either ready to speak the moment they are called (ready) or waiting for something to happen first, like a question to be asked (waiting).

A key rule that falls out of the state list: on a single processor, only one process can be in the running state at any instant. Many processes can be ready at the same time, but the hardware executes one instruction stream at a time, so the running state has at most one occupant.

3.2.2 Transitions Between States

The transitions tie the states together, and they are driven by three forces: scheduling, interrupts, and I/O.

A brand-new process moves to the ready state first — it is admitted and waits for the CPU. From ready, it is picked for execution and enters the running state. While running, an interrupt can occur (interrupts were discussed in an earlier session). At that moment the state changes back to ready: the process does not die, it simply steps aside. Once the interrupt is dealt with, the process moves again from ready to running and continues. If, while running, the process needs an input or output operation, it leaves the CPU and enters the waiting state — it is waiting for an event, an input, or an output to complete. When the wait is over, the process returns to the ready state, and from ready it resumes execution. Finally, when the process completes its execution, it reaches the terminated state, meaning it has just finished.

That is the complete state diagram: new → ready → running → (waiting, and back) → terminated, with interrupts moving a running process back to ready.

Each arrow has a name, and naming the arrows makes the diagram testable:

  • Admit — new → ready. The operating system accepts the process into the pool of executable processes.
  • Dispatch — ready → running. The scheduler picks this process and hands it the CPU.
  • Interrupt / time-out — running → ready. The process is preempted: it could have continued, but the CPU is being taken away (a clock interrupt or a higher-priority need).
  • Event wait (I/O request) — running → waiting. The process asks for input or output and cannot proceed until it completes.
  • Event occurs (I/O completion) — waiting → ready. The awaited event has happened; the process can again compete for the CPU.
  • Release — running → terminated. The process finishes (or aborts).

Pitfalls:

  • Treating "waiting" as the same as "ready". A ready process only lacks the CPU; a waiting process cannot run even if handed the CPU, because the event it needs has not happened yet.
  • Thinking an interrupt kills the running process. An interrupt moves running → ready (or running → waiting, if the interrupt starts an I/O operation); the process survives and resumes later.
  • Believing a process can go straight from new to running. Every new process must pass through ready first.

Visually, the state diagram is a set of five circles (or boxes) arranged left to right: new on the left, then ready, then running in the middle, waiting below running, and terminated on the right. The arrows between the circles carry the transition names above: admit enters from the left, dispatch points from ready to running, event wait points from running down to waiting, event occurs points from waiting back up to ready, interrupt points from running back to ready, and release exits to terminated. If you can redraw that picture from memory, you own the entire process-state model.

Recap: Five states (new, ready, running, waiting, terminated) connected by six named transitions, driven by scheduling, interrupts, and I/O. Bridge: everything the operating system knows about a process at any moment is packed into one record — the process control block, the next topic.

3.3 The Process Control Block (PCB)

3.3.1 What a PCB Records

Every process carries a record that describes it completely: the process control block (PCB). If you look at a PCB, you can understand everything about the process it belongs to. The PCB is the operating system's filing card for one process — one card per process, updated on every state change. The PCB holds:

  • The state of the process (new, ready, running, waiting, or terminated).
  • The process number — each process is given an ID as soon as it is created. (How IDs are assigned comes up later.)
  • The program counter (PC) — the location of the next instruction to be executed for this process.
  • The registers — whatever registers the process needs. This is the hardware state of the process: accumulators, index registers, stack pointers, general-purpose registers, and condition-code flags.
  • Memory information — how the process sits in memory. This may be the base and limit register values, or the page and segment tables, depending on the memory system in use.
  • Accounting information — for example, how much CPU time the process has used, the time elapsed since the process started, and its time limits.
  • I/O status information — which files have been allocated to the process and which I/O devices it uses.

The list is easier to remember if you notice what it is for: the PCB must contain everything that needs to be saved when the process leaves the CPU and restored when it returns. The state, the PC, and the registers are exactly the pieces the hardware needs to resume an interrupted computation; the memory, accounting, and I/O information are the pieces the operating system needs to manage the process day to day. The textbook puts it in one sentence: the PCB is the repository for any information that may vary from process to process.

Real-world: the accounting idea is visible on every desktop — the Windows taskbar shows the CPU utilization percentage, which is exactly this kind of per-system accounting information made visible. That percentage is the operating system's accounting data, gathered process by process, rolled up and displayed.

3.3.2 Memory and Registers

The registers deserve a closer look because they are limited. If registers are available, the process uses them; if they are not — and the number of registers always depends on the processes and is limited — the information has to be kept in main memory instead.

Memory also has levels. Everything cannot fit in main memory at once: whatever is necessary for a process is brought from secondary storage into main memory, and from main memory it is taken and executed by the CPU. The PCB ties these pieces together — state, PC, registers, memory layout, accounting, and I/O status are all in one place so that the operating system can manage the process without hunting for its details.

Think of the register file as a small, fast desk surface and main memory as a big shelf behind it. A working process keeps its current values on the desk; when the desk is full or the process is swapped out, everything moves to the shelf (main memory, or even secondary storage) so the desk can serve the next process. Because registers are scarce, the saved register values live in the PCB — which itself lives in main memory (or in the kernel's data structures) between runs.

The same layering repeats at a larger scale: a process's full code and data sit on disk (secondary storage) until they are needed, are loaded into main memory when the process becomes active, and are read by the CPU from main memory one instruction at a time. The PCB records where in this chain the process currently stands — which parts are in memory, and what the CPU-side state is.

3.3.3 Accounting and I/O Status Information

The accounting side records CPU utilization, elapsed time since the process started, and time limits. The I/O status side answers questions like: how many files are allocated to this process, and which devices does it use? Suppose ten processes exist and five of them need a printer — an output device. Some processes go to the console (another output device), some need the keyboard (an input device). All of that appears in the I/O status information of the PCB.

The accounting fields matter for billing and for fairness: a time-sharing system needs to know how long each process has run so it can enforce time limits and so the operator can tell which processes are consuming the machine. The I/O fields matter for resource management: before the system allocates a device, it can look at the requesting process's PCB and see which files and devices it already holds. Together the two sides complete the picture — the PCB tells the operating system who the process is (state, number, PC, registers), where it lives (memory), how much it has used (accounting), and what it is touching (I/O status).

Recap: The PCB is one complete record per process — state, number, PC, registers, memory, accounting, and I/O status — updated on every state change, and the single place the operating system looks to save, restore, and manage a process. Bridge: processes carry these records, but a process can also contain lighter units of execution — threads — which are the next comparison.

3.4 Processes versus Threads

3.4.1 Lightweight and Heavyweight Execution

A process can contain multiple threads, and a thread is itself a task. The difference between a process and a thread is stated in two words: a thread is lightweight and a process is heavyweight execution. Those words have a concrete meaning.

A thread (a unit of execution inside a process) is called lightweight because creating one costs almost nothing: the operating system does not build a new address space, a new file list, or a new set of resources for it. A process is called heavyweight because creating one costs a lot: the whole address space and resource set must be set up separately. The professor's two words are the entire contrast in miniature — lightweight means cheap to create and cheap to switch, heavyweight means expensive to create and expensive to switch.

Purpose of the contrast: when an application wants to do several things at once (type while spell-checking, load a page while rendering it), it can either spawn several processes or spawn several threads inside one process. The choice is a cost trade: threads give speed and sharing, processes give isolation. This lecture uses the contrast to explain why the process is the heavyweight unit; the full thread model is studied in detail in the next session.

3.4.2 Address Space: Shared or Separate

When a thread is created from a process, no new space is allocated for it: the thread shares the address space of the process that created it. The same code and data areas are common to the process and its threads.

A new process is a different story. If a process creates another process, the creator is the parent process and the new one is the child process. For a child process, everything is allocated separately: whatever exists for the parent is effectively doubled — code, data, and the rest occupy a separate space. That duplication is why a process is heavyweight while a thread is lightweight. Threads are covered in detail later; for now the key contrast is: threads share the creator's address space, processes do not.

The practical consequence of sharing versus separate space is safety versus speed. Because threads of one process live in the same address space, they can exchange data simply by reading and writing shared variables — no communication mechanism needed — but a buggy thread can also corrupt data that other threads rely on. Because processes live in separate address spaces, one process cannot accidentally overwrite another process's memory, but exchanging data between them requires a deliberate inter-process communication (IPC) mechanism, which the later topics in this lecture cover.

Dimension Thread Process
Creation cost Lightweight — no new address space Heavyweight — separate address space and resources
Address space Shares the creating process's space Own separate space (parent/child)
Data exchange Direct via shared variables Requires IPC (shared memory or message passing)
Failure isolation A bad thread can hurt its whole process A crashed process does not touch other processes
Typical count per program Many inside one process One or several per application

When to pick which: use threads when the tasks need to share data constantly and cheaply (one document, several panels); use separate processes when tasks must be isolated from each other's failures (one browser tab crashes — the other tabs keep working, as Chrome's design shows later in this lecture).

Pitfalls:

  • Saying "a thread has its own address space". A thread shares the address space of the process that created it; the process is the unit that owns an address space.
  • Thinking "heavyweight" is a criticism of processes. Heavyweight means costly to create — and the payoff is isolation.
  • Confusing threads with child processes. A child process is a full new process with duplicated code and data; a thread is a lighter execution unit inside an existing process.

Recap: Thread = lightweight execution unit sharing the creator's address space; process = heavyweight unit with its own separate space, which is why its creation duplicates code and data. Bridge: after the general model, the lecture turns to a concrete case — how one real system, Linux, represents processes and their identifiers.

3.5 Process Representation in Linux

3.5.1 The Process Identifier and pid_t

Every process has an identifier — in Linux it is the PID (process identifier). The PID is a variable that is capable of storing a process identifier, and that variable has a type. The type is predefined as pid_t. This mirrors C's way of typing variables: if you write int c, you are declaring that c is a variable capable of storing an integer value. Similarly, pid_t is the type of a variable capable of storing a process identifier. (The source garbles the underscore in this type name; it is pid_t.)

The type name says what the variable can hold, exactly as in ordinary C typing. A declaration like

pid_t child_pid;

creates a variable that is guaranteed to be big enough to hold any process identifier the system can produce. This matters because PIDs are not ordinary small integers on every system: the range is chosen by the system, and pid_t is defined so that user code never has to guess the width. The textbook confirms the model: in Unix, each process is identified by its process identifier, which is a unique integer, and most operating systems — including Unix and the Windows family — identify processes by a unique process identifier, typically an integer.

3.5.2 Process Structures and the First Processes

Alongside the PID type, Linux defines structures for each kind of process information: a structure for the parent process, a structure for the child process, a structure describing how many files a process has, and a structure for the address space of the parent process. Each kind of information has its own structure.

These structures are the Linux realization of the process control block: the C structure task_struct holds all the necessary information for representing a process — its state, scheduling and memory-management information, the list of open files, and pointers to the process's parent and to any of its children. The list of open files and the parent/child pointers correspond exactly to the "structures" the professor lists: one for the parent, one for the children, one for the file count, one for the address space. In the kernel, all active processes are linked in a doubly linked list of task_struct, and a kernel pointer named current points to the process currently executing.

The first process that executes on a Linux system has PID 1. After it come the login process, and for secure shell connections the sshd process. These PIDs are predefined: that ID is never allocated to a user-defined process. The process list itself forms a tree — an example tree contains bash, ps (the process status command), and emacs (an editor), where each entry has its own ID. The Linux details are presented as an example only; they are not the core of the course.

The tree picture is the same process tree you would draw on paper: PID 1 (init) sits at the root; a login session hangs below it; the user's shell bash is a child of that session; and commands the user runs — ps to list processes, emacs to edit a file — are children of the shell, each with its own PID. This is why on a real machine the shell is called a parent process and the commands it launches are its children. On any Linux system you can reproduce the tree yourself: ps prints every running process with its PID, and ps -el prints the parent of each process so the tree can be traced back to PID 1.

Pitfalls:

  • Writing the type without the underscore (pidt or pid t). The predefined type name is exactly pid_t.
  • Thinking PID 1 can be reused by user programs. PID 1 is predefined for the first system process and is never allocated to a user-defined process.
  • Confusing the PID with the process's data. The PID is just the identifier (the number on the filing card); the task_struct structures hold the full description.

Recap: In Linux every process has a PID of the predefined type pid_t; full per-process detail lives in structures (the task_struct family), the first process has PID 1, and processes form a tree with login/sshd and user shells like bash. Bridge: with several processes alive at once, the system has to keep them organized — which is exactly what process queues do.

3.6 Process Queues

3.6.1 Job, Ready, and Device Queues

With several processes in the system, the CPU must be kept busy — maximum CPU utilization is the goal. To achieve it, the operating system switches execution from one process to another, and the switching can be done on any basis: first-come-first-serve or time-shared, whatever the policy is. But before a process can be brought to the CPU, the system has to maintain queues — processes can be brought for execution only if they are organized in queues.

Hook: a CPU is either working or idle, and an idle CPU earns nothing. The way an operating system keeps the CPU working is to keep a line of waiting processes — queues — and switch between them. No queue, no order, no way to pick the next process fairly.

There are different types of queues:

  • Job queue — the set of all processes in the system. Every process that enters the system lands here first; it is the complete membership list.
  • Ready queue — the processes present in main memory and ready for execution. Not all jobs are in the ready queue, and not all processes are in main memory, because of lack of space or shortage of space.
  • Device queue — a set of processes waiting for an input device or an output device. Each device has its own queue: if a system has five devices connected, it has five device queues, one per device.

Processes are always migrating between these queues, and each queue is managed in the same way.

The three queues form a pipeline, not a single line. A process enters through the job queue, is admitted to the ready queue when it is brought into main memory, is dispatched to the CPU from the ready queue, and when it asks for input or output it joins the device queue for that device. When the device finishes its work, the process comes back to the ready queue. The queueing picture is the standard way textbooks represent process scheduling: each queue is a box, each resource (CPU, device) is a circle serving the queue, and the arrows show the direction of process flow.

3.6.2 How a Queue Is Structured

Each queue has a header (the head) and a tail. The head points to the first process and the tail points to the last process. Each process in the queue carries its information in its PCB, and the processes are linked to one another with pointers: each process points to the next one, the head points to the first, and the tail points to the last.

The queue is so a linked list of process control blocks: the head is a pointer to the first PCB, each PCB carries a pointer field to the next PCB in the queue, and the tail marks the end. In the textbook's words, a ready-queue header contains pointers to the first and final PCBs in the list, and each PCB includes a pointer field that points to the next PCB in the ready queue. Adding a process means pointing the tail's next-pointer at the new PCB and moving the tail; removing a process means splicing its predecessor's pointer around it.

Why the PCB is the queue element: every piece of information the scheduler needs — state, priority, saved registers, memory location — travels with the process in its PCB. A queue of processes is really a queue of PCBs threaded together by pointers. When the queue is empty, the head is null (points at nothing).

3.6.3 Device Queues in a Real System

The ready queue is not the only queue, and there is not just one device queue. If a system has five devices connected, it has five device queues. An example system walks through several devices:

Worked example — device queues in a small system:

  • Magnetic tape — if there are two magnetic tapes, there are two device queues: unit 0 is one tape, unit 1 is the other. In the example, no process is currently attached, so both queues are null — their heads point at nothing because no process is waiting for either tape.
  • Disk — the disk is secondary storage. Its queue has a head pointing to the first process and a tail pointing to the last one: several processes are waiting for the disk, so the head and tail point to different PCBs.
  • Terminal unit — terminal is a general term; an instance of a terminal will have a particular process. In the example, the first and the last pointers both point to a single process, because only one process is waiting on that terminal — a one-element queue whose head and tail coincide.

Sense-check: three device types, and the queue shape changes with the demand. Empty device (tape) → null queue; busy shared device (disk) → multi-process queue; lightly used device (terminal) → single-element queue where head equals tail.

This layout is typical of the ready queue and the other device queues.

3.6.4 Why Processes Move Between Queues

When a process is in the ready queue, it can be brought to the CPU for execution — but not all processes fit in the CPU, and only one process is taken into the CPU at a time. Once running, several events can move the process out:

  • If it needs an input or output device, it is taken away from the CPU and placed in the I/O (device) queue; whenever an I/O request is made, that happens.
  • If it is a time-sharing process and its time slice expires, it must leave the CPU. The professor gives the slice as on the order of a few seconds — "two seconds... to 20 seconds" as heard in the recording. Modern systems use a far smaller quantum: the standard textbook figure for a time quantum is 10 to 100 milliseconds, and the exact value is a policy choice of each operating system. Either way, the rule is the same: after the slice, whether the process completed or not, it is relinquished from the CPU and returns to the ready queue to wait for its turn again.
  • If the running process forks — creates a child — the child has its own separate address space and gets executed separately. Forking means creating a child process. After the child completes, it may go back to a queue for its next execution.
  • If an interrupt occurs, the process waits for the interrupt; once the interrupt routine completes, the process returns to the ready queue if necessary — otherwise it is terminated.

That is the process scheduling representation: queues with PCBs linked by pointers, and processes migrating between them as the CPU, I/O, and interrupts demand.

Pitfalls:

  • Thinking the ready queue contains every process in the system. The job queue holds all processes; the ready queue holds only those in main memory and ready to run.
  • Believing each process has one home. A process migrates constantly — ready → running → device queue → ready — and this movement is the normal life of a process.
  • Assuming a queue is a fixed array. Queues here are pointer-linked lists of PCBs, grown and shrunk as processes are added and removed.
  • Conflating "time slice expired" with "process finished". The slice is a cap, not a completion signal: the process is preempted mid-work and returns to the ready queue.

Recap: Job queue = all processes; ready queue = processes in memory ready to run; device queue = processes waiting on one device. Queues are head-and-tail linked lists of PCBs, and processes migrate between them. Bridge: the act of moving one process out of the CPU and another in is so important it has its own name — context switching — the next topic.

3.7 CPU Scheduling and Context Switching

3.7.1 One Process in the CPU at a Time

The operating system executes processes, and at any moment only one process can be in execution in the CPU. When multiple processes are present, the CPU must be used effectively — maximum utilization again — which means the OS must switch from one process to another. That switching is called context switching.

Hook: a single-core CPU is like a single lecturer with many students' papers to grade. The lecturer can grade only one paper at a time, but by putting a paper down and picking up the next, every student makes progress — as long as the lecturer remembers exactly where each paper was left. The "exactly where it was left" part is the context, and the act of putting one paper down and picking up another is the context switch.

On a single-processor system there is never more than one running process. If there are more processes, the rest wait until the CPU is free and can be rescheduled. The purpose of switching is not speed of one process but usefulness of the whole machine: while one process waits for input or output, another computes.

3.7.2 When a Process Leaves the CPU

A running process leaves the CPU for any of the reasons from the queue discussion: an I/O request moves it to a device queue, time-slice expiration moves it back to the ready queue, forking puts a child in execution, and an interrupt sends it to wait. In every case something has to be saved before the CPU can do anything else.

The list of reasons is exactly the state-transition list from the process-states topic, seen from the CPU's side: the CPU cannot simply abandon a process, because when the process returns it must resume at the same instruction with the same register values. The saved "something" is the process's context — its complete current state.

3.7.3 The Context Switch Walkthrough

Here is the classic two-process example. Imagine two processes, process 0 and process 1. Process 0 is in execution when an interrupt occurs — or a system call is made. System calls have many forms: opening a file, reading or writing a file, creating a directory, changing the mode of a file — any of these can happen while a process runs.

Worked example — a full context switch between two processes:

  1. Save old: process 0 is executing. An interrupt or system call arrives (say, an open-file request). The operating system saves the state of process 0 into its own process control block, PCB 0. The saved state is the context: program counter, registers, process state — everything process 0 needs to resume.
  2. Load new: after saving the state of process 0, the operating system loads the state from PCB 1. Until that moment, process 1 was idle, because only one process can be in execution at a time. Now process 1 runs, starting exactly where its saved context says it stopped.
  3. Run: process 1 executes for some time.
  4. Switch back: another interrupt occurs; the state of process 1 is saved into PCB 1, and the system loads the state from PCB 0 — picking up exactly where process 0 left off — and process 0 resumes execution.

Sense-check: after two switches, both processes have run, and each was interrupted and later resumed precisely at its own next instruction — that precision is only possible because each save captured the full context.

This repeated save-and-load, switching from one process to another, is the context switch. The context of a process — its complete information — lives in its PCB, and switching contexts means saving the old process's context and loading the new one's.

Context switching is not casual travel from one process to another; proper work is done each time. The whole thing happens within a fraction of a second, so from the user's point of view nothing seems to happen at all — the user sees one smooth session, while underneath the system is saving and restoring contexts dozens of times per second.

3.7.4 Overhead and Hardware Support

The context-switch time is always an overhead: it takes time to save the state of the old process and time to bring the new process into memory for execution. The overhead grows if the system itself is complex — a complex system means more switch time and more overhead. Everything depends on the hardware.

"Overhead" here means pure cost: during a context switch the system does no useful work at all — no user instruction executes while the kernel is saving and restoring registers. Context-switch time varies from machine to machine depending on memory speed, the number of registers that must be copied, and the existence of special instructions (such as a single instruction that loads or stores all registers). Typical textbook speeds are a few milliseconds.

The hardware can help. If each CPU has multiple sets of registers, multiple processes can be loaded at once and executed, because the states do not have to be flushed out and reloaded every time; the overhead involved is reduced. That is the hardware angle on context switching: more register sets, less overhead.

Some processors (for example, the Sun UltraSPARC) provide multiple sets of registers; on such hardware a context switch can be reduced to changing a pointer to the current register set — no copying of registers to memory at all — provided there are enough register sets for the active processes. Once more active processes exist than register sets, the system falls back to copying registers to and from memory, as before.

Pitfalls:

  • Thinking a context switch is free. It is pure overhead: no useful work happens during the save and load, and complex systems pay more per switch.
  • Believing the process that was interrupted is lost. An interrupt saves the context first; the process steps aside and resumes exactly where it stopped.
  • Confusing a system call with a process switch. A system call triggers the same save-and-load machinery when it leads to a different process taking the CPU — the call itself is just the event that forces the switch.
  • Assuming more cores remove the need for context switching. Even with multiple cores, each core still switches between the processes assigned to it; cores add parallel execution, they do not remove preemption.

3.7.5 Student Questions and Answers

Q: Is multiprocessing less efficient?

A: No. With multiple cores — and modern chips have many cores, each core having its own CPU — each core can do work, so the time taken to complete a process becomes very fast. It is not a complex drawback. The main thing we have to bear is the expense. Depending on the architecture and the system, processors may even have 64 cores. We can execute multiple operations by paying the cost of accessing those cores. For example, compare an Intel i7 with an i9 — or an i5 — and look at the speed difference, then look at how much you have to pay for the different Intel versions. It is not only Intel: ARM is there, and NVIDIA has its own processors with their own number of cores, which they use in their organization for their business.

The doubt behind the question is easy to sympathize with: "if switching between processes costs overhead, wouldn't running several processes at once make the machine slower?" The correction: the switch overhead is the cost of sharing one core. With several cores, the processes do not merely share — each core executes its own process, so completion time falls, and the overhead per core stays tiny. What the student should remember is the trade the professor names: more cores cost money, and the price difference between processor tiers (Intel i5, i7, i9; ARM designs; NVIDIA chips) is the market reflection of that added hardware.

Recap: Only one process executes per CPU at a time; leaving the CPU always means saving context to the PCB and loading another process's context — the context switch — which is pure overhead the hardware can reduce with multiple register sets, and multiprocessing with many cores buys speed at the price of hardware expense. Bridge: who decides when to switch? The schedulers — three of them — are the next topic.

3.8 Schedulers

3.8.1 The Long-Term Scheduler and Degree of Multiprogramming

Whenever processes are discussed, schedulers must be discussed too. There are three schedulers: the long-term scheduler (also called the job scheduler), the medium-term scheduler, and the short-term scheduler (also called the CPU scheduler).

The long-term scheduler selects the process that should be brought into the queue first. Its first job is bringing processes into the ready queue — a process must be in the queue before it can come for execution in the CPU. The long-term scheduler is not invoked frequently; it may be slow and infrequent, because its job is one step: put the process in the queue, then it can later be brought for execution.

The long-term scheduler controls the degree of multiprogramming — the number of jobs or programs that are in memory for execution. Because this scheduler decides what enters the ready queue, there will always be a limit on how many processes are present in the ready queue at once.

The word "degree" is the clue to the scheduler's power: the long-term scheduler chooses how many processes compete for memory at a time. In a batch system, far more processes are submitted than can be executed immediately; they are spooled to disk and kept for later. The long-term scheduler selects processes from that pool and loads them into memory — so it literally controls the number of processes in memory. Since minutes may pass between one new process and the next, the long-term scheduler has time to choose carefully, and it can afford to be slow.

3.8.2 The Short-Term Scheduler

The short-term scheduler (CPU scheduler) does the opposite of the long-term one. Whichever process is ready for execution is brought into the CPU and executed. The short-term scheduler runs for only a few milliseconds at a time and is invoked very fast and very frequently. In today's systems, the short-term scheduler runs far more often than the long-term one.

The short-term scheduler selects from among the processes that are ready to execute and allocates the CPU to one of them. Frequency is its defining feature: a process may run for only a few milliseconds before waiting for I/O, so the short-term scheduler must run at least once every 100 milliseconds — it is invoked fast, frequently, and must itself be fast, because time spent scheduling is time the CPU is not executing user work.

Scheduler Also called Selects Runs how often Controls
Long-term Job scheduler Processes from the job pool into the ready queue Rarely — minutes apart Degree of multiprogramming
Short-term CPU scheduler One ready process for the CPU Very often — at least every 100 ms Which process runs next
Medium-term (Swapping) A process to remove from memory / bring back As needed (memory pressure, process mix) Memory load and process mix

When to pick which: every system has a short-term scheduler; time-sharing systems such as Unix and Windows typically have no long-term scheduler and simply put every new process into memory, while batch systems rely on the long-term scheduler to admit jobs; the medium-term scheduler appears only in systems that need swapping.

3.8.3 The Medium-Term Scheduler and Swapping

There is also a medium-term scheduler, which can be added to the system. Suppose we want to decrease the number of processes in memory at once. Even if a process is not completed — if it has only partially executed — we can swap out the process, taking the partially executed process out of the CPU and out of memory. Why would we do that? Because we should not let a single process occupy the CPU continuously; then the CPU may not be utilized properly and the other processes would have to stop. That should not happen.

If the swapped-out process cannot be placed in the ready queue, it is put into secondary storage and later brought back to the ready queue. The medium-term scheduler is effectively a combination of the short-term and the long-term schedulers — it does both kinds of work.

The swap has the same effect as admission control but in reverse: removing a process from memory lowers the degree of multiprogramming, and bringing it back later raises it again. In the textbook's picture, the medium-term scheduler sits between the ready queue and secondary storage: swap out removes a partially executed process from memory (and from contention for the CPU), and swap in reintroduces it so execution continues where it left off. Swapping may be necessary to improve the process mix or because memory requirements have overcommitted available memory.

Scope — when swapping is needed: one process must never hold the CPU continuously while others wait. If a single process cannot be allowed to keep the CPU, the medium-term scheduler removes it from memory — even mid-execution — and restores it later from secondary storage. The cost of swapping is real (the process's state must travel between memory and disk), so it is used only when the trade pays.

3.8.4 I/O-Bound and CPU-Bound Processes

Processes can be described by what they spend their time on. An I/O-bound process spends most of its time doing input/output operations rather than executing on the CPU. Its CPU burst time — burst time means execution time — is very small, and its I/O time is large. A CPU-bound process is the opposite: it spends most of its time executing in the CPU, with a very long CPU burst time and very little I/O time.

Both have advantages and disadvantages. With CPU-bound processes, there are always processes in the ready queue waiting for execution; if one process takes most of the CPU's time, the rest of the processes have to wait a long time, and starvation — indefinite waiting — results. With I/O-bound processes, the CPU often has no work, so it sits idle and its utilization is low. CPU utilization should always be maximum — this is stressed as very, very important for any system.

The two types are not good and bad — they are two halves of one balance. The long-term scheduler's careful selection is aimed precisely at a good mix: if all processes are I/O-bound, the ready queue is almost always empty and the short-term scheduler has little to do; if all processes are CPU-bound, the I/O waiting queues are almost always empty and the devices go unused. A system with the best performance combines both kinds, so the CPU and the devices are never idle at the same time.

Pitfalls:

  • Thinking starvation only happens to unlucky processes. It is a structural outcome: CPU-bound processes hog the CPU, and the rest wait indefinitely — the professor flags this as the danger of CPU-bound mixes.
  • Believing I/O-bound processes are harmless. They leave the CPU idle, and idle CPU means lost utilization — also flagged as unacceptable.
  • Using "burst" without knowing it means execution time. Burst time is the run of CPU execution between I/O waits; a short burst with long I/O time is the signature of an I/O-bound process.

3.8.5 Scheduling on Mobile Systems

Mobile systems illustrate these trade-offs in the real world. In the earlier versions of iOS, only one process was allowed to execute at a time; the others were suspended. The reason is a limit: only one full-time process, controlled with the help of the user interface. If multiple processes always ran in the background and memory was always occupied, the battery and the lifetime of the mobile phone would suffer. So iOS limited the system to a single foreground task rather than running more processes in the background.

Android does not do all these things the same way. Android allows both foreground and background processes, but within limits. It takes a service and performs that service; the service keeps running in the background, and because of that the foreground processes are not delayed. The background services have no user interface, and their memory usage is lower. Whether later iOS versions now allow more processes is uncertain — the point stands that mobile OS design balances concurrency against battery and memory.

The mobile trade: on a phone, "always running everything" is not a win — it drains the battery and shortens the device's life. iOS chose a single foreground process with the rest suspended; Android allows background services with no user interface and lower memory use so the foreground stays fast. Both designs are scheduling policy made visible: the same processes-and-queues machinery, tuned for a battery budget instead of an idle CPU budget.

Recap: Long-term scheduler admits processes and sets the degree of multiprogramming; short-term scheduler picks the next process to run, fast and often; medium-term scheduler swaps partially executed processes out to secondary storage and back; I/O-bound and CPU-bound processes must be mixed or the CPU or the devices starve. Bridge: enough about choosing processes — how do processes actually come into existence and leave it? Operations on processes (creation and termination) are next.

3.9 Operations on Processes

3.9.1 Creating a Process: The Process Tree

A process can be created, it can be executed, and at last it can be terminated — those are the operations on processes.

Whichever program we are executing is the parent process. The parent can create child processes, and a child can in turn create another child, and so on, as long as there is a need. The result is a tree of processes. Each process is identified and managed with the help of its process identifier (PID).

Not every resource is separate for every process. The address space is created separately and the code is separate, but certain data has to be shared between the parent and the child — how that sharing takes place is covered later. Two execution options exist: the parent and child can execute concurrently, or the parent can wait until the child completes. Either option is allowed.

The two free choices of process creation:

  1. Execution: the parent continues executing concurrently with its children, or the parent waits until some or all of its children terminate.
  2. Address space: the child is a duplicate of the parent (same program and data), or the child gets a new program loaded into it.

Unix gives you both choices: fork() first creates a duplicate child, and then exec() replaces the child's memory image with a new program. Windows collapses the choice: CreateProcess() loads the new program directly into the child's address space at creation.

The tree picture is not decoration — it is the operating system's actual bookkeeping. Every process knows its parent (the process that created it) and its children (the processes it created), and those links form the tree. On Unix, ps -el prints enough information for each process to trace its ancestry all the way back to the root process.

3.9.2 Creating a Process in Unix with fork

The Linux process list shows the tree idea in practice: the first process has PID 1, then login, then sshd for secure shell; these IDs are predefined and never allocated to user-defined processes. In C, a process is created with the fork command.

The fork() call creates a child process with its own ID. The return-value convention needs care because the source garbles it: fork() returns 0 to the child process, returns the child's PID to the parent, and a negative value (typically −1) signals an error — the child was not created. This is the standard convention, confirmed by the reference treatment: the return code for fork() is zero for the new (child) process, whereas the nonzero process identifier of the child is returned to the parent. When the error case occurs, the standard error stream — which always represents the console — prints an error message and the call returns. If the fork succeeds, the child gets its own ID and executes a separate program.

The single most surprising thing about fork() is that one call returns twice: the child continues from the same point in the code as the parent, and the only difference between the two processes is the value fork() returned. That return value is the branch test — it is how the same code behaves differently in the two processes.

Worked example — the classic Unix fork program (the professor's example):

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid;

    /* fork a child process */
    pid = fork();

    if (pid < 0) { /* error occurred */
        fprintf(stderr, "Fork Failed");
        return 1;
    }
    else if (pid == 0) { /* child process */
        execlp("/bin/ls", "ls", NULL);
    }
    else { /* parent process */
        /* parent will wait for the child to complete */
        wait(NULL);
        printf("Child Complete");
    }
    return 0;
}

Step by step:

  1. fork() creates a child process with a PID. Both processes now run the same code, each with its own return value.
  2. The program checks the PID: a negative PID means an error — the child process was not created; the standard error (the console) prints Fork Failed and the program returns.
  3. If fork succeeded, the child has its own ID (in the child, pid is 0) and goes to a different address space; it starts executing a different command — in the example, the ls command, given by its full path /bin/ls through the execlp() call.
  4. After executing that command, the child reaches exit.
  5. Meanwhile the parent executes the wait command: the parent waits for the child to complete, then resumes.
  6. After the child completes, the parent's printf("Child Complete") gives output for reference, and both processes come out.

Sense-check: exactly one process is created by the fork(), so a single run of this program yields two processes — the parent (which prints Child Complete after waiting) and the child (which becomes ls). The error branch exists so a failed creation cannot silently run the child's code.

Observations from real Linux behavior: the ID of the child process is usually one more than the ID of the parent process, and we cannot know in advance which process executes first — it depends on the scheduling done by the operating system. At the end, the parent waits for the child, and then both terminate. It is also possible that the parent waits for the child to complete, then resumes and completes itself — both behaviors happen, and we do not control which.

3.9.3 Creating a Process in Windows with CreateProcess

Windows also creates separate processes, with a different mechanism. Before creating the child, memory has to be allocated: if the memory already exists, the space is emptied before the start of the child process. Variables for the startup information and the process information are allocated zeroed memory, and their size is determined so the memory can be allocated. Then the CreateProcess function creates the child process.

The two structures explain the steps: STARTUPINFO specifies properties of the new process (window size, appearance, handles to standard input and output), and PROCESS_INFORMATION receives a handle and the identifiers of the newly created process and its thread. The program calls ZeroMemory() on both structures before CreateProcess() runs — that is the "allocated zeroed memory" the professor describes. Unlike fork(), CreateProcess() expects no fewer than ten parameters, and the new program is loaded into the child's address space at creation time, not after.

Worked example — opening MS Paint on Windows (the professor's example):

  1. CreateProcess tries to create the process for MS Paint. The application name is given, and pointers to the zeroed STARTUPINFO and PROCESS_INFORMATION structures are passed.
  2. If the child process is not created successfully, the function returns an error.
  3. If it succeeds, the Paint application opens.
  4. While Paint runs, the parent waits. The WaitForSingleObject function waits for the child to complete — it is passed the child's process handle (the pi.hProcess field) and blocks until that process exits.
  5. Once the child completes, both processes close the handle that was opened, and the program comes out.

Sense-check: the parent does nothing while Paint is open; the moment Paint exits, WaitForSingleObject returns, the handles are closed, and the program ends. The wait is the same idea as wait() in Unix, implemented with a handle instead of a process ID.

Windows programming of this kind is somewhat difficult compared to Unix, which is why Windows programming courses are less common.

3.9.4 Terminating a Process

Once a process has been created, it has to be terminated at some point. A process can terminate itself with the exit system call, or the parent can wait for the child to complete, at which point the resources that were allocated to the process are deallocated.

A process reaches a normal end when it finishes its final statement and asks the operating system to delete it with the exit() system call. At that point the operating system deallocates all of the process's resources — physical and virtual memory, open files, and I/O buffers — and may pass a status value to the parent through wait().

Sometimes a process terminates abnormally with the abort system call. Aborting is needed when:

  • the child has exceeded the allocated resources,
  • the task that was allocated to the child is no longer required,
  • or the parent is exiting, so the child should not continue.

These are the same three reasons the reference lists: the child has exceeded its allocated resources, the task assigned to the child is no longer required, and the parent is exiting while the operating system does not allow a child to continue.

If the parent terminates, the child must stop too. This is cascading termination: all children, grandchildren, and whatever was created thereafter must be terminated. The termination always has to be initiated by the operating system, and each parent process has to wait for the termination of its child using the wait system call.

Pitfalls:

  • Thinking a process can be terminated only by itself. A parent can abort a child — but only its own children; otherwise users could kill each other's jobs.
  • Forgetting that exit is the normal path and abort is the abnormal one. Abort exists for resource overuse, obsolete tasks, and parent exit.
  • Confusing cascading termination with ordinary cleanup. Cascading means the whole subtree dies: children, grandchildren, and everything created after — always initiated by the operating system.

3.9.5 Zombies and Orphans

Two special names follow from these rules. If a child process has exited but no parent is waiting for it — the parent has not invoked wait — the child is a zombie process. If the parent terminates without invoking wait, the child becomes an orphan process. These are different terminologies that must be understood distinctly.

The two cases are easy to mix up, so it helps to fix them by their causes:

  • Zombie: the child is dead, but its parent has not yet collected it with wait(). The child's code is gone, but the operating system still keeps its entry (PID and status) so the parent can read how it ended. The zombie persists until the parent calls wait().
  • Orphan: the parent died first, without waiting. The child is still alive — it is now an orphan. On Unix the operating system solves the problem by assigning the orphan to the init process (PID 1) as its new parent, so the child still has a parent to collect its status when it eventually finishes.

In one sentence: the zombie is a dead child nobody collected; the orphan is a live child whose parent died. The first is a cleanup problem, the second a custody problem — and the reference's rule for orphans is that the init process becomes the new parent so the child's status still gets collected.

3.9.6 Real-World: The Chrome Browser

Real-world: Chrome is the well-known example. In its early days, Chrome ran a single process for the whole application: if something happened to that process, the other processes also got hanged or crashed. Later, Chrome moved to multiple processes, one per interface — each tab renders its pages in its own process, whether the page is HTML or JavaScript, and plugins can also run within a particular tab. The result is multiprocessing: more than one process runs at a time, and even if one process is affected, it causes no trouble to the other running processes. That is exactly the benefit the process model provides.

Chrome is the process-tree model in production: the browser creates a process per tab, each tab is a child of the browser process, and the failure of one child does not cascade into the others. Compare this with the cascading termination rule — that rule exists for systems that chose to kill children when the parent dies; Chrome deliberately inverts the idea and isolates children from each other, which is precisely the isolation that separate address spaces buy.

Recap: Processes are created (Unix fork() — returns 0 to the child, the child's PID to the parent, −1 on error; Windows CreateProcess()), executed (concurrently or by parent waiting), and terminated (exit normally, abort abnormally, cascading termination when the parent dies); a dead child nobody collected is a zombie, a live child whose parent died is an orphan, and Chrome's per-tab processes show the value of isolation. Bridge: processes that work together — independent and cooperating processes — are the next step toward communication.

3.10 Independent and Cooperating Processes

3.10.1 What Makes a Process Independent or Cooperating

A process can be independent or cooperating. An independent process does not affect other processes and is not affected by them. A cooperating process, in one way or another, affects — or is affected by — other processes.

The textbook puts the test in one line: any process that does not share data with any other process is independent; any process that shares data with other processes is a cooperating process. The dividing line is data. Two calculators running side by side, each with its own inputs, are independent — one cannot change the other's result. A download manager feeding a video player is cooperating: the player's behavior depends on the data the downloader produces.

3.10.2 Why Cooperation Is Worth It

Cooperation is not a luxury; there are four concrete reasons to want cooperating processes.

  • Information sharing — suppose three processes need some data. If each is given a separate copy, each copy consumes space and storage in memory. Instead, one shared portion of memory can be used by all the processes. That saves storage.
  • Computation speedup — if a big job has many subtasks, each subtask can be done in parallel, increasing the speed of execution.
  • Modularity — a huge program (a very big process) can be divided into many subtasks, for readability and also for execution. This is one of the main benefits.
  • Convenience — the user, as a programmer, finds the system easier to use: if two or three programs need to access a particular data item, they can take it from the same place instead of each fetching it from a different storage location.

The shared data can be anything: a program, a function, or a variable. If a variable is used by all the processes, declaring it once is enough; it can be created once and used many times by many processes. (How the sharing is actually used is a separate matter, covered later.)

The four reasons map onto everyday engineering situations. Information sharing is one shared address book instead of three copies that can disagree. Computation speedup is dividing a large calculation among several workers. Modularity is splitting one enormous program into small, testable pieces — a system of cooperating processes is easier to build and maintain than one giant process. Convenience is a shared scratchpad: several tools read the same configuration file instead of each holding its own stale copy.

Pitfalls:

  • Calling a process cooperating just because it runs at the same time. Concurrency alone does not make processes cooperate — sharing data does.
  • Assuming sharing always means shared memory. Sharing can be a shared file, a shared variable, or messages — the point is that one process's data affects another.
  • Forgetting the cost side of speedup: parallel subtasks speed execution only when the machine has multiple processing elements; on a single CPU, the same subtasks just share time.

3.10.3 The Two IPC Models

For cooperating processes to work, they need inter-process communication (IPC): the processes share memory or data and communicate among themselves. There are two very important models of IPC:

  • Message passing — different messages, say M0 through MN, are held in memory, and each process — process A or process B — goes and picks from the queue. The messages are common to both processes.
  • Shared memory — a portion of memory is dedicated particularly to the message or the data, and process A and process B go and access only that particular place.

Both models get examined next, starting with a shared-memory example.

The two models differ in who works the machinery:

Dimension Message passing Shared memory
Where data lives In messages sent between processes In one dedicated shared region
Who moves the data The operating system (system calls) The processes themselves (plain memory reads/writes)
Speed Slower — kernel intervention per message Faster — no kernel help after setup
Best for Small amounts of data, different machines Large amounts of data, same machine

When to pick which: use shared memory for speed and convenience when the processes are on one machine and the data volume is large; use message passing when the processes may be on different machines or the data is small and must not conflict.

Recap: Independent processes share no data and are unaffected by each other; cooperating processes share data for information sharing, computation speedup, modularity, and convenience — and they communicate through one of two IPC models, message passing or shared memory. Bridge: the lecture now works the shared-memory model in full detail with its classic example, the bounded buffer problem.

3.11 Shared Memory and the Bounded Buffer Problem

3.11.1 The Producer-Consumer Problem

The classic shared-memory example is the bounded buffer problem, and its most famous instance is the producer-consumer problem. There is a shared data item — a buffer — with a fixed size. (The source garbles the size declaration — "declared as time"; the standard treatment declares a fixed size, conventionally called , for example slots.) A producer is a process that produces an item; a consumer is a process that consumes the item.

Real-world: the supermarket analogy makes it vivid. In a supermarket there are n items; the items are produced by producers and supplied to the supermarket, and consumers buy them. Suppose the producer produces only 10 items instead of 100, but the number of consumers is large. Take bread: bread is consumed by most consumers. If only 10 bread packets are there and 100 consumers want them, the 100 consumers cannot all buy from this supermarket; the same producer has to supply other supermarkets too, and it becomes a very big problem. That is the intuition: production and consumption must balance.

The analogy maps directly onto the mechanism. The supermarket shelves are the buffer; the bakery trucks are the producer; the shoppers are the consumer. If production lags demand, shelves go empty and shoppers wait; if the shelves are full, the bakery has nowhere to put fresh bread and must wait. The buffer is bounded precisely because the shelves hold a fixed number of items.

The two pointer variables: the buffer is a shared array of slots, and two shared variables track it. The variable in points to the next slot to be filled — the place where the producer will put its next item. The variable out points to the next slot to be taken — the place from which the consumer will take its next item. in advances on production, out advances on consumption, and comparing the two tells whether the buffer is full, empty, or in between.

The buffer has a fixed size. Until the buffer is empty, the producer can produce items; if, say, 10 items have to be produced, the producer fills the buffer with them. Two variables, in and out, track the state of the buffer. If the buffer size is reached — in equals the buffer size — the buffer is full: every slot is filled and no further item can be placed.

3.11.2 Walking Through the Buffer with in and out

The algorithm works like this:

  • Each time an item is produced, it is placed in the buffer, and the in variable — the position of the next item to be filled — is incremented. in indicates the location in the buffer where each item is going to be filled.
  • When in equals the buffer size, the buffer is full, and the producer cannot fill any more items.
  • The consumer tries to consume. The out variable tells the position of the next item to be taken.
  • If out and in are equal, the buffer is empty — everything has been taken out (a mid-sentence self-correction makes the point: the buffer is empty because items have been taken out, not because they have been filled). The consumer has to wait until the producer puts in an item.
  • Otherwise the consumer can consume: it takes an item from the buffer and uses it, and after using it, out is incremented.

So: in == buffer size means the buffer is full; in == out means the buffer is empty. The producer and consumer are two separate processes, and the buffer they both use is the shared memory — the shared portion of the communication.

Worked example — producer and consumer on a 10-slot buffer:

  1. Produce: the producer creates an item and places it at position in. The first item goes to slot 0, and in is incremented from 0 to 1. The second item goes to slot 1, and in becomes 2. After 10 items, in is 10 — and in equals the buffer size, so the buffer is full. The producer must stop and wait for space.
  2. Consume: the consumer checks out and in. While they differ, an item is available. It takes the item at slot out (slot 0 first) and increments out to 1. After all 10 items are taken, out reaches 10 — and now out equals in (both 10), so the buffer is empty. The consumer must wait for a new item.
  3. Balance: production fills toward in == buffer size; consumption drains toward in == out. The producer can resume only when the consumer has freed slots, and the consumer can resume only when the producer has filled slots.

Sense-check: with a 10-slot buffer, the producer never writes a 11th item while full, and the consumer never takes an item from an empty buffer — the in and out comparisons enforce both rules.

One refinement from the standard treatment: the textbook version wraps the buffer around (a circular array), so full is written as , which allows at most items in the buffer at once. The professor's rule (in == buffer size means full, in == out means empty) is the simpler, linear version of the same idea and is the one to use for this course.

3.11.3 Who Controls the Shared Memory

A key property of the shared-memory model: the communication is under the control of the user processes, not the operating system. If two processes wish to access a particular area, that access is taken care of by the user process; the system does not handle it. That means the producer and the consumer must synchronize their access to the buffer themselves. How the synchronization takes place is covered later — the point for now is that the OS hands over the shared region and the processes manage the coordination.

The operating system's role stops at handing over the region: it establishes the shared memory, and from then on every read and write is an ordinary memory access with no kernel help. The professor's warning follows directly: if the consumer tries to consume before the producer has produced, there is nothing to consume — the consumer must wait, and it falls into starvation. The two processes must be synchronized; that is very important.

Pitfalls:

  • Consuming before producing. If the consumer runs ahead of the producer, the buffer is empty and the consumer starves — the professor flags this as the central synchronization danger.
  • Letting both run unsynchronized. The producer and consumer must coordinate; the OS provides the shared region but does not police the timing.
  • Mixing up the two fullness rules. Full means in == buffer size (every slot filled); empty means in == out (everything taken out — the correction runs from "filled" to "taken out").
  • Forgetting that shared memory needs an agreement. The processes must agree that a region is shared — the OS normally keeps processes out of each other's memory, and shared memory exists only because the processes deliberately removed that restriction.

Recap: The bounded buffer pairs a producer and a consumer around a shared fixed-size buffer tracked by in (next slot to fill) and out (next slot to take): in == buffer size means full, in == out means empty, and the user processes — not the OS — are responsible for synchronizing. Bridge: when processes do not share memory, the other IPC model takes over — message passing, next.

3.12 Message Passing

When memory is not going to be shared between two processes, a message can be passed between them. Real-world: the mail system — sending mail from one person to another means a message is passed from one person to another, and not just to one person but to any number of persons. That is a type of inter-process communication.

Message passing has mainly two operations: send and receive. The message itself can be fixed in size or variable.

For two processes P and Q to communicate, they have to form a link: I should know the ID of the other process, and the other process should know mine; only then can messages be exchanged. Implementing message passing forces us to answer several design questions:

  • How is the link established when it is needed?
  • Can a link be associated with more than one process?
  • How many links can there be between any two processes?
  • What is the capacity of the link — the size of the message it can accommodate?
  • Should the link be bidirectional (both ways) or unidirectional (only one way)?

The two primitives: send(message) and receive(message) are the whole API of message passing. A message-passing facility provides at least these two operations, and messages can be fixed-sized or variable-sized — a trade: fixed-size messages make the system-level implementation straightforward but the programmer's job harder; variable-size messages need a more complex implementation but simplify programming. The link between P and Q is the abstract channel over which messages travel.

The mail analogy carries the important part: the message system is how processes communicate without sharing an address space — which makes it the natural choice when the processes live on different computers connected by a network.

A communication link can be physical or logical. A physical link uses shared memory, a hardware bus, or a network. A logical link can be direct or indirect, synchronous or asynchronous, and can use automatic or explicit buffering. All these options come up next.

The distinction is the same as a road versus a route: the physical link is the real conduit (shared memory, a bus, a network cable), while the logical link is the design of how send and receive behave over that conduit — who is named, who waits, and where messages sit between send and receive.

3.12.3 Direct Communication

In direct communication, both processes exchange the message directly. The send operation names the ID of the destination process and the message; the receive operation names the process from which the message is received and the message itself.

The properties of direct communication:

  • The link is established automatically.
  • There is only one pair of communicating processes — a link exists only between that pair.
  • There is only one link between them.
  • The link is unidirectional; sometimes it may be bidirectional.

In the standard formulation the primitives look like send(P, message) — send a message to process P — and receive(Q, message) — receive a message from process Q. This is called symmetric addressing: both sides name the other. A variant, asymmetric addressing, lets only the sender name the recipient: send(P, message) paired with receive(id, message), where id is filled in with the name of whichever process sent. The price of direct naming is limited modularity: if a process's identifier changes, every process that names it must be updated.

3.12.4 Indirect Communication: Mailboxes and Ports

In indirect communication, messages go through a mailbox. As soon as a message is sent, it is put into the mailbox; from the mailbox it goes to the recipient's mailbox; from that mailbox the recipient can access it at any time. The mailbox is also called a port, and each port has an ID.

The mailbox is shared, and the link can be established with more than one process. The link can be unidirectional or bidirectional — these are the properties of indirect communication.

To use indirect communication we create a mailbox (we communicate only through a mailbox, for sending and receiving), and we destroy the mailbox later when it is no longer required. The two parameters used are: send to mailbox A and receive from mailbox A.

The mailbox is an object into which messages are placed and from which they are removed, with a unique identification — in POSIX message queues, for example, an integer identifies each mailbox. The primitives become send(A, message) and receive(A, message): processes communicate only through the mailbox, so the mailbox — not the process — is the named entity. This indirection is what makes indirect communication more modular than direct naming: the processes never name each other, and a mailbox can outlive the processes using it.

Sharing a mailbox raises a question. Suppose P1, P2, and P3 all share mailbox A; P1 sends, and P2 and P3 receive. Can we tell who gets the message — P2, P3, or both? If both receive it, they may both try to change it; we have to decide whether that is acceptable. The resolution offered: if we can associate only one link with at most two processes, then at a time only one process can execute the receive operation; the receiver is selected by the system, and the sender has to be notified which receiver (P2 or P3) got the message. Messages communicated through indirect communication must not be tampered with — that is an important property.

The resolution is one of three standard choices: restrict a link to at most two processes; allow at most one process at a time to execute receive; or let the system pick the receiver arbitrarily (round-robin, say) and identify the receiver to the sender. Each choice removes the "both P2 and P3 take the same message" conflict.

3.12.5 Blocking and Non-Blocking Message Passing

Message passing can be blocking or non-blocking.

Blocking means synchronous: in a blocking send, as soon as the sender has sent a message, it waits for the receiver — the sender stays blocked until the receiver receives the message, and only then comes out of the blocking. In a blocking receive, the receiver is blocked until a message is available to it. Both together are synchronous.

Non-blocking means asynchronous: in a non-blocking send, the sender sends the message and continues with some other job right away. In a non-blocking receive, the receiver accepts the message and does other work, or checks for the message later. The message can be a valid message or a null message — anything is possible.

The standard names for the four combinations: blocking send (sender waits until the message is received), non-blocking send (sender sends and resumes), blocking receive (receiver blocks until a message is available), and non-blocking receive (receiver retrieves either a valid message or a null).

The key warning: if both the sender and the receiver are blocking, we reach the situation where each is stuck waiting for the other. That should not be there. Either we block the sender or we block the receiver — not both. The producer-consumer case is the same lesson: the producer produces an item and the consumer consumes it, and the two should not run in parallel unsynchronized. The rate of the consumer consuming items should not increase compared to the producer. If the consumer tries to consume before the producer has produced, there is nothing to consume, so the consumer must wait — and it falls into starvation. Both sides must be synchronized; that is very important.

Pitfalls:

  • Blocking both sides. If send and receive are both blocking, each process waits for the other forever — a rendezvous can deadlock the pair. Block one side; never both.
  • Letting the consumer run ahead of the producer. Consuming before producing means nothing to consume — the consumer starves. Synchronization is the cure.
  • Forgetting that blocking send = synchronous and non-blocking = asynchronous. The two vocabularies describe the same two behaviors.

3.12.6 Buffering Options

The buffering of messages can be implemented in three ways:

  • Zero capacity — the link cannot queue any messages; the sender must wait until the receiver receives the message. Both are blocking, which, as warned, should not occur.
  • Bounded capacity — the queue can hold some length of n messages. The sender sends and fills the queue; once the queue is full, the sender has to wait. The receiver waits when the queue is empty. Only one of them waits at a time, not both.
  • Unbounded capacity — the queue capacity is unbounded, effectively infinite: the sender never waits to send messages, and the receiver never waits to receive.

Whichever implementation we try, some problem is involved: we should not make both sender and receiver blocking, and we should not be restricted to a fixed length of n messages — that also has issues. The point to remember: only one side should ever wait.

Capacity What the queue holds Sender's behavior Receiver's behavior
Zero Nothing (no buffering) Blocks until the receiver receives Blocks until a message arrives
Bounded At most messages Waits when the queue is full Waits when the queue is empty
Unbounded Potentially infinite Never waits Never waits

The table compresses the whole lesson: the capacity choice decides who waits. Zero capacity makes waiting unavoidable and forces both sides to rendezvous; bounded capacity limits the queue to messages so only the side facing a full/empty condition waits; unbounded capacity removes waiting entirely — but "effectively infinite" memory is a strong assumption. The rule that survives every variant: only one side should ever wait at a time.

Recap: Message passing uses send and receive over a link, which can be physical or logical; direct communication names the peer process, indirect communication routes through a mailbox (port) shared by several processes; blocking (synchronous) versus non-blocking (asynchronous) decides who waits — block the sender or the receiver, never both; buffering is zero, bounded, or unbounded. Bridge: all of this theory shows up in real systems — POSIX shared memory, Mach, Windows ALPC, sockets, RPC, and pipes are the practice, next.

3.13 IPC Systems in Practice

3.13.1 POSIX Shared Memory

In POSIX — the Unix-family standard — IPC can use the shared-memory concept. The shared memory must be created first, and then the processes can access it. Creating shared memory is done with a system call: if the shared memory does not exist, it is created (the standard call is shmget, derived from "SHared Memory GET"); if it already exists, it is opened for reading and writing.

Access permissions are given for all three user classes: the owner, the group, and others. The permission value in the example is 6, meaning : 4 is the read permission and 2 is the write permission — both permissions are granted to all three classes. The shared memory is opened (created if it does not exist, otherwise opened for reading and writing), and the size is set — the maximum size. After the memory has been created, an ID is written; that ID is called the descriptor, and with the help of the descriptor the size is fixed. Then a message is written into the shared memory.

The descriptor is the integer identifier returned by shmget(), and "the size is fixed" refers to the size parameter of the same call: the second parameter of shmget() is the size in bytes of the segment. A process that wants to use the region attaches it to its own address space with shmat() (SHared Memory ATtach), which returns a pointer to the attached region; the process writes its message through that pointer — in the standard example, a 4,096-byte segment is created, attached, and the message is written to it through the returned pointer. When a process is done, it detaches with shmdt(), and the segment can be removed with shmctl().

The POSIX shared-memory sequence — who calls what:

  1. shmget(key, size, mode) — create (or open) the shared-memory segment; returns the descriptor, an integer identifier. The mode gives the permissions.
  2. shmat(id, NULL, 0) — attach the segment to the process's address space; returns a pointer to the region.
  3. Write or read through the pointer — an ordinary memory access from here on; other attached processes see the update.
  4. shmdt(ptr) — detach the region when it is no longer needed.
  5. shmctl(id, IPC_RMID, ...) — remove the segment from the system.

The permissions are the classic Unix mode bits: read is 4, write is 2, execute is 1. Permission value 6 = means read and write, and setting it for owner, group, and others (the three classes) grants both rights to all three.

3.13.2 Mach Message Passing

The Mach kernel uses the message-passing system. Three system calls are involved: send, receive, and RPC. Whenever we send and receive, a mailbox — otherwise called a port — is involved, and the port has to be allocated first for communication.

Mach is a message-based operating system (developed at Carnegie Mellon University, and the basis of the Mac OS X kernel). Most of its communication — including most system calls — travels as messages. The three calls are msg_send() (send a message to a mailbox), msg_receive() (receive a message), and msg_rpc() (send a message and wait for exactly one return message — a remote procedure call, which models a subroutine call that can work between systems). A port is created with port_allocate(), which allocates the mailbox and space for its queue of messages.

If the mailbox is full and the sender and the receiver are both flexible, four options exist:

  • The receiver may wait for n milliseconds.
  • The receiver may wait indefinitely.
  • The receiver may return immediately (when the receiving side finds nothing).
  • The receiver may temporarily store the message somewhere else.

For the sender, when the port is full, it can wait; or it can wait for some milliseconds and do its work; or after sending the message it can come out; or it can store the message somewhere and send it later. Any of these behaviors can happen in Mach-type IPC systems.

The reference account sharpens exactly who gets which options: when the mailbox is full, it is the sending thread that has the four choices — (1) wait indefinitely for room, (2) wait at most n milliseconds, (3) not wait at all and return immediately, or (4) temporarily cache the message with the operating system and deliver it later. The receiving thread, when no message is waiting, can wait at most n milliseconds or not wait at all. The professor's list and the standard list describe the same four behaviors; on an exam, remember them as the flexible send-and-receive options of Mach-type IPC.

3.13.3 Windows ALPC

Windows uses a different mechanism: ALPC — advanced local procedure calls. ALPC works only for processes that are present on the same system; it is not RPC, it is LPC (local procedure calls), so remember that distinction. A port has to be established between the two processes:

Worked example — the ALPC connection between a client and a server:

  1. A handle has to be set with the port: the client makes a request for a connection port to be established with the server.
  2. Once the handle has been requested, a port — in the sense of a mailbox — is created as a portion of memory, and the handle is returned to both the client and the server.
  3. A handle is an ID used for communication. It has to be written to both the client and the server: the client should know which ID to send to, and the server should know from which ID it has received the message.
  4. If the message is very small — less than 256 bytes — a portion of memory called a section object is created and shared. If the message is greater than 256 bytes, the handling is done differently.

Sense-check: after step 3, both sides hold the same communication ID, so the client can address messages to the server and the server can identify the client's messages; the size rule then decides the transport — small messages travel one way, large messages another.

One correction from the reference treatment: in the standard account the small/large rule is the other way around — messages up to 256 bytes are copied through the port's message queue, while larger messages are passed through a section object, which sets up a region of shared memory and avoids the data copy. Both versions agree on the key facts: the handle (ID) is shared by both sides, the port is the mailbox, and the section object is the shared-memory mechanism. Remember the professor's version for the course, and keep the reference's assignment (256-byte boundary with the section object for large messages) in mind.

Windows XP implements the same design under the name LPC (local procedure call): a connection port is a named object visible to all processes, the client opens a handle to the subsystem's connection port and sends a connection request, the server creates two private communication ports and returns the handle of one to the client, and the two sides then use their port handles to send messages and listen for replies. ALPC is the modern, security-hardened evolution of that LPC mechanism — same-system communication only, never over a network.

3.13.4 Client-Server Communication with Sockets

In a client-server system, the client is one system and the server is another; the client may exist somewhere and the server somewhere else. Communication in that case happens with the help of sockets. A socket is an endpoint for communication: the client is the one that makes a request, and the server creates another socket to receive the information; the server responds — one side makes a request, the other responds.

To send information from one system, we need an address. Real-world: without an address, how could a postman deliver a letter? The postman needs the address to know to whom to deliver it. The letter may be delivered within the same area or the same town, or posted from one town to another — in every case an address is needed, and both the sender's address and the receiver's address matter.

In system terms, the receiver's address is the IP address plus the port number — together these form the address. The IP address identifies a system; the port number identifies a process. Not any number can be a port number:

  • All numbers less than 1024 are well-defined ports, reserved for standard services: FTP, SNMP, SMTP, and HTTP each have their own standard port number. Users cannot use these.
  • A user who sends information from a system can be assigned a port number greater than 1024 and at most 65535.
  • There is a special loopback address, 127.0.0.1, which refers to the system we are running on.

Communication from one host to another takes place with the help of the socket: whenever a socket is created, the IP address and the port number are provided.

The socket is defined as an endpoint for communication, identified by an IP address concatenated with a port number; a connection between two processes is a pair of sockets, one at each end. A server listens on a specified port, and once a request arrives it accepts the connection from the client socket. Standard services listen on their well-known ports — a telnet server on port 23, FTP on port 21, a web server on port 80 — and the client side is assigned an arbitrary port above 1024 (for example, port 1625), so every connection is a unique pair of sockets. The loopback address 127.0.0.1 is how a machine talks to itself: a client and server on the same host communicate through TCP/IP using 127.0.0.1 as the server's address.

3.13.5 TCP and UDP Sockets in Java

Java (like any system) offers two flavors of socket. A connection-oriented socket refers to TCP — the Transmission Control Protocol — a connection-oriented service that gives reliable, guaranteed delivery of the message. A connectionless socket is UDP — the User Datagram Protocol — a connectionless service.

In Java's API, connection-oriented TCP sockets use the Socket class (and ServerSocket on the listening side), connectionless UDP sockets use the DatagramSocket class, and MulticastSocket (a subclass of DatagramSocket) sends to multiple recipients. The difference in one line: TCP guarantees delivery of a byte stream over an established connection; UDP fires datagrams with no guarantee, in exchange for less overhead.

Worked example — the Java date server (the professor's example):

  1. A socket is created with the class ServerSocket, and a port number is mentioned in the constructor — the server will listen on that port.
  2. Once the socket has been created successfully, the server waits for connections from clients.
  3. When a connection arrives, it is accepted with the accept function; the server blocks on accept() until a client asks for a connection, and accept() then returns a socket the server can use to talk to that client.
  4. An object is created for the PrintWriter — it writes something to the output screen: the server writes the date (the server name prints the date). The PrintWriter lets the server write to the socket with the ordinary print() and println() methods.
  5. Once the information has been written to the socket, the connection is closed, and the server waits for the next connection.
  6. There can be n clients, and each client may request a particular piece of information.

Sense-check: the loop is server-typical — listen, accept, answer, close, listen again — so the same server serves any number of clients, one connection at a time, each asking for the current date and getting it over TCP.

3.13.6 Remote Procedure Calls

RPC — remote procedure call — lets processes on two systems that are elsewhere communicate. The process in the client system packs the information and sends it; the packing happens in a stub on the client side. The receiving side has a skeleton in the server system. Packing the parameters to send is marshalling; unpacking the parameters on the server side is unmarshalling.

RPC is the abstraction of the procedure call applied across machines: the client calls a procedure as if it were local, and the RPC machinery hides the network. The client stub stands in for the remote procedure; when the client invokes the procedure, the RPC system calls the appropriate stub with the parameters, the stub locates the port on the server and marshals the parameters into a transmittable form, and the message goes to the server, where the server-side stub (the skeleton) unmarshals them and invokes the real procedure. Return values travel back the same way.

The receiver must be able to understand what the client sent, so the data is written in a common form — an external data representation (XDR) — that is portable across different architectures, whether big-endian or little-endian. In Windows, the specification can be written in MIDL, the Microsoft Interface Definition Language.

The portability problem is real: a big-endian machine stores the most significant byte of a 32-bit integer first, a little-endian machine stores the least significant byte first, and neither order is "better". XDR is the machine-independent middle form: the client marshals its machine-dependent data into XDR before sending, and the server unmarshals the XDR into its own machine-dependent form.

The execution of RPC works in steps:

  1. The user calls the kernel — everything has to be done with the help of the kernel.
  2. The kernel sends the information to call a particular procedure X.
  3. The kernel finds the address first — like finding a phone number in order to make a match.
  4. Once it gets the address, it sends the information.
  5. The receiving side finds the response for the message; when it gets the message, it sends it back to the client.
  6. The routing details — where the message is coming from, where it should go, what the port number is, and what the message is — are all resolved while waiting for and receiving the message.

Finding the address (step 3) is called binding: the port number of the needed procedure must be discovered before any call. One way is fixed ports predetermined at compile time; the more flexible way uses a rendezvous daemon (also called a matchmaker) on a well-known port — the client asks the daemon for the address of the procedure it needs, gets the port number back, and then sends its calls to that port.

3.13.7 Pipes: Ordinary and Named

The last communication mechanism is the pipe. Real-world: think of the pipe in a house — a pipe carries water from one place to another, and it has two ends, one for sending and one for receiving. That is a form of communication. For system pipes, we have to check: is it unidirectional (only one way) or bidirectional (both ways)? Is there a relationship between the processes involved? Can the pipe be used over a network?

A pipe acts as a conduit allowing two processes to communicate; it was one of the first IPC mechanisms in early Unix systems. Four design questions decide the kind of pipe: unidirectional or bidirectional; if bidirectional, half duplex or full duplex; whether a relationship (such as parent-child) must exist between the processes; and whether the pipe can work over a network.

There is an ordinary pipe and a named pipe. The ordinary pipe is used between the parent and the child — in the ordinary case the communication is only between the parent and the child. (The source's phrasing here is garbled — "it can be accessed outside the process that created the pipe" followed by "only it is between the parent and the child". The standard property resolves the contradiction: an ordinary pipe cannot be accessed from outside the process that creates it, so in practice it is created by a parent and inherited by a child through fork() — meaning an ordinary pipe requires a parent-child relationship.) The named pipe can be accessed without any parent-child relationship.

In Windows, the ordinary pipe is called the anonymous pipe. The example shows two descriptors, fd0 and fd1: with the help of one descriptor we read, and with the other descriptor we write. We can assume the consumer is at one end and the producer at the other — the producer writes something and it is consumed. (The source garbles which descriptor reads and which writes; the standard convention resolves it: fd[0] is the read end of the pipe and fd[1] is the write end.) One descriptor puts the item in, the other gets it out.

The producer-consumer pattern is exactly how pipes are used: the producer writes to the write end, the consumer reads from the read end, and the pipe buffers the data in between. In Unix the pipe is created with the pipe(fd) function returning the two descriptors; both processes close the end they do not use, and reads and writes are ordinary read() and write() calls because the pipe is treated as a special type of file.

The named pipe is generally more powerful: it is bidirectional, there is no need of a parent, and several processes can use the same named pipe. Both Windows and Unix use named pipes. The ordinary pipe, in contrast, cannot be used outside the parent-child relationship.

In Unix, named pipes are called FIFOs, are created with mkfifo(), appear as ordinary files in the file system, and continue to exist until explicitly deleted; in Windows they are created with CreateNamedPipe(). Ordinary pipes exist only while the communicating processes are alive — once both sides finish and terminate, the ordinary pipe ceases to exist, while a named pipe persists. Both systems agree on the summary: ordinary pipes need a parent-child relationship and work only on the same machine; named pipes drop the relationship requirement, allow several processes to share one pipe, and are the more powerful tool.

Recap: Real IPC systems map the two models onto practice — POSIX shared memory (shmget/shmat/shmdt/shmctl, permissions 6 = 4 + 2 read + write for owner/group/others); Mach message passing with ports and flexible send/receive options; Windows ALPC with connection ports, handles, and section objects; sockets (IP + port, well-known ports below 1024, loopback 127.0.0.1, TCP vs UDP) in Java; RPC with stub, skeleton, marshalling, unmarshalling, XDR, and MIDL; and pipes — ordinary (parent-child, unidirectional, fd[0] reads, fd[1] writes) versus named (bidirectional, no parent needed, several processes).

Exam Guidance Summary

  • Mid-semester coverage: the mid-semester exam covers everything up to and including process synchronization. Scheduling problems are part of the material; synchronization itself is not a problem area, but it is a very important topic.
  • Test week: the test is to be conducted in the second week of February — February 12th to 18th. The schedule will be announced on the portal as and when it is fixed.
  • Textbook: the prescribed textbook is Abraham Silberschatz — any version can be referred to. William Stallings is another reference; anything else needed is available online. Three sets of notes have been shared so far; more references can be requested.
  • Recorded sessions: the recorded sessions cover the whole syllabus for both the mid exam and the final exam. The model is a flipped one: problem-type topics are solved in the live contact sessions, while the recorded sessions carry the core material — go through the recorded sessions, then come back to the contact session. If you cannot attend a session, the recorded version is available to catch up.

Q: Are the recorded sessions available on the portal, and do they cover the syllabus?

A: Yes — the recorded sessions cover the whole syllabus for both the mid exam and the final exam. Problem-type topics are solved in the live contact sessions. The model is flipped: work through the recorded sessions first, then come back to the contact session. If you cannot attend, the recorded version lets you catch up.

  • Upcoming topics: the next session covers threads in detail, followed by some of the processor scheduling topics.
  • How to study: keeping CPU utilization at its maximum is stressed repeatedly, and the queue/state mechanics and the IPC distinctions (shared memory vs message passing, direct vs indirect, blocking vs non-blocking, zero/bounded/unbounded buffering) are the concepts to hold onto — several of these points were emphasized with "remember this" signals during the session.

Exam note: the mid-semester exam reaches up to and including process synchronization, and scheduling problems are examinable material. The four IPC contrast pairs are the concepts to hold onto — shared memory vs message passing, direct vs indirect communication, blocking vs non-blocking, and zero/bounded/unbounded buffering — together with the queue/state mechanics that were stressed with "remember this" signals.

Key Industry Applications

  • Windows taskbar shows live CPU utilization percentages — the accounting side of process management made visible (3.3).
  • Linux represents every process with a PID of type pid_t; the first process has PID 1, followed by login and sshd; the process tree includes bash, ps, and emacs (3.5).
  • iOS and Android show opposite mobile scheduling policies: early iOS allowed one foreground process with the rest suspended to protect battery; Android allows background services within limits (3.8).
  • Chrome moved from a single-process design to one process per tab (HTML, JavaScript, plugins) so that one failure does not crash the others — a working example of multiprocessing (3.9).
  • MS Paint is used to show Windows CreateProcess with WaitForSingleObject (3.9).
  • Supermarket and bread is the analogy for the producer-consumer balance: too few items, too many consumers, and production must spread across more suppliers (3.11).
  • POSIX shared memory with owner/group/others permissions (6 = 4 + 2, read + write) underlies Unix-family IPC (3.13).
  • Mach uses message passing with ports and flexible send/receive options (3.13).
  • Windows ALPC handles same-system communication with handles and section objects; messages under 256 bytes use a shared section object (3.13).
  • Sockets (TCP for reliable connection-oriented delivery, UDP for connectionless) are used in client-server systems; the address is IP + port, ports below 1024 are reserved for standard services (FTP, SNMP, SMTP, HTTP), and 127.0.0.1 is the loopback address (3.13).
  • Java ServerSocket with accept and PrintWriter shows the server side of socket programming (3.13).
  • RPC with stub, skeleton, marshalling, unmarshalling, and XDR lets clients and servers on different architectures (big-endian or little-endian) call remote procedures; MIDL writes the specification in Windows (3.13).
  • Ordinary (anonymous) pipes and named pipes provide unidirectional and bidirectional communication; named pipes are the more powerful choice because they work without a parent-child relationship (3.13).
  • Multi-core processors from Intel (i5, i7, i9), ARM, and NVIDIA show that speed comes at a price — the expense of more cores (3.7).

OS Lecture 3 notes · Processes in Operating Systems

Operating Systems· undergraduate· 2026-08-15

Sections Breakdown

13.1 The Concept of a Process

Programs as passive entities versus processes as active ones; the memory layout of a process and the job/task terminology.

23.2 Process States

The five states - new, ready, running, waiting, terminated - and the six transitions between them.

33.3 The Process Control Block (PCB)

The record that describes a process completely: state, number, program counter, registers, memory, accounting, and I/O status.

43.4 Processes versus Threads

Why threads are lightweight execution units that share an address space while processes are heavyweight and isolated.

53.5 Process Representation in Linux

PIDs of type pid_t, the task_struct family, PID 1, and the Linux process tree.

63.6 Process Queues

Job, ready, and device queues as pointer-linked lists of PCBs, and how processes migrate between them.

73.7 CPU Scheduling and Context Switching

Why only one process runs per CPU at a time, the save-and-load context switch, its overhead, and hardware support.

83.8 Schedulers

Long-term, short-term, and medium-term schedulers, the degree of multiprogramming, swapping, and I/O-bound versus CPU-bound processes.

93.9 Operations on Processes

Process creation (fork and CreateProcess), termination, cascading termination, zombies and orphans, and Chrome's per-tab processes.

103.10 Independent and Cooperating Processes

The dividing line of shared data, the four benefits of cooperation, and the two IPC models.

113.11 Shared Memory and the Bounded Buffer Problem

The producer-consumer problem, the in and out pointers, and why user processes must synchronize.

123.12 Message Passing

Send and receive over physical or logical links: direct and indirect communication, blocking, and buffering.

133.13 IPC Systems in Practice

POSIX shared memory, Mach ports, Windows ALPC, sockets, RPC, and ordinary versus named pipes.

14Exam Guidance Summary

Exam coverage, test schedule, textbook, and study priorities from the session.

15Key Industry Applications

Real systems that illustrate the lecture's concepts: Linux, Chrome, Windows, iOS, Android, and more.

Undergraduate students in Operating Systems

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

The Concept of a Process

Must-know: A program is passive (instructions sitting on disk); a process is active (instructions in execution, one is always running). The process memory picture: text, data, stack, heap, and program counter.

⚠️ Top pitfall: Treating the program counter as a memory region — it is a register holding the address of the next instruction.

Self-check: Which two growth directions do the heap and the stack follow?

Connects to: 3.2, 3.3

Process States

Must-know: Five states: new, ready, running, waiting (blocked), terminated. Only one process can be running at a time; interrupts move running back to ready; I/O waits move running to waiting, and waiting returns to ready when the event occurs.

⚠️ Top pitfall: Confusing waiting with ready: a waiting process lacks an event, not just the CPU.

Self-check: What transition name takes a process from waiting back to ready?

Connects to: 3.3, 3.7

The Process Control Block (PCB)

Must-know: PCB contents: state, process number, program counter, registers, memory information, accounting information, I/O status. Registers are limited, so saved state lives in main memory; processes move from secondary storage to main memory to the CPU.

⚠️ Top pitfall: Forgetting that the PCB holds the saved register values — without them the process could not resume exactly where it stopped.

Self-check: Name the seven kinds of information a PCB records.

Connects to: 3.2, 3.6

Processes versus Threads

Must-know: A thread is lightweight and shares the address space of its creating process; a process is heavyweight, and a child process gets a separate address space with duplicated code and data.

⚠️ Top pitfall: Claiming threads have their own address space — they share the process's space; only processes are separate.

Self-check: Why is creating a thread cheaper than creating a process?

Connects to: 3.5, 3.9

Process Representation in Linux

Must-know: PID is stored in a variable of the predefined type pid_t; Linux uses structures per kind of process information; PID 1 is the first process and is never given to user processes; the process list is a tree.

⚠️ Top pitfall: Writing the type name without its underscore: it is pid_t, not pidt.

Self-check: Which process has PID 1 on a Linux system, and why is that ID never reused?

Connects to: 3.3, 3.9

Process Queues

Must-know: Job queue = all processes; ready queue = processes in main memory ready to run; device queue = processes waiting on a particular I/O device. Queues are pointer-linked lists of PCBs with a head and a tail; time-slice expiry sends a process back to the ready queue.

⚠️ Top pitfall: Treating the ready queue as the full process list — it holds only the in-memory, runnable processes.

Self-check: If a system has five devices, how many device queues does it have?

Connects to: 3.3, 3.7

CPU Scheduling and Context Switching

Must-know: A context switch saves the running process's state into its PCB (save into PCB 0, load from PCB 1, and back) and is pure overhead; context-switch time depends on hardware, and multiple register sets reduce it. Multiprocessing with many cores completes work fast; the main drawback is the expense.

⚠️ Top pitfall: Assuming context switching is free or that interrupts destroy the running process — the process is saved in its PCB and resumes later.

Self-check: What exactly is saved into PCB 0 during a context switch?

Connects to: 3.2, 3.8

Schedulers

Must-know: Long-term (job) scheduler: admits processes, controls the degree of multiprogramming, slow and infrequent. Short-term (CPU) scheduler: picks a ready process, few milliseconds, very frequent. Medium-term scheduler: swap out partially executed processes to secondary storage and bring them back. CPU utilization should always stay maximum; CPU-bound processes cause starvation; I/O-bound processes leave the CPU idle.

⚠️ Top pitfall: Conflating the schedulers: the long-term scheduler admits into the ready queue, the short-term dispatches to the CPU, the medium-term swaps.

Self-check: Which scheduler controls the degree of multiprogramming, and which must run at least every 100 ms?

Connects to: 3.6, 3.7

Operations on Processes

Must-know: fork() returns 0 to the child, the child's PID to the parent, and a negative value (−1) on error. The parent can wait() for the child. exit() terminates normally; abort() abnormally (resource overuse, obsolete task, parent exiting). Cascading termination kills children and grandchildren when the parent dies. Zombie = exited child with no parent waiting; orphan = child whose parent terminated without waiting.

⚠️ Top pitfall: Getting fork's return convention wrong: the child sees 0, the parent sees the child's PID, −1 means the child was not created.

Self-check: What does fork() return to the parent, and what does it return to the child?

Connects to: 3.3, 3.5

Independent and Cooperating Processes

Must-know: Cooperating processes affect (or are affected by) other processes through shared data. Four benefits: information sharing, computation speedup, modularity, convenience. Two IPC models: message passing (messages M0..MN picked from a queue) and shared memory (a dedicated shared region).

⚠️ Top pitfall: Treating any two running processes as cooperating — cooperation requires shared data.

Self-check: Name the four benefits of cooperating processes.

Connects to: 3.11, 3.12

Shared Memory and the Bounded Buffer Problem

Must-know: in points to the next slot to fill; out points to the next slot to take. Buffer full when in == buffer size; buffer empty when in == out. Communication is controlled by the user processes, not the OS, so the producer and consumer must synchronize; consuming before producing leaves nothing to consume and causes starvation.

⚠️ Top pitfall: Saying the buffer is empty because it was never filled — the correction: it is empty because items have been taken out (in equals out).

Self-check: With a 10-slot buffer, what value of in means the buffer is full?

Connects to: 3.10, 3.12

Message Passing

Must-know: send and receive are the two operations; direct communication names the peer, indirect uses a mailbox (port) with an ID. Blocking = synchronous (sender waits until received, receiver blocks until available); non-blocking = asynchronous. Never block both sender and receiver — each would wait forever. Buffering: zero (no queue), bounded (n messages), unbounded (infinite).

⚠️ Top pitfall: Blocking both sender and receiver at the same time — each waits forever for the other; only one side should ever wait.

Self-check: In indirect communication, what two parameters do send and receive take?

Connects to: 3.10, 3.13

IPC Systems in Practice

Must-know: POSIX shared memory: shmget creates the segment, permission 6 = 4 + 2 (read + write) granted to owner, group, and others. Mach: msg_send/msg_receive/msg_rpc over ports. ALPC: same-system only, handles returned to both sides, section object for messages. Sockets: address = IP + port; ports below 1024 are well-defined (FTP, SNMP, SMTP, HTTP); users get ports above 1024 up to 65535; 127.0.0.1 is loopback. RPC: stub marshals, skeleton unmarshals, XDR is the portable form, MIDL on Windows. Ordinary pipe: parent-child only, fd[0] reads, fd[1] writes; named pipe: bidirectional, no parent needed.

⚠️ Top pitfall: Confusing which pipe descriptor does what: fd[0] is the read end, fd[1] is the write end.

Self-check: Why can't a user program use port 80?

Connects to: 3.10, 3.11, 3.12

Exam Guidance Summary

Must-know: Mid-semester exam covers everything up to and including process synchronization; scheduling problems are examinable. Test week February 12-18, schedule announced on the portal. Textbook: Silberschatz (any version), reference: Stallings. Recorded sessions cover the whole syllabus for mid and final exams in flipped mode.

⚠️ Top pitfall: Assuming the recorded sessions skip problem-type topics — those are solved in the live contact sessions; the recordings carry the core material.

Self-check: Up to which topic does the mid-semester exam cover?

Connects to: 3.7, 3.8, 3.12

Key Industry Applications

Must-know: Named real-world anchors: taskbar CPU utilization (accounting), Linux PID 1/pid_t tree, Chrome one-process-per-tab isolation, POSIX permission 6 = 4 + 2, ports below 1024 reserved (FTP, SNMP, SMTP, HTTP), 127.0.0.1 loopback, named pipes more powerful than ordinary pipes.

Self-check: Which real product demonstrates the isolation benefit of multiprocessing?

Connects to: 3.3, 3.5, 3.8, 3.9, 3.13

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.