├── log.md ├── 6.1 Deadlocks.md ├── 9. Secondary Storage.md ├── 8. Cache Memory.md ├── README.md ├── 2.3 Process Synchronization.md ├── 1.2 System Structures.md ├── 2.0 Process Concept.md ├── 2.1 Multithreaded Programming.md ├── 6. Deadlock - Part 3 (Distributed Environment).md ├── 3. Process Managment (Disitributed Environment).md ├── 12 Mass Storage Structure.md ├── 10. Buffer Cache.md ├── 6. Deadlock - Part 1 (Theory).md ├── 13 IO Systems.md ├── 4. Concurrent Processing.md ├── 6. Deadlock - Part 2 (Algorithms).md ├── 1. Introduction to Operating Systems.md ├── 2. Process Management.md ├── 2.2 Process Scheduling.md ├── 5. Critical Section Problem.md ├── 11. File Representation.md └── 7. Memory Management.md /log.md: -------------------------------------------------------------------------------- 1 | ### Log Sheet 2 | - **7/25/2016** - Finished notes on process management and started notes on process management (Distributed Environment). 3 | - **7/26/2016** - Finished notes on concurrent management and started critical section problem; started notes on deadlock problem 4 | - **7/27/2016** - Finished notes on deadlock problem and started notes on memory management. 5 | - **7/28/2016** - Finished notes on memory management and started notes on cache memory. 6 | - **7/29/2016** - Finished notes on cache memory and started notes on secondary storage and buffer cache. 7 | - **8/01/2016** - Finished notes on file representation -------------------------------------------------------------------------------- /6.1 Deadlocks.md: -------------------------------------------------------------------------------- 1 | ## Chapter 6.1: Deadlocks 2 | 3 | - A process waiting for access to another resource which is also held by other waiting processes. 4 | 5 | - A process must request a resource before using it and must release it after using it. 6 | 7 | - sequence of processes requesting and using resources are made through system calls: request -> use -> release 8 | 9 | - Deadlock occurs when the following four conditions arise simultaneously: 10 | + Mutual Exclusion - only one process at a time can use the resource. 11 | + Hold and Wait - A process must be holding at least one resource and waiting to acquire additional resources that are currently being held by other resources. 12 | + No preemption - a resource can only be released by the process that has acquired it; no preemption of resource are allowed. 13 | + Circular wait - every process in a set is waiting for a resource that is being held by another process in the set, to create a circular waiting scheme. 14 | 15 | - > If a resource-allocation graph does not have a cycle, then the system is not in a deadlock state. If there is a cycle, then the system may or may not be in a deadlocked state. 16 | 17 | - -------------------------------------------------------------------------------- /9. Secondary Storage.md: -------------------------------------------------------------------------------- 1 | ## Chapter 9: Secondary Storage 2 | 3 | - The different types of secondary storage devises are: 4 | + Random Access Memory (RAM)(Main Memory) - (access time very small) 5 | + Serial Access Storage Devise (e.g., magnetic tape) - (access time very high) 6 | + Direct Access Storage Devise DASD (e.g., Mag Disk, Magnetic drum, floppy disk, compact disk) 7 | 8 | - __Character Devise__ - data can be access byte by byte. (e.g., printer) 9 | 10 | - __Block Devise__ - access an entire block first to access a particular byte. (e.g., Magnetic tape) 11 | 12 | - Floppy disks contain different __concentric circles__ called __tracks__ as they are called in magnetic tapes. Tracks are then subdivided into __sectors__. to access a particular byte in a floppy disk, it must first access the track and the particular sector. 13 | 14 | - In __single-sided disks__, the data can be stored on one side of the disk. As for the __double-sided disks__, the data can be stored on both sides of the disk. 15 | 16 | - For a double-sided disk, there must be two read/write heads present. For this to work, there must be a __head no__ as well. Head no decides which side of the disk is being accessed. 17 | 18 | - On a hard disk, in order to access a __block no__, there needs to be a three component address: __head no__, __cylinder no__, __sector no__. 19 | 20 | - When there is a secondary storage present, the CPU first checks the cache memory for an instruction to execute; if not available, then the main memory; if not found then check the secondary storage. 21 | 22 | - A __buffer cache__ is maintained to reduce the amount of overhead when accessing the hard disk. The buffer cache is maintained in the system area of the main memory. The buffer cache is only managed by the operating system and not by any user or program. In order for efficient access of data from the secondary storage, the buffer cache is used as a bridge between main memory and secondary storage. 23 | -------------------------------------------------------------------------------- /8. Cache Memory.md: -------------------------------------------------------------------------------- 1 | ## Chapter 8: Cache Memory 2 | 3 | - Cache memory is the most costly but the fastest of all memory types. Cache memory usually lies between CPU and memory. Use to store the data / instructions that are frequently used by the system. It is also used to store the page / segment map tables. 4 | 5 | - For cache memory, the physical address generated by the CPU has two components __(Block number, word in block)__. 6 | 7 | 8 | - The three types of cache memory are as follows: __Associative__, __Direct Mapped__, __Set Associative__. 9 | 10 | ### Associative Cache Memory 11 | 12 | - A __tag address__ is compared with the physical address generated for a particular instruction. If there is a match, then that instruction exist in cache memory. If not, the go to main memory fetch the data/instruction and put it in cache memory and save the tag. This tag memory / table work similar to the segment/page map table. 13 | 14 | - __Parallel search__ is used to search if the block number exists or has a tag address. 15 | 16 | ### Direct Mapped Cache: 17 | 18 | - Every component in main memory will now have two main components __(Tag, Group)__ including the __byte__ component. 19 | 20 | - MM blocks is represented using a multi-dimensional array, in which each component is a block of memory. Each block of memory will belong to one cache line in cache memory which is 256 in size. The columns in the memory block represent a tag and every row represent a group. 21 | 22 | - A memory block number is stored in cache memory directly and the tag number is used to keep in the tag memory. The __valid bit__ says that the corresponding section in cache exists. 23 | 24 | - In the direct mapped cache, there is only a __5-bit comparator__, unlike in Associative cache where there is a comparator for each tag component. 25 | 26 | - Cache miss is more higher in Direct mapped cache since only one particular block of memory can be placed in a specific cache line. In the associate cache, any block can go any available cache line, there, less probability of cache miss. 27 | 28 | ### Set Associative cache 29 | 30 | - Set associate cache uses a combination of both previous techniques to make the most of the two techniques. 31 | 32 | - In this type of cache setup, instead of having a specific cache line for each memory block, you would have a __set of cache lines__ where the block of memory can go. This will reduce the number of cache misses. This is also referred two as __two-way set associative cache__. The number of sets can be changed and the higher it is the less hits will occur; there is more to compare with. 33 | 34 | - In set associative there is a trade-off between the number of misses (higher) and the amount of hardware required (less). 35 | 36 | - In this setup (2 sets), there has to be two misses by the comparator to be an actual miss. Conversely, there only needs to be one hit to be considered a hit. 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### :point_right: About 2 | 3 | This is a friendly handbook which is useful for familiarizing yourself with the basics of **Operating System Concepts**. I am preparing this handbook as part of ["The Bit Series"](http://elvissaravia.com/books/). Read more about our manifesto [here](http://elvissaravia.com/books/). Also, feel free to submit pull requests as there might be plenty of grammatical errors, spelling errors, and even inconsistencies in the content. I will try to update the book as frequently as possible. I am also keeping a log of my continuous updates [here](https://github.com/omarsar/os/blob/master/log.md). It is tough doing this alone, so I am definitely crediting anyone for their contributions. 4 | 5 | ### :point_right: Table of Content 6 | 7 | - Chapter 1 - [Introduction to Operating Systems](https://github.com/omarsar/os/blob/master/1.%20Introduction%20to%20Operating%20Systems.md) 8 | - Chapter 2 - [Process Management](https://github.com/omarsar/os/blob/master/2.%20Process%20Management.md) 9 | - Chapter 3 - [Process Management (Distributed Environment)](https://github.com/omarsar/os/blob/master/3.%20Process%20Managment%20(Disitributed%20Environment).md) 10 | - Chapter 4 - [Concurrent Processing](https://github.com/omarsar/os/blob/master/4.%20Concurrent%20Processing.md) 11 | - Chapter 5 - [Critical Section Problem](https://github.com/omarsar/os/blob/master/5.%20Critical%20Section%20Problem.md) 12 | - Chapter 6 - [Deadlock - Part 1 (Theory)](https://github.com/omarsar/os/blob/master/6.%20Deadlock%20-%20Part%201%20(Theory).md) 13 | - Chapter 6 - [Deadlock - Part 2 (Algorithms)](https://github.com/omarsar/os/blob/master/6.%20Deadlock%20-%20Part%202%20(Algorithms).md) 14 | - Chapter 6 - [Deadlock - Part 3 (Distributed Environment)](https://github.com/omarsar/os/blob/master/6.%20Deadlock%20-%20Part%203%20(Distributed%20Environment).md) 15 | - Chapter 7 - [Memory Management](https://github.com/omarsar/os/blob/master/7.%20Memory%20Management.md) 16 | - Chapter 8 - [Cache Memory](https://github.com/omarsar/os/blob/master/8.%20Cache%20Memory.md) 17 | - Chapter 9 - [Secondary Storage](https://github.com/omarsar/os/blob/master/9.%20Secondary%20Storage.md) 18 | - Chapter 10 - [Buffer Cache](https://github.com/omarsar/os/blob/master/10.%20Buffer%20Cache.md) 19 | - Chapter 11 - [File Representation](https://github.com/omarsar/os/blob/master/11.%20File%20Representation.md) 20 | 21 | ### :point_right: Status 22 | - Draft stage 23 | 24 | ### :point_right: TODO List 25 | - Add examples through images. 26 | - Add proper tables and fix code segments 27 | 28 | ### :point_right: Contributor 29 | - [Elvis Saravia](http://elvissaravia.com) 30 | - Email: ellfae@gmail.com 31 | 32 | ### :point_right: Potential book cover 33 | ![alt-text-1](https://github.com/omarsar/omarsar.github.io/blob/master/images/os.png?raw=true) 34 | 35 | ### :point_right: More from "The Bit Series" 36 | ![alt-text-2](https://github.com/omarsar/omarsar.github.io/blob/master/images/machine-learning.png?raw=true) 37 | 38 | - [Machine Learning: A Friendly Handbook](https://github.com/omarsar/machine_learning_fundamentals) 39 | -------------------------------------------------------------------------------- /2.3 Process Synchronization.md: -------------------------------------------------------------------------------- 1 | ## Chapter 2.1: Process Synchronization 2 | 3 | - Main concept of process synchronization - mechanism to ensure the orderly execution of **cooperation processes** (processes that affect or be affected by other running processes) that share a logical address space (sharing code and data through threads), so that data consistency is maintained. 4 | 5 | 6 | - **Critical section** - a segment code in which the process may be changing common variables, update a table, writing a file; when one process is executing in its critical section, no other process is allowed to execute in its critical section. 7 | 8 | - critical section contains entry section, critical section, exit section, remainder section. 9 | 10 | - The critical section problem is to design a protocol that the process can use to cooperate. 11 | 12 | - Three requirements to solve critical section problem: 13 | + Mutual exclusion - only one process can be executing in its critical section at the same time. 14 | + Progress - after process is chosen to execute in its critical section, it must finish executing here so that other processes wanting to execute in their critical section can do so. 15 | + Bounded waiting - process wanting to access their critical section are imposed with a bound or limit as to how many times they can access their critical section. 16 | 17 | - test_and_set and compare_and_swap - Hardwares instruction to solve the critical section problem. 18 | 19 | - Mutex locks - obtain mutual exclusion 20 | 21 | - **Semaphores** behave a lot like mutex locks; semaphores provide more sophisticated ways to synchronize their activities; similar to mutex locks, classical semaphore suffers from busy waiting problem; instead of putting the process in busy waiting, the process is blocked and put into a WAITING state. 22 | 23 | - In a single processor, semaphores use in interrupts to achieve atomicity; interrupts ensure that process do not interleave; In a multiprocessor environment, interrupts are disabled; SMP use locking techniques, such as spinlocks, to ensure that wait() and signal() are performed atomically. 24 | 25 | - A set of processes is in a deadlocked state when every process in the set is waiting for an event that can be caused only by another process in the set. 26 | 27 | - Priority inversion - there might be a case where a lower priority is holding a resource that another lower priority process get access of; and that a higher priority process wants to access; a solution is to only have two priorities and give higher priority to processes that are holding resources needed by other higher-priority processes. 28 | 29 | - **Reader writer locks** generally require more overhead to establish than semaphores or mutex locks. 30 | 31 | - **Monitors** - is a high level ADT to avoid problems with programmers setting the wait() and signal() commands in the wrong order; setting these commands in the wrong order may cause deadlock or violation of mutual exclusion. 32 | 33 | 34 | - Multithreaded applications present an increased risk of race conditions and deadlocks. 35 | 36 | - **Memory transaction** (atomic opertions) - if all opertions (read / write) in a transaction are completed, the memory transaction is committed; no locks are involved and this is usually handled by the computer and not the developer; software transactional memory (STM) - implements transaction memory in software (decided whent to run statements (read/write) concurrently); Hardware transactional memory (HTM) - uses hardware cache hierarhies and cache coherency protocols to manage and resolve conflicts involoving shared data residing in separate processor's cache. 37 | 38 | - In **Functional programming** variables are immutable (don't change state or value once they have been set). -------------------------------------------------------------------------------- /1.2 System Structures.md: -------------------------------------------------------------------------------- 1 | ## Chapter 1.2: System Structures 2 | 3 | - Functionalities of the Operating System for a user: 4 | + User Interface 5 | + Program Execution 6 | + I/O operations 7 | + File-system manipulation 8 | + Communications 9 | + Error Detection 10 | 11 | - Functionalities of the Operating System for efficient operation: 12 | + Resource Allocation 13 | + Accounting 14 | + Protection and Security 15 | 16 | - User Interfaces: 17 | + Command Line Interface (CLI) 18 | + Batch interface - files with a batch of executable commands 19 | + Graphical User Interface (GUI) 20 | 21 | - > System calls provide an interface to the services made available by an operating system. 22 | 23 | - An example of a **system call** is when copying and generating a new file. 24 | 25 | - System calls can be grouped intro six major categories: process control, file manipulation, device manipulation, information maintenance, communications, and protection. 26 | 27 | - Examples of system calls available in process control: 28 | + `fork` - starts a new process 29 | + `exec` - loads program into memory 30 | + `exit` - terminate process and return status code 31 | 32 | - Examples of system calls available in file management: `create`, `delete`, `read`, `write`, `reposition`, `close`, `get_file_attributes`, and `set_file_attributes`. 33 | 34 | - Examples of system calls available for device management: `request`, `release`, `read`, `write`, and `reposition`. 35 | 36 | - I/O devise and files include very similar operations that are they are sometimes combined into one file - devise structure. 37 | 38 | - Examples of system calls available for information maintenance: `time`, `date`, `dump`, `get_process_attributes`, and `set_process_attributes`. 39 | 40 | - Examples of system calls available for communication: `get_hostid`, `get_processid`, `open_connection`, `close_connection`, `accept_connection`, `wait_for_connection`, `read_messages`, `write_messages`, `close_connection`, `shared_memory_create`, and `shared_memory_attach`. 41 | 42 | - Two models of interprocess communication 43 | + message-passing model - communication processes exchange messages with one another to transfer information 44 | + shared-memory model - processes exchange information by reading and writing data in the shared areas of memory. OS does not interfere with this communication and the processes are responsible to avoid writing in the same sectors of memory. 45 | 46 | - message passing is great for smaller amounts of memory and intercomputer communication. Whereas shared memory is great because of memory speed available. Shared memory also has problem with protection and synchronization of memory access. 47 | 48 | 49 | - **Protection** provides a mechanism for controlling access to the resources. 50 | 51 | - Examples of system calls available for protection: `get_permission`, `set_permission`, `allow_user`, and `deny_user`. 52 | 53 | 54 | - **System programs** lie between operating system and application programs. They sometimes represent interface to system calls. 55 | 56 | - Categories of system programs: 57 | + File Management 58 | + Status information 59 | + File modification 60 | + Programming-language support 61 | + Program loading and execution 62 | + Communications 63 | + Background Services (daemons) 64 | 65 | - Principle for designing operating systems: 66 | + mechanisms - how to do something 67 | + policies - what will be done 68 | 69 | - Example of policy and mechanism: timer construct is a mechanism for ensuring CPU protection, but deciding how long the timer is to be set for a user is a policy decision. 70 | 71 | - Routines for performance: interrupt handler, I/O manager, memory manager, and CPU scheduler. 72 | 73 | - Operating system structures/designs: layered approach, microkernels, and modules. 74 | 75 | - > On more computer systems, a small piece of code known as the **boostrap program** or **boostrap loader** located the kernel, loads into main memory, and starts its execution. 76 | -------------------------------------------------------------------------------- /2.0 Process Concept.md: -------------------------------------------------------------------------------- 1 | ## Chapter 2.0: Process Concept 2 | 3 | - Process - a program in execution 4 | 5 | - The words job and process are frequently used interchangeably 6 | 7 | - program counter - specifies the next instruction to execute 8 | 9 | - Components of a process: 10 | + text section - program code 11 | + program counter - current activity 12 | + stack - temporary data (function parameters, return addresses,and local variables) 13 | + data section - global variables 14 | + heap - memory that is dynamically allocated during process run time 15 | 16 | - States of a process: 17 | + new - the process is being created 18 | + running - instructions are being executed 19 | + waiting - the process is waiting for some even to occur 20 | + ready - the process is waiting to be assigned to a processor 21 | + terminated - the process has finished execution 22 | 23 | - Each process is represented in the OS as a process control block (PCB), which include: 24 | + Process state 25 | + Program counter 26 | + CPU registers 27 | + CPU-scheduling information 28 | + Memory-management information 29 | + Accounting information 30 | + I/O status information 31 | 32 | - Threads allows a process to perform more that one task concurrently (e.g., typing and spell-checking) 33 | 34 | - Multicore systems allow for multiple thread to run concurrently. PCB is expanded to hold information for each thread. 35 | 36 | - Representation of a process in LINUX: 37 | 38 | ```C 39 | long state; /* state of the process */ 40 | struct shed_entity se; /* scheduling information */ 41 | struct task_struct *parent; /* this process's parent */ 42 | struct list_head children; /* this process's children */ 43 | struct files_struct *files; /* list of open files */ 44 | struct mm_struct *mm; /* address spec of this process */ 45 | 46 | /* change the state of current process */ 47 | current->state = new_state; 48 | ``` 49 | 50 | - **Dispatch** - when a process is selected for execution 51 | 52 | - **Context switch** - when the CPU is switched to another process, which requires a state save of the current process and a state restore of a different process. 53 | 54 | - **Cascading termination** - when a parent process exits all other children processes of that process are also terminated. 55 | 56 | - Reasons for process cooperation: 57 | + Information sharing 58 | + Computation speedup 59 | + Modularity 60 | + Convenience 61 | 62 | - Two types of buffers in shared-memory systems: 63 | + unbounded buffer - the consume may have to wait for new items, but the producer can always produce new items. 64 | + bounded buffer - the consumer must wait if the buffer is empty, and the producer must wait if the buffer is full 65 | 66 | - In contrast to **shared-memory** environment, **message-passage** systems allow processes to communicate and to synchronize their actions without sharing the same address space. 67 | 68 | - **Round robin** - processes take turns to receiving messages which uses mailbox (indirect communication environment). 69 | 70 | - Message passing may be either blocking or nonblocking: 71 | + blocking send - the sending process is blocked until the message is received by the receiving process or by the mailbox 72 | + nonblocking send - the sending process sends the message and resumes operations 73 | + Blocking receive - the receiver blocks until a massage it available 74 | + Nonblocking receive - the receiver retrieve either a valid message or a null 75 | 76 | - > In contrast to IPC messages, the message exchanged in PRC communication are **well structured** and are thus no longer just packets of data. 77 | 78 | - > Long term scheduling is the selection of processes that will be allowed to contend for the CPU. Normally, long-term scheduling is heavily influenced by resource-allocation considerations, especially memory management. Short-term scheduling is the selection of one process from the ready queue. 79 | 80 | - > Ordinary pipes allow communication between parent and child processes, while named pipes permit unrelated processes to communicate. 81 | -------------------------------------------------------------------------------- /2.1 Multithreaded Programming.md: -------------------------------------------------------------------------------- 1 | ## Chapter 2.1: Multithreaded Programming (MP) 2 | 3 | - Benefits of multithreaded programming 4 | + Responsiveness - user experience improved 5 | + Resource Sharing - share of code and data 6 | + Economy - no need to create process all over again 7 | + Scalability - can take advantage of multiprocessor environment 8 | 9 | - > MP provides a mechanism for more efficient use of these multiple computing cores and improved concurrency; consider an application with four threads. on a system with single computing core, concurrency merely means that the execution of the threads will be interleaved, because the processing core is capable of executing only one thread at a time. On a system with multiple cores, concurrency means that the threads can run in parallel, because the system can assign a separate thread to each core. 10 | 11 | 12 | - In multicore environment threads can run in parallel. 13 | 14 | - > A system is parallel if it can perform more than one task simultaneously. In contrast, a concurrent system supports more than one task by allowing all tasks to make progress. Thus, it is possible to have concurrency without parallelism. CPU schedulers were designed to provide the illusion of parralelism by rapidly switching between processes in the system, thereby allowing each process to make progress (such processes were running concurrently, but not in parallel). 15 | 16 | 17 | - Areas of challenge when programming for multicore systems: + Identifying tasks - tasks that can run independent of one another 18 | + Balance - tasks should perform equal work for efficient use of cores 19 | + Data splitting - 20 | + Data dependency 21 | + Testing and debugging 22 | 23 | - Two type of parallelism: data parallelism and task parallelism 24 | 25 | - Two type of threads: user threads and kernel threads 26 | 27 | - Many to one model (many user threads to one kernel threads); doesn't take advantage of multiple processing cores; multiple threads are unable to run in parallel. 28 | 29 | 30 | - One to one model (one user thread to one kernel thread); overhead in the creation of kernel thread; support parallelism 31 | 32 | - Many to many mode (developers can create as many user threads as necessary, and the corresponding kernel threads can run in parallel) 33 | 34 | - **Synchronous threading** - occurs when the parent thread creates one or more children and then must wait for all its children to terminate before it resumes - fork-join strategy; lots of data sharing. 35 | 36 | - **Asynchronous threading** - once the parent creates a child thread, the parent resumes execution, so that the parent and child can execute concurrently; little data sharing 37 | 38 | - **Immutable** - once value it set, it cannot be changed. 39 | 40 | - Implicit threading - creating and managing thread is transfered from developer to compilers and run-time libraries. 41 | 42 | - Three type of implicit threading: 43 | + Thread pools - dealing with problem of unlimited threads (these can exhaust system resources, such as CPU time or memory); it entails creating multiple threads on process startup which are to be used when a request is made; avoids waiting for new threads to be created; limits the amount of threads created; 44 | + OpenMP 45 | + Grand Central Dispatch (GCD); iOS 46 | 47 | - Parallel region - blocks of code that may run in parallel 48 | 49 | - Signal - indicate that an event has occurred. 50 | 51 | - Synchronous signal - signals are delivered to that same process that performed the operation that caused the signal (e.g., illegal memory access and division by 0). 52 | 53 | - Asynchronous signal - signals are generated by an event external to a running process (e.g., CTRL-C). 54 | 55 | - Default signal handler is when the signal is handled by kernel; user-defined signal handler is when a user overrides the handler. 56 | 57 | - Thread Cancellation terminates **target threads** in two ways: 58 | + Asynchronous cancellation - one thread immediately terminates target thread; can lead to resources not freed up due to canceling of a thread. 59 | + Deferred cancellation - A target thread periodically checks whether it should terminate, allowing it an opportunity to terminate itself in an orderly fashion; safer canceling of threads (avoids any unexpected termination or cancellation of threads.) 60 | 61 | 62 | - Threads belonging to a process share the data of the process; In some cases, each thread might need its own copy of data, which is called **Thread local storage (TLS)**. 63 | 64 | - Lightweight process (LWP) a data structure between the user thread and kernel thread. Serves as a virtual processor in which the user thread can run. This is also known as the scheduler activation, where communication between the user thread library and the kernel happens; 65 | 66 | - **Upcall** when the kernel must inform an application about certain events. Upcalls are handled by the thread library with an upcall handler, which runs on a virtual processor. -------------------------------------------------------------------------------- /6. Deadlock - Part 3 (Distributed Environment).md: -------------------------------------------------------------------------------- 1 | ## Chapter 6: Deadlock Part 3 (Distributed Environment) 2 | 3 | - Resources and processes are located on different machines. 4 | 5 | - In distributed environment, the deadlock avoidance algorithm is avoided. That is because the avoidance algorith depends on information in advance, which is difficult to produce or know in a distributed environment. The desirable algorithms to use in such environment are deadlock detection &recovery or deadlock prevention. 6 | 7 | - A __central coordinator__ maintains information on the resources requested from processed in the distributed environment. This information can be sent periodically from the machine executing the processes. Or it can be sent after every process finishes execution. 8 | 9 | 10 | - Scenario for analysis: 11 | ```Pascal 12 | // Machine 0 (R means resource and P means process) 13 | Pa <-- Rs, Pa --> Rr, Rr --> Pb 14 | 15 | // Machine 1 16 | Pc --> Rs, Pc < -- Rt 17 | 18 | // The global resource graph maintained by central coordinator 19 | Pc --> Rs, Pc < -- Rt,Pa <-- Rs, Pa --> Rr, Rr --> Pb 20 | ``` 21 | 22 | - Currently there is no cycle, so there is no deadlock. If _Pb_ releases _Rr_ and requests _Rt_ from _Machine_1 , there is a possibility that there will be a false alert of deadlock, which is also called __False Deadlock__. This happens because the central coordinator got update from _Machine 1_ first before the release request of _Rr_ from _Machine 0_. 23 | 24 | - This can avoided using a __global clock__ that contains the __timestamp__ of the request and releases of resources. 25 | 26 | - Such approach is also called a __Centralized Deadlock Detection__. 27 | 28 | - There is another type of deadlock detection mechanism called __Distributed Deadlock Detection__. 29 | 30 | - Distributed Deadlock Detection Scenario for Analysis: 31 | 32 | ```Pascal 33 | // Machine 0 34 | P0, P1, P2 35 | 36 | // Machine 1 37 | P3, P4, P5 38 | 39 | // Machine 2 40 | P6, P7, P8, P9 41 | 42 | // processes requests can be represented as a direct edge between processes 43 | // Example 44 | 45 | // Machine 0 46 | P0 --> P1 // Process P0 is waiting for a resource acquired by P1 47 | P1 --> P2 48 | P2 --> P3 // P3 is a process in Machine_1 49 | 50 | // Machine 1 51 | P3 --> P4 52 | P3 --> P5 53 | P4 --> P6 // P6 is a process in Machine 2 54 | P5 --> P7 // P7 is a process in Machine 2 55 | 56 | // Machine 2 57 | P6 --> P8 58 | P7 --> P9 59 | P8 --> P0 // P0 is a process in Machine_0 60 | ``` 61 | 62 | - When a process requests a resource held by another process. There is message being generated by a process. The message contains three bits of information as follows: 63 | 64 | ```Pascal 65 | // Structure of message; 66 | < Pi, Pj, Pk > //Pi is process blocked; Pj is current process; Pk is requested process 67 | 68 | // As P0 requests resources from P1, the following message is generated 69 | P0: < 0, 0, 1 > 70 | 71 | // P1 modifies message and sends out modified version 72 | // P1 --> P2 73 | P1: < 0, 1, 2 > 74 | 75 | // P2 --> P3 76 | P2: < 0, 2, 3 > 77 | 78 | // P3 --> P4 79 | P3: < 0, 3, 4 > 80 | 81 | // P3 --> P5 82 | P3: < 0, 3, 4 > 83 | 84 | // P5 --> P7 85 | P5: < 0, 5, 7 > 86 | 87 | // P7 --> P9 88 | P7: < 0, 7, 9 > 89 | 90 | // P4 --> P6 91 | P4: < 0, 4, 6 > 92 | 93 | // P6 --> P8 94 | P6: < 0, 6, 8 > 95 | 96 | // P8 --> P0 97 | P8: < 0, 8, 0 > 98 | 99 | // Here the system realizes that Pi and Pk are identical and that there is a cycle creating a deadlock state. 100 | 101 | ``` 102 | 103 | - One simple solution to break the deadlock is to have P0 commit suicide -- terminates itself. 104 | 105 | - Given that another process _P6_ is blocked, the message generated by _P6_ will be < 6, 6, 8 >. There will a situation where _P6_ will receive a message like < 6, 4, 6 > from _P4_. In such cases both _P0_ and _P6_ will need to be terminated. 106 | 107 | - In the scenario above, to avoid the termination of the two processes, an additional information can be inserted into the message before sending it out. the additional information contains the list of processes through which the message has passed. Now, the process that gets terminated is the last highest message, which in this case is process _P8_. Moreover, _P6_, when blocked, will also receive a message similar to _P0_. This ensures that the process to be killed is _P8_ instead of the two processes. The disadvantage is that more information has to be circulated before making a decision. 108 | 109 | - Deadlock Prevention scenario: In this mechanism, a timestamp is assigned to each process. This timestamp is used to decide whether a resource requested by a process is granted or the process needs to wait. The following shows some of the criteria using this timestamp 110 | 111 | ```Pascal 112 | // Given two processes with the following relationship 113 | // Relationship: Pi is requesting resource allocated to Pj 114 | Pi -- > Pj 115 | 116 | Ti = 20 // execution time of process Pi 117 | Tj = 10 118 | 119 | // checks 120 | If Ti > Tj then Pi waits 121 | If Ti <= Tj then Pi is terminated 122 | ``` 123 | -------------------------------------------------------------------------------- /3. Process Managment (Disitributed Environment).md: -------------------------------------------------------------------------------- 1 | ## Chapter 3: Process Management (Distributed Environment) 2 | 3 | - There are two types of models in the distributed environment, where the operating system operates and executes jobs differently: **Workstation Model** and **Processor Pool Model**. CPU allocation works different in the distributed environment. 4 | 5 | 6 | - In the workstation model, users are assigned a different workstation to operate in the network. Here every user has access to processing power. 7 | 8 | 9 | - In the processor pool model, users are provided a high-end terminal and connected to a high-end server (with many CPUs). In processor pool model, users are not provided access to processing power. The processing power is kept at a central location or the high-end server in this case. 10 | 11 | 12 | - In the distributed environment there are two types of CPU allocation techniques: **Non-migratory** and **Migratory**. 13 | 14 | 15 | - **Non-migratory** is static in nature. In this environment, the processor in which a job is scheduled to execute is fixed and not editable. That means, the job remains in that process for its entire life time. 16 | 17 | 18 | - In the **migratory** model, a process can be shifted for execution from one processor to another processor. The shifting of jobs is sometimes necessary to improve the system's performance. 19 | 20 | 21 | - When tasks are divided into subtasks what are the requirements for the entire process to execute concurrently? 22 | 23 | 24 | - **Concurrent Processing** - Statements make up a particular tasks. The aim of the operating system is to find out how many statement of a task can be executed concurrently or simultaneously. If a statement doesn't depend on another, then it can be concurrently be processed with another statement that also doesn't depend on another statement; so both statement cannot depend on another statement. 25 | 26 | 27 | - Given the example of statements below, 28 | 29 | | Si | task | 30 | |----|-----------| 31 | | S1 | a = x + y | 32 | | S2 | b = z + 1 | 33 | | S3 | c = a + b | 34 | | S4 | d = c - 1 | 35 | 36 | - S1 and S2 can be processed concurrently since they don't depend on any of the other statements. Conversely, S3 and S4 cannot be processed concurrently since they must wait for S1 and S2 to finish executing. 37 | 38 | 39 | - This whole process of what gets to be executed first can be represented using **directed graphs** or more accurately ***precedence graph**, where one node (represented as a statement) points to another if it required by another node. 40 | 41 | 42 | - The **read set** of a given statement is the set of variables which are only referred in ***S(i)***; their values are not modified by ***S(i)***. The ***write set*** is the set of variables which are referred and modified by ***S(i)***. 43 | 44 | 45 | - ***R(S1) = {x, y}*** and ***W(S1) = {a}***; ***R(S2) = {z}*** and ***W(S2) = {b}***;***R(S3) = {a, b}*** and ***W(S3) = {c}***; ***R(S4) = {c}*** and ***W(S3) = {d}***. 46 | 47 | 48 | - A concurrent scenario: If ***R(S(i))*** (read set of si) and ***W(S(j))*** (write set of sj) do not intersect with each other; If ***W(S(i))*** and ***W(S(j))*** do not intersect; If ***W(S(i))*** and ***R(S(j))*** do not intersect; If ***R(S(i))*** and ***R(S(j))*** does or doesn't intersect. 49 | 50 | 51 | - Concurrents tasks, regardless of the order in which they are executed, will produce the same overall output. 52 | 53 | 54 | - **Precedence relation** refers to the relationship of nodes (statements) within a precedence graph. 55 | 56 | 57 | - Algorithm for implementing concurrent processing (an example): 58 | - The arcs of the precedence graph: S1 --> S2; S1 --> S3; S2 --> S4; S4 --> S5; S4 --> S6; S5 --> S7; S6 --> S7; S3 --> S7. 59 | 60 | 61 | ```C 62 | begin 63 | count = 3 // three processes are to be joined at a point 64 | S1 65 | fork L1 //creates two processes: one starts execution at L1 (L1: S3) and one that follows L1 66 | S2 67 | S4 68 | fork L2 //(L2:S6) 69 | S5 70 | join count // L3: decrease count by 1; if count != 0 then quit else continue 71 | S7 72 | end 73 | ``` 74 | 75 | - The **join** statement terminates a process then decreases count by 1. When count becomes 0 then it proceeds to the other process in the graph. Essentially, the join statement is taking all the processes, terminating them and exiting from to one final process (S7 in the example provided above). 76 | 77 | - There might be cases where the processes can be joined to multiple points instead of just one point as in the example provided above. In such cases, defining a single count variable in not sufficient. 78 | 79 | - Algorithm for an example that uses multiple joins: 80 | - The arcs representing the precedence graph: S1 --> S2; S1 --> S3; S2 --> S4; S2 --> S5; S4 --> S6; S5 --> S6; S3 --> S7; S6 --> S7. 81 | 82 | 83 | ```C 84 | begin S1 85 | count_1 = 2 86 | count_2 = 2 87 | fork L1 // L1:S3 (goto L4) 88 | S2 89 | fork L2 // L2:S5 (goto L3) 90 | S4 91 | join count_1 //L3 92 | S6 93 | join count_2 //L4 94 | S7 95 | end 96 | ``` 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /12 Mass Storage Structure.md: -------------------------------------------------------------------------------- 1 | ## Chapter 1.2: System Structures 2 | 3 | - Bus connects the drive to the computer 4 | 5 | - Host controller (end of bus) allows for transferring of commands (via messages) to the disk controller (built inside the drive). 6 | 7 | - Disk controllers usually have a built-in cache. 8 | 9 | - SSD - have no moving parts, no latency, consume less power; expensive, less capacity, shorter life span; directly connected to the system bus (PCI); 10 | 11 | - magnetic tapes - slower access time than magnetic disks and main memory; these types of tapes are mainly used for backups; 12 | 13 | - Disk scheduling: 14 | + **FCFS** - problem with request on heads far apart 15 | + **Shortest-seek-time-first (SSTF) algorithm** - takes care of the requests close to the head (with least seek time); a form of SJF algorithm and may lead to starvation of some requests 16 | + **SCAN scheduling** - scan back and fort and service requests as they are being scanned; avoid starvation 17 | + **Circular Scan (C-SCAN) scheduling** - scan back and fort but only service requests one way 18 | + **LOOK Scheduling** - only scans until meeting the last request on one end and then moves back. 19 | 20 | - Disk Management: 21 | + Low-level formatting - fills the disk with a special data structure for each sector; includes partitioning disk into cylinders and logical formatting (creation of a file-system) 22 | + Boot Block - stores bootstrap program which initializes all aspects of the system, from cPU registers to devise controllers and the contents of main memory, and then starts the operating system; a disk that has boot partition is called a boot disk or system disk; master boot record (MBR) contains boot code and partition table. 23 | + Bad Blocks - bad block are search for so as to make unusable; these are usually replaced with a spare; to ensure optimization the replacement sector is chosen with the cylinder 24 | 25 | - Virtual memory is a feature of an operating system (OS) that allows a computer to compensate for shortages of physical memory by temporarily transferring pages of data from random access memory (RAM) to disk storage. 26 | 27 | - swap space - moving process information (code and data segments) from main memory -- because of critical low point -- to a swap space location on disk 28 | 29 | - swap space can be located on the file system or the raw partitions; the trade-off is between the convenience of allocations and management in the file system and the performance of swapping in raw partitions. 30 | 31 | - Redundancy arrays of independent disks (RAID) - setting up disk in parallel to improve performance (higher data-transfer rate) and reliability; 32 | + Improving reliability via Redundancy - 33 | * Mirroring - a logical disk consists of two physical disks and every write is carried out on both disks (mirrored volume); rate of failure is reduced because there is a low probability that both disks will fail at the same time (this also depends on the time it take to repair a disk); 34 | + Improve in Performance via Parallelism - Data stripping or bit level stripping (consist of splitting the bits of each byte across multiple disks); every disk participates in every access, so the number of accesses that can be processed per second is about the same as on a single disk, but each access can read eight times as many data in the same time as on a single disk; block-level stripping (in blocks); this increases throughput 35 | 36 | - Mirroring provides high reliability, but it is expensive. Stripping provides high data transfer rate, but it does not improve reliability. 37 | 38 | 39 | - Raid Levels - different levels to provide redundancy at lower cost by using disk stripping combined with "parity" bits 40 | + RAID level 0 - disk arrays with stripping but no redundancy 41 | + RAID level 1 - disk mirroring 42 | + RAID level 2 - uses ECC to detect corrupted data; data is stored in disk using stripping; further disk then hold error-correction bits; if one disk fail the remaining bytes stored in the other disks and the associated error-correction bits can be read from other disks to reconstruct the damaged data; three disk overhead 43 | + RAID level 3 - one disk overhead, less disk require; parity bits of sectors are computed and compared to to see if data has been lost of corrupted; uses bit stripping 44 | + RAID level 4 - uses block stripping; one parity disk 45 | + RAID level 5 - data and parity bits are distributed among disks; a parity block cannot store parity for blocks in the same disk, because a disk failure would result in loss of data as well as of parity, and hence loss would not be recoverable; avoid overuse of a single parity disk 46 | + RAId level 6 - almost like level 5 but also uses redundancy to guard against multiple disk failures; error correcting code is used; 47 | + RAID level 0 + 1 and 1 + 0 - 48 | * RAID 0 + 1 combines the idea of RAID level 0 and RAID level 1 to increase performance and reliability; it also requires more disks; if a single disk fails then one entire strip is lost; disks are stripped then mirrored; 49 | * RAID level 1 + 0 - first disks are mirrored then stripped; if one disk fails then other mirror is still available for restoring. 50 | 51 | -------------------------------------------------------------------------------- /10. Buffer Cache.md: -------------------------------------------------------------------------------- 1 | ## Chapter 10: Buffer Cache 2 | 3 | - Two components are stored in the buffer cache: __Header Area__ and __Data Area__. Data area contains one block of the secondary storage. When there is a memory miss, then the CPU checks the buffer cache to see if the data block is in the data area before going directly to the secondary storage. If the block of data is in the buffer cache, then it is taken directly into user area of memory. If the data block is not in the buffer cache, then the CPU must go to access the data block from disk and then bring into the buffer cache. 4 | 5 | - Essentially, the purpose of the buffer cache is used to reduce the frequency of disk access. 6 | 7 | - If all the buffer cache is being used then one block is victimized using the LRU technique used in the Page memory management technique. 8 | 9 | - The header area is used for block identification. It contains __device no__, __block no__ and __status__ which is obtained from the block found in the secondary storage. 10 | 11 | - Blocks found in the buffer area are maintained using double-linked lists. One of the pointer point to the Data Area. Another pointer points to __next buffer hash queue__. And the next pointer is pointing to the __previous buffer on hash queue__. Another one point to the __next buffer on free buffer list__. And the last one which points to the __previous buffer on free buffer list__. 12 | 13 | - The __status__ field contains the following information: 14 | + buffer is currently blocked 15 | + buffer contains corrupted data 16 | + buffer delayed write (when preparing changes to be rewritten to secondary storage) 17 | + buffer <--> secondary storage (reading and writing) 18 | + A process is currently waiting for the buffer to become free 19 | 20 | 21 | - Using LRU, the block that is replaced in case the buffer doesn't contain any empty blocks, is the one that is at the head of the linked list. And the block being recently put into buffer is put at the tail of the list. 22 | 23 | - Hash queues are used to reduce the search complexity that checks if a block exists in buffer cache. 24 | 25 | - If there are four block in the hash queue, the way to check where to place the block in the queues is decided by the following formula: (n mode 4). Below is an example of buffers in the hash queue. 26 | 27 | ```Pascal 28 | H0 <--> 28 <--> 4 <--> 64 <--> 29 | H1 <--> 17 <--> 5 <--> 97 <--> 30 | H2 <--> 98 <--> 50 <--> 10 <--> 31 | H3 <--> 3 <--> 35 <--> 99 <--> 32 | ``` 33 | 34 | - There could be a case where a buffer exist in the free buffer list and hash queue list. In other words free buffer list is a subset of hash queue list, where the free buffer list in entirely present in the hash queue list. The two lists can be connected using the pointers. 35 | 36 | 37 | - The algorithm for obtaining a block, executed by the kernel: 38 | 39 | ```Pascal 40 | While (buffer not found): // run this block until buffer was made free to load in block 41 | if (block in hash queue): 42 | if (buffer locked): 43 | sleep (event: buff become free); //process waits (go to sleep) 44 | continue; 45 | mark buff busy; // lock buffer 46 | remove buff from free list; 47 | return buffer; 48 | else: 49 | if (there are no buffer on free list): 50 | sleep (event: any buff become free); 51 | continue; 52 | remove buff from F.L.; 53 | if (buff marked delayed write); 54 | asynchronous write buffer to disk; 55 | continue; 56 | remove from old hash queue 57 | put buff in new H.Q. 58 | return buff; // buffer made available to process to read in new block. 59 | ``` 60 | 61 | - Algorithm for releasing block 62 | 63 | ```Pascal 64 | wakeup all processes: waiting for any buffer to become free; 65 | wakeup all processes: waiting for this buffer to become free; 66 | 67 | if (buffer contents valid and not old): 68 | enqueue buff at end of free list; // protecting recently used buffer 69 | else: 70 | enqueue buffer at head of free list; 71 | 72 | unlock the buffer 73 | ``` 74 | 75 | - When the kernel finds that a buffer is in a __delayed write state__, the content of that buffer is immediately written back to disk. After the writing is done, then the buffer is returned back to the free list (head of free list since it has already been saved back to disk). 76 | 77 | - Algorithm for reading block: 78 | 79 | ```Pascal 80 | // output (free buffer or a buffer containing the block that is requested) 81 | get buffer for block; invokes (get block algorithm); 82 | 83 | if (buffer data is valid): 84 | return the buffer to the process; 85 | 86 | initiate disk read into the buffer; 87 | process goes to sleep (event: disk read is complete); 88 | return (buffer); 89 | ``` 90 | 91 | - Algorithm for block read ahead (reading a block from disk into buffer cache) 92 | 93 | ```Pascal 94 | if (first block is not in cache): 95 | get buffer for first block; 96 | initiate disk read; 97 | 98 | if (second block is not in cache): 99 | get buff for second block; 100 | initiate disk read (asynchronous); 101 | 102 | if (first block in cache): 103 | return buffer; 104 | ``` 105 | 106 | - Algorithms for writing block 107 | 108 | ```Pascal 109 | Initiate disk write; 110 | if (write operation is synchronous): 111 | process goes to sleep until (event: write operation is complete) 112 | release buffer; invoke release block algorithm 113 | 114 | elseif (buffer marked as delayed write): 115 | mark buffer as old 116 | return buffer to head of free list 117 | ``` 118 | 119 | -------------------------------------------------------------------------------- /6. Deadlock - Part 1 (Theory).md: -------------------------------------------------------------------------------- 1 | ## Chapter 6: Deadlock Part 1 (Theory) 2 | 3 | - __Review:__ The main function of the operating system is to manage the resources which are to be shared by more than one processes. 4 | 5 | - Given two processes _P1_ and _P2_, requiring two resources _A_ and _B_... There could be a scenario where _P1_ has acquired resource _A_ and is waiting for resource _B_, which has been acquired by another process _P2_, which is also waiting for the same process that was initially acquired by _P1_. This situation leads to a __deadlock__ where both processes will never complete since they both depend on resources which were previously acquired by either one them. 6 | 7 | - The deadlock problem can also be modeled this way: _P1_ acquires _A_ and _P2_ acquires B; _P1_ needs _B_ to complete and _P2_ needs _A_ to complete. 8 | 9 | - To avoid deadlock the assigning of resources to processes needs an efficient assigning mechanism. This can done using the concept of requesting where a process that needs a resource must first request to use that resource. A life cycle or sequence of a request for a resource: __Request__ --> __Use__ --> __Release__. 10 | 11 | - __Deadlock Definition__: 12 | > A set of processes is said to be in deadlock when every process in the set waits for an event that can only be caused by another process in the set. 13 | 14 | - In our previous example, _P1_ is waiting for the __release event__ by the other process _P2_. 15 | 16 | - There are four conditions for the occurrence of a deadlock: 17 | - 1. __Mutual Exclusion__ - at least one resource in the system which cannot be shared simultaneously by more than one process. 18 | - 2. __Hold and Wait__ - a process is holding and waiting for a resource. 19 | - 3. __No preemption__ - no interrupt of the process running. 20 | - 4. __Circular Wait__ - an extension of Hold and Wait. 21 | 22 | - If we can ensure that one of the condition from the list above is not present, then we can say that the system is deadlock free. 23 | 24 | - In a system, we can have the following resources: 25 | + Finite number of resources. 26 | + Various types of resources. 27 | + Every type has a number of identical resource instances. 28 | 29 | - A deadlock situation can be represented using a graph technique (Resource Allocation Graph). Given a graph G = (V,E), Vertices (V) represent processes and resource type. Edges (E) represent request edges (_Pi_, _Rj_) and allocation edges (_Rj_, _Pi_). 30 | 31 | - Given the processes _P1_, _P2_ and _P3_; and the following resources _R1_, _R2_, _R3_ and _R4_. _R1_ and _R3_ have one resource instance while _R2_ and _R4_ each have 2 and 4 instances, respectively. Below are the following requests and allocations: 32 | 33 | ```Pascal 34 | P1 --> R1; // request 35 | P1 <-- R2(2); // instance 2 36 | P2 <-- R1; // allocation 37 | P2 --> R3; 38 | P2 <-- R2(1); 39 | P3 <-- R3; 40 | ``` 41 | 42 | - The above situation doesn't present a deadlock. 43 | 44 | - If we modify the list of allocation and requests as the following: 45 | 46 | ```Pascal 47 | P1 --> R1; // request 48 | P1 <-- R2(2); // instance 2 49 | P2 <-- R1; // allocation 50 | P2 --> R3; 51 | P2 <-- R2(1); 52 | P3 <-- R3; 53 | P3 --> R2; // this request presents a deadlock; there is not enough instances in R2 54 | ``` 55 | 56 | - If there is a cycle in the graph, __there is a possibility__ of a deadlock, especially if there is only _one instance of a resource type_. 57 | 58 | - Deadlocks are better handling using an avoid strategy rather than detecting and killing processes. 59 | 60 | - There are three strategies for dealing with deadlock: 61 | + 1. __Deadlock Prevention__ - in this approach, the system prevents deadlock by breaking any of the four criteria that make up a deadlock. Restriction is placed on the way processes request resources. 62 | + 2. __Deadlock Avoidance__ - no restriction on the way processes request resources; such method avoids the natural cycle that leads to deadlock. 63 | + 3. __Deadlock Detection and Recovery__ 64 | 65 | 66 | - __Deadlock Prevention__ and the four criteria: 67 | + __ME__ - cannot be broken since a nonshareable resource can only be acquired by one process. 68 | + __Hold and Wait__ - when a process requests a resource, a condition can be placed for that process to release any resources that is has acquired before. Another way is to have a process execute only when it has requested and acquired all necessary resources (inefficient use of resources). Starvation is also possible since another process might be waiting for a resource that is always allocated to some other process. 69 | + __No preemption__ - Given the following scenario: _Pi_ --> _Rj_ --> _Pj_; _Pi_ is not allowed to request any other resource if _Rj_ is being held by another process. Additionally, _Pi_ must release all other resources which it had acquired before. 70 | + __Circular Wait__ - Allows the process to request resources in a particular order. Establish a mapping function to achieve this. 71 | 72 | - Circular wait mapping function: 73 | 74 | ```Pascal 75 | F: R -> N 76 | F(Ri) < F(Rj) 77 | P1 --> R1 --> P2 --> R2 --> P3 --> ... Rn-1 --> Pn --> Rn --> P1 78 | 79 | // in order to avoid the circular wait it must follow the following rules 80 | F(R1) > F(Rn) 81 | F(R2) > F(R1) 82 | ... 83 | F(Rn) > F(Rn-1) 84 | ``` 85 | 86 | - One way this may not be possible is when the system doesn't know the order in which the processes requested the resources. 87 | 88 | - __Deadlock Avoidance:__ The processes must let the system know in advance what are the maximum requirements for requesting and acquiring resources. Once knowing all the requirements of the processes, then it can take decisions to make some processes wait in order to avoid deadlock. One such algorithm used to solve this problem is called the __Banker's Algorithm__. -------------------------------------------------------------------------------- /13 IO Systems.md: -------------------------------------------------------------------------------- 1 | ## Chapter 13 I/O Systems 2 | 3 | - > Device drivers - present a uniform device-access interface to the I/O susbsystem, much as system calls provide a standard interface between the application and the operating system. 4 | 5 | - PCI bus connects fast devices; expansion bus connects all the other slower devices such as the keyboard, serial and USB ports; 6 | 7 | - A bus is a set of wires connecting the devise to a port (serial port) on the machine. 8 | 9 | - Small Computer System Interface (SCSI) is a bus connected to the SCSI controller 10 | 11 | - A controller a collection of electronics that can operate a port, bus, or a device; a serial-port controller controls the signal on the wires of a serial port. 12 | 13 | - The controller has one or more registers for data and control signals - this is how processors are able to give commands and data to a controller in order to accomplish and i/o transfer. 14 | 15 | - The system uses two techniques to control devices: 16 | + instruction directly to device registers - specify a transfer of a byte or word to the i/o port address. 17 | + memory-mapped i/o - the device control registers are mapped into the address space of the processor; in other words, communication happens from the CPU to main memory where the device registers are mapped; this is faster and more efficient than simple instructions sent directly to the i/o port address. 18 | 19 | - I/O port consists of four main registers: 20 | + data-in register - to get input 21 | + data-out register - to send output 22 | + status register - bits read by the host (usually contain information about the state of the device; or device error) 23 | + control register - allows for changing the mode of a device; or even enables parity checking 24 | 25 | 26 | - > It is more efficient for the hardware controller to notify the CPU when the device becomes read for service, rather than to require the CPU to poll repeatedly for an I/O completion. The hardware mechanism that enables a device to notify the CPU is called an interrupt; this interrupt is done via the interrupt-request line which is sensed by the CPU; the task is then dispatched to the interrupt handler by the CPU, and the handler clears the interrupt by servicing the device; then the CPU resumes processing of interrupted task; 27 | 28 | - Interrupt control hardware - handles defers of interrupt handling; dispatching to the proper interrupt handler without first polling all the devices to see which one raised the interrupt; and distinguishing between high and low level priority interrupts and can respond with the appropriate level of urgency. 29 | 30 | - interrupt vector hold addresses that point to the head of the interrupt handlers for making it more efficient to find the appropriate interrupt handler. 31 | 32 | - the CPU has two interrupt request lines: 33 | + non-maskable - handles memory errors 34 | + maskable - handle device-generated interrupts 35 | 36 | - Direct Memory Access (DMA) - is in charge of large transfer which are delegated from the CPU; usually hold pointer of source, destination and then operate the memory bus directly, to perform the transfers without the CPU. 37 | 38 | - The purpose of the device driver layer is to hide the differences among device controllers from the I/O subsystem of the kernel; this provides the creator of new devices to write device drivers for the OS to support their devices or design devices that are compatible with an existing host controller interface (such as SATA) 39 | 40 | - Several services - scheduling, buffering, caching, spooling, device reservation, and error handling -- are provided by the kernel I/O subsystem and build on the hardware and device-driver infrastructure. 41 | 42 | - buffer are areas of memory set aside for dealing with data transfer decoupling and data transfer size between devices or between a device and an application. also used for copy semantics (making sure that the data provided by the application is always the latest version); another way to achieve this is to copy the application buffer to the kernel buffer so that the disk write is performed from the kernel buffer. 43 | 44 | - a cache is a region of fast memory that hold copies of data; the difference of the buffer and the cache is that the buffer usually holds the only copy of the data and the cache hold a copy the data on faster storage 45 | 46 | - a spool is a buffer that holds output for a device, such as a printer, that cannot accept interleaved data streams. 47 | 48 | 49 | - Protected memory can avoid complete system malfunction. Network problems can be dealt with resubmitting system calls by the operating system. Permanent reasons (such as when device controllers become defective) are not recoverable by the OS. 50 | 51 | - Error handling - a bit of information (errno), in form of an integer, is returned by the system call 52 | 53 | - I/O protection - Instead of having users perform direct I/O, the request must go through the OS by means of a system call; memory protection also protect any memory-mapped and I/O port memory locations from user access. in case of memory-mapped graphics controller, for example, the kernel provides locking mechanism to protect overload on the device. 54 | 55 | - In summary, the I/O subsystem coordinates a collection of services that are available to applications and to other parts of the kernel. The I/O subsystem supervises these procedures: 56 | + management of the name space for files and devices 57 | + access control to files and devices 58 | + operation control 59 | + file-system space allocation 60 | + device allocation 61 | + buffering, caching, and spooling 62 | + I/O scheduling 63 | + device-status monitoring, error handling, and failure recovery 64 | + device-driver configuration and initialization 65 | 66 | - The upper levels of the I/O subsystem access devices via the uniform interface provided by the device drivers. 67 | 68 | - The device name also has the form of a name in the file-system name space. When UNIX looks up this name in the file-system directory structures, it finds not a device number. The major device number identifies a device driver that should be called to handle the I/O to this device. The minor device number is passed to the device driver to index into a device table. The corresponding device-table entry gives the port address or the memory-mapped address of the device controller. 69 | 70 | -------------------------------------------------------------------------------- /4. Concurrent Processing.md: -------------------------------------------------------------------------------- 1 | ## Chapter 4: Concurrent Processing 2 | 3 | - There are two types of structures in concurrent processing: `Fork/Join` and `Cobegin/Coend`. 4 | 5 | - Given the following example of precedence graph: S1 --> S2; S1 --> S3; S2 --> S4; S2 --> S5; S4 --> S6; S5 --> S6; S3 --> S7; S6 --> S7. The following is the algorithm that satisfies the `Cobegin/Coend` construct: 6 | 7 | ```C 8 | begin 9 | S1; 10 | Cobegin 11 | S3; 12 | begin //sequential process 13 | S2; 14 | Cobegin 15 | S4; 16 | S5; 17 | Coend 18 | S6; 19 | end 20 | Coend 21 | S7; 22 | end 23 | ``` 24 | 25 | - In the `Cobegin/Coend` structure, it ensures that the parent process continues execution. Conversely, in the `Fork/Join` structure, there is no guarantee since either the parent or child process can continue the execution and the processes are terminated. 26 | 27 | 28 | - Given the following example of precedence graph: S1 --> S2; S1 --> S3; S2 --> S4; S4 --> S5; S4 --> S6; S3 --> S6; S5 --> S7; S6 --> S7; 29 | 30 | 31 | - The previous example cannot be represented using a `Cobegin/Coend` structure because of the redundant relation between S3 --> S6. On the other hand, the precedence graph can be represented using `Fork/Join` structure, which makes this structure more stronger than the `Cobegin/Coend` structure. 32 | 33 | 34 | - **Producer/Consumer Problem**: The items produced by the producer process are consumed by the consumer process. The consumer process will be able to consume items independent of producer process. If the consumer process consumes items at a faster rate than what the producer process can produce, then it will have to naturally wait. On the other hand, if the producer process produces items at a faster rate than what the consumer process can consumer, then some items are lost. A typical example of such model is the printer. The computer is the produce and the printer in the consumer. The printer consumes the items in the form of printing the items. 35 | 36 | 37 | - A **buffer** can be used to deal with the speed mismatch between producing and consuming rate. 38 | 39 | 40 | - There are two types of buffers: **Unbounded buffer** and **Bounded buffer**. Bounded buffer would force the producer process to wait when the buffer gets full. Unbounded buffer forces the consumer process to wait if the rate of consumer process is higher than the rate of the produce process. There could be a time when the unbounded buffer has no items. 41 | 42 | 43 | - The producer/consumer problem can be algorithmically represented using the following code: 44 | 45 | ```java 46 | type item = ; 47 | var buffer = array[0..n-1] of item; 48 | in, out = 0..n-1; 49 | nextp, nextc: item; 50 | in = 0; 51 | out = 0; 52 | ``` 53 | 54 | - The `Cobegin/Coend` structure for the _producer process_ is as follows: 55 | 56 | ```C 57 | cobegin 58 | producer: //producer process 59 | begin 60 | repeat 61 | ... 62 | produce an item in nextp; 63 | ... 64 | while(in+1) mod n = out do skip; 65 | buffer[in] = nextp; //input next item in buffer location (in) 66 | in = (in+1) mod n; 67 | until false; 68 | end 69 | end 70 | coend 71 | ``` 72 | 73 | - The `Cobegin/Coend` structure for the _consumer process_ is as follows: 74 | 75 | ```C 76 | cobegin 77 | consumer: //consumer process 78 | begin 79 | repeat 80 | while in = out do skip; //check that the buffer is empty; if not, goto next line. 81 | nextc = buffer[out]; 82 | out = (out+1) mod n; 83 | ... 84 | consume item in nextc; 85 | ... 86 | until false; 87 | end 88 | end 89 | coend 90 | ``` 91 | 92 | - In the algorithm above, we assume that both the consumer and producer process goes on producing and consuming items indefinitely. Additionally, the algorithm assumes that at least one location in the buffer must be free. A _counter_ is used to keep track of how many items are in the buffer so as to facilitate the inserting and getting items from the buffer. The algorithm can be modified to use the counter as follows: 93 | 94 | ```C 95 | cobegin 96 | producer: //producer process 97 | begin 98 | repeat 99 | ... 100 | produce an item in nextp; 101 | ... 102 | __while count = n do skip;__ //if the counter is the same as the amount of items, skip below process. 103 | buffer[in] = nextp; //input next item in buffer location (in) 104 | in = (in+1) mod n; 105 | __count = count + 1;__ 106 | until false; 107 | end 108 | end 109 | coend 110 | ``` 111 | 112 | ```C 113 | cobegin 114 | consumer: //consumer process 115 | begin 116 | repeat 117 | __while count = 0 do skip;__ //check if true; if not, goto next line. 118 | nextc = buffer[out]; 119 | out = (out+1) mod n; 120 | count = count - 1; 121 | ... 122 | consume item in nextc; 123 | ... 124 | until false; 125 | end 126 | end 127 | coend 128 | ``` 129 | 130 | - The statement `count = count + 1` -- used by the producer process --is written in a high-level language, which translates to the following assembly line instructions: 131 | 132 | ```C 133 | A: R1 = count // R1 = register 1 134 | B: R1 = R1 + 1 135 | C: count = R1 136 | ``` 137 | 138 | - The statement `count = count - 1` -- used by the consumer process --is written in a high-level language, which translates to the following assembly line instructions: 139 | 140 | ```C 141 | D: R2 = count // R1 = register 1 142 | E: R2 = R2 - 1 143 | F: count = R2 144 | ``` 145 | 146 | - The process scheduler can process the above tasks in the following manner: A, B, D, E, C, F. The following is the procedure in details, assuming that count is initialized with a value of 7: 147 | 148 | ```C 149 | A -> R1 = 7 150 | B -> R1 = 8 151 | C -> R2 = 7 152 | E -> R2 = 6 153 | C -> count = 8 154 | F -> count = 6 155 | ``` 156 | 157 | - From the above computation, the value of count is not equal to 7, therefore, it creates an error. Such error happened because one process was terminated to continue another without any precautions to protect the value of the counter. Another way to say explain this behavior is to understand that the value of count is in the write set of both the producer processor and consumer processor. Initially, it was pointed out that in order for two tasks to run concurrently, the two tasks cannot be in the write set of two concurrently running processes. 158 | 159 | -------------------------------------------------------------------------------- /6. Deadlock - Part 2 (Algorithms).md: -------------------------------------------------------------------------------- 1 | ## Chapter 6: Deadlock Part 2 (Algorithms) 2 | 3 | - Banker's Algorithm: 4 | 5 | ```Pascal 6 | // conditions 7 | n --> processes 8 | m --> resource types 9 | 10 | // vector needed by the algorithm 11 | Available_m: Available[i] = k // number of instances k for resource i 12 | 13 | Max_nXm: Max[i, j] = k // maximum requirement of every process inside matrix; every row vector represents the resource requirement of the i_th process 14 | 15 | Allocation_nXm: Allocation[i, j] = k //current allocation of resources to processes. 16 | 17 | Need_nXm: Need[i, j] = k // the resources that will be required by process. Calculate using difference between Max and Allocation. 18 | ``` 19 | 20 | - Actual algorithm (runs every time a process request a resource): 21 | 22 | ```Pascal 23 | // a request 24 | Pi -> Request_i 25 | 26 | // algorithm 27 | // check if the request is less than Need vector 28 | if Request_i <= Need_i 29 | then go to step 2 30 | else 31 | error 32 | 33 | // check if a valid request 34 | if Request_i <= Available 35 | then go to step 3 36 | else 37 | wait 38 | 39 | // modify Available, Allocation and Need vector by request granted 40 | // the system pretends to have allocated the resources 41 | Available = Available - Request_i 42 | Allocation_i = Allocation_i + Request_i 43 | Need_i = Need_i - Request_i 44 | 45 | Check if this new state is safe, i.e., if a safe sequence exists. 46 | ``` 47 | 48 | - A __safe sequence__ is the way processes are allocated resources and guarantees that every process completes execution. However, it may not be the same order in which the resources requested resources. Below is the algorithm used to check if a sequence is safe: 49 | 50 | - __Safety Algorithm__: 51 | 52 | ```Pascal 53 | // Step 1: initialization 54 | Work = Available 55 | Finish = False 56 | 57 | // Step 2 58 | Find an i such that Finish[i] = False and Need[i] <= Work 59 | If no such i, go to step 4 60 | 61 | // Step 3: 62 | Work = Work + Allocation_i 63 | Finish = True 64 | go to step 2 65 | 66 | // Step 4: 67 | If Finish[i] = True for all i 68 | then system is Safe 69 | ``` 70 | 71 | - When a system in __Unsafe__ it may lead to deadlock and it doesn't mean it will always cause a deadlock. When it is safe, it will always avoid a deadlock. 72 | 73 | 74 | - An example illustrating the process deadlock avoidance: 75 | 76 | ```Pascal 77 | // Resources 78 | A = 10 // 10 instances 79 | B = 5 80 | C = 7 81 | 82 | // Processes 83 | P0, P1, P2, P3, P4 84 | 85 | // The process requests in advance in form of matrix 86 | // Allocation, Max, Available 87 | // Allocation and Max requirements are given; Available = (Resource Instances - Allocated) 88 | // Example 89 | | | | Allocation | | | Max | | | Available | | 90 | |----|---|------------|---|---|-----|---|---|-----------|---| 91 | | | A | B | C | A | B | C | A | B | C | 92 | | P0 | 0 | 1 | 0 | 7 | 5 | 3 | 3 | 3 | 2 | 93 | | P1 | 2 | 0 | 0 | 3 | 2 | 2 | | | | 94 | | P2 | 3 | 0 | 2 | 9 | 0 | 2 | | | | 95 | | P3 | 2 | 1 | 1 | 2 | 2 | 2 | | | | 96 | | P4 | 0 | 0 | 2 | 4 | 3 | 3 | | | | 97 | 98 | 99 | // Need Matrix (Max - Allocation) 100 | | | | Need | | 101 | |----|---|------|---| 102 | | | A | B | C | 103 | | P0 | 7 | 4 | 3 | 104 | | P1 | 1 | 2 | 2 | 105 | | P2 | 6 | 0 | 0 | 106 | | P3 | 0 | 1 | 1 | 107 | | P4 | 4 | 3 | 1 | 108 | 109 | // Safety sequence check 110 | Work = 3 3 2 111 | Finish 112 | 113 | // P0 cannot complete Need(P0) is not less than current Work 114 | 115 | // Execution of process P1 116 | P1: Finish[1] = True; 117 | Work = 532 // work is calculate by adding allocation to current work 118 | 119 | // P2 cannot complete Need(P2) is not less than current Work 120 | 121 | // Execution of process P3 122 | P3: Finish[3] = True; 123 | Work = 7 4 3 124 | 125 | // Execution of process P4 126 | P4: Finish[4] = True; 127 | Work = 7 4 5 128 | 129 | // Execution of process P0 130 | P0: Finish[0] = True; 131 | Work = 7 5 5 132 | 133 | // Execution of process P2 134 | P2: Finish[2] = True; 135 | Work = 10 5 7 136 | 137 | // The safe sequence is: 138 | P1, P3, P4, P0, P2 139 | ``` 140 | 141 | - Safe sequence can be different depending on the order of how processes are processed. 142 | 143 | - Assuming there is a new future request _R1 = [1,0,2]_ from _P1_ , which already has Need request of _N(P1) = [1, 2, 2] _, what are the result using the same algorithm from above: 144 | 145 | ```Pascal 146 | P1: Request = 1 0 2 147 | Pass first two checks of the algorithm then goto next step 148 | 149 | Available = 2 3 0 150 | 151 | // Modify Allocation for P1 only (Previous Allocation + Request) 152 | 153 | | | | Allocation | | 154 | |----|---|------------|---| 155 | | | A | B | C | 156 | | P0 | 0 | 1 | 0 | 157 | | P1 | 3 | 0 | 2 | 158 | | P2 | 3 | 0 | 2 | 159 | | P3 | 2 | 1 | 1 | 160 | | P4 | 0 | 0 | 2 | 161 | 162 | // Modify Need for P1 only (Previous Need - Request) 163 | | | | Need | | 164 | |----|---|------|---| 165 | | | A | B | C | 166 | | P0 | 7 | 4 | 3 | 167 | | P1 | 0 | 2 | 0 | 168 | | P2 | 6 | 0 | 0 | 169 | | P3 | 0 | 1 | 1 | 170 | | P4 | 4 | 3 | 1 | 171 | 172 | // Check if safe 173 | 174 | // P0 cannot be executed 175 | 176 | // Execute P1 if Need(P1) < Available and calculate Available (Available + Allocation) 177 | Available: 5 3 2 178 | 179 | // Execute P3 180 | Available: 7 4 3 181 | 182 | // Execute P4 183 | Available: 7 4 5 184 | 185 | // Execute P0 186 | Available: 7 5 5 187 | 188 | // Execute P2 189 | Available: 10 5 7 190 | 191 | // Safe sequence 192 | P1, P3, P4, P0, P2 193 | ``` 194 | 195 | - __Exercise:__ Try P0 with a request of 0 2 0 196 | 197 | - __Detection and Recovery Algorithm__ stucture: 198 | 199 | ```Pascal 200 | // initializations 201 | Available_m 202 | Allocation_nXm 203 | Request_nXm 204 | 205 | // Main Algorithm 206 | // Step 1 207 | Work_m = Available 208 | Finish[i] = False if Allocation_i != 0 // no requests for resources yet 209 | = True if Allocation_i = 0 210 | 211 | // Step 2 212 | Find and i such that Finish[i] = False and Request_i <= Work 213 | If no such i, then go to Step 4 214 | 215 | // Step 3 216 | Work = Work + Allocation_i 217 | Finish = True 218 | then go to step 2 219 | 220 | // Step 4 221 | If Finish[i] = False for some i 222 | then there is a deadlock 223 | ``` 224 | 225 | - After this process then the algorithm must choose the Process to terminate so as to recover from deadlock. 226 | 227 | - The time complexity of the Banker's algorith is _O(mXn^2)_. But it can be reduced to _O(n^2)_. -------------------------------------------------------------------------------- /1. Introduction to Operating Systems.md: -------------------------------------------------------------------------------- 1 | ## Chapter 1: Introduction to Operating System Concepts 2 | 3 | - What is the operating system? The operating system is a program that efficiently manages the resources (hardware) of a computer system. The OS is in charge of efficiently managing several resources, such as CPU, main memory, secondary storage and I/O devises, so that users can simultaneously work and complete the desired tasks. 4 | 5 | - > The operating system provides an environment in which the user can execute programs in an efficient and convenient manner. 6 | 7 | - **User View:** an OS designed for ease-of-use, some performance and no resource utilization. 8 | 9 | - Resource Utilization is needed when having multiple user connecting to a minicomputer or mainframe through a terminal. 10 | 11 | - **System View:** the operating system is the resource allocator, managing CPU time, memory space, I/O devices and file-storage space. The operating can also be a control program, where there is a need to control the various I/O devises and user programs. 12 | 13 | - > Main memory is a **volatile** storage devise that loses its contents when power is turned off or otherwise lost. 14 | 15 | - Although there is no universally accepted definition of an OS, it is also called the kernel. Besides the kernel there are two other types of programs: **system programs**, associated with the OS but are not part of the kernel, and **application programs**, which include all programs not associated with the operations of the system. 16 | 17 | - The occurrence of an event is usually called an **interrupt**. Software interrupts are referred to as system calls or monitor calls. 18 | 19 | - A **device driver** provides the OS with an interface to the devise through the **device controller**. The device controller moves data between the peripheral devises that it controls and its local buffer storage. 20 | 21 | - The advantages of multi-processor systems: increased throughput, economy of scale, and increased reliability. 22 | 23 | - Two kinds of multi-processor systems: 24 | + **Asymmetric MP**: each processor is assigned a task and there is a boss processor which controls the other processors. 25 | + **Symmetric MP (SMP)**: each processor performs all tasks within the OS. 26 | 27 | 28 | - **Clustered Systems:** can be structured asymmetrically or symmetrically. Such systems provide high availability and high-performance computing. Clustered systems are useful to achieve parallelization as in the case of map/reduce programs. 29 | 30 | - **Distributed Lock Manager (DLM):** a function used in clustering technology to supply access control and locking to ensure that no conflicting operations occur. 31 | 32 | - **Job pool:** storage of jobs, usually on disk rather than in main memory because of the size (usually too large to fit on main memory). 33 | 34 | - **Multiprogramming** is essential to avoid idle state of CPU; it allows for maximum and efficient use of the CPU since a job is always under execution. 35 | 36 | - **Time sharing (multitasking)** - is an extension to multiprogramming where there is usually an interface where the user can interact with the computer system. 37 | 38 | - > A program loaded into memory and executing is called a process. 39 | 40 | - **Job scheduling** - technique used by a system to decide which job to bring to memory for execution. This is important in time-shared OSs where multiple users are submitting multiple job which required some type of CPU time. Managing the jobs in memory also requires memory management. CPU scheduling involves efficiently deciding which job will run first from a list of READY jobs. 41 | 42 | - Reasonable response time can be achieved by **swapping** or **virtual memory**. 43 | 44 | - Privileged instructions are used to tag those instructions that are illegal to be executed in user mode. Such instructions must only run on kernel mode. Examples of such instructions include switch to kernel mode, I/O control, timer management, and interrupt management. 45 | 46 | - > System calls provide the means for a user program to ask the operating system to perform tasks reserved for the operating system on the user program's behalf. 47 | 48 | - OS for Process management: 49 | + Scheduling processes and threads on the CPUs 50 | + Creating and deleting both user and system processes 51 | + Suspending and resuming processing 52 | + Providing mechanisms for process synchronization 53 | + Providing mechanisms for process communication 54 | 55 | - OS for Memory Management: 56 | + Keeping track of which parts of memory are currently being used and who is using them 57 | + Decide which process and data to move into and out of memory 58 | + Allocating and deallocating memory space as needed 59 | 60 | - An OS can be viewed from several points of view: **services**, **interface**, and **component and interconnections**. 61 | 62 | 63 | - The CPU cannot execute anything stored in secondary storage (e.g., magnetic tapes and hard disks). The CPU only executes what is in main memory. The program stored on the hard disk must be brought into memory first in order for the CPU to access and execute it. 64 | 65 | 66 | - How is the CPU time shared among users (multiprogramming system)? We need to think about this. How to manage the memory blocks is handled by the operating system. 67 | 68 | 69 | - The way in which main memory and secondary memory is accessed is different or have different access mechanism. The **devise driver** is responsible to look for instructions or data from the secondary storage and pass it to main memory, so that it can be accessed or executed by the CPU. 70 | 71 | 72 | - Input/Output devises - The devises used to input/output information or instructions to/from a computer system (e.g., keyboard and monitor). Hard disks are also known as input devises since they contain data blocks that are read and input into main memory for execution. Similarly, they serve as a output devise as they are used to save the output of an executed program, for example. Essentially, secondary storages could be considered some sort of special type of I/O devises. 73 | 74 | 75 | - How does the OS operates in a distributed computing system? 76 | 77 | - CPU is also known as **process manager**. A **process** is a program in execution. The OS manages the CPU so that is can be shared by more than one process simultaneously. The CPU cannot garuantee the immediate execution of a job that contains a **waiting time** and **turnaround time**. The waiting time is known as the time taken for the CPU to start the execution a job after it has first been submitted *(ti -t0)*; *ti* representing the time the CPU starts the execution of the job and *t0* being the time the job was submitted for execution. The turnaround time is known as the time taken for the CPU to finish the execution of a job after it has first been submitted *(tp - t0)*. 78 | 79 | 80 | - Ideally, the user requires that the waiting and turnaround time be as minimum as possible, despite the fact that the CPU is busy executing other tasks. 81 | 82 | 83 | - Usually, the jobs are scheduled to execute in a **"first come, first serve"** manner, also knows as sequence of execution. The OS has control on what is executed next but not really on how the job arrives, which are specified by the user. Changing the scheduling of how the job are executed based on the amount of time taken and not in order in which they arrive, the total waiting average time for each job is reduced significanlty. That is, the OS tries to optimize for the best average waiting time and choose that schedule to execute specificied jobs by the user/s. The jobs with shorter time are executed first while the jobs with the longer times are executed last. This type of scheduling is commonly known as **"Shortest Job First"**. 84 | 85 | -------------------------------------------------------------------------------- /2. Process Management.md: -------------------------------------------------------------------------------- 1 | ## Chapter 2: Process Management 2 | 3 | ### Process Management - Part 1 4 | 5 | - All programs consist on mainly two types of operations, **CPU Burst and I/O Burst**, which alternate in execution sequence. The first and last bursts in a program are usually of type CPU Burst. In between we have an alternating sequence of CPU bursts and I/O bursts. 6 | 7 | 8 | - In order for a job to prepare for loading into main memory for execution, it must go from a **NEW STATE** to **READY STATE**. A job can only be executed by the CPU only when it is in a **READY STATE**. When the CPU is executing the job the state changes to **ACTIVE STATE**. After the job's final CPU burst is finished the state changes to **HALTED STATE**. 9 | 10 | 11 | - When the job or process performs I/O burst operation then you can remove it from CPU and put into a state called **WAITING STATE** or **I/O WAITING STATE**. During this transition of state the CPU become available and can perform the CPU burst of another job or process. 12 | 13 | 14 | - After waiting state the job can be moved to either ready state or active state. There is no way of guarantee that the CPU will be ready to continue execution of the job since it might also be executing the CPU burst operation of another process. A cleaner way to deal with the execution of the job is to move it into ready state, so that the CPU can execute it when it becomes available. This allows for a multiprocessing system even when there is just one CPU available. 15 | 16 | 17 | - The OS must have an idea about what is next CPU burst duration of a job. This way it can decide on what job to execute from the READY queue. There is no way to know the exact time of the next job's CPU burst, so there has to be a predicted value instead. This is only needed for the SJF (Shortest job first) scheduling algorithm and not the FCFS (first come first serve) scheduling algorithm. In this way the job with the shortest CPU burst is to be executed first. 18 | 19 | 20 | - There is another type of scheduling called the **Priority Scheduling**, where you can specify different priority levels for jobs. In this type of scheduling the job with the highest priority level is executed first and not necessarily the job with the shortest CPU burst as in the SJF algorithm. In fact, the SJF algorithm is considered a special type of priority scheduling where the job with the shortest CPU burst time is given higher priority. 21 | 22 | 23 | - FCFS, SJF, and PRIORITY scheduling are also known as **Non-preemptive scheduling**. The difference with non-preemptive and **preemptive** is that preemptive scheduling allows a job that is being executed by the CPU to be interrupted and stopped to make the CPU available to other jobs or processes. SJF and PRIORITY can be modified to become a preemptive scheduling. That is, now the OS looks at the READY queue for jobs that are coming in and if the new job requires less CPU burst time than the first job then the first job is halted and then the new job with the new shortest CPU burst time is executed. Essentially, the READY queue is constantly being monitored for new job arrivals. Another way to think about preemptive scheduling is that the OS keeps comparing the remaining time of the current job being processed by the CPU and comparing it with all the time units of the jobs in the READY queue, including the newly arrived ones. If there is another job that requires less time, then the current job is halted and the CPU is made available for the newly arrived job. When SJF scheduling is modified to operate as a preemptive scheduling algorithm it is renamed to **SRT** or **SHORTEST REMAINING TIME** scheduling. 24 | 25 | 26 | - On the other hand, when the PRIORITY scheduling algorithm is modified to operate as a preemptive scheduling algorithm there is a high possibility that a job -- that was already in the READY queue -- will starve of CPU time; there is a job that will never be executed if the priority of the newly arrived job is always higher. (OBSERVATION: Assuming jobs that require low executing time keep coming into the READY queue, one job (already in the READY queue) with high execution time will probably never obtain CPU time to be executed. This problem can be subsided through a concept commonly known as aging. This means that the priority of a job is not only decided by the system administrator but also by the amount of time it has remained in the READY queue. This ensures that a job that was initially set with very low priority and had spent some time in the READY queue to never starve of CPU time. 27 | 28 | 29 | - Concretely, preemptive scheduling allows for a job to go from an **ACTIVE STATE** to **READY STATE** directly, without needing to go to a **WAITING STATE**. This whole process of changing a job state is called the **Process State Diagram**. 30 | 31 | 32 | - A job that requires significantly more CPU burst time than I/O burst time is also known and **CPU-bound** or **compute-bound** jobs. Conversely, a job that requires significantly more I/O burst time than CPU burst time is also known as **I/O bound** jobs. 33 | 34 | 35 | - In the event that there is too much compute-bound jobs or too much I/O-bound jobs in the READY queue, there will be inefficient use of resources by the computer system. The desired case would be to have a balanced mix of compute-bound jobs and i/o-bound jobs inserted into the READY queue. One way to maintain this balance is to have a scheduler deciding what type of jobs that are taken from the NEW STATE into the READY queue. Similarly, there is a scheduler that moves a job from a READY state into an ACTIVE state and it is known as **SHORT TERM SCHEDULER**. A scheduler that puts a job from the NEW queue into the READY queue is known as **LONG TERM SCHEDULER**. 36 | 37 | 38 | - The main difference between the long-term and short-term schedulers is the time taken to move a job into the different states. Short-term scheduler deals with the important task of moving a job from READY state (usually residing in memory) into ACTIVE state (resides in CPU). Therefore, it is important that this scheduler is fast and efficient. Whereas for the long-term scheduler is just deciding what jobs go into the READY queue so it is not required to be as fast as short-term scheduler. 39 | 40 | 41 | ### Process Management - Part 2 42 | 43 | 44 | - A **round-robin** scheduling is a form of preemptive scheduling, where all jobs are treated equally and taken from the **head** of the queue. The jobs are scheduled for execution on a first-come, first-serve basis. In this model, there is a timer set by the CPU on the jobs. If a job required more that the time set by the CPU, then that job is processed and then moved back to the READY state. Other jobs that meet the criteria of time set by the CPU, are completely executed and then moved into a HALT state or I/O waiting state (if it needs I/O processing). This model needs a programmable timer, which is in charge of interrupting jobs that require more time than what the CPU has been set to execute. 45 | 46 | 47 | - A **Multi-Level Queue (MLQ)** scheduling allows for multiple READY queues where jobs can be categorized in different queues. Some jobs may be categorized by CPU burst time requirement. This means that jobs which are CPU bound (require more CPU time) loose priority. The jobs which are in the first queue are I/O bound (require less CPU time) and are executed first before executing jobs in the remaining queues. 48 | 49 | 50 | - The disadvantage of **MLQ** is that some jobs might become I/O bound (require less CPU time) but they cannot be moved from the designated queue. To fix this problem, there is another queue called the **Multi-level feedback queue (MLFQ)**. Here jobs can be moved around the different level of queues depending on their requirements. Each job is executed and interrupted by the timer if the CPU time is above the time requirements set by the CPU. Once the timer interrupts then the job goes back into the queue depending on how much remaining time it requires. Similarly, jobs that are in the bottom queues (CPU bound jobs) can be moved to higher-priority queues if they require less CPU time. The difference between the MLQ and MLFQ is that MLFQ allows for checking of the nature of the job so as to make the necessary shifting between queues. 51 | 52 | 53 | - In summary, there are two types of scheduling which are categorized into preemptive and non-preemptive scheduling: 54 | 55 | | Non-Preemptive | Preemptive | 56 | |----------------|-------------------| 57 | | FCFS | SRT | 58 | | SJF | Round Robin | 59 | | Priority | Multi-level queue | 60 | 61 | 62 | -------------------------------------------------------------------------------- /2.2 Process Scheduling.md: -------------------------------------------------------------------------------- 1 | ## Chapter 2.1: Process Scheduling 2 | 3 | - **Process scheduling** is also known as CPU scheduling, which is the basis of multiprogrammed operating systems. 4 | 5 | - There are two types of scheduling: process scheduling and thread scheduling 6 | 7 | - Multiprogramming - to have some process running at all times, to maximize CPU utilization. 8 | 9 | - Fundamental idea of CPU scheduling: When a process submits an I/O request, it goes into a WAIT state and so it makes the CPU idle; the idea of multiprogramming is to take away the CPU from that process and give it to another process for execution; this pattern continues; 10 | 11 | - Processes alternate between CPU burst (CPU execution) and I/O burst (I/O WAIT). 12 | 13 | - I/O bound program - short CPU bursts 14 | 15 | - CPU bound program - long CPU bursts 16 | 17 | - The above distribution play an important role in selecting an appropriate CPU scheduling algorithm. 18 | 19 | 20 | - CPU scheduling may happen under the following four circumstances: 21 | + 1) Process switches from RUNNING STATE to WAITING STATE (i/o request, wait() child to finish execution) 22 | + 2) Process switches from RUNNING STATE to READY STATE (interrupt occurs) 23 | + 3) Process switches from WAITING STATE to READY STATE (completion of I/O) 24 | + 4) Process terminates 25 | 26 | - 1 and 4 are nonpreemptive or cooperative scheduling schemes (only happens when terminating process or switching to WAITING STATE). 2 and 3 are preemptive. 27 | 28 | - > The **dispatcher** is the module that gives control of the CPU to the process selected by the short-term scheduler. NOTE: short term scheduler is in charge of moving processes from READY state to ACTIVE or RUNNING state; long-term scheduler is in charge of moving processes from NEW state to READY state. 29 | 30 | 31 | - Criteria for choosing CPU scheduling algorithm: 32 | + CPU Utilization - make the CPU as busy as possible 33 | + Throughput - the number of processes (executing in CPU) per time unit 34 | + Turnaround time - the sum of periods spent waiting to get into memory, waiting in the READY queue, executing on the CPU, and doing I/O. 35 | + Waiting Time (preemptive - affected by CPU scheduling) - the sum of the periods spent waiting in the READY queue. 36 | + Response time - in contrast to turnaround time, it is the time taken to start responding not the time it takes to output the response. 37 | 38 | - It is desirable to maximize CPU utilization and throughput and to minimize turnaround time, waiting time, and response time. 39 | 40 | - CPU scheduling algorithm essentially decides which process in the READY queue is to be allocated the CPU for execution. 41 | 42 | - **First Come First Serve (FCFS) scheduling** - is nonpreemptive (CPU release happens only when process terminates or upon i/o request); average waiting time varies depending on the order of processes, which usually have different CPU burst times; this scheduling is not appropriate for time-sharing systems, where it is important that each user get a share of the CPU at regular intervals. 43 | 44 | - **Shortest Job First Scheduling (SJF)** - used frequently in long-term scheduling, where the process time limit is used to calculate the length of the next CPU request. With short-term scheduling there is no way to know the length of the next CPU burst; one approach to solve that problem is to use approximation and predicting the CPU burst time of the next process. SJF is preemptive and nonpreemptive. 45 | 46 | - **Preemptive SJF scheduling** is sometimes called shortest-remaining-time-first scheduling, where the remaining CPU burst time of the process currently in the CPU is compared with the burst time of the newly arrived process in the READY queue; if new process has less CPU burst time, then the current running process is preempted and the new process is allocated the CPU for execution; for this scheduling algorithm the order of arrival time is also considered; also, a remaining time must be kept 47 | 48 | - **Priority scheduling** - The SJF algorithm is an example of the general priority scheduling algorithm; they can be preemptive (if newly arrived process have higher priority than currently running process, it is preempted) and nonpreemptive (put new process at the head of the queue); this algorithm suffers from **indefinite blocking or starvation** (waiting indefinitely for CPU time because of very low priority); a solution to starvation is a concept called **aging** (increase priority based on time waiting). 49 | 50 | - **Round Robin Scheduling (RR)** - designed for time-sharing systems; similar to FCFS scheduling but preemption is added to enable context switching between processes; a time quantum is assigned to run processes and interrupts are scheduled (if process CPU burst time exceeds a time quantum) to move to other process once that quantum has passed; this continue until each process has been given enough quantum time to execute fully; when process is preempted it is put back to ready queue; if quantum is high, it may require less context switching; average turnaround time can be improved if process finish their CPU burst in a quantum of 1 unit time; if quantum time is too large then it degenerates to a FCFS policy; a rule of thumb is that 80% of the CPU bursts should be shorter than the time quantum 51 | 52 | - **Waiting time** is calculated by subtracting arrival time from visiting time (in case preempted, then visiting time - finished time of last execution before interrupt occurred) 53 | - **Turnaround time** is calculated by adding waiting time and Burst time. 54 | 55 | - A **multilevel queue scheduling** partitions the ready queue into several separate queues based on the categories of processes (e.g., foreground (interactive) and background (batch) processes); each separate queue might use different CPU scheduling algorithm; **multilevel feedback queue** allow processes to between queues. 56 | 57 | - Thread scheduling - first, user-level threads are scheduled by the thread library to run on an available lightweight process (LWP) using process-contention scope (PCS) (competition among threads within the same process). 58 | 59 | - To decide which kernel-level thread to schedule unto a CPU, the kernel uses system-contention scope (SCS) (competition among all threads in the system, regardless of process they belong to). 60 | 61 | - **Multiprocessor Scheduling**: 62 | + **Asymmetric multiprocessing (AMP)** - one approach to CPU scheduling in a Multiprocessor system has all scheduling decisions, I/O processing, and other system activities handles by a single processor. The other processors execute only user code; reduces data sharing since only processor accesses the system data structures 63 | + **Symmetric multiprocessing (SMP)** - each processor has it's own process scheduling; ensure that processors not execute the same process; 64 | 65 | - **Processor affinity** - don't allow processes to migrate between processors; might encounter problem (overhead) with invalidating and repopulating cache memory between the processors; soft affinity (may allows process to migrate between processors); hard affinity (doesn't allows processes to migrate between processors) 66 | 67 | - **Load balancing** - a scheme to keep the workload evenly distributed across all processors in an SMP system, so as to avoid idle CPU; this scheme defeats the processor affinity; also, only necessary where each processor has its own private queue of eligible processes to execute. 68 | 69 | - **multicore processor** - having multiple processor cores on the same physical chip 70 | 71 | - **Real-Time scheduling** - soft real-time scheduling (no guarantee as to when a critical process will be scheduled); hard real-time scheduling (a task must be served by its deadline); event latency occurs from when an event occurs to when it is serviced; 72 | 73 | - Two type of latencies affect the performance of real-time systems: 74 | + Interrupt latency - the period of time from the arrival of an interrupt at the CPU to the start of the routine that services that interrupt. 75 | + Dispatch latency - the amount of time require for the scheduling dispatcher to stop one process and start another; achieved through providing preemptive kernels 76 | 77 | - **Real time operating system** - must support a priority based algorithm with preemption to serve process as soon as they are requested to be executed; this algorithm supports soft-real time functionality but not ensure hard real-time functionality. 78 | 79 | - Scheduling Algorithm Evaluation: 80 | + Analytic (deterministic modeling) - uses the given algorithm and the system workload to produce a formula or exact numbers to evaluate the performance. 81 | + Queueing models are often only approximations of real systems 82 | + Simulations - simulate the algorithms using events and random process generation 83 | + Implementation - code the actual algorithm and test in on real system -------------------------------------------------------------------------------- /5. Critical Section Problem.md: -------------------------------------------------------------------------------- 1 | ## Chapter 5: Critical Section Problem 2 | 3 | - Whenever a variable (e.g., counter) is being accessed and modified by two different processes, such as the consumer and producer process, this kind of problem is known as the __critical section problem__. 4 | 5 | - A plausible solution is to only allow either the producer process or consumer process to access the variable, finish its operation and then allow for the other to access and modify the variable. This can also be called the __atomic property__. It can also be referred to as __mutually exclusive__. 6 | 7 | - These are the following requirements that must be met to solve the critical section problem: 8 | + __Mutual Exclusion (ME)__ - only one process should be given access to the critical section at a time. 9 | + __Progress__ - Given a set of processes _P = {P1, P2, ...,Pn}_, only one process _Pi_ should be allowed to enter the __critical region_. The rest of processes must wait until _Pi_ finishes. The decision about who goes into the critical section can only be taken by the processes that are set to go into the critical section, and must be taken in finite time. 10 | + __Bounded Waiting__ - When a process _Pi_ has put a request to enter the critical section at time _To_, it must wait until no process is running in the critical section. It also means that no other process can enter the critical section before the selecting process _Pi_. It serves as a assurance that its request to enter the critical section is satisfied. 11 | 12 | - The __Entry Section__ - decides what process can enter into the critical section. A __lock__ is used to indicate that no other process can enter the critical section. 13 | 14 | - The __Exit Section__ - decides when the critical section becomes unlocked and free to accept another process. 15 | 16 | - The __Remainder Section__ - this is part of the code that doesn't involve the critical section. 17 | 18 | - Given _P1_ and _P2_ and a shared variable __turn__, which takes value of 1 and 2, the following code inserts a process into the critical section: 19 | 20 | ```Pascal 21 | P(i): While turn != i do skip; //execute next line if its a process turn 22 | CS 23 | turn = j; // change turn 24 | ``` 25 | 26 | - The previous code satisfies ME, ensuring that no two processes enter the critical section simultaneously. 27 | 28 | - If a process _P1_ in the critical section terminates and gives control to a Process _P2_ that doesn't want to go into the critical section, we won't be able to meet progress. The reason is because _P2_ doesn't have an exit statement that indicates to give the critical section to another process _P1_ 29 | 30 | - The following algorithm satisfies the __Progress__ criteria: 31 | 32 | ```Pascal 33 | flag: array [0..1] of boolean 34 | P(i) While flag[j] do skip; // decides if _Pi_ can go into the critical section 35 | flag[i] = True; 36 | CS 37 | flag[i] = False; 38 | ``` 39 | 40 | - With the above code, progress is satisfied but ME is not satisfied. 41 | 42 | ```Pascal 43 | // Shared variables 44 | var flag: array[0..1] of boolean; 45 | turn: 0..1; 46 | P(i): flag[i] = true 47 | turn = j; // check if _Pj_ wants access to Critical Section 48 | while(flag[j] and turn = j) do skip; // if any is false, then execute next line. 49 | CS 50 | flag[i] = false; 51 | ``` 52 | 53 | - The above algorithm meets the three requirements: ME, progress and boundedness. Therefore, it is a correct solution to solve the critical section problem for two processes. 54 | 55 | - For _n_ process, the following algorithm is the solution: 56 | 57 | ```Pascal 58 | Common (Global) variables: 59 | var flag: array[0..n-1] of (idle, want_in, in_cs); 60 | turn: 0..n-1; 61 | P(i): var j:0..n; 62 | // Entry section of code 63 | repeat 64 | flag[i] = want_in; 65 | j = turn; 66 | while j!= i do 67 | if flag[i] != idle then j = turn; 68 | else j = (j+1) mod n; 69 | flag[i] = in_cs; 70 | j = 0; 71 | while (j < n) and (j = i or flag[j] != in_cs) do 72 | j = j + 1; 73 | until (j >= n) and (turn = i or flag[turn] = idle); 74 | turn = ij; 75 | // critical section 76 | CS 77 | // exit section of code 78 | j = (turn + 1) mod n; 79 | while (flag[i] = idle) do j = (j + 1) mod n; 80 | turn = j; 81 | flag[i] = idle; 82 | // remainder section 83 | RS 84 | ``` 85 | - There are some other implementations that can provide a solution to the critical section problem. These solution are categorize in two types of solutions: __Software Solution__ and __Hardware Solution__. An example of a hardware solution used in solving the critical solution problem is given in the following snippets of code: 86 | 87 | ```Pascal 88 | function Test_and_Set(var target: boolean): boolean; 89 | begin 90 | Test_and_Set = target; 91 | target = True; 92 | end; 93 | ``` 94 | ```Pascal 95 | Procedure swap(var a,b: boolean); 96 | var temp: boolean; 97 | begin 98 | temp = a; 99 | a = b; 100 | b = temp; 101 | end; 102 | ``` 103 | 104 | ```Pascal 105 | Common (Global) variables: 106 | var waiting: array[0..n-1] of boolean; 107 | lock: boolean; 108 | P(i): // local variables 109 | var j: 0..n-1; 110 | key: boolean; 111 | // entry section 112 | waiting[i] = true 113 | key = true; 114 | while (waiting[i] and key) do key = Test_and_Set(lock); 115 | waiting[i] = false; 116 | // the critical section 117 | CS 118 | // the exit section (this decides what goes into CS next) 119 | j = (i + 1) mod n; 120 | while (j != i) and (not waiting[j]) do j = (j + 1) mod n; 121 | if j = 1 then lock = false; 122 | else waiting[j] = false; 123 | ``` 124 | 125 | - __Semaphore variable__ - used to solve of the problems encountered by the operating system. It can be an integer variable which could be initialized and can be accessed only through two atomic operations: _P operation_ and _V Operation_. A process that is making some changes to a variable has to be completed in order to start the execution of another process on that same variable. Only then can the system ensure proper __context switching__. 126 | 127 | ```Pascal 128 | S 129 | P(S): While S <= 0 do skip; 130 | S = S - 1; 131 | V(S): S = S + 1; 132 | ``` 133 | ```C 134 | mutex: semaphore; // assuming mutex = 1; 135 | P(i): P(mutex) 136 | CS 137 | V(mutex) 138 | RS 139 | ``` 140 | 141 | - The semaphore variable mutex keeps switching the value from 0 to 1 and 1 to 0 to indicate whether another process can enter the critical section. This satisfies the ME requirement. 142 | 143 | -__ Process synchronization__ : states that a semaphore variable _Sj_ can only be executed if a semaphore variable _Si_ has already executed and terminated. The algorithmic solution for this is as follows: 144 | 145 | ```Pascal 146 | sync: semaphore; 147 | sync = 0; 148 | P(sync) 149 | Sj 150 | 151 | Si 152 | V(sync) 153 | 154 | ``` 155 | 156 | - The above solution can be used in the synchronization of two processes, running in parallel. At some point, a process _P1_ is waiting for the execution of a set of instruction in _P2_. Semaphore variables can be used to allow for waiting and syncing the process with the input of the other. 157 | 158 | - Given the following example of precedence graph: S1 --> S2; S1 --> S3; S2 --> S4; S4 --> S5; S4 --> S6; S3 --> S6; S5 --> S7; S6 --> S7... 159 | - It was said that it can be solved using the `Fork/Join` construct but not using the `Cobegin/Coend` construct. 160 | - This precedence graph can also be represented using semaphore variables and below is a solution: 161 | 162 | ```Pascal 163 | var a,b,c,d,e,f,g : semaphore <--0 164 | begin 165 | cobegin 166 | begin 167 | S1; V(a);V(b); 168 | end; 169 | begin 170 | P(a); S2; S4; V(c); V(d); 171 | end; 172 | begin 173 | P(b); S3; V(e); 174 | end; 175 | begin 176 | P(c); S5; V(f); 177 | end; 178 | begin 179 | P(d); P(e); S6; V(g); 180 | end; 181 | begin 182 | P(f); P(g); S7; 183 | end; 184 | coend 185 | end; 186 | ``` 187 | 188 | - In the above solution, ever block is sequential. All processes are running concurrent but must wait for V(x) to finish execution. 189 | 190 | - With the code above, there is a new problem that arises. This problem is called __busy waiting problem__. What this means is that the processes are keeping the CPU busy without getting any positive result. This can be reduced by modifying the definition of semaphore variables. 191 | 192 | - The new definition of the semaphore variable looks like the following: 193 | 194 | ```Pascal 195 | type semaphore = record 196 | value: integer; 197 | L: list of processes; 198 | end; 199 | var S: semaphore; // s.value and s.L 200 | 201 | // P operation 202 | P(S): S.value = S.value - 1; 203 | if s.value < 0 then 204 | begin 205 | add this process to S.L; 206 | block; // putting the process in the wait state (no CPU consumption) 207 | end; 208 | end; 209 | 210 | // V operation 211 | V(S): S.value = S.value + 1; 212 | if s.value <= 0 then 213 | begin 214 | remove process P from S.L; 215 | wakeup(P); // change state from WAIT to READY 216 | end; 217 | end; 218 | ``` 219 | 220 | - Semaphore variables can also be used to solve the Producer/Consumer Problem. The following is a possible implementation: 221 | 222 | ```Pascal 223 | full, empte, mutex: semaphore; 224 | nextp, nextc: item; 225 | full = 0; 226 | empty = n; // indicate the number of items in buffer 227 | mutex = 1; // maintain mutual exclusion of buffer 228 | 229 | // Producer process 230 | P: repeat 231 | produce an item in nextp; 232 | ... 233 | P(empty) 234 | P(mutex) 235 | ... 236 | add nextp to buffer; 237 | ... 238 | V(mutex) 239 | V(full) 240 | until false; 241 | 242 | // Consumer process 243 | C: repeat 244 | P(full); 245 | P(mutex); 246 | ... 247 | remove an item from buffer to nextc; 248 | ... 249 | V(mutex) 250 | V(empty) 251 | ... 252 | until false; 253 | ``` 254 | 255 | - Another problem that can be solved using semaphore variables is the __Reader/Writer__ Problem. Only one writer process should be allowed to _access_ the file at a time. Also, many reader processes can access the file simultaneously. But if the one writer process is _modifying_ a file, then no reader process should be given access to that file. If there is one reader process accessing the file, then no other writer process should be allowed to access the file. 256 | 257 | - The following implementation is a solution for the reader/writer problem using semaphore variables: 258 | 259 | ```Pascal 260 | mutex, wrt: semaphore; <-- 1 261 | readcount: integer; <-- 0 262 | 263 | // reader process 264 | R: 265 | P(mutex); 266 | readcount = readcount + 1; 267 | if readcount = 1 then P(wrt); 268 | V(mutex) // before this statement is executed, no other process can execute the previous two lines of code. 269 | ... 270 | Read 271 | ... 272 | P(mutex); 273 | readcount = readcount - 1; 274 | if readcount = 0 then V(wrt); // release the file when exiting the last reader process. 275 | V(mutex); 276 | 277 | // writer process 278 | W: 279 | P(wrt); 280 | ... 281 | Writes 282 | ... 283 | V(wrt); 284 | ``` 285 | 286 | - In such operations, __atomicity__ in ensured by providing priority levels in the CPU scheduling. 287 | 288 | -------------------------------------------------------------------------------- /11. File Representation.md: -------------------------------------------------------------------------------- 1 | ## Chapter 11: File Representation 2 | 3 | ### File System 4 | 5 | - File is the smallest allotment of logical secondary storage; 6 | 7 | - Directory consist of the file's name and its unique identifier 8 | 9 | - File operations carried provided by the OS 10 | + Creating a file - find space, then write entry to directory 11 | + Writing a file - find file, write pointer to keep track of where to write next 12 | + Reading a file - find file, read pointer to keep track of where to read next 13 | + Repositioning within a file - find file in directory, reposition current-file position pointer (no i/o required) 14 | + Deleting a file - find file, release space, erase the directory entry 15 | + Truncating a file - find file, delete all other information, and keep all other attributes (e.g., name) 16 | 17 | - Open File Table - keeps track of all open file and avoids the need to search the directory all over again. 18 | 19 | - Open file - keeps track of the file information as per access by all processes; these include: file pointer, file-open count, disk location of the file, access right; 20 | 21 | - current file position pointer - keeps track of read and write pointer 22 | 23 | - Internal fragmentation occurs because blocks are not entirely used and the remaining space accumulate to cause fragmentation. 24 | 25 | - Access methods: 26 | + Sequential Access - read and write back and forward from a file 27 | + Direct Access - random access to read and write 28 | 29 | - Any entity (partition) containing a file system is known as a volume (virtual disk). The volume may be a subset of a devise, a whole devise, or multiple devises linked together into a RAID set (used for providing protection from disk failure). 30 | 31 | - user file directory (UFD) - in a two level directory every user has a directory; when a user logs in, the system's master file directory (MFD) is searched - the MFd is indexed by user name or account number, and each entry points to the UFD for that user. 32 | 33 | 34 | - absolute path - contain all the path of a file from the root directory 35 | 36 | - relative path - contain the path of a file from the current directory 37 | 38 | - Acyclic graph, different from tree structured directory, allows for sharing of files and directories. 39 | 40 | - Garbage collection involves traversing the entire file system, to recover unused disk space. 41 | 42 | - Distributed File Systems - instead of terminating all operations, the DFS delays access with the hope that the remote host will become available again. 43 | 44 | - A files session : a series of open() and close() operations 45 | 46 | - Immutable file system - the share content is fixed and cannot be change (read-only mode). 47 | 48 | - The most general scheme to implement identity dependent access is to associate with each file and directory and access-control list (ACL) specifying user name and the types of access allowed for each user. 49 | 50 | 51 | ### File Implementation 52 | 53 | - logical file system - knows the structure of directories 54 | 55 | - File control block (FCB) - contains information about a file (ownership, permission, etc.): 56 | + file permissions 57 | + file dates (create, access, write) 58 | + file owner, group, ACL 59 | + file size 60 | + file data blocks or pointers to file data blocks 61 | 62 | 63 | - On disk, the file system may contain information about: 64 | + Boot block - usually the first block in a volume 65 | + Volume control block - size of the blocks, free-block count, free-block pointers 66 | + Directory Structure - file names and associated inode numbers 67 | + FCB - details of a file and identifier to associate with a directory 68 | 69 | - in-memory contains system-wide open-file table and per-process open file table 70 | 71 | - in memory mount table - contains information of the volumes mounted 72 | 73 | - Buffers hold the file-system blocks when they are being read from disk or written to disk. 74 | 75 | - The virtual file system (VFS) sit in the middle of the file system interface and files/directory, thus, distinguishing local files from remote ones; local files are distinguished according to their file system; The VFS is based on a file-representation structure, called a vnode, that contains a numerical designator for a network-wide unique file (necessary to support NFS); VFS only find what operation to execute based on the type of object (it looks into the object's function table to decide which exact version of the function to execute). 76 | 77 | - Directory Implementation: 78 | + Linear List - list of file names with pointers to the data blocks; linear search to find a file 79 | + Hash Table - hash table with pointer to the entry in linear list; decrease search time; can allow collision with two file names hash to the same location; solved by having each hash entry become a linked list; 80 | 81 | - Three methods for allocating disk space effectively so that file can be accesses quickly: 82 | + Contiguous - space is allocated contiguously, minimizing seek time; supports both sequential and direct access; directory entry of a file contains address of starting block and length of the block; difficult to find contiguous space for a new file since blocks could be randomly spread; also suffers from external fragmentation; another problem is that it is difficult to know the size of a file 83 | + Linked - each file is a linked list of disk blocks, scattered anywhere in the disk; each block contains a pointer to the next block; each directory entry has a pointer to the first disk block of the file; doesn't suffer from external fragmentation; only effective for sequential access files (read must happen from the first block of the file and find connecting blocks through the pointers); inefficient for direct-access; pointers also require storage; cluster of blocks instead of single blocks may reduce the space needed to store the pointers to each block; cluster approach suffers from internal fragmentation as more space is wasted through clusters than by single blocks; file allocation table (FAT) keeps entries of each disk block to support direct access; FATs are usually stored at the beginning block of a volume; 84 | + Indexed - each file has an index block, which is an array of disk block addresses; the directory contains the address of the index block only; it supports direct access, without suffering from external fragmentation; one drawback is the size of index block 85 | 86 | 87 | - Free space manage (keeping a list of free blocks): 88 | + bit map - each bit represent a block and it has 1 for unallocated, 0 for allocated 89 | + linked list - first pointer to the disk block is stored in memory and the rest of blocks have a pointer to a next free block; FAT has this capability already 90 | + grouping - storing addresses of n free blocks in the first free block; then those n-1 free blocks have a lost block which points to another set of free blocks; 91 | + counting - store address of first free block and number of contiguous blocks in the free blocks list 92 | + ZFS - a log plus a balanced tree is the free list. 93 | 94 | - Disk controller - moves disk tracks into main memory and then to cache if needed 95 | 96 | - Buffer cache - blocks are kept under the assumption that they will be used again shortly 97 | 98 | 99 | 100 | 101 | - Index node (Inode) - stores the __disk layout__ of every file in the directory. 102 | 103 | - The logical disk layout contains __boot block__, __super block__, __inode list__, __data block__. 104 | 105 | - The location information of data blocks is stored in the Inode list. 106 | 107 | - Super block contains: 108 | + how large the file system is 109 | + how many files it can store 110 | + where to find the free blocks in the file system 111 | + what are the free inodes 112 | 113 | - For every read/write to a file, the Inode must be referenced because it contains the address and other key information of that file on disk. 114 | 115 | - To speed up the access of a data block from the file system, a copy of the inode list for that directory, called __incore inode__, is stored in main memory. When the inode is stored in the disk it is simply called __disk inode__. 116 | 117 | - The disk inode contains the following information: 118 | + Ownership : who owns a specific file / directory 119 | * group / individual 120 | + Type (regular file or directory) 121 | + Permission field (read/write/execute - rwx) 122 | + access time 123 | + modification time 124 | + inode modified time 125 | + file size 126 | + disk address of different data blocks (TOC) 127 | 128 | - The __Incore inode__ contains the following information: 129 | + Status 130 | * 1. Locked 131 | * 2. A process is waiting for inode to become unlocked 132 | * 3. Changed 133 | * 4. File (corresponding to inode) changed or not 134 | + Device no. of File System 135 | + Inode Identification number (disk inode stored sequentially, therefore, doesn't need an identification number) 136 | + pointer fields (hash list and free list) 137 | + Reference Count - number of times inode is being referenced 138 | 139 | - Whenever the reference count of an inode becomes 0, then the inode is pushed into the __inode free list__. 140 | 141 | - To access a specific byte in a file, a process must first get information about the address of that file in the inode. The inode is then locked (to avoid any changes) and the block address is obtained from the TOC. After the address has been obtained then the inode is unlocked again. When the inode become locked, no other process can access the file. 142 | 143 | - __The inode table of contents (TOC)__: 144 | + The entries in a TOC contain 12 blocks of address. The first 10 addresses (0-9) contain the addresses of the block which contains file data. They are also called direct pointers. 145 | + The next address (10) is a __single indirect pointer__ pointing to a block (containing a set of pointers). The set of pointers then point to a block which contains the file data. 146 | + The next address (11) is a __double indirect pointer__ pointing to a block (containing a set of pointers) which also points to another block containing a set of pointers. The last block of pointers then point to a block that contains the file data. 147 | + Similarly, the next address (12) contains a __triple indirect pointer__ that follows the same structure as the previous. 148 | 149 | ```Pascal 150 | // Assuming: 151 | Block Size --> 1 KByte 152 | 153 | // then... 154 | Address of Block --> 4 Bytes 155 | Every address block will contain 256 pointers 156 | 157 | Direct pointers (10 addresses) can access --> 10 KBytes 158 | Single Indirect Pointers --> 256 KBytes 159 | Double Indirect Pointers --> 256 X 256 KBytes 160 | Triple Indirect Pointers --> 256 X 256 X 256 KBytes (16 GBytes) 161 | ``` 162 | 163 | - During any file operation: 164 | + 1. First, check if the inode of that file is in memory (the file could potentially be used by another process) 165 | + 2. Get a free inode to overwrite with the inode of the new file (obtained from secondary storage). 166 | 167 | - Algorithm for getting Inode 168 | 169 | ```Pascal 170 | While (not done) 171 | // if inode in memory 172 | if (inode in inode cache) 173 | if (inode locked) 174 | sleep (event: inode is unlocked); // wait for inode to unlock 175 | continue; 176 | if (inode on free list) // reference count = 0 177 | remove inode from free list; 178 | increment Reference count field; 179 | return (inode); 180 | 181 | // if the inode not in memory 182 | if (no inode in free list) 183 | return (error); 184 | remove (new inode from free list); 185 | reset inode number and file system; 186 | remove inode from hash old queue; 187 | add inode into new hash queue; 188 | read disk inode from the disk; 189 | put disk inode into new incore inode (block read algorithm); 190 | initialize the inode; // reference set to 1 191 | return (node); 192 | ``` 193 | 194 | - Algorithm for release inode: 195 | 196 | ```Pascal 197 | lock inode if not already locked; 198 | decrement reference count field; 199 | if (reference == 0) 200 | if (inode changed) 201 | update disk inode; 202 | put inode on free inode list; 203 | release inode lock; 204 | ``` 205 | 206 | - Directory (a special kind of file) 207 | 208 | - The type field indicates if the file is a directory or file. 209 | 210 | - The content of a directory contains a list of inode numbers and file names as provided in the example below: 211 | 212 | | Inode | File Name | Description | 213 | |-------|-----------|----------------------------| 214 | | 83 | . | entry of current directory | 215 | | 50 | .. | entry of parent directory | 216 | | 61 | passwd | entry of subdirectory | 217 | | 30 | bin | entry of subdirectory | 218 | | 99 | mkfs | entry of subdirectory | 219 | | ... | ... | ... | 220 | 221 | - File path of the subdirectory `passwd` can be specified as `passwd` or `/etc/passwd`. 222 | 223 | - Algorithm for converting a path name to the inode number 224 | 225 | ```Pascal 226 | if (pathname starts from root) 227 | working inode = root inode; 228 | else 229 | working inode = current directory; 230 | 231 | while (there is more pathname) 232 | read next pathname component from input; 233 | verify working directory inode is of directory and access permission okay; 234 | read directory; 235 | 236 | if (component matches an entry): 237 | get inode number for matched component; 238 | release working inode; 239 | working inode = inode of matched component; 240 | 241 | else: 242 | return (no inode); 243 | return (working inode); 244 | ``` 245 | 246 | - All the previous algorithms assume that an inode is already bound to a file. This means that for creating a new file there are other operations which are needed to allocate an new inode to a file. The free inode is obtained from the free inode list found in the __super block__. 247 | 248 | - The highest inode number in the super block free inode list is called the __remember inode__. This inode indicates where unallocated inodes will be available or free in the disk inode. 249 | 250 | - Algorithm for allocating a new inode 251 | 252 | ```Pascal 253 | while (not done) 254 | if (super block locked) 255 | sleep (event: s/b/ become free); 256 | continue; 257 | 258 | if (s.b. inode list empty) 259 | lock s.b. 260 | get remember inode for free inode search; 261 | search for free inodes until s.b. full OR no more free inodes; 262 | unlock s.b.l 263 | wakeup (event: s.b. free); 264 | if (not free inode found on disk) 265 | return (no inode); 266 | set remembered inode; 267 | 268 | get inode number from s.b. inode list; 269 | get inode (iget); // get incore inode 270 | initialize inode; 271 | write inode to disk; 272 | decrement free inode count; 273 | return (inode); 274 | ``` 275 | 276 | - Algorithm for freeing inode 277 | 278 | ```Pascal 279 | increment free inode count 280 | if (super block locked) 281 | return; 282 | if (super block free inode list is full) 283 | if (inode number requested is less than remembered inode) 284 | set remembered inode = input inode number; 285 | else 286 | store inode number in s.b. inode list 287 | return; 288 | ``` 289 | 290 | - The super block also contains a __free data block list__, which is only a single block that contains a list of other data block numbers which are free. 291 | 292 | - A free data block list contains a linked list, which in turn contain pointers to other list of blocks. 293 | 294 | - A data block can never be allocated if its not found in the super data block list. 295 | 296 | - Whenever a block is allocated, a buffer has to also be simultaneously allocated. Only then can the disk block along with allocated buffer be accessed by a process. 297 | 298 | - Algorithm for allocation of disk blocks: 299 | 300 | ```Pascal 301 | While (s.b. locked) 302 | sleep (event: s.b. not locked) 303 | 304 | remove block no. from s.b. free block list; 305 | 306 | if (removed last block from list) 307 | lock s.b.; 308 | remove block just taken from free list; // invoke block read algorithm 309 | copy block numbers into s.b.; 310 | release block buffer; 311 | unlock s.b.; 312 | wakeup process(event: s.b. not locked) 313 | 314 | get buffer for block removed from s.b. list; // invoke get block algorithm 315 | initialize buffer 316 | decrement free block count; 317 | mark s.b. modified; 318 | return buffer; 319 | ``` 320 | -------------------------------------------------------------------------------- /7. Memory Management.md: -------------------------------------------------------------------------------- 1 | ## Chapter 7: Memory Management 2 | 3 | 4 | - Base register, limit register 5 | 6 | - Address binding - how a program is loads from source code to main memory and the way addresses are generated. 7 | + Compile time 8 | + Load Time 9 | + Execution Time 10 | 11 | - Memory Management Unit (MMU) - run-time mapping from virtual addresses to physical addresses. 12 | 13 | - Dyamic Loading - routine loading that resides on disk and brought to memory when needed by a process 14 | 15 | - Dynamic linking - dyanmically linked libararies (Dll) - could be reused by many processes running; loaded in execution time; can help with disk and memory space since each process doesn't have to load their language library. 16 | 17 | - MM Solution - Swapping (moving process temporatily from memory to backing store) - makes it possible for the total physical address of all processes to exceed the real physical memory of the system, thus increasing the degree of multiprogramming in a system. 18 | 19 | - The CPU calls the dispatcher when it is ready to execute next process in the ready queue. the dispatcher is in charge of checking if a process is loaded in memory, if it's not and there is not free memory region the dispatcher swaps out a process currently in memory and then swaps in the desired process. 20 | 21 | - Swappng rules: 22 | + never swap a process with pending I/O 23 | + could be improved if only a portion of a process is swapped. 24 | + used only when a free memory threshold is exceeded 25 | 26 | - An interrupt vector is the memory location of an interrupt handler, which prioritizes interrupts and saves them in a queue if more than one interrupt is waiting to be handled. 27 | 28 | - In contiguous memory allocation, each process is contained in a single section of memory that is contiguous to the section containing the next process. 29 | 30 | 31 | - Memory Allocation strategies: 32 | + First fit - The first hole in memory that is big enough (faster, space saving, external fragmentation) 33 | + Best fit - smallest hole that is big enough (space saving, external fragementation) 34 | + Wirst fit - the largest hole is picked 35 | 36 | - Internal fragmentation - is when there is unused memory that is internal to a partition of memory space. 37 | 38 | - External fragmentation happens when there is enough total memory (usually in scattered memory partitions) to satisfy a request but its not contiguous. Compaction is a solution (put all free spaces together) - only possible at execution time where addresses can be dynamically relocated. 39 | 40 | - Other solution to external fragmentation involves allocation of memory in a random manner, which is achieved by allowing logical addresses of processes to be nonconiguous (segmentation). 41 | 42 | - Segmentation - is a memory management scheme where logical address is a collection of segments which include addresses () with segment name and offset within the segment. permits the physical address space of a process to be non-contiguous. still cannot resolve external fragmentation 43 | 44 | - paging is a memory management scheme that avoids external fragmentation and the need for compaction. It also solves the proble of fitting memory in chunks of varying sizes onto the backing store (the way swapping does it). Paging might suffer from internal fragmentation. 45 | 46 | - A 32-bit CPU uses 32-bit addresses, meaning that a given process space can only be 2^32 bytes (4GB). Therefore, paging lets us use physical memory that is larger than what can be addressed by the CPU's address pointer length. 47 | 48 | 49 | - Trying to reference a segment beyond the segment limit or before segment limit will cause a trap by the OS. 50 | 51 | - Process Control Block (PCB, also called Task Controlling Block, process table, Task Struct, or Switchframe) is a data structure in the operating system kernel containing the information needed to manage a particular process. The PCB is "the manifestation of a process in an operating system". 52 | 53 | - The os maintains a copy of the page table for each process, which is then accessed by the dipatcher. This access increases context-switch time, which is solved using fast registers. Page table is stored in fast registers (each register containg a few pages); too many registers and it is better to use page-table base register (PTBR), which is stored in memory. PTBR allows for quick change in page table since it is just one table, reducing context-switch time. PTBR needs double access to memory. A better solution is translation look-aside buffer (TLB). TLB is associative high speed memory - a fast lookup hardware cache (almost like a key value hash table). TLB stores page number and frame number paired entries. TLB is part of the intruction pipeline and executed as part of it, adding no performance penalty. 54 | 55 | 56 | - paging allows for sharing of reentrant code that is allocated in memory through frames. Each process using the reentrant code need only to point to that frame instead of allocating memory to run the same reentrant code. 57 | 58 | - Memory protection in a paged environment is done by protecting bits associated with each frame (kept in the table). Bits can be valid-invalid bit. 59 | 60 | 61 | - page implementation: 62 | + Hierarchical paging - have outer and inner pages (appropriate for 32 bit systems) 63 | + Hash page tables - can handle address space larger that 32 bits; uses linked list to store entries 64 | + Inverted page tables - instead ogf having one table for each process, just have one big table that stores process id to unique idendify the page entry in the page table; it saves storage space but it more time consuming to search the page table. 65 | 66 | 67 | 68 | - __Bare Machine__ - A system in where the designer is to decide how the memory should be addressed. This kind of system is used for dedicated system such as a computer controlling a plant. 69 | 70 | - The typical computer utilizes a __Resident Monitor__, where part of the memory is used by the operating system. In such system, one part of the memory is used by the __operating system__ and the remaining part of the memory is used to load user programs (__User Area__). 71 | 72 | - A user program is not permitted to touch the memory that is used by the Operating System. This is achieved by having a __Fence Address__. The fence address acts a fence or a marker that cannot be surpassed by the user program. 73 | 74 | - There are two ways to keep checking if a user program is not surpassing the Fence Address: Software Check or Hardware check. Software check is slow. A hardware solution checks in parallel whether the address generated by the CPU has jumped the Fence Address. If yes, then set a trap, if no then allows for the CPU to use the memory. 75 | 76 | - The problem with the hardware solution is that it is fixed. Meaning that the hardware might not be compatible with an operating system that requires more or less allocated memory. A solution is to use a __Fence Register__ which is reloaded if the operating system is upgraded. 77 | 78 | - Memory can be divided into different areas besides just the o/s and user area. One of the areas called __buffer1__ can be used to swap out jobs (terminate jobs and put them back to secondary storage). The other area called __buffer 2__ can be used to swap in job from a secondary storage into main memory. The other areas (o/s and user) work separately. The user area is now able to take a process that has completed its CPU burst and move it into _buffer 1_. 79 | 80 | 81 | - To divide and protect the different memory areas a fence address is also used. A job in buffer 2 does not have to be transferred to user area. The fence address can be shifted up and encompass the area where the job is located (buffer 2) so that the user area can now execute that job. More memory partitions allow for better memory management. 82 | 83 | - __Multiprogramming with Fixed number of Tasks__ - here main memory will partitioned into many fixed segments or areas, including the o/s area. An instruction pointer can be used to make different areas of the memory become active. In this case, a segment must be protected using two registers: A __lower bound register__ and an __upper bound register__. 84 | 85 | - If the CPU process is trying to access an area below the lower bound address and above the upper bound address, then that process is not allocated memory. 86 | 87 | - A __base register__ and a __limit register__ can be used to check if the CPU generated address is requesting to access a part of the memory that is not allowed for that process. If the CPU logical address is less than the limit register, then that logical address is added to the Base address and given access to the physical address in memory. If the logical address is more that the limit address, then is send to a trap. 88 | 89 | - The way the operating system decides where a job is loaded into main memory is based on the fixed size of the partition in memory. 90 | 91 | - __First Fit Algorithm__ - A partition is allocated fully to a job or not based on an algorithm that checks and allocated which is the first segment of the memory that is more than or equal to the size required by the process. Because it is fixed, a partition cannot be allocated to other jobs when a job has been loaded into it. There could be a case where the memory partition allocated to a process is bigger in size than what the process requires. The remaining allocated memory is wasted and also referred to as an __internal fragmentation__. 92 | 93 | 94 | - __Best Fit Algorithm__ - allocates the memory partition that leads to the minimum internal fragmentation. This means that it has to check _all segments_ in memory to choose the best one for the size required by the process. The complexity of this algorithm is a lot higher than the previous algorithm. 95 | 96 | - There could be a case when the partitions in memory are all smaller in size than what is required by a process. In such scenario, there is a case of __external fragmentation__ because there are free memory partition of which not can be used. 97 | 98 | - A __memory module__ keep track on information about the main memory partition sizes and statuses. 99 | 100 | - The status (Free / Allocated) of each memory partition is kept in what is called a __Partition Allocation Table__. 101 | 102 | - Another type of memory management is called __Multiprogramming with Variable number of Tasks__ (MVT). In such case, we don't have a fixed number of partitions. In fact, in MVT, the number and size of partition vary depending on the size requirement from different jobs. 103 | 104 | - In the MVT, initially (no jobs) there will be two partitions in memory: O/S area and User area. 105 | 106 | - Assuming we have the following jobs: 107 | 108 | ```Pascal 109 | // memory size 256K 110 | // initial memory allocation 111 | O/S Partition = 40K 112 | User Partition = 216K 113 | 114 | // memory requirements for jobs using a FCFS scheduling 115 | J1 = 60K , 10 time units (CPU burst time) 116 | J2 = 100K, 5 time units 117 | J3 = 30K, 20 time units 118 | J4 = 70K, 8 time units 119 | J5 = 50K, 15 time units 120 | J6 = 60K, 9 time units 121 | 122 | // Allocation happens the following way 123 | 124 | // J1 allocation 125 | 2 Partition: 40K --> 100K 126 | Unallocated Partition : 156K 127 | 128 | // J2 Allocation 129 | Partition: 100K --> 200K 130 | Unallocated Partition: 56K 131 | 132 | // J3 Allocation 133 | Partition: 200K --> 230K 134 | Unallocated Partition: 26K 135 | ``` 136 | 137 | - No other jobs can be loaded into memory since all of them exceed the size of the remaining partition. The remaining unallocated partition become an external fragmentation. 138 | 139 | - After 5 time units, J2 will complete execution and the partition held by J2 will change its state to unallocated. 140 | 141 | ```Pascal 142 | O/S Partition: 0 --> 40K 143 | 144 | // J1 allocation 145 | 2 Partition: 40K --> 100K 146 | Unallocated partition: 100K --> 200K (after J2 released it) 147 | 148 | // J3 Allocation 149 | Partition: 200K --> 230K 150 | Unallocated Partition: 26K (230K - 256K) 151 | 152 | // J4 Allocation 153 | Partition: 100K --> 170K 154 | Unallocated Partition: 30K (170K - 200K) // this makes two unallocated partitions in memory 155 | ``` 156 | 157 | - After 5 more time units, J1 will complete execution 158 | 159 | ```Pascal 160 | O/S Partition: 0 --> 40K 161 | 162 | Unallocated partition: 60K (40K - 100K) (after J1 released it) 163 | 164 | // J4 Allocation 165 | Partition: 100K --> 170K 166 | Unallocated Partition: 30K (170K - 200K) 167 | 168 | // J3 Allocation 169 | Partition: 200K --> 230K 170 | Unallocated Partition: 26K (230K - 256K) 171 | 172 | // J5 Allocation 173 | Partition: 40K --> 90K 174 | Unallocated Partition: 10K (90K - 100K) 175 | ``` 176 | 177 | - In the above given scenario, there is a total of 66K of memory which is unallocated. The remaining job only requires 60K. In order to use the remaining memory, all the free unallocated partition can be compacted together contiguously to make a bigger partition (size: 66K). Such technique is called __Memory Compaction__. After memory compaction jobs _J5_, _J4_, _J3_ and _J6_ will occupy a total of 250K memory with a remaining unallocated partition of size 6K. 178 | 179 | - In the MVT, two tables are kept: A partition allocation table and a __free area table__ (containing the free unallocated spaces). Free area table contains the size of the area and the starting address of the partition. 180 | 181 | - In MVT, you can allow internal fragmentation by merging a very small free area to an already allocated partition. This may avoid some cost. 182 | 183 | - Memory compaction is a very costly solution. To come up with a better solution the __Paged Memory Management__ technique was introduced. 184 | 185 | - In paged memory management, every process is divided into a number of pages. Processes are divided into a __number of pages__. Memory is divided into a __number of frames__. The page size is the same size as the frame size. 186 | 187 | - Any page can be loaded into any of the frames. But there has to be some mapping stating in which frames in memory those pages were loaded. From a frame number it is possible to find out the physical address on the frame in memory where the job pages were allocated. 188 | 189 | - Every pages a _P_ number of bytes (Page size) with a logical address _L_. A logical address has two components: a page number _p_ and an offset/displacement _d_ within that page. This information is stored in a __Page Map Table (PMT)__ Those components are calculated as follows: 190 | 191 | ```Pascal 192 | // logical address calculation 193 | p = L div P // integer division 194 | d = L mod P 195 | 196 | // physical address calculation 197 | Physical address = (f-1) * P + d 198 | ``` 199 | 200 | - Using the paging technique is beneficial because a job's pages can be loaded into memory frames without those frames having to be contiguous as in the previous technique (MFT) or (MVT). This also avoids the problem and complexity of compaction. 201 | 202 | - In PMM, there will be internal fragmentation if the page size if (p = L div P) have a remainder. The maximum number of internal fragmentation is equal to (P - 1). 203 | 204 | - If a user is trying to access an inaccessible memory frame through a program, the PMT keeps a bit (0/1) that checks if that program page corresponds to that particular requested frame. If the page contains a 0 bit that means there is no corresponding page for that frame. 205 | 206 | - In PMM, the __modular structure__ of the program is broken. 207 | 208 | - Instead of having pages, a job is broken down in segments that contain specific modules of the program. Such technique is called __Segmented Memory Management__. 209 | 210 | ### Segmented Memory Management (SMM) 211 | 212 | - Given the following program with the specific functions of modules: 213 | 214 | ```Pascal 215 | // functions of different size 216 | Main() 217 | Sqrt() 218 | Factorial() 219 | Billing() 220 | ``` 221 | 222 | - In SMM, the program is broken down to segments, where each segment contain one of the functions or module of that program. 223 | 224 | - Each segment is then loaded into memory just as the MVT technique -- with a Base address and Limit address. First, the CPU generates a logical address (s, d) for each segment to be loaded into memory. A __segment memory table__ is needed to map the logical address to the physical address in memory. The offset of the segment cannot exceed the limit address of that segment in memory. 225 | 226 | - The first check is comparing if the offset of the segment _d_ is less than limit. If yes, the base is added to the _d_ to set the physical address in main memory. 227 | 228 | - The order of the segments can be implements based on the same order the modules or functions were coded in the program. 229 | 230 | - SMM and PMM can be combined to achieve a new technique called the __Paged Segmented Memory Management__ (PSMM). 231 | 232 | - In PSMM, the CPU generates a _(s, d)_ logical address pair for a program. Then _s_ is used to calculate the _limit_ and _PMT Base_ and stored in a __segment table__. Given that _s_ is divided into a number of pages, if _d_ is less that _limit_ then _d_ is broken into two components _(p, d')_. Then add page number _p_ with the _PMT Base_ to give a particular entry within the PMT table of _s_. The entries in _PMT_ contain the frame number _f_ . Lastly, _f_ and _d'_ are used to give the physical address in main memory. 233 | 234 | ### Demand Paging (Virtual Memory Management) - Another memory management technique. 235 | 236 | - VMM is a paging technique so there will be equal size of frames or segments allocated in main memory to load the program on. 237 | 238 | - In VMM, there is no need to have all the pages loaded into memory at the same time. The instructions of a program can wait for other to execute and the be loaded into main memory when needed. This means that in order for a job to start execution there is no need to load the entire logical space pages of that job into main memory. 239 | 240 | - The following is a flowchart of the way VMM works: 241 | 242 | ```Pascal 243 | Step 1: Start Processing an Instruction 244 | Step 2: CPU Generate Address 245 | Step 3: Page number is generated 246 | Step 4: Check PMT if Page available in Main Memory 247 | 248 | If page in MM: 249 | Step 5: Fetch the data & compute the instruction 250 | Step 6: CPU advances to the next instruction 251 | Step 7: Process continues 252 | Else: 253 | Step 5: Page fault interrupt is processed 254 | ``` 255 | 256 | - Process of page fault interrupt: 257 | 258 | ```Pascal 259 | // Step 5: 260 | Step 1: 261 | If there is no free block in MM: 262 | Step 2: Select a page in MM for replacement 263 | Step 3: Adjust PMT (change bit) 264 | Step 4: Check if page was modified 265 | If yes: 266 | Step 5: Write back into Secondary Storage 267 | Else: 268 | Step 6: Free address for the new page 269 | Else: 270 | Step 2: Get Disk Access of the new page from FMT (File Map Table) 271 | Step 3: Read in Page 272 | Step 4: Adjust PMT for new page 273 | Step 5: Restart the interrupted instruction 274 | Step 6: Continue processing instructions 275 | ``` 276 | 277 | - There are many page replacement techniques. The simplest technique is called __FIFO (First in First out)__ replacement. The page that was loaded first in memory is the page that is terminated or replaced by the new one. 278 | 279 | - Another techniques is called the Optimal replacement technique, where the page being replaced is not needed anytime near in the future. In the case of the FIFO technique, the page being replace might be the page to used next and thus incur a cost (including I/O operation and more page interrupts). __Least Recently Used (LRU)__ replacement techniques is an approximation of the OPTIMAL replacement technique. 280 | 281 | - Given a list of page numbers referred to by a user program: 282 | 283 | ```Pascal 284 | // order of page numbers 285 | 7, 0, 1 ,2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1 ,7, 0, 1, 286 | 287 | // three memory frames are available 288 | [ ] 289 | [ ] 290 | [ ] 291 | ``` 292 | 293 | - FIFO functions as follows: 294 | 295 | ```Pascal 296 | // page 7 issues a page interrupt and then is loaded into MM 297 | // no page replacement in needed since there is free frame 298 | [7] 299 | [ ] 300 | [ ] 301 | 302 | // page 0 is referred, page interrupt, no page replacement 303 | [7] 304 | [0] 305 | [ ] 306 | 307 | // page 1 referred, page interrupt, no page replacement 308 | [7] 309 | [0] 310 | [1] 311 | 312 | // page 2 referred, page interrupt, yes page replacement using FIFO 313 | [2] 314 | [0] 315 | [1] 316 | 317 | // page 0 nothing happens 318 | 319 | // page 3 referred, page interrupt, yes page replacement 320 | [2] 321 | [3] 322 | [1] 323 | 324 | // page 0 325 | [2] 326 | [3] 327 | [0] 328 | 329 | // page 4 330 | [4] 331 | [3] 332 | [0] 333 | 334 | // page 2 335 | [4] 336 | [2] 337 | [0] 338 | 339 | // page 3 340 | [4] 341 | [2] 342 | [3] 343 | 344 | // page 0 345 | [0] 346 | [2] 347 | [3] 348 | 349 | // page 3, 2 - nothing happens 350 | 351 | // page 1 352 | [0] 353 | [1] 354 | [3] 355 | 356 | // page 2 357 | [0] 358 | [1] 359 | [2] 360 | 361 | // page 0, 1 362 | 363 | // page 7 364 | [7] 365 | [1] 366 | [2] 367 | 368 | // page 0 369 | [7] 370 | [0] 371 | [2] 372 | 373 | // page 1 374 | [7] 375 | [0] 376 | [1] 377 | ``` 378 | 379 | - With the above process, two ratios can be calculated: __hit ratio__s and __miss ratio__. 380 | 381 | - Hit ratio is the number of hits divided by the number of access (5/20). Miss ratio is (15/20). 382 | 383 | - OPTIMAL function as follows: 384 | ```Pascal 385 | // page 7 386 | [7] 387 | [ ] 388 | [ ] 389 | 390 | // page 0 391 | [7] 392 | [0] 393 | [ ] 394 | 395 | // page 1 396 | [7] 397 | [0] 398 | [1] 399 | 400 | // page 2 (replace number of 7 since it needed late on in future) 401 | [2] 402 | [0] 403 | [1] 404 | 405 | // page 0 406 | // page 3 (replace 1) 407 | [2] 408 | [0] 409 | [3] 410 | 411 | // page 4 412 | [2] 413 | [4] 414 | [3] 415 | 416 | // page 2 417 | // page 3 418 | 419 | // page 0 420 | [2] 421 | [0] 422 | [3] 423 | // page 3 424 | // page 2 425 | // page 1 426 | [2] 427 | [0] 428 | [1] 429 | 430 | // page 2 431 | // page 0 432 | // page 1 433 | // page 7 434 | [7] 435 | [0] 436 | [1] 437 | 438 | // page 0 439 | // page 1 440 | ``` 441 | 442 | - miss ratio (9/20); hit ratio (11/20). 443 | 444 | - Least Recently Used (LRU) function as follows: 445 | 446 | ```Pascal 447 | // page 7 referred 448 | [7] 449 | [ ] 450 | [ ] 451 | 452 | // page 0 453 | [7] 454 | [0] 455 | [0] 456 | 457 | // page 1 458 | [7] 459 | [0] 460 | [1] 461 | 462 | // page 2 (replace with recently used page) 463 | [2] 464 | [0] 465 | [1] 466 | 467 | // page 0 468 | // page 3 469 | [2] 470 | [0] 471 | [3] 472 | 473 | // page 0 474 | // page 4 475 | [4] 476 | [0] 477 | [3] 478 | 479 | // page 2 480 | [4] 481 | [0] 482 | [2] 483 | 484 | // page 3 485 | [4] 486 | [3] 487 | [2] 488 | 489 | // page 0 490 | [0] 491 | [3] 492 | [2] 493 | 494 | // page 3 495 | // page 2 496 | // page 1 497 | [1] 498 | [3] 499 | [2] 500 | 501 | // page 2 502 | // page 0 503 | [1] 504 | [0] 505 | [2] 506 | 507 | // page 1 508 | // page 7 509 | [1] 510 | [0] 511 | [7] 512 | 513 | // page 1 514 | // page 7 515 | // page 0 516 | // page 1 517 | ``` 518 | - miss ratio (12/20). hit ratio (8/20). 519 | 520 | - Overall, _OPTIMAL_ works best and _FIFO_ is the worst algorithm. 521 | 522 | - __Locality of reference__ - says that once some data / instruction in accessed, the probability that this data / instruction accessed again is high. This ensures that the pages that are most recently used are not replaced but instead the page that was least recently used. 523 | 524 | 525 | - A miss usually includes access to a secondary storage which takes a lot more time what is needed to access the main memory. Hit usually includes access to main memory, therefore, there is efficiency and speed. 526 | 527 | - The segmentation table and page map table is put into __cache memory__ because it is constantly being accessed by CPU. It wouldn't be deficient to loaded into main memory since there will always be an extra access to memory upon a mapping of logical address to physical address. 528 | 529 | --------------------------------------------------------------------------------