Operating Systems 2022F Lecture 3

From Soma-notes
Revision as of 18:45, 17 September 2022 by Soma (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


Video from the lecture given on September 15, 2022 is now available:

Sorry for the lack of subtitles and bad video quality, there were technical issues.

Textbook Readings


Lecture 3

Sorry about the Zoom technical glitches, we'll see how Teams works!

The key material in this course is all in the tutorials
 - assignments are intended to see if you've learned what you should from
   the tutorials

Remember for the tutorials you have until the corresponding assignment is due
 - you should try to finish them in a week
 - you get marks by showing your work to a TA
    - it doesn't have to be written, you just need to convince
      your TA that you made a serious effort at the tutorial

In general, I'd try to get checked off at the end of the tutorial time
 - you may not have finished, but you can generally get checked off if
   you spend most of the time in tutorial working on the material
 - you'll likely want to spend more time afterwards making sure you understand everything
    - getting checked off just means you are on the way to understanding
      the material, not that you've finished

Part of this class is realizing you have lots to learn, you'll always
have lots to learn, and developing the skills to only learn *what you
need to* to accomplish the tasks in front of you.

 - assembly language/machine code
 - libraries & library code
 - system calls

Machine code: the code the CPU actually runs
 - bunch of numbers!
 - we don't want to program this way

You can program in machine code directly, but that is VERY painful
But there is a human-readable version: assembly code

You can actually translate machine code into assembly, it is called "disassembly" of code

Typically when we look at raw binary data, like machine code, we see it in hexadecimal notation.  Why?
 - why not do decimals?
 - because each hex number corresponds to 4 bits
    - decimal numbers don't align on bit boundaries
 - octal is also used for a similar reason
    - each digit is 3 bits

Now there are many kinds of machine code
 - that's what we are talking about when we say things like x86-64, ARMv7, etc

When we create a program in a compiled language such as C, most tool chains do the following:
  Source -> assembly -> object files -> complete binary (runnable program)

When looking at assembly
 - symbols starting with a . are assembler directives, they don't directly translate to machine code.
 - symbols ending with a : are memory locations that are referenced elsewhere
 - symbols starting with % refer to registers
   - fixed storage in the CPU that can actually be operated on
   - (data in memory can't be directly manipulated, it must be loaded into a register)
 - symbols starting with $ are constants (i.e., numbers)
 - in assembly language, functions get their arguments by looking into registers
   - which ones?  that is purely by convention, and it varies
   - if there are too many arguments, they go on the stack

We mostly don't work in assembly because it is
 - painful/overly verbose
 - minimal error checking (you can do whatever you want)
 - and is architecture specific
    - not so portable

But sometimes we HAVE to work in assembly
 - because that is how you can access low-level CPU and hardware functionality

Specifically, you HAVE to make system calls in assembly language, you can't make them directly in C or any other language

So, what is a system call?

2254   Emacs       |                      |
1122   Firefox     |    The Linux Kernel  |  The hardware
1111   ls          |                      | 

So, to a first approximation, the kernel is the part of the system that controls/manages the hardware, allows regular programs to run

Turns out that regular processe do run directly on the CPU
  - they are in machine code, compiled from another language
  - BUT, the CPU is running in a limited capability mode

The CPU runs in multiple modes
 - supervisor mode (ring 0 on Intel/AMD chips, x86)
    - CPU can access all resources
 - user mode (ring 3 on Intel/AMD chips, x86)
    - CPU can only access limited sets of resources

Firefox, Emacs run in user mode
the kernel runs in supervisor mode

A system call is a request from user mode to supervisor mode
  - looks like a function call in code, often
  - BUT, causes the CPU to switch modes, and thus in a system
    call it can do things that can't be done otherwise

If something is running in Ring 0/supervisor mode, it can access EVERYTHING
  - i.e., it can modify the kernel
  - so everything is at risk
  - and errors will crash your system

A "kernel panic" is equivalent to a segfault in userpsace (but in kernel space)
  - but is much more serious obviously because the kernel is potentially corrupted

What's different between user mode and supervisor mode?
 - RAM
   - user mode: limited
   - supervisor mode: all
 - network
   - user mode: sockets/defined routes
   - supervisor mode: can change network protocols, change how IP and TCP work
 - disk
   - user mode: files, directories, must obey file permissions
   - supervisor mode: disk blocks, filesystems (how files/directories are implemented), can ignore and in fact implements file permissions

Kernel allocates resources and implements abstractions on those resources

Because the CPU must change modes to run kernel code, you can't get make a system call through a simple function call (which just starts executing code at a different memory location)

If a process wants to get more RAM, make a thread, access files, the network, the screen, keyboard - it MUST deal with the kernel, and thus must make system calls

The set of all processes and regular programs on a system is called "userspace" to distinguish it from "kernel space", i.e., the stuff in the kernel

"breaking userspace" means the API provided by the kernel has changed in a way that makes programs break
 - normally kernels are developed so they don't change their API in ways that break existing programs.  If they want to do new things they add new interfaces (system calls, etc)

Different kernels can have very different number of system calls
 - microkernels have very few system calls
 - the Windows NT kernel and the Linux kernel have lots of system calls
    - and the number keeps growing

Windows has a system call interface, but it is private
 - programs make calls to the Win32/64 library, and it translates those into system calls in ways that may change between Windows releases

An OS kernel is the basic technology that allows us to run multiple programs on a system at the same time and not have them mess with each other too much.

"call" in assembly is a function call, it is NEVER a system call
  - BUT, it can be a call to a wrapper around a system call

For example, the read system call is invoked using the C library read() function.

In the C library, if you call a function defined in <unistd.h>, it is generally a thin wrapper around the corresponding system call

NOTE THAT there exist system calls that have no standard wrapper
 - regular programs shouldn't be calling it, apparently
 - There is a C function "syscall" that allows you to make such system calls.

So back to the tool chain
  Source -> assembly -> object files -> complete binary (runnable program)

The object file is just a translation of the assembly into machine code
To get a complete binary, ADDITIONAL CODE must be included
  - and by default, it is quite a bit of code
  - this code comes from the C library, implements all the things a C
    program expects to be there
      - standard functions
      - sets up the environment (code that runs before and after main())

Note that the default binary was much smaller but generated a lot more system calls, and the "-static" one was larger but generated fewer system calls

Basic idea
 - default is "dynamic", it loads most of the code at runtime from
   separate files
 - "static" binaries have all the code included, no need to load in
   other files (libraries) at runtime, they were added in at compile time
   (linked in)

 - we will discuss why next time



#include <stdio.h>

int main(int argc, char *argv[])
        printf("Hello COMP 3000!\n");
        return 0;


	.file	"class-hello.c"
	.section	.rodata
	.string	"Hello COMP 3000!"
	.globl	main
	.type	main, @function
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	%edi, -4(%rbp)
	movq	%rsi, -16(%rbp)
	leaq	.LC0(%rip), %rax
	movq	%rax, %rdi
	call	puts@PLT
	movl	$0, %eax
	.cfi_def_cfa 7, 8
	.size	main, .-main
	.ident	"GCC: (Ubuntu 11.2.0-19ubuntu1) 11.2.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
	.string	"GNU"
	.align 8
	.long	0xc0000002
	.long	3f - 2f
	.long	0x3
	.align 8