Operating Systems — What every application programmer must know

Vijay
10 min readJan 24, 2019

--

An operating system (OS) is a critical component found on many devices that contain a computer, this includes laptops, smartphones and video game consoles. A software engineer (at any level) should understand the basics of an operating system and have general knowledge of what’s going on under-the-hood of a computer.

This post provides a general overview of various concepts with some of the more commonly asked interview questions about the topic. It is meant to serve as an informative introduction into operating systems, as well as assist in preparing for a software engineering role. All of the topics discussed in this document are implemented within some of the most popular types of operating systems seen below.

Types of Operating Systems

Fundamentals of an Operating System

What is the main purpose of an operating system?

An operating system is the most important piece of software that runs on a computer. It manages the computer’s computational activities associated with the hardware and provides services for application-level programs. Functions of an operating system include resource allocation, file system management, memory management, and security.

At the core of every operating system is the kernel which performs the above functions (manages communication between hardware and software). An operating system converts the 1's and 0’s flowing around at the computer chip-level into human-readable graphics that can be seen and interacted with by a user. All user software needs to go through the operating system in order to make use of the hardware, whether it be a mouse-click or a complex Internet application feature. The OS coordinates access to the central processing unit (CPU), memory, and storage to ensure each program gets what it needs.

Basic Operating System Components

What makes up an operating system?

  1. Process Management

A process is an instance of a program in execution. When we write a computer program in a text file (independent of the programming language or OS) and then execute the program, it becomes a process within the system which performs all the instructions defined in the program. Once the program is loaded into memory, and it becomes a process, the operating system creates a Process Control Block (PCB) for it. The PCB includes an integer identifying the process (PID), as well as several other pieces of information needed to keep track of it.

The image below shows the structure of a Process Control Block:

PCB

Program counter: Pointer to the address of the next instruction to be executed for this process.

CPU registers: Where the process needs to be stored for execution when in a running state.

Memory Management Information: The page table, memory limitations, and the segment table depending on memory used by the OS.

The actual process (a program loaded into memory) is divided into four sections — stack, heap, text, and data. The image below shows a simplistic layout of a process inside memory:

Process Layout

2. Threads and Parallelism

A thread a.k.a. a lightweight process is simply a flow of execution with its’ own program counter (to keep track of instructions), system registers (to store variables), and stack (to store execution history). Threads cannot exist outside of a process, and more specifically each thread belongs to exactly one process (several threads can exist within a single process depending on the processor). Some information is shared between threads such as code segments, data segments, and open files — this means that when one thread alters a code segment memory item, peer threads are aware.

Thread Layout

Kernel-level threads drastically improve the performance of an operating system by reducing the overhead needed for various operations that would otherwise require many separate processes. In a similar regard, user-level threads have been successfully implemented in network servers, web servers, and mission-critical applications because they improve application performance via parallelism.

What advantages do threads provide?

  • Threads minimize the context switching time.
  • Use of threads provides concurrency within a process.

What resources are shared between threads?

  • Threads share code, and data segments but each has its’ own stack. Threads have independent call stacks, however the memory in other thread stacks is still accessible (although not recommended). Threads also share the same memory space while separate processes do not.

3. Scheduling

At it’s core this is a really simple concept… scheduling is the means in which work is allocated to resources for completion. “Work” can be a multitude of different tasks such as virtual computation elements like threads, and processes, which then need to be scheduled onto hardware resources such as processers, network links, and expansion cards.

Scheduling is primarily implemented to ensure that computer resources are kept busy (load balancing), and users are able to share system resources (i.e. CPU time, disk drives) affectively. The scheduler is a module in the operating system which selects the next jobs to be loaded into the system and the next process to execute.

