00:00:00
[Music]
00:00:04
hello and welcome in this tutorial i'll
00:00:08
show you what it takes to build an
00:00:10
operating system from scratch so let's
00:00:13
jump straight into it first let's see
00:00:16
what tools we will need for editing text
00:00:18
any text editor will do I will use the
00:00:21
micro editor which I really like because
00:00:22
it uses common keyboard shortcuts but
00:00:24
you can use anything you like we will
00:00:26
use make to build our project NASM will
00:00:28
be our assembler which we will use to
00:00:30
assemble our assembly code we will also
00:00:33
need some virtualization software i will
00:00:35
use camera but you can use any
00:00:36
virtualization software you like such as
00:00:38
VirtualBox VMware or anything else I
00:00:41
will be using Ubuntu in this tutorial if
00:00:43
you want to follow on Windows your best
00:00:46
option would be to use the windows
00:00:47
subsystem for Linux you can find a
00:00:50
tutorial on how to set that up
00:00:51
in the video description the second
00:00:53
option would be cygwin which has most of
00:00:55
the tools that we need on mac OS you
00:00:58
shouldn't have any trouble finding these
00:00:59
tools using homebrew let's create a
00:01:02
source folder in our project and inside
00:01:04
we'll make a file called main dot ASM
00:01:08
the first part of our operating system
00:01:10
will be written in a programming
00:01:12
language called assembly later we will
00:01:14
be using C but right now we don't really
00:01:17
have a choice and we really have to use
00:01:19
assembly so what exactly is this
00:01:22
assembly thing shortly the assembly
00:01:25
language is the human readable
00:01:26
interpretation of machine code when you
00:01:29
compile a program in a programming
00:01:31
language such as C or C++ it gets
00:01:34
converted into machine code which is the
00:01:36
language that the processor understands
00:01:39
assembly makes it easier for us humans
00:01:42
to read and write machine code higher
00:01:45
level programming languages such as C or
00:01:48
C++ need to be translated by the
00:01:50
compiler into machine code this involves
00:01:53
many steps like building an abstract
00:01:55
syntax tree and a huge amount of
00:01:56
optimization assembly is much simpler
00:01:59
because the instructions simply need to
00:02:02
be converted into their machine code
00:02:03
representation by a tool called an
00:02:05
assembler an unsavoury instruction is
00:02:08
made up of a mnemonic or a keyword and
00:02:12
the number of parameters which are
00:02:14
called operands typically zero one or
00:02:16
two operands an important thing I'd like
00:02:19
to mention about the assembly language
00:02:21
is that there are differences between
00:02:23
different processors the instructions
00:02:26
supported on an x86 processor that you
00:02:28
find in laptops and desktops are
00:02:30
different than instructions supported on
00:02:33
an arm CPU which is found in smartphones
00:02:35
or tablets even different processors
00:02:38
with the same architecture might have
00:02:40
differences for example SSC is a feature
00:02:44
introduced in the Pentium 3 processor
00:02:46
line that didn't exist in the Pentium 2
00:02:48
line however newer processors easily
00:02:51
keep backwards compatibility so that all
00:02:54
the programs can still run without any
00:02:56
modification backwards compatibility for
00:02:59
the x86 architecture goes so far back
00:03:02
that it can still run software on a
00:03:04
modern computer that was designed for
00:03:06
the 8086 CPU the first CPU ever made
00:03:09
using the x86 architecture which was at
00:03:12
least 40 years ago so to be clear we
00:03:17
will be writing our operating system for
00:03:18
the x86 architecture we
00:03:21
means that we'll be using the x86
00:03:23
assembly language
00:03:27
so what happens when you pour on your
00:03:31
computer first the bio sticks in
00:03:34
performing all sorts of tests showing a
00:03:36
fancy logo and then the part that's the
00:03:39
most important to us it stars the
00:03:41
operating system so how does it do that
00:03:45
there are actually two ways in which the
00:03:48
BIOS can load an operating system in the
00:03:51
first method which is now called legacy
00:03:53
booting the BIOS loads the first block
00:03:55
of data or the first sector from each
00:03:58
boot device into memory until it finds a
00:04:00
certain signature once it finds that
00:04:02
signature it jumps to the first
00:04:04
instruction in the loaded block and this
00:04:07
is where our operating system starts the
00:04:10
second method is called efi which works
00:04:13
a bit differently in this mode the bios
00:04:16
looks for a certain efi partition on
00:04:18
each device which contains on special
00:04:20
efi programs for the moment we won't be
00:04:24
covering efi will only look at legacy
00:04:26
mode now that we know how the bios loads
00:04:29
our operating system here's what we need
00:04:31
to do we will write some code assemble
00:04:34
it and then we will put it in the first
00:04:36
sector of a floppy disk we also need to
00:04:39
somehow add that signature that the bios
00:04:42
requires after that we can test our
00:04:44
operating system so let's begin coding
00:04:48
we know that the bios always puts our
00:04:51
operating system at address 7000 so the
00:04:54
first thing we need to do is give our
00:04:56
sender this information this is done
00:04:58
using the org directive which tells the
00:05:01
assembler to calculate all memory offset
00:05:03
starting at 7000 changing this line to
00:05:07
another number won't make the bios load
00:05:09
a different address it will only tell
00:05:12
the assembler that the variables and
00:05:14
labels from our code should be
00:05:16
calculated with the offset 7000 before
00:05:20
we continue I need to explain the
00:05:21
difference between a directive and an
00:05:23
instruction a directive is a way of
00:05:26
giving the assembler a clue about how to
00:05:28
interpret our code when instruction is
00:05:31
translated into a machine code
00:05:33
instruction a directive won't get
00:05:35
translated it is only giving a clue to
00:05:38
the assembler
00:05:39
next we tell our assembler to emit
00:05:42
16-bit code as I mentioned before any
00:05:45
x86 CPU must be backwards compatible
00:05:48
with the original 8086 CPU so if an
00:05:51
operating system that was designed for
00:05:53
the 8086 is run on a modern CPU it still
00:05:56
needs to think that it's running on an
00:05:58
8086 because of this the CPU always
00:06:02
starts in 16-bit mode bits is also a
00:06:06
directive which tells the assembler to
00:06:08
emit 16-bit code writing bits 32 won't
00:06:12
make the processor running 32-bit mode
00:06:14
it is only directive which tells the
00:06:17
assembler to emit 32-bit code now I'll
00:06:20
define the main labels to mark where our
00:06:22
code begins for now we just want to note
00:06:25
that the BIOS loads our operating system
00:06:28
correctly so I'll only write a halt
00:06:31
instruction which holds the processor in
00:06:33
certain cases the CPU can start
00:06:36
executing again so I'll just create
00:06:38
another hold label and then jump to it
00:06:40
this way if the CPU ever starts again it
00:06:43
will be stuck in an infinite loop it's
00:06:46
not a good idea to allow the processor
00:06:48
to continue executing beyond the end of
00:06:50
our program our program is almost done
00:06:52
all that's left to do is add that
00:06:54
signature that the BIOS requires the
00:06:56
BIOS expects that the last two bytes of
00:06:59
the first sector are a a 5 5 we will be
00:07:03
putting our program on a standard 1.44
00:07:06
megabytes floppy disk where one sector
00:07:08
has 512 bytes the BIOS requires that the
00:07:12
last 2 bytes of the first sector are a a
00:07:15
5 5 we can ask Nasim to emit bytes
00:07:19
directly by using the DB directive which
00:07:22
stands for declare constant byte the
00:07:26
times directive can be used to repeat
00:07:27
instructions or data here we use it to
00:07:30
pad our program so that it fills up to
00:07:33
510 bytes after which we declare the two
00:07:36
byte signature in NASM the dollar sign
00:07:40
can be used to obtain the assembly
00:07:41
position of the beginning of the current
00:07:43
line and the double dollar sign gives us
00:07:46
the position of the beginning of the
00:07:48
current section in our case dollar -
00:07:52
dollar
00:07:53
other is about the length of the program
00:07:55
so far measured in bytes finally we
00:07:58
declared a signature DW is a directive
00:08:02
similar to DB but it declares the two
00:08:04
byte constant which is generally
00:08:06
referred to as a word
00:08:07
with this we have successfully written
00:08:10
our first operating system so far it
00:08:12
doesn't really do anything but stop the
00:08:14
processor let's test it if it works I
00:08:17
created a bill directory to keep things
00:08:19
organized for building the project I
00:08:21
created a make file I added a rule to
00:08:25
build the main dot ASM code using NASM
00:08:28
an output in a binary format
00:08:38
I added a rule to build
00:08:41
Kimmage where I simply took the binary
00:08:43
file previously built and padded it with
00:08:46
zeros until it has 1.44 megabytes
00:08:52
finally we can test our little operating
00:08:55
system you can use any virtualization
00:08:57
software you want such as VirtualBox VM
00:09:00
where I use camel because it's really
00:09:02
easy to setup and it can be used from a
00:09:04
command line as you can see the system
00:09:07
boots from floppy and then it does
00:09:08
nothing exactly as we expected so far
00:09:11
our operating system does nothing and
00:09:13
does it perfectly now that we know it
00:09:16
works let's go back to the code and
00:09:18
print a hello world message to screen
00:09:20
before I start explaining how you can do
00:09:22
that I need to explain some basic
00:09:24
concepts about the x86 architectures
00:09:28
all processors have a number of
00:09:30
registers which are really small pieces
00:09:32
of memory that can be written and read
00:09:34
very fast and are built into the CPU
00:09:37
here's a diagram of holder registers on
00:09:40
an x86 CPU there are several types of
00:09:43
registers the general-purpose registers
00:09:45
can be used for almost any purpose the
00:09:48
index registers are usually used for
00:09:51
keeping indices and pointers they can
00:09:53
also be used for other purposes the
00:09:56
program counter is a special register
00:09:58
which keeps track of which memory
00:10:00
location the current instruction begins
00:10:01
the segment registers are used to keep
00:10:04
track of the currently active memory
00:10:06
segments which I will explain in just a
00:10:09
moment there is also a Flags register
00:10:11
which contains some special flags which
00:10:14
are set by various instructions there
00:10:16
are a few more special purpose registers
00:10:17
but will only introduce them when we
00:10:19
need them now we'll talk a bit about RAM
00:10:23
memory the 8086 CPU had a 20-bit address
00:10:27
bus this meant that you could access up
00:10:30
to 2 to the power of 20 which means
00:10:33
about one megabyte of memory at the time
00:10:36
typical computers had around 64 to 128
00:10:39
kilobytes of memory so the engineers at
00:10:42
Intel thought this limit was huge for
00:10:44
various reasons they decided to use a
00:10:47
segment and offset addressing scheme for
00:10:49
memory in this scheme you address memory
00:10:52
by using two 16-bit values the segment
00:10:55
and the offset each segment contains 64
00:10:59
kilobytes of memory where each byte can
00:11:01
be accessed using the offset value
00:11:03
segments overlap every 16 bytes this
00:11:07
means that you can convert a segment
00:11:08
offset address to an absolute address by
00:11:11
shifting the segment four bits to the
00:11:13
left were multiplying it by 16 and then
00:11:17
adding the offset this also means that
00:11:20
there are multiple ways of addressing
00:11:21
the same location in memory for example
00:11:24
the absolute address 7000 which is where
00:11:28
the BIOS flows our operating system can
00:11:30
be written as any combination that you
00:11:32
can see on the screen there are some
00:11:35
special registers which are used to
00:11:37
specify the actively used segments CS
00:11:40
contain
00:11:41
the code segment which is the segment
00:11:43
the processor executes code from the IP
00:11:46
or program counter register only gives
00:11:48
us the offset the D s and D s registers
00:11:52
are data segments newer processors
00:11:54
introduced additional data segments FS
00:11:57
and GS SS contains the current stack
00:12:00
register in order to access the outside
00:12:03
one of these active statements we need
00:12:05
to load that Simon into one of these
00:12:07
registers the code segment can only be
00:12:09
modified by performing a jump now how do
00:12:13
you reference a memory location from
00:12:15
assembly you use this syntax a segment
00:12:18
register followed by a colon followed by
00:12:20
an expression which gives the offset put
00:12:22
between in brackets the segment register
00:12:24
can be omitted in which case the DSL
00:12:26
register will be used the processor is
00:12:29
capable of doing some arithmetic for us
00:12:31
as long as we use this expression the
00:12:33
base and index operands can be any
00:12:35
general-purpose processor registers in
00:12:37
16-bit mode there are a few limitations
00:12:40
however only B P and B X can be used as
00:12:43
base registers and only Si and di can be
00:12:46
used as index registers these
00:12:49
limitations exist because of how the
00:12:51
8086 CPU was originally designed where
00:12:54
they had to put such limitations to keep
00:12:56
the complexity down another example of
00:12:59
one such limitation is that we can't
00:13:01
write constants to the segment registers
00:13:03
directly we have to use an intermediary
00:13:05
register with the introduction of the
00:13:08
386 processor just a few years later
00:13:10
32-bit mode was introduced which pretty
00:13:13
much rendered 16-bit mode obsolete a lot
00:13:17
of newer cpu features were simply not
00:13:18
added to the 16-bit mode because it is
00:13:21
absolute and it only exists for
00:13:23
backwards compatibility it is still
00:13:25
useful to learn because most of the
00:13:27
things that apply to a 16-bit mode apply
00:13:29
to a 32-bit or 62 bit mode and it is
00:13:32
much simpler its main use today is in
00:13:35
the startup sequence most operating
00:13:37
systems switch to 32 or 64-bit mode
00:13:40
immediately after starting up we will do
00:13:43
the same thing in a future video but we
00:13:45
can't just yet for now we are limited to
00:13:47
the first sector of a floppy disk that
00:13:49
is 512 bytes which is very little space
00:13:52
once we are able to load a
00:13:54
from the floppy disk we can do a lot
00:13:56
more all operating systems have to do
00:13:59
the same thing in order to boot but
00:14:00
until we get there let's get back to
00:14:03
referencing our memory locations so I
00:14:06
already talked about the base and index
00:14:09
operands the scale and displacement
00:14:11
operands are numerical constants the
00:14:13
scale can only be used in 32 and 64-bit
00:14:16
modes and it can only have a value of
00:14:19
one to four or eight the displacement
00:14:22
can be any signed integer constant all
00:14:25
the operands in a memory reference
00:14:26
expression are optional so you only have
00:14:29
to use whatever you need so here's an
00:14:32
example first I defined a label which
00:14:34
points to a word having the value 100
00:14:37
the first instruction puts the offset of
00:14:40
the label into the ax register the
00:14:43
second instruction puts the memory
00:14:45
contents per our label point set since
00:14:47
we didn't specify a segment register D s
00:14:50
is going to be used we haven't used the
00:14:52
base index or scale but only a constant
00:14:54
which is the offset for label points to
00:14:57
in assembly labels are simply constant
00:14:59
which points to a specific offset here's
00:15:02
a more complicated example where we want
00:15:04
to read this third element in an array
00:15:06
in this example we put the offset of the
00:15:09
array into BX and the index of the third
00:15:12
element in Si since we use zero-based
00:15:15
indexing the third element is array of
00:15:18
two and each element in the array is a
00:15:21
word which is a two bytes wide so we put
00:15:24
in si the value 4 you can see here that
00:15:28
we use the multiplication symbol the
00:15:30
assembler is capable of calculating the
00:15:32
result of constant expressions and
00:15:34
putting the result in the resulting
00:15:36
machine code however you can try to move
00:15:39
BX ax times 2 ax is not known at compile
00:15:43
time so it is not a constant for that
00:15:46
you have to be used the multiply
00:15:48
instruction referencing memory is the
00:15:51
only place where you can put registers
00:15:53
in an expression finally we put into ax
00:15:57
the third element in the array by
00:15:59
referencing the memory location at BX
00:16:02
plus si BX is our base register and si
00:16:06
is our
00:16:06
in this register now back to our
00:16:08
operating system the code segment
00:16:11
register has been set up for us by the
00:16:13
BIOS and it points to segment 0 there
00:16:16
are some biases out there which actually
00:16:18
jump to our code using a difference I
00:16:20
meant an offset such as segment 7c 0
00:16:23
offset 0 but the standard behavior is to
00:16:26
use segment 0 offset 7000 we don't know
00:16:30
if the data segment an extra segment are
00:16:32
properly initialized so this is what we
00:16:34
have to do next since we can't write a
00:16:37
constant directly to a segment register
00:16:39
we have to use an intermediary register
00:16:42
we will use a X the move instruction
00:16:45
copies a reader from the source on the
00:16:47
left side to the destination on the
00:16:50
right side we also set up the stack
00:16:53
segment to 0 and a stack pointer to the
00:16:56
beginning of our program so what exactly
00:16:58
is this stack basically the stack is a
00:17:03
piece of memory that we can accessed in
00:17:05
a first in first out manner using the
00:17:07
push and pop instructions the stack also
00:17:09
has a special purpose when using
00:17:11
functions when you call a function the
00:17:13
return address is added to the stack
00:17:15
when you return from a function the
00:17:17
processor will read the return address
00:17:19
from the stack and then jump to it
00:17:21
another thing to note about the stack is
00:17:23
that it grows downwards SP points to the
00:17:27
top of the stack when you push something
00:17:29
SP is decremented by the number of bytes
00:17:32
pushed and then the data is written to
00:17:34
memory this is why we set up the stack
00:17:36
to point to the start of our operating
00:17:37
system because it grows downwards if we
00:17:40
set it up to the end of our program it
00:17:43
would overwrite our program we don't
00:17:45
want that so we just put it somewhere
00:17:46
where it won't overwrite anything the
00:17:49
beginning of our operating system is a
00:17:50
pretty safe spot now we'll start coding
00:17:54
a Buddhist function which prints a
00:17:55
string to the screen always document
00:17:58
your assembly functions so our function
00:18:00
will receive a pointer to a string in
00:18:02
DSS i and it will print characters until
00:18:05
it encounters a null character because I
00:18:08
decided to write the function above main
00:18:10
I have to add a jump instruction above
00:18:12
so main is still the entry point to our
00:18:14
program first I push the registers that
00:18:17
I'm going to modify to the stack
00:18:19
after which we enter the main loop the
00:18:23
load SB instruction loads apart from the
00:18:26
address DSS I into the AL register and
00:18:29
then increments si next I wrote the loop
00:18:32
exit condition the or instruction
00:18:34
performs a bitwise or and store the
00:18:37
result in the left hand side operand in
00:18:39
this case al orange a value to itself
00:18:42
will modify the value at all but what it
00:18:45
will modify is the Flex register if
00:18:47
there is multi zero the zero flag will
00:18:50
be set the next instruction is the
00:18:52
conditional jump which jumps to the down
00:18:54
label if the zero flag is set so
00:18:57
essentially if the next character is
00:18:59
null we jump outside the loop there's
00:19:02
something I forgot when I recorded the
00:19:03
video or jump instruction to the loop
00:19:05
label so that the code will loop after
00:19:07
exiting the loop we pop the registers we
00:19:10
previously pushed in reverse order and
00:19:12
then we'll return from this function so
00:19:14
far our function takes a string iterates
00:19:17
every character until it encounters the
00:19:19
null character and then exits what's
00:19:21
left to do is to print the character to
00:19:23
the screen the way we can do that is
00:19:26
using the BIOS as the name suggests the
00:19:29
BIOS or the basic input/output system
00:19:31
does more than just start a system it
00:19:34
also provides some very basic functions
00:19:36
which allow us to do some very basic
00:19:37
stuff such as writing text to the screen
00:19:40
so how exactly do we call the BIOS to
00:19:43
print the character for us the answer is
00:19:45
that we use interrupts so what are
00:19:48
interrupt an interrupt is basically a
00:19:51
signal which makes the processor stop
00:19:53
whatever it is doing to handle that
00:19:55
event there are three possible ways of
00:19:58
triggering an interrupt the first way it
00:20:00
is through an exception an exception is
00:20:03
generated by the CPU if a critical error
00:20:06
is encountered and it cannot continue
00:20:08
executing for example dividing by zero
00:20:10
will trigger an interrupt operating
00:20:13
systems can use these interrupts to stop
00:20:15
the misbehaving process or to attempt to
00:20:17
restore it to working order hardware can
00:20:20
also trigger interrupts for example when
00:20:23
a key is pressed on the keyboard or when
00:20:25
the disk controller finished performing
00:20:26
enough synchronous read the third way in
00:20:29
which interrupts can be triggered is
00:20:31
through the int instruct
00:20:33
shun interrupts are numbered from 0 to
00:20:36
255 so the instruction requires a
00:20:39
parameter indicating the interrupt
00:20:41
number to trigger the BIOS install some
00:20:44
interrupt handlers for us so that we can
00:20:46
use its functionality
00:20:48
typically the BIOS reserves an interrupt
00:20:51
number for a category of functions and
00:20:53
the value in the aah register is used to
00:20:56
choose between the available functions
00:20:57
in that category to print text to the
00:21:01
screen we will need to call interrupts
00:21:03
10 hexadecimal which contains the video
00:21:06
services category by setting eh to 0 a
00:21:10
hexadecimal will call the right text in
00:21:13
teletype mode function here's a detailed
00:21:16
description of this function so what we
00:21:19
need to do in order to call this
00:21:20
function is to set the aah registered to
00:21:23
0e hexadecimal al to the ASCII character
00:21:27
that we want to print and B H to the
00:21:30
page number the build parameter is only
00:21:32
used in graphics mode so we can ignore
00:21:34
it because you're currently running in
00:21:36
text mode when I recorded the video I
00:21:39
forgot to set the page number to zero
00:21:41
after that we call interrupts one zero
00:21:45
hexadecimal finally let's add a string
00:21:48
containing the text hello world followed
00:21:50
by a new line
00:21:53
to add a new line you need to print both
00:21:56
the line feed and the carriage return
00:21:58
characters I created an awesome macro so
00:22:01
that I don't have to remember the hex
00:22:03
codes for these characters every time to
00:22:05
declare string we use the DB directive
00:22:08
which conveniently allows us to write as
00:22:10
many characters as we want all that left
00:22:13
to do is to set the SSI to address of
00:22:16
the string and then call the Podesta
00:22:19
let's now test our program
00:22:29
you
00:22:32
because I forgot to put that jump
00:22:34
instruction I only go to the age after
00:22:37
fixing the issue the message helloworld
00:22:39
is displayed great so we have
00:22:43
successfully written a tiny apartment
00:22:45
system which can print text to the
00:22:47
screen this was a lot of work and we
00:22:50
learned a lot of new stuff about how
00:22:51
computers work we'll continue the next
00:22:54
time when we will improve our assembly
00:22:56
skills and learn some new stuff by
00:22:58
extending our operating system to print
00:23:00
numbers to the screen after that we get
00:23:03
into the complex task of loading stuff
00:23:05
from the disk thank you for watching and
00:23:08
see you the next time bye bye
00:23:12
[Music]