Chapter 3: Processes: Scheduling, IPC, and Client-Server Communication

0:00 / 0:00
Report an issue

Welcome to Last Minute Lecture.

This free chapter overview is designed to help students review and understand key concepts.

These summaries supplement, not replace, the original textbook and may not be redistributed or resold.

For complete coverage, always consult the official text.

Have you ever stopped to think about what's actually going on inside your phone?

Or your laptop?

All that invisible activity?

Right, it's like this constant hum of, well, stuff happening.

Calculations, data moving around.

It does kind of feel like magic sometimes, but today we're pulling back the curtain a bit.

Yeah, because a lot of that magic really boils down to one fundamental concept in computing.

If you've ever wondered how your computer seems to juggle, you know, a dozen things at once.

Or how one act can do multiple things seemingly at the same time.

Exactly.

Then this deep dive is definitely for you.

We're aiming to give you a shortcut to understanding the concept that makes it all work, the process.

And our guide for this is a key chapter from a classic textbook, Operating System Concepts by Sobershots, Galvin, and Ganya.

It's a great resource.

So let's dig in.

What is a process?

At its simplest, it's just a program that's actually running in execution.

That's the core idea.

It's the basic unit of work for a modern computer.

You know, it wasn't always like this.

Early computers, they ran one program, period.

Start to finish, yeah.

Like that single chef making one dish you mentioned.

Pretty much.

But then we needed computers to do more, to run things concurrently, or at least seem like they were running side by side.

And that's where the whole idea of a process really came into its own, to manage all that.

Exactly.

It brought order to the chaos.

And even on, say, a simple embedded device, maybe one that doesn't even do multitasking for the user,

the operating system itself is still running its own internal processes just to manage things like memory and keep the system ticking over smoothly.

You just don't see them.

OK, so if it's a program running, what does that look like in memory?

How is it structured?

Good question.

Think of a process's memory space as being divided typically into four main sections.

Four sections, OK.

Right.

At the bottom, you've got the text section.

That's the actual code, the program instructions.

Fixed size, usually.

Got it.

Code.

Then there's the data section.

This holds your global variables.

Again, usually a fixed size.

OK, code and globals.

What else?

This is where it gets dynamic.

You have the heap section.

This is memory that gets allocated while the program is running.

Ah, so it can grow.

It can grow, it can shrink, it's flexible.

Think of it like expanding storage space.

And the fourth section.

That's the stack section.

It usually grows downwards toward the heap.

The stack is all about temporary data for function calls.

Like what?

Things like function parameters, the address where the function should return to when it's And any local variables inside that function,

it grows when you call a function, shrinks when you return.

It's very dynamic.

So it's really important to get this distinction.

A program file on your disk is just that passive, right?

Like a blueprint.

Exactly.

Inert.

But a process is that blueprint being built, being executed.

It's active.

It has a program counter saying run this instruction next.

It has resources.

OK, that makes sense.

Like if I open two different web browser windows.

Perfect example.

You're running two processes of the same browser program.

The code, the text section, is identical for both.

But they're data sections.

They're heaps.

They're stacks.

They're totally separate.

Each window or even each tab sometimes gets its own private workspace in memory.

And you mentioned a process can even run other code.

Yeah, like the Java Virtual Machine, the JVM.

The JVM itself is a process.

And then inside that JVM process, your Java code runs.

It's like a mini runtime environment within a process.

Wow.

OK, so processes aren't static.

They change state.

Constantly.

Think of a simple life cycle.

Five main states.

First, new it's just being created, then it usually goes to ready.

It's waiting its turn for the CPU.

Waiting in line.

Basically.

Then it gets the CPU and it's running.

Its instructions are actually executing.

But remember, only one process per CPU core can be truly drying,

running at any single moment.

OK, so what else can happen?

It might need to pause.

If you wait for something like reading data from the disk, that's the waiting state.

Waiting for IO, maybe?