Important scheduling concepts to understand —

  • Schedulers can be broken down into long-term, medium-term and short-term depending on the lifecycle for which the functions need to be performed.
  • Schedulers dictate what processes to run on a system, the degree of concurrency to be supported at any one time, and how the split between I/O intensive and CPU intensive processes is to be handled.
  • An I/O-bound process is one that spends more of its time doing I/O than it spends doing computations. A CPU-bound process, in contrast, generates I/O requests infrequently, using more of its time doing computations.
  • A dispatcher is a scheduling module that gives control of the CPU to the process selected by the short-term scheduler. It receives control in kernel mode as the result of an interrupt or system call.
  • Several algorithms exist for implementing scheduling (i.e. distributing resources among components which simultaneously and asynchronously request them.

The main function of scheduling in the operating system is to provide a mechanism for sharing CPU time amongst threads and processes.

4. Memory Management

An extremely important function of an operating system is memory management. Memory management is the capability of an operating system to move processes back and forth between main memory (RAM) and disk.

Quick refresher:

  • A hard disk or “hard drive” is a physical set of magnetic discs that can hold several gigabytes of data. Disk space refers to how much space is available on the hard disk for storing files (i.e. save a document, or install a new program).
  • Memory refers to the random access memory (RAM) inside the computer. Physically it is a set of small chips known as memory modules which are used to store actively running programs on the computer. For example, when the computer boots up, the operating system’s interface and other start-up processes are loaded into main memory… another example is when you open a program like Microsoft Word.

In the OS, memory management keeps track of every single memory location (both virtual and physical), whether allocated to a process or free. It checks how much memory is to be allocated to each process and decides when the process will get the memory. RAM can be accessed hundreds of times faster than the hard drive, which is why active processes are loaded into RAM, and this functionality is vital to an operating system.

5. I/O Device Management

Another important function of an operating system is to manage various I/O devices including mouse, keyboards, touch pad, disk drives, display adapters, USB devices, Bit-mapped screen, LED, Analog-to-digital converter, On/off switch, network connections, audio I/O, printers etc.

An I/O system is required to take an application I/O request and send it to the physical device, then take whatever response comes back from the device and send it to the application. The operating system provides a mechanism for the CPU to pass information to and from an I/O device. There are three approaches available to communicate between the CPU and a device: Special Instruction I/O, Memory-mapped I/O, Direct memory access (DMA).

Other important components —

File System

  • File creation/deletion.
  • Support for hierarchical file systems
  • Update/retrieval operations: read, write, append, seek

Security

  • Resources — CPU cycles, memory, files, devices
  • Users — authentication, communication

User Interface

  • Character-Oriented shell — sh, bash, terminal
  • GUI — Windows, Linux
Simplified Structure of the Linux Operating System

Interview Questions and Answers

What is a deadlock?

  • A deadlock occurs when two or more processes all block one another from completing execution. Expanded slightly — a deadlock occurs when a process or thread enters a waiting state because a requested system resource is held by another waiting process, which in turn is waiting for another resource held by another waiting process. The concept is termed deadlocked because a process cannot continue execution until a resource is allocated to it.

How would you prevent a deadlock?

  • The technique for deadlock avoidance is to have a resource hierarchy. Make sure that all threads acquire locks to resources in the same order. This avoids the deadlock scenario where thread 1 holds lock A and needs lock B while thread 2 holds lock B and needs lock A. With a lock hierarchy, both threads would have to acquire the locks in the same order (i.e. A before B).
  • Strictly speaking, a mutex is locking mechanism used to synchronize access to a resource. Only one task (can be a thread or process based on OS abstraction) can acquire the mutex. It means there is ownership associated with mutex, and only the owner can release the lock (mutex).
  • Semaphore is signaling mechanism (“I am done, you can carry on” kind of signal). For example, if you are listening to music on your cellphone and at the same time your friend calls you, an interrupt is triggered upon which an interrupt service routine (ISR) signals the call processing task to wakeup.

See the Dining Philosopher’s problem for a classic scenario that intelligently describes deadlock —

When writing multi-threaded applications, one of the most common problems experienced are race conditions. What is a race condition?

  • Remember that two or more threads can access shared data… in the situation where both try to access a data segment at the same time, we refer to both threads as “racing” to access/change the data. Most thread scheduling algorithms can swap between threads at any time so the order in which the shared data will be accessed is unknown. The example below depicts a situation in which one thread is performing a “check-then-act”, but leaves open the possibility of another thread modifying something in the middle of the action.
if (x == 5) // The "Check" {    
y = x * 2; // The "Act"
// If another thread changed x in between "if (x == 5)" and
// "y = x * 2" above, y will not be equal to 10.
}
  • The most common, and fail-safe method for preventing race conditions from occurring is to utilize a locking mechanism. Prior to performing the “check” act, we can utilize a lock or mutex to guard the resource from access by concurrent threads until a specific piece of code is executed. In the example below, “obtain lock” can be implemented programmatically in all a variety of ways.
// Obtain lock
if (x == 5) // The "Check" {
y = x * 2; // The "Act"
}
// Release lock
  • In Java, a locking mechanism can be implemented using the synchronized keyword. Java provides a way of creating threads and using a synchronization method so that only one thread can access the resource at an given time. All threads attempting to enter the synchronized block are blocked until the thread inside the synchronized block executes and exists.
//This class' shared object will be accessed by threads class Counter  implements Runnable{
int c = 0;
@Override
public void run() {
synchronized(this){
// incrementing
c++;
System.out.println(Thread.currentThread().getName() + "c")};
}
}
}
public class RaceConditionDemo{
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(counter, "ThreadOne");
Thread t2 = new Thread(counter, "ThreadTwo");
Thread t3 = new Thread(counter, "ThreadThree");
t1.start();
t2.start();
t3.start();
}
}
Output:ThreadTwo 1
ThreadThree 1
ThreadOne 1

In the simple example above, we are incrementing the integer “c” each time a thread starts. We wrap the functionality in a synchronized keyword to ensure the mutable objects in the code block are inaccessible by concurrent threads.

Internally Java uses a so called monitor also known as monitor lock or intrinsic lock in order to manage synchronization. This monitor is bound to an object, e.g. when using synchronized methods each method share the same monitor of the corresponding object.

If you’ve made it this far, congrats! In summary the purpose of this post was to highlight the basic components that make-up an operating system and provide an overview of it’s major functionalities.

In this post we —

  • Defined an operating system and dove into it’s key components
  • Provided an overview of processes and threads
  • Touched on race conditions — common points of error that developers’ must know how to handle

For continued learning — check out the links below for more information about important concepts as they relate to the Java programming language!

--

--