Chapter 8: System Software Part of any computer system is the system software This is software that supports our use of the computer We will examine some OS topics here, but as this material is covered in detail in CSC 460, we will skip over a lot of it (sections 8.3, 8.5-8.7) The original role of the OS was to provide an interface between application software and hardware The programmer used to have to write the code that interacts with hardware (e.g., printer, disk drive, memory) the OS alleviated the need for the programmer to write this code Rudimentary OSs were introduced in the 1960s, called resident monitors because the code would always be resident in memory This code would be called upon whenever a new program was to be run or whenever the current program needed system resources Today, the OS not only provides the hardware/software interface, but directly supports the user as a complex interface and resource manager Read the history in section 8.2.1, but we will skip it in lecture
OS Components The kernel is the core of the OS It supports the process manager, scheduler, resource manager and I/O manager It is responsible for scheduling, synchronization, protection/security, memory management and interrupt handling Other elements of the OS are Shells – the user-specified environment Utilities – programs usually run by the OS or user to keep the environment reasonably maintained (e.g., antiviral software, disk backup, disk defragmenter, screen saver, file manager) We now examine services providing by the OS
User Interface The interface actually takes on two roles: Interface between user and hardware How I/O requests are handled This includes interrupt mechanisms, bus arbitration, protection mechanisms, and so forth We covered some of this in chapter 7 and won’t go into any more detail here Interface between user and software How the user commands the system to perform operations whether the commands are to applications software or OS Today, this is done through a GUI (menus, buttons, icons) In the past, this was done through command line interpreters and still is common in OSs like Unix/Linux A command line interpreter must perform proper interpretation of an instruction. In an OS like MS-DOS, this is fairly simplistic. But in Linux/Uhix, there are many steps including: brace expansion tilde expansion command substitution arithmetic expansion pattern matching quoting alias substitution variable evaluation redirection tab completion
Process Manager Starting programs upon request Find executable code in the file system, load into memory Start the process give process a status and move to appropriate queue Handle scheduling of programs Process admitted to the system, placed in waiting queue Process moved to memory, placed in ready queue Program gets attention by the CPU context switching occurs between processes to move the CPU between processes Terminating the process and freeing its resources Process manager also Handles interprocess communication Communicates with other aspects of the OS kernel to track resource usage resource manager file manager memory manager Modern operating systems use multitasking, so scheduling is typically a round robin approach where all active processes are present in a “ready queue” and the CPU switches off between them. However, as a process requires I/O, the process is usually moved into an I/O queue and brought back to the ready queue once the I/O has completed. Batch processing would require that the OS decide which process to next move from a request queue to the ready queue. Multiprogrammed systems would load several programs into the ready queue and use a scheduling algorithm to select a process to execute for some time until it required I/O, and then switch to another. Various scheduling algorithms include FIFO, priority, and shortest job first. You may study these in 460/560. While processes are running, they may need to have synchronized access to shared data or resources. Synchronized access means that if one process attempts to access some datum, x, it is only allowed if no other process is currently accessing it. Once the process completes using datum x, it is freed for other processes. The important thing to realize is the “access” means from the point it loads the datum from memory into a register until it is moved back to memory. Between those two points, the current process could be interrupted by the OS and the CPU could move onto another process. So for instance, if P0 has loaded x into R1 and has added 1 to it but not stored x back to memory, and is interrupted, another process, P1, who wants to access x should not be allowed to because x may have been modified but not returned to memory. The variable x is called a “critical section” and protected via synchronization mechanisms. In order to ensure synchronization is upheld, we make access to a critical section mutually exclusive. This leads to another problem, see the next slide. Synchronization is an important topic, covered in detail in 460/560.
Resource Management Resources are finite They include disk/tape drives, memory, CPU, network connection, printer, etc Processes might need more resources than are available The OS allocates a resource to a process At that point, the resource is usually dedicated no other process can use the resource until the first process lets go of it this is known as mutually exclusive access – why is it necessary? This can lead to deadlock where processes hold resources and need other resources currently held by other processes P0 P1 R0 R1 Process P0 currently has access to R0 and needs R1 while Process P1 currently has access to R1 and needs R0 result – deadlock, neither P0 nor P1 are able to continue but neither release their resources! Here we see that two processes that access two resources could wind up in a deadlocked situation. Assume P0 gains access to R0 because no other process is using R0. At some point, the OS interrupts the CPU and switches to P1. P1 gains access to R1 because no other process is using it. Now P1 wants access to R0 but cannot have it yet because R0 is being held by P0. At some point, the OS interrupts P1 and switches back to P0. Now assume P0 wants access to R1. At this point, P0 cannot continue until it has access to R1 and P1 cannot continue until it has access to R0, a deadlocked situation. If such a situation is caught, the OS may kill off one or both processes in hopes of getting around the deadlock, but if the OS does not detect deadlock, the processes hang forever! Deadlock is covered in 460/560 as well. How does the OS switch from process to process? We have already talked about the interrupt system and how an interrupt causes a process to stop momentarily while the OS is involved to handle the interrupt. One form of interrupt is because a process wants I/O, basically it interrupts itself. Another form occurs by the system timer. The timer is set for some number of clock cycles. After each machine cycle, the timer is decremented. Upon reaching 0, the timer interrupts the CPU so that the OS can perform a “context switch”, which invokes a different, waiting process. Thus, one process is interrupted for another. This is how multitasking works. Assume the timer is set for 1000 clock cycles and there are four active processes. Then P0 runs for 1000 clock cycles, followed by P1, followed by P2, and P3 and then back to P0. Because the CPU is so fast, the user would not notice the time that the CPU is not running P0, having switched to P1-P3. For memory fragmentation, see the next slide.
Synchronization & Atomic Instructions Process manager must synchronize access to shared memory/resources When a process wants access, it issues a request command if the resource is unavailable, this process moves to a wait queue once the resource becomes available, the next process in the queue is selected to hold onto the resource We don’t want to interrupt the request command so we make it an atomic instruction (part of the instruction set) Atomic instructions are non-interruptible even though they perform multiple steps such as “compare-and-swap” and “test-and-lock”
Memory Management, Modes CPUs typically operate in one of two modes: User mode only has access to some operations and limited memory space access Privileged mode (also known as system mode, administrator mode or kernel mode) has access to all operations and memory When in user mode, program instructions may request access to resources/memory To gain access, the OS must switch modes Mode is a bit in the status flags Memory is managed separately from resource management but is another type of resource Manages virtual memory Decides how many frames each processor (or user) is given Allocates memory, should memory be allocated in contiguous blocks? Memory management also searches for memory violations Memory management is all about how the OS decides how much memory to permit for each process. Prior to virtual memory, the OS would select a process that could fit into memory. The process is given a contiguous chunk of memory. For instance, if P0 needs 100M, P1 needs 50M, p2 needs 150M, P3 needs 80M, the OS needs 120M and the computer has 640M, we would see this: 0 100 200 300 400 500 600 640 OS---------P0--------P1---P2------------P3----- Assume that P1 terminates, freeing up a 50M fragment, and P4 is waiting but requires 180M. We have enough space (140 free after P3 and 50 free between P0 and P2) but it is not contiguous, so we have a fragment. Eventually, we will have a number of small fragments. With virtual memory, we remove all the fragments because each frame is the same size and a program needs a different number of frames. The OS might provide every program the same number of frames, or may use an algorithm to decide how many frames to provide for each program (proportionate to the size of the program).
Security and Protection In addition to deadlock, resource sharing leads to the situation where a process wants to use a resource that is owned by another process Or the user who runs the first process wants to access a resource owned by a different user Protection mechanisms must be enforced in the OS to make sure that this cannot happen Otherwise, one use could access/erase/alter files owned by another user Security mechanisms extend this idea of protection to networks so that a user is not able to access resources of the system (including the CPU and memory) unless they have been authorized to do so Through some form of authentication mechanism such as logging in with a private password Without security, systems would be susceptible to illegal access
Assemblers The assembler is a program that translates an assembly program into machine language Recall that there is a 1-to-1 mapping of assembly language instructions to machine instructions unlike a high level language instruction which might require several to dozens of machine instructions So the assembler’s task is not too difficult Translate the mnemonic into the appropriate op code Translate the operands into Addressing mode and addressing specifications Literals into binary equivalence Variable names and Labels into memory locations and/or offsets Compiler the list of variables and functions into a symbol table What would happen if the assembler places variables and labels into specific memory locations? the program/code would be non-relocatable Consider the following MARIE code: Load X Subt Y Skipcond 01 Jump Next Add #1 Store X Next:… To convert each instruction to machine language, we need to simply swap the operand for the appropriate op code, and the variable or branch with the appropriate memory location. The assembler creates a symbol table to denote where each variable will be stored. For instance, if the program requires 150 bytes of storage, then X and Y will be stored at 151, 152 respectively. If the Load instruction is the 20th instruction, then Next is at memory location 27. The translation is easy.
Linkers and Loaders It is the job of the linker to make the connections between variables and functions in your program and the library files It is the job of the loader to make the connections between variable names and labels, and memory locations When you use library files of functions, those files are pre-assembled/compiled But your program needs to reference memory locations Where will your code and the library code be placed? Recall in your C programs the use of such library files as stdio.h with functions like scanf and printf. Not only will your program have to be loaded into memory, but so will those external functions. The linker, which will usually execute alongside the compiler, will locate the executable code (compiled code) of the functions you are referencing and add them to the executable code of your program. The loader’s job is to merely load the executables into memory when the program starts executing. A dynamic linker and loader is commonly used to reduce the compile time and load time needed. In a windows environment, such a library is given the extension .dll. Note in Linux/Unix, you can compile C code without linking. But in MS Studio, the two are combined.
Compilers Just as with assembly code, a program written in a high-level language must also be translated into machine language This is the job of the compiler But the compiler has a much harder job Consider the following code: This is translated into at least 15 Intel assembly instructions! for(j=0;j<n;j++) if(a[j] > b[j] – k) c[k++] = a[j]; A high level language is far more complex than assembly language because the typical high level language instruction takes several machine instructions to execute it, so the compiler’s task is much harder. The code given above has the following individual machine instructions: load j compare to n branch if less than compute b[j]’s location and load it load k and subtract from b[j] compute a[j]’s location and load it compare the value to the subtraction if not greater than, branch to the bottom of the loop compute c[k]’s location and store a[j] there increment k Bottom of loop: increment j branch to the top As can be seen in the figure, compilers divide the compilation process into several distinct steps: lexical analysis (breaking the program code into lexemes, or individual units such as identifier, assignment operator, plus operator, semicolon, etc), syntactic analysis which creates a parse tree for every instruction, semantic analysis (type checking, parameter checking, etc) and finally code generation, which is optionally followed by or including code optimization. Usually compilers make multiple passes through a program progressively breaking into smaller chunks resulting eventually in a (possibly optimized) machine language program The steps of a compiler
The Run-Time Stack We examine one last system oriented feature The run-time stack is used to implement function calls for most languages When a function is called, an “activation record instance” is pushed onto the stack This record contains storage space for All local variables All parameters The return address (where to return to when the function terminates) The return value (if the function returns a value) When a function is called, the OS uses a special register called the Stack Pointer (SP) to determine where this new activation record instance is pushed When the function terminates, the PC is reset to be the return address, the return value is returned to the function/operation that called the function, and the activation record instance is popped off the stack, with the SP adjusted to the new top NOTE: without the run-time stack, recursion is not possible! The run-time stack is the glue that holds a program’s execution together. Every time a function (or method) is called, the run-time stack is used to indicate where to return to when the function/method terminates, and to pass parameters (the parameters are placed on the run-time stack) and for local variables. The compiler sets up an activation record for every function and when it is called, the OS generates an instance (a copy) and pushes it onto the run-time stack. The ARI has space for all these values (return location, parameters, local vars, plus some other bookkeeping pointers). The stack pointer (SP), a special CPU register, always points to the current top of stack. When a function is called, an ARI is pushed onto the stack and the SP is readjusted. When a function terminates, the ARI is popped from the stack and the SP is reset to the new top of stack. The next slide shows an example.
Example Notice that main has 3 variables. There is no return location because, after main terminates, the program terminates. All other functions require a return location indicated in the program as points Q+1 (1 instruction after Q), R+1 and S+1. Each ARI has storage space for local vars, parameters, and if the function is not void, a return value. You can see how the run-time stack changes as the program executes. Some of the later topics of this chapter are covered in CSC 407/507 – Concepts of Programming Languages.