[OS] How OS manages Threads — concurrency part2

Yong Seung lee
4 min readDec 14, 2022

--

In part 1, we talked about what is concurrency.

Today, we are going to talk about HOW OS DOES THIS!

Thread is a unit of concurrency and OS executes multiple threads in order that the “scheduler” decides. Let’s think about a single-core case. When you run multiple threads, you need a mechanism to switch to another thread. Remember, we cannot run multiple threads simultaneously because we only have one core. Instead, we are switching between threads in a really short period, so that it looks like we are doing things simultaneously.

The mechanism OS uses to switch among threads is using an “Interrupt.” What interrupt does is literally interrupt the currently running thread and switch to another thread and start running that thread. This is important in terms of “fairness.” we should be “fair” among threads and give all threads a fair amount of opportunity to be run. Then, how can we just stop the currently running thread and start running another thread? OS should somehow save the current thread’s process and bring this thread back later to resume from that point.

Before we talk about how OS does this, we should talk about the life cycle of the thread first. Below is the life cycle of the thread.

From the book, Operating systems Principles, and Practice by Anderson

(1) When the thread is created, it goes to Ready Queue, which means this thread is ready to run.

(2) Then, later if the scheduler chooses to run that thread, it pops off that thread from the Ready Queue and loads this thread to the CPU.

(3) If the running thread got blocked (by join, lock, or conditional variable), it goes to Wait Queue. Threads inside the Wait Queue are just waiting for some events to happen and move them to Ready Queue. After it goes to Wait Queue, the scheduler picks a thread from the “Ready Queue” and the CPU starts running that thread. Remember, since the scheduler only picks the thread from Ready Queue, threads inside the Wait Queue should wait until it goes to the Ready Queue to get a chance to be run.

(4) If the running thread called “yield” or got interrupted, it goes to Ready Queue, not Wait Queue. When an interrupt is fired by hardware, the interrupt handler chooses a function to run from the interrupt vector table and runs that function. Then, that function saves the state of the current thread and runs the scheduler to choose the next thread to run.

(5) If that thread is done, then it will exit by releasing its resources.

Now, let’s talk about how OS saves the current state of the thread and resume from the point it got suspended. OS uses TCB(Thread Control Block). Suppose we want to save the snapshot of the current function, what should we save? We should save all variables’ values and the current PC(program counter). TCB holds this information. It holds per-thread states, such as stack information(local variables), register values, and metadata(thread ID, owner, scheduling priority) of the thread. Thus, when the scheduler pops off a thread from the Queue, it just pops off the thread’s TCB (exactly, it is the pointer to that TCB, because it is expensive to copy TCB every time we are moving around TCB to different queues, so we store the pointer to that TCB in the queue). Then, we load the information in that TCB to the CPU’s register, PC, and memory.

Operating systems Principles, and Practice by Anderson

One important thing is a transition between each thread happens in the OS code. Functions in Interrupt Vector Table, lock, wait, and join are all OS codes. When a user application calls these functions, it traps to OS and does this transition. It is great that OS gives this abstraction because users do not have to worry about messing up this whole complicated process. There are lots of things that can go wrong, such as one thread just monopolizing the CPU. We will talk about this more when we talk about the internals of the thread library and how to properly use it.

--

--