Instructor: Dr. Sukhjit Singh Sehra
The operating system acts as middleware between applications and hardware.

Multiprogramming - More than one program is loaded in memory and can be active at the same time
Multitasking (Time-sharing) - The CPU switches rapidly among processes so users can interact with multiple jobs concurrently
Multi-user - The operating system supports multiple users simultaneously using isolation and protection mechanisms
Enabled by:

OS design nicely separates into three pillars, with security as a transcendental layer covering/overarching all pillars.

virtual layer that represents the physical resource that allows multiple applications or processes to share the resource without interfering with each other.Can we run multiple cpu.c programs at the same time, and what happens when we do?
Can we run multiple cpu.c programs at the same time, and what happens when I do?
❌ Only one cpu.c will run; the others will not run until it finishes
❌ All programs run at full speed because the OS creates extra CPUs
✅ All programs will run, but each appears to have its own CPU because the OS virtualizes and shares the processor
The CPU hardware has a wire called the interrupt-request line that the CPU senses after executing every instruction.
When the CPU detects (an asynchronous event) that a controller has asserted a signal on the interrupt-request line.
The operating system preserves the state of the CPU by storing the registers and the program counter
It reads the interrupt number and jumps to the interrupt-handler routine by using that interrupt number as an index into the interrupt vector.
It then starts execution at the address associated with that index.
It performs a state restore and executes a return from interrupt instruction to return the CPU to the execution state prior to the interrupt.
\\mem.c
#include <unistd.h> // getpid(), sleep()
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main() {
int *p = (int *)malloc(sizeof(int));
assert(p != NULL);
printf("(%d) address of p: %p\n", getpid(), (void *)p);
*p = 0;
for (int i = 0; i < 10; i++) {
sleep(1);
(*p)++;
printf("(%d) address of p: %p | p: %d\n", getpid(), (void *)p, *p);
fflush(stdout);
}
free(p);
return 0;
}On modern OS, the numerical virtual addresses may differ across processes, but the illusion of private physical memory remains intact.
The OS is juggling many things at once, first running one process, then another, and so forth.
Modern multi-threaded programs also exhibit the concurrency problem. OS must handle concurrent events and untangle them as necessary.

Hide concurrency from independent processes
Manage concurrency from dependent processes by providing synchronization and communication primitives
Challenge: providing the right primitives
\\threads.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
volatile int counter = 0;
int loops;
void *worker(void *arg) {
(void)arg;
for (int i = 0; i < loops; i++) {
counter++; // intentionally NOT atomic (race condition demo)
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: threads <loops>\n");
return 1;
}
loops = atoi(argv[1]);
pthread_t p1, p2;
printf("Initial value : %d\n", counter);
pthread_create(&p1, NULL, worker, NULL);
pthread_create(&p2, NULL, worker, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("Final value : %d\n", counter);
return 0;
}\\threads.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
volatile int counter = 0;
int loops;
void *worker(void *arg) {
(void)arg;
for (int i = 0; i < loops; i++) {
counter++; // intentionally NOT atomic (race condition demo)
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: threads <loops>\n");
return 1;
}
loops = atoi(argv[1]);
pthread_t p1, p2;
printf("Initial value : %d\n", counter);
pthread_create(&p1, NULL, worker, NULL);
pthread_create(&p2, NULL, worker, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("Final value : %d\n", counter);
return 0;
}\\threads.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
volatile int counter = 0;
int loops;
void *worker(void *arg) {
(void)arg;
for (int i = 0; i < loops; i++) {
counter++; // intentionally NOT atomic (race condition demo)
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: threads <loops>\n");
return 1;
}
loops = atoi(argv[1]);
pthread_t p1, p2;
printf("Initial value : %d\n", counter);
pthread_create(&p1, NULL, worker, NULL);
pthread_create(&p2, NULL, worker, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("Final value : %d\n", counter);
return 0;
}Inconsistent Result: We see the two threads when executed for large number produces inconsistent result. Why? Concurrency problem (Race Condition)?
\\threads_mutex.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
volatile int counter = 0;
int loops;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *worker(void *arg) {
(void)arg;
for (int i = 0; i < loops; i++) {
/* lock/unlock around the critical section */
pthread_mutex_lock(&lock);
counter++; // now protected
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: threads <loops>\n");
return 1;
}
loops = atoi(argv[1]);
pthread_t p1, p2;
printf("Initial value : %d\n", counter);
pthread_create(&p1, NULL, worker, NULL);
pthread_create(&p2, NULL, worker, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("Final value : %d\n", counter);
return 0;
}OS is a gatekeeper, it ensures and enforces security. OS is also privileged and therefore frequently attacked.
How do we guarantee that user does not explicitly set the mode bit to “kernel”?
System call changes mode to kernel, return from call resets it to user
Transition from User to Kernel Mode

Textbook: Chapter 1 from Arpaci-Dusseau, Remzi H., and Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces, 1.10 Edition, Arpaci-Dusseau Books, LLC, 2018.
Reference: Chapter 1 from A. Silberschatz, P.B. Galvin, and G. Gagne. Operating System Concepts, 10th Edition; 2018; John Wiley and Sons.
Thanks
🚀 Welcome to CP386 Course
🤔 Any Questions?

Operating Systems