Exactly.

Or waiting for a signal.

Once that event happens, it doesn't go straight back to running.

It goes back to ready.

Back in the queue again.

Yep.

And finally, when it's all done, it enters the terminated state.

Finished.

So how does the OS keep track of all this for potentially thousands of processes?

Ah, with a very important data structure.

The process control block, or PCB.

Sometimes called a task control block.

PCB, OK.

What's in it?

Everything the OS needs to know about that process.

Its current state, running, waiting, etc.

The program counter showing the next instruction, all the CPU register values.

Like a snapshot of its brain.

Kinda, yeah.

Plus scheduling info, like its priority.

Memory details, where its sections are located, IO status, like what files it has open.

It's the process's entire context.

The complete dossier you called it.

Everything needed to pause it and restart it later.

Precisely.

And this whole idea naturally leads into threads.

A traditional process has one thread of execution.

Like that word processor example that can type or spell check, but not both at once.

But modern apps do do both.

Because modern OCs allow a process to have multiple threads.

Each thread is like a mini process within the main process, sharing the code and data sections but having its own stack and registers.

Ah, so one thread types, another spell checks, maybe another saves in the background.

All within the same word processor process.

Exactly.

Much more responsive.

Especially on multi -core CPUs, where threads can truly run in parallel.

So managing all these processes and threads,

that's scheduling, right?

Making sure everything gets a turn.

That's the core of it.

The OS scheduler has a couple of main goals.

One is multi -programming, just keeping the CPU as busy as possible.

Always have something ready to run.

Maximize utilization.

Right.

The other goal is time sharing.

This is about switching the CPU between processes so quickly.

That it feels like they're all running simultaneously, even if they're actually taking tiny turns.

You got it.

That's what gives you that interactive feel.

The OS has to rapidly switch focus.

Does the scheduler treat all processes the same?

Not really.

It often distinguishes between, say, IO -bound processes, ones that spend most of their time waiting for input or output.

Like waiting for you to type something, or for a network packet.

Yeah.

And CPU -bound processes, which are heavy on computation, always ready to use the CPU.

The scheduler tries to balance these, maybe giving IO -bound ones quicker access when they're ready to keep peripherals busy.

And you mentioned queues, processes waiting in line.

Right.

There's usually at least a ready queue.

That's for all the processes sitting in the ready state, just waiting for a CPU core to become free.

Okay.

And then there are wait queues.

Often there's a separate queue for each specific event a process won't be waiting for.

Like one queue for processes waiting for disk drive one, another for disk drive two, another for keyboard input.

So when a process needs IO, it leaves the ready queue and goes to a specific wait queue.

Exactly.

And when the IO finishes, the OS moves it back to the ready queue, not directly back to running.

Fascinating flow.

But switching between processes, that takes time, right?

It does.

That switch is called a context switch, and it's absolutely crucial.

When the OS decides to switch,

maybe an interrupt happened, or the running process's time slices up.

It has to save everything about the current process.

Everything.

Its state,

program counter, all the CPU registers, memory pointers,

all that stuff gets saved into its PCB.

Okay.

Saved away.

Then the OS picks the next process from the ready queue and loads its context, all that saved info from its PCB, back onto the CPU.

And that switching time?

That's pure overhead.

No useful application work is happening during the context switch itself.

It's just the OS shuffling things around.

But it's the price we pay for multitasking.

Absolutely.

It's the invisible engine making it all work smoothly.

You see the impact of these choices in mobile multitasking, for example.

How so?

Well, early iOS versions were very restrictive.

Only one user app could really run properly at a time.

Others were heavily suspended or terminated.

Right, I remember that.

Then it evolved to allow more background activity.

Android, though, was designed with multitasking and background services more in mind from the start.

Think music streaming while you use another app.

So different OS designs, different approaches to process management lead to different user experiences.

Definitely.

It directly affects responsiveness and battery life on those devices.

Okay, so beyond managing them, how do processes even come into being?

Creation and termination.

Right.

Typically, a process creates another process.

The creator is the parent.

The new one is the child.

This creates a sort of family tree structure.

A tree.

And each process gets a unique ID.

Yep.

A process identifier, or PID, usually just an integer.

On Linux, if you look at the process tree, you'll often see PID 1 is the systemed process.

That's the ancestor of almost everything else.

Pretty much all user processes trace back to it.

It's the first user space process the kernel starts.

Historically, this was a process called init.

So how does a parent actually create a child?

Is it the same everywhere?

Two main styles.

In Uniax and Linux, it's usually a two -step process.

First, the parent calls fork.

Fork.

Like a fork in the road.

Kind of.

It creates an inexact duplicate of the parent process.

Same memory content, same open files, everything.

Now you have two identical processes running from the instruction right after the fork call.

Whoa.

Okay, two identical processes.

Then what?

The child process, which knows it's the child because fork returns zero to it.

Usually then calls another function, typically one from the exec family.

Then exec.

Replaces the child's entire memory space with a brand new program loaded from an executable file.

So the child transforms itself into a completely different running program.

So duplicate, then replace.

Interesting.

How does Windows do it?

Windows uses a single function, create process.

You tell it which program executable to run, and it handles creating the new process and loading that program into it all in one go.

No initial duplication step like fork.

More direct, maybe.

More direct, yeah.

But the function itself has a lot of parameters to configure everything up front.

Okay.

And getting rid of processes.

Termination.

Usually a process terminates itself by calling an exit system call when it's finished its work.

It tells the OS, I'm done.

Clean up my resources.

Like memory, open files.

Exactly.

But a parent can also terminate a child process forcefully.

And sometimes, if a parent exits, the OS might perform cascading termination, automatically killing off all the child processes too.

What about those weird states?

Zombies?

Ah, yes.

A zombie process is one that has terminated, called exit.

But its parent hasn't yet acknowledged its death, basically.

The parent needs to call a function like wait to get the child's exit status.

So the process is dead, but its entry in the OS process table still exists.

Right.

Holding that exit status.

Zombies usually don't stick around long.

The parent eventually calls wait.

But if the parent terminates before the child does,

it becomes an orphan process.

In Linux, these orphans are typically adopted by the system process, PID1.

System then acts as their foster parent and calls wait on them when they eventually terminate, preventing them from becoming permanent zombies.

That's surprisingly organized.

And you mentioned mobile OCs handle termination differently too, like Android.

Yeah, because mobile devices have tighter memory constraints.

Android maintains an importance hierarchy.

If it needs memory, it starts killing processes, but it starts from the least important.

What's least important?

At the bottom are empty processes, just hanging around.

Then background processes, doing something unseen.

Then service processes, like that background music player.

Then visible processes, not in focus, but maybe affecting what is in focus.

And finally, the most important is the foreground process, the app you're actively using right now.

So it tries hard not to kill what you're actually interacting with or your background music.

Exactly.

It's a very pragmatic approach for resource management on phones and tablets.

OK, this is all fascinating.

Managing processes, creating them, ending them.

But what about making them talk to each other?

Interprocess communication or IPC?

Crucial stuff.

Why do processes need to talk?

Well, for information sharing, copy paste is a classic example.

A word processor process needs to share data with an email process.

Make sense.

Also for computation speed up.

Break a big job into pieces, have multiple processes or threads, work on them in parallel, then combine the results.

Needs communication.

And modularity, right, building a big system out of smaller communicating parts.

Definitely.

Easier to develop, debug, maintain.

So how do they talk?

Two main ways, fundamentally.

OK.

Shared memory and message passing.

Shared memory sounds direct.

It is.

The processes basically agree on a chunk of memory that they can both access, directly read from it, write to it, like a shared whiteboard.

And message passing.

More like sending letters or packages.

