Supporting Multicore Environments System Composition

5 10 15 20 25 30 0.2 0.4 0.6 0.8 1 average response time utilization 4 x MM1 MM4 Obviously, using a shared queue is substantially better. The banks and post offices are right. And once you think about it, it also makes sense: with a shared queue, there is never a situation where a client is queued and a server is idle, and clients that require only short service do not get stuck behind clients that require long service — they simply go to another server. Exercise 145 Why is it “obvious” from the graphs that MM4 is better? But what about preemption? The queueing analysis is valid for the non-preemptive case: each client sits in the queue until it is picked by a server, and then holds on to this server until it terminates. With preemption, a shared queue is less important, because clients that only re- quire short service don’t get stuck behind clients that need long service. However, this leaves the issue of load balancing. If each processor has its own local queue, the system needs explicit load balancing — migrating jobs from overloaded processors to underloaded ones. This ensures fair- ness and overall low response times. However, implementing process migration is not trivial, and creates overhead. It may therefore be better to use a shared queue after all. This ensures that there is never a case where some job is in the queue and some processor is idle. As jobs are not assigned to processors in this case, this is called “load sharing” rather than “load balancing”. The drawback of using a shared queue is that each job will probably run on a different processor each time it is scheduled. This leads to the corruption of cache state. It is fixed by affinity scheduling, which takes the affinity of each process to each processor into account, where affinity is taken to represent the possibility that relevant state is still in the cache. It is similar to simply using longer time quanta.

10.2 Supporting Multicore Environments

189 Chapter 11 Operating System Structure An operating system does a lot of things, and therefore has many components. These can be organized in various ways. In addition, operating systems may need to control various hardware platforms, including distributed and parallel ones.

11.1 System Composition

Operating systems are built in layers It is hard to build all the functionality of an operating system in a single program. In- stead, it is easier to create a number of layers that each add some functionality, based on functionality already provided by lower layers. In addition, separate modules can handle different services that do not interact. For example, one can implement thread management as a low level. Then the memory manager, file system, and network protocols can use threads to maintain the state of multiple active services. The order of the layers is a design choice. One can implement a file system above a memory manager, by declaring a certain file to be the backing store of a memory segment, and then simply accessing the memory. If the data is not there, the paging mechanism will take care of getting it. Alternatively, one can implement memory management above a file system, by using a file for the swap area. All the layers can be in one program, or not The “traditional” way to build an operating system is to create a monolithic system, in which all the functionality is contained in the kernel. The kernel is then a big and complex thing. All the system tables and data structures are shared by different parts of the system, which can access them directly this is what causes the mutual exclusion problems mentioned in Chapter 4. 190 The more modern approach is to use a microkernel, that just provides the basic functionality. The rest of the operating system work is done by external servers. Each of these servers encapsulates the policies and data structures it uses. Other parts of the system can only communicate with it using a well-defined interface. This distinction is also related to the issue of where the operating system code runs. A kernel can either run as a separate entity that is, be distinct from all pro- cesses, or be structured as a collection of routines that execute as needed within the environment of user processes. External servers are usually separate processes that run at user level, just like user applications. Routines that handle interrupts or sys- tem calls run within the context of the current process, but typically use a separate kernel stack. Some services are delegated to daemons In any case, some services can be carried out by independent processes, rather than being bundled into the kernel. In Unix, such processes are caled daemons. Some dae- mons are active continuously, waiting for something to do. For example, requests to print a document are handled by the print daemon, and web servers are implemented as an http daemon that answers incoming requests from the network. Other daemons are invoked periodically, such as the daemon that provides the service of starting user applications at predefined times.

11.2 Monolithic Kernel Structure