kernel booting process
TRANSCRIPT
-
7/25/2019 Kernel Booting Process
1/16
Kernel booting process. Part 1.
From the bootloader to the kernel
If you have read my previous blog posts, you can see that sometime ago I
started to get involved with low-level programming. I wrote some posts about
x86_64 assembly programming for Linux. At the same time, I started to dive
into the Linux source code. I have a great interest in understanding how low-
level things work, how programs run on my computer, how they are located in
memory, how the kernel manages processes and memory, how the network
stack works at a low level and many many other things. So, I decided to write
yet another series of posts about the Linux kernel for x86_64.
Note that I'm not a professional kernel hacker and I don't write code for the
kernel at work. It's just a hobby. I just like low-level stuff, and it is interesting
for me to see how these things work. So if you notice anything confusing, or if
you have any questions/remarks, ping me on twitter 0xAX, drop me
an emailor just create an issue. I appreciate it. All posts will also be
accessible at linux-insidesand if you find something wrong with my English or
the post content, feel free to send a pull request.
Note that this isn't the official documentation, just learning and sharing
knowledge.
Required knowledge
Understanding C code
Understanding assembly code (AT&T syntax)
Anyway, if you just start to learn some tools, I will try to explain some parts
during this and the following posts. Ok, simple introduction finishes and now
we can start to dive into the kernel and low-level stuff.
-
7/25/2019 Kernel Booting Process
2/16
All code is actually for kernel - 3.18. If there are changes, I will update the
posts accordingly.
The Magic Power Button, What happens next?
Despite that this is a series of posts about the Linux kernel, we will not start
from the kernel code (at least not in this paragraph). Ok, you press the magic
power button on your laptop or desktop computer and it starts to work. After
the motherboard sends a signal to the power supply, the power supply
provides the computer with the proper amount of electricity. Once the
motherboard receives the power good signal, it tries to start the CPU. The
CPU resets all leftover data in its registers and sets up predefined values for
each of them.
80386and later CPUs define the following predefined data in CPU registers
after the computer resets:
!" $%&&&$ '( )*+*,-./ $%&$$$ '( 01)* $%&&&&$$$$
The processor starts working in real mode. Let's back up a little to try and
understand memory segmentation in this mode. Real mode is supported on all
x86-compatible processors, from the 8086all the way to the modern Intel 64-
bit CPUs. The 8086 processor has a 20-bit address bus, which means that it
could work with a 0-0x100000 address space (1 megabyte). But it only has
16-bit registers, and with 16-bit registers the maximum address is 2^16 - 1 or
0xffff (64 kilobytes). Memory segmentationis used to make use of all the
address space available. All memory is divided into small, fixed-size segments
of 65536 bytes, or 64 KB. Since we cannot address memory above 64 KB with
16 bit registers, an alternate method is devised. An address consists of two
parts: a segment selector which has an associated base address and an
offset from this base address. In real mode, the associated base address of a
segment selector is (*23*4- (*+*,-./ 5 67. Thus, to get a physical address in
memory, we need to multiply the segment selector part by 16 and add the
offset part:
-
7/25/2019 Kernel Booting Process
3/16
"89):,1+;
-
7/25/2019 Kernel Booting Process
4/16
(N'O!?P( Q JR?SO?" = $%&&&&&&&$T H = JR?SO?"T H/*)*- H @ Q
5CH/*)*-F H = 6U T VWONC$%$$FT X X
Now the BIOS starts: after initializing and checking the hardware, it needs to
find a bootable device. A boot order is stored in the BIOS configuration,controlling which devices the BIOS attempts to boot from. When attempting
to boot from a hard drive, the BIOS tries to find a boot sector. On hard drives
partitioned with an MBR partition layout, the boot sector is stored in the first
446 bytes of the first sector (which is 512 bytes). The final two bytes of the
first sector are $%UUand $%11, which signals the BIOS that this device is
bootable. For example:
T T P.-*@ -8:) *%13Y+* :) Z/:--*4 :4 !4-*+ ;))*30+9 )94-1% T [V!O( 67\ [?R]
$%^,$$\ 0..-@ 3.K 1+_ G`G 3.K 18_ $%$* 3.K 08_ $%$$ 3.K 0+_
$%$^ :4- $%6$ a3Y b -:3*) U6$MCbMbbF
-
7/25/2019 Kernel Booting Process
5/16
In this example we can see that the code will be executed in 16 bit real mode
and will start at 0x7c00 in memory. After starting it calls the 0x10interrupt
which just prints the `symbol. It fills the rest of the 510 bytes with zeros and
finishes with the two magic bytes $%11and $%UU.
You can see a binary dump of this with the .0a
-
7/25/2019 Kernel Booting Process
6/16
NOTE: As you can read above the CPU is in real mode. In real mode,
calculating the physical address in memory is done as follows:
"89):,1+; $%&&&&F G$%6$&&*&G
Where $%6$&&*&is equal to 6SV > 7EiV M 670. But a 8086processor, which is
the first processor with real mode, has a 20 bit address line and AjA$ =
6$EfU^7is 1MB. This means the actual memory available is 1MB.
General real mode's memory map is:
$%$$$$$$$$ M $%$$$$$hkk M R*1+ S.
-
7/25/2019 Kernel Booting Process
7/16
Now that the BIOS has chosen a boot device and transferred control to the
boot sector code, execution starts from boot.img. This code is very simple
due to the limited amount of space available, and contains a pointer which is
used to jump to the location of GRUB 2's core image. The core image beginswithdiskboot.img, which is usually stored immediately after the first sector in
the unused space before the first partition. The above code loads the rest of
the core image into memory, which contains GRUB 2's kernel and drivers for
handling filesystems. After loading the rest of the core image, it
executesgrub_main.
2/e0J31:4initializes the console, gets the base address for modules, sets the
root device, loads/parses the grub configuration file, loads modules etc. At theend of execution, 2/e0J31:4moves grub to normal
mode. 2/e0J4./31+J*%*,e-*(from 2/e0M,./*o4./31+o31:4H,) completes the last
preparation and shows a menu to select an operating system. When we select
one of the grub menu entries,2/e0J3*4eJ*%*,e-*J*4-/9runs, which executes
the grub 0..-command, booting the selected operating system.
As we can read in the kernel boot protocol, the bootloader must read and fill
some fields of the kernel setup header, which starts at $%$6&6offset from the
kernel setup code. The kernel headerarch/x86/boot/header.Sstarts from:
H2+.0+ 8
-
7/25/2019 Kernel Booting Process
8/16
r "/.-*,-*
r !o? 3*3./9 8.+* r $;$$$$ >MMMMMMMMMMMMMMMMMMMMMMMM> r
R*)*/K*< &./ V!?( r p*1K* 1) 3e,8 1) Y.)):0+* e4e)*< s
s r '.3314< +:4* r C'14 1+). 0* 0*+.Z -8* t>6$$$$ 31/nF
t>6$$$$ >MMMMMMMMMMMMMMMMMMMMMMMM> r (-1,no8*1Y r k./
e)* 09 -8* n*/4*+ /*1+M3.MMMMMMMMMMMMMMMMMMMMMMMM>
r i*/4*+ )*-eY r O8* n*/4*+ /*1+M3. r V..- +.1 t > ):q*.&Ci*/4*+V..-(*,-./F > 6
where tis the address of the kernel boot sector loaded. In my
case tis $%6$$$$, as we can see in a memory dump:
The bootloader has now loaded the Linux kernel into memory, filled the
header fields and jumped to it. Now we can move directly to the kernel setup
code.
-
7/25/2019 Kernel Booting Process
9/16
Start of Kernel Setup
Finally we are in the kernel. Technically the kernel hasn't run yet, we need to
set up the kernel, memory manager, process manager etc first. Kernel setup
execution starts from arch/x86/boot/header.Sat _start. It is a little strange at
first sight, as there are several instructions before it.
A Long time ago the Linux kernel had its own bootloader, but now if you run
for example:
d*3eM)9)-*3M%f7J7E K3+:4eqMhH6fM2*4*/:,
You will see:
-
7/25/2019 Kernel Booting Process
10/16
Actually 8*1
-
7/25/2019 Kernel Booting Process
11/16
It means that segment registers will have the following values after kernel
setup starts:
2) = &) = *) =
-
7/25/2019 Kernel Booting Process
12/16
Actually, almost all of the setup code is preparation for the C language
environment in real mode. The next stepis checking the ))register value and
making a correct stack if ))is wrong:
3.KZ w))_ w
-
7/25/2019 Kernel Booting Process
13/16
In the second scenario, ())!=
-
7/25/2019 Kernel Booting Process
14/16
When ';PJm(NJxN;"is not set, we just use a minimal stack from J*4
(O;'iJ(!vN:
BSS Setup
-
7/25/2019 Kernel Booting Process
15/16
The last two steps that need to happen before we can jump to the main C
code, are setting up the BSSarea and checking the "magic" signature. First,
signature checking:
,3Y+ b$%U1U111UU_ )*-eYJ):2 a4* )*-eYJ01