Operating System: Three Easy Pieces --- Concurrency: An Introduction (Note)

Thus far, we have seen the development of the basic abstractions that the OS performs. We have

seen how to take a single physical CPU and turn it into multiple virtual CPUs, thus enabling the

illusion of multiple programs running at the same time. We have also seen how to create the

illusion of a large, private virtual memory for each process; this abstraction of the address space

enables each program to behave as if it has its own memory when indeed the OS is secretly

multiplexing address spaces across physical memory (and sometimes, disk).

In this note, we introduce a new abstraction for a single running process: that of a thread. 

Instead of our classic view of a single point of execution within a program (i.e., a single PC

where instructions are being fetched from and executed), a multi-threaded program has more

than one point of execution (i.e., multiple PCs, each of which is being fetched and executed

from). Perhaps another way to think of this is that each thread is very much like a separate

process, except for one difference: they share the same address space and thus can access

the same data.

The state of a single thread is thus very similar to that of a process. If has a program counter

(PC) that tracks where the private set of registers it uses for computation; thus, if there are two

threads of that are running on a single processor, when switching from running one (T1) to

running the other (T2), a context switch must take place. The context swtich between threads 

is quite similar to the context switch between processes, as the register state of T1 must be

saved and the register state of T2 restored before running T2. With processes, we saved state

to a process control block; now, we will need one or more thread control blocks to store the 

state of each thread of a process. There is one major difference, though, in the context switch

we perform between threads as compared to processes: the address space remains the same

(i.e., there is no need to switch which page table we are using).

One other major difference between threads and processes concerns the stack. In our simple

model of the address space of a classic process (which we can now call a single-threaded

process), there is a single stack, usually residing at the bottom of the address space.

However, in a multi-threaded process, each thread runs independently and of course may

call into various routines to do whatever work it is doing. Instead of a single stack in a

space, there will be one per thread. Let's say we have a multithreaded process that has

two threads in it; the resulting address space looks different.

In this figure, you can see two stacks spread throughout the address space of the process.

Thus, any stack-allocated variables, parameters, return values, and othre things that we

put on stack will be placed in what is sometimes called thread-local storage, i.e., the stack

of the relevant thread.

You might also notice how this ruins our beautiful address space layout. Before, the stack

and heap could grow independently and trouble only arose when you ran out of room in

the address space. Here, we no longer have such a nice situation. Fortunately, this is

usually OK, as stacks do not generally have to be very large (the exception being in

programs that make heavy use of recurison). 

posted on 2015-11-04 22:57  Persistence  阅读(133)  评论(0编辑  收藏  举报

导航