One process packages up data into a message and asks the OS to deliver it to another process.

The OS access the postal service.

What are the trade -offs?

Shared memory can be super fast once it's set up, because data doesn't need to be copied by the OS kernel.

Processes just access the memory directly.

But you have to be careful about coordinating access.

Right.

Two processes writing to the same spot at the same time could be bad.

Exactly.

Message passing avoids that direct conflict as the OS manages the delivery.

It's often simpler to program initially, especially for smaller amounts of data, and works naturally across different machines on a network.

But there's usually more overhead involved in copying the messages.

Can you give a real -world example of using lots of processes?

Google Chrome is a perfect case study.

It's famously a multi -process browser.

How does that work?

There's a main browser process handling the UI, bookmarks, network requests.

But then generally, each website or tab you open runs in its own separate renderer process.

Ah, isolation.

Precisely.

If code on one website crashes or hangs, it usually only takes down that one renderer process that the main browser and your other tabs keep running.

It's huge for stability.

And security too, presumably.

Big time.

Those renderer processes run in a restricted sandbox.

They have very limited permission to access your files or the network directly.

So even if a malicious website tries something nasty, the damage it can do is severely contained.

Smart design.

Okay, let's dive a bit deeper into these IPC mechanisms.

Shared memory first.

How does it actually work?

The key is that the participating processes ask the OS to map the same physical region of memory into their own virtual address spaces.

So process A is addressed 0 by 1 ,000 and process B is addressed 0 by 5 ,000 might actually point to the exact same RAM location.

So they have a shared window into the same physical memory.

Exactly.

Then they just read and write to those addresses like normal memory.

The OS steps out of the way after setting it up.

You mentioned coordination is key.

How is that typically handled?

Well, that gets into synchronization, which is a whole topic itself.

But a classic scenario is the producer -consumer problem.

Like the compiler and assembler example.

Yeah.

Or a web server producing data for a client.

One process, producer creates data, the other consumer uses it.

They communicate via a shared buffer, maybe a circular array in that shared memory region.

A buffer, a temporary holding area.

Right.

The producer adds items to the buffer, the consumer removes them.

They need variables, also in shared memory, to track where the next item goes in pointer and where the next item comes from, out pointer.

And they need rules like the producer can't add to a full buffer and the consumer can't take from an empty one.

Exactly.

That's the synchronization challenge, making sure they don't mess each other up.

Modern systems provide APIs like Piosysh shared memory functions, Schmopen, a map, to create and map these shared regions easily.

Okay.

Moving on to message passing.

The OS acts as the intermediary here.

Right.

The basic operations are send destination, message, and receive.

Source message.

Need to figure out the destination and source.

How do processes name each other?

Two main ways.

Direct communication.

Process A explicitly says, send this message to Process B.

Simple, but less flexible.

If Process B's name changes, A's code breaks.

What's the alternative?

Indirect communication.

Processes send messages to and receive messages from mailboxes or ports.

Think of them like shared message queues identified by some ID.

So Process A sends to Mailbox 5 and Process B and maybe C and D receives from Mailbox 5.

Exactly.

D couples the sender and receiver.

More flexible.

The OS usually manages these mailboxes.

And sending and receiving?

Can processes wait or do they just fire and forget?

That's synchronization and message passing.

It can be blocking, synchronous, or non -blocking.

Asynchronous.

Blocking means a blocking send waits until the receiver has actually received the message, or at least until the OS has taken responsibility for it.

A blocking receive waits until the message actually arrives.

And non -blocking?

A non -blocking send just sends the message off and continues immediately.

A non -blocking receive checks if there's a message.

If yes, it gets it.

If no, it returns immediately with an indicator saying, nothing here.

So if both send and receive are blocking, they have to meet up?

That's called a rendezvous.

They synchronize at the point of message transfer.

What about buffering?

Where do messages wait if the receiver isn't ready?

Good point.

The communication link, often implemented by the OS, usually has a queue.

Three possibilities.

Zero capacity.

No buffer.

Sender must wait for receiver pure rendezvous.

Bounded capacity.

Finite buffer size.

Sender waits only if the buffer is full.

Unbounded capacity.

Infinite buffer.

Sender theoretically never waits.

Unbounded seems tricky in reality.

It is due to finite memory.

Bounded is most common.

Systems like mock, influential and macosios, use sophisticated message passing with ports, and clever tricks like mapping memory instead of copying for speed on the same machine.

Windows has its own internal system called ALPC for similar efficient local communication.

OK, what about simpler forms?

You mentioned pipes.

Pipes are one of the oldest and simplest IPC forms, especially in UNX.

Think of it literally like a pipe stuff goes in one end, comes out the other.

It's a conduit.

Ordinary pipes are typically unidirectional.

One process writes, one process reads.

And crucially, they usually require a parent -child relationship.

Why?

Because the parent creates the pipe before calling fork.

Both the parent and the new child then inherit the file descriptors for the read -write ends of the pipe.

They then usually close the ends they don't need.

Ah, so fork duplicates the pipe connection.

Exactly.

The classic example is the command line, that's less.

Listing files piped into the pager.

Right.

L is the producer, writing its output to the pipe.

Less is the consumer, reading its input from the pipe.

Simple.

Elegant.

Windows has a similar concept called anonymous pipes.

Are there more powerful pipes?

Yes.

Named pipes, or FIFOs in UNX.

First in, first out.

These are more persistent.

They have a name in the file system, processes can find them by name, they don't require a parent -child link, and multiple processes can potentially use them.

They can often be bidirectional too, especially the Windows version.

So more like a public communication channel.

Sort of, yeah.

More flexible than ordinary pipes.

Okay.

Finally, let's talk about communication that goes beyond one machine.

Client -server.

Right.

Often over a network.

The fundamental mechanism here is usually sockets.

Sockets.

I've heard that term.

What are they?

A socket is an endpoint for communication.

Think of it like one end of a telephone connection.

For network communication, it's typically identified by an IP address, which machine, and a port number, which application or service on that machine.

IP address plus port number equals a specific destination.

Exactly.

In the client -server model, a server process listens for incoming connection requests on a specific well -known port number.

Like a WIP server listens on port 80 for HTTP requests.

And the client.

The client initiates the connection to the server's IP address and port.

The client gets assigned its own temporary port number on its end.

Once the connection is established, you have a pair of sockets, one on the client, one on the server, forming a unique communication channel.

Like that phone call being established?

Excisely.

Data can then flow back and forth as a stream of bytes.

Sockets are the foundation for most network communication, like web browsing, email, et cetera.

But they sound pretty low -level, just a stream of bytes.

They are.

That's why we have higher -level abstractions built on top, like remote procedure calls, or RPCs.

RPCs, making a remote call look local.

That's the goal.

You want to call a function or method on a server on another machine, as if it were just a normal function call in your own code.

How does that work under the hood?

RPC systems use stubs.

The client code calls a local stub function.

This stub packages up the function parameters into a message, a process called marshalling, which also handles data format differences between machines.

Marshalling, preparing the data for travel.

Right.

The stub sends this message, usually via sockets, to the server.

On the server, there's a listener daemon that receives the message, figures out which function needs to be called, and passes the message to a server -side stub.

Which unpacks it.

It unmarshalls the parameters and calls the actual procedure on the server.

When the procedure finishes, the result is marshalled into a reply message, sent back to the client stub, which unmarshalls it and returns it to the original caller.

Hides all the network complexity.

That's the idea.

RPCs also need to worry about network reliability, what happens if a message gets lost.

They often aim for, at most once, or exactly once,

semantics for calling the remote function.

And finding the server.

Does it always use a well -known port?

Sometimes.

But often, servers register themselves with a matchmaker, or directory service, on a known port.

The client contacts the matchmaker first, to find out the actual port the desired service is currently listening on.

More flexible.

And RPC isn't just for networks, right?

You mentioned Android.

Yeah, Android uses an RPC mechanism heavily within the device for its binder framework.

This lets different application components, potentially running in separate processes, like an app talking to a background service, communicate efficiently using this high -level call interface.

ADL is the tool developers use to define these interfaces.

Wow.

Okay, quite a journey.

So let's try and wrap this up.

We started with the basics.

Right.

A process is just a program running, the fundamental unit of work.

We saw its lifecycle, new, ready, running, waiting, terminated, and how the OS tracks it all in the PCB, that digital passport.

We looked at how the OS juggles them with scheduling, using queues and context switches, that essential overhead, making multitasking possible.

And we covered creation via fork exec, or create process, termination, and those odd cases like zombies and orphans.

Then, crucially, how processes talk.

Shared memory for direct, fast access, message passing for structured exchange via the OS, simpler pipes, and network -spanning sockets and the abstraction of RPCs.

It really underpins everything we do with computers, doesn't it?

All these concepts, mostly invisible.

Completely.

From your smartwatch to the cloud servers running global services, it's all built on these ideas of managing processes, sharing resources, and enabling communication.

It really makes you think.

As we move towards even more distributed systems, billions of IoT devices, edge computing everywhere, how will these core ideas need to change?

That's the big question, isn't it?

Orchestrating potentially trillions of processes globally.

What new challenges in scheduling, communication, and security will that bring?

It's an evolving field.

Definitely something to ponder.

The hidden world inside our devices is vast.

It really is.

You can actually peek into it on Windows, Open Task Manager,

on Linux or Mac OS.

Try the keeps or top or street commands in the terminal.

You'll see these processes listed out.

Give it a try.

See the concepts we discussed in action on your own machine.

Thanks for diving deep with us.

ⓘ This audio and summary are simplified educational interpretations and are not a substitute for the original text.

Chapter SummaryWhat this audio overview covers
Processes represent the fundamental execution units within operating systems, embodying the dynamic instantiation of programs in action. A process encompasses the executable code, the current execution state including the program counter and CPU registers, and all resources allocated to support that execution. Understanding how operating systems manage processes requires examining several interconnected layers: the representation of process information through the process control block, the lifecycle progression through distinct operational states, the mechanisms for allocating CPU time, and the coordination strategies for enabling processes to interact. The process control block serves as the data structure maintaining all essential information about a process, encompassing CPU register values, memory management details, accounting information, and status flags. Processes transition through five primary states during their lifetime—moving from creation as new processes through readiness for execution, active running state, waiting periods when blocked on resources, and eventual termination. Process scheduling operates across three temporal scales: long-term scheduling controls which programs enter the system, medium-term scheduling manages the balance between memory and execution, and short-term scheduling determines moment-to-moment CPU allocation among ready processes. CPU behavior alternates between computation-intensive bursts and input-output operations, patterns that inform scheduling algorithm design. Context switching enables multitasking by saving and restoring process states, though this mechanism incurs measurable computational overhead. Beyond individual process management, operating systems must facilitate interaction between processes through interprocess communication mechanisms. Shared memory approaches allow multiple processes to access common data regions, requiring careful synchronization to prevent conflicts. Message-passing approaches enable processes to exchange information through kernel-mediated message transmission, proving more suitable for distributed environments. Communication facilities such as pipes and sockets implement these models in practical systems. Client-server architectures represent a distributed computing pattern where processes request services from designated server processes, frequently using remote procedure calls to abstract network communication as local function invocation. Together, these concepts establish how modern operating systems coordinate autonomous processes into coherent, concurrent computational ecosystems.

Using this chapter to study? Last Minute Lecture is free and student-run. If it helped, consider supporting the project.

Support LML ♥