|
@@ -4,13 +4,13 @@ Kernel booting process. Part 2.
|
|
First steps in the kernel setup
|
|
First steps in the kernel setup
|
|
--------------------------------------------------------------------------------
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
-We started to dive into linux kernel insides in the previous [part](linux-bootstrap-1.md) and saw the initial part of the kernel setup code. We stopped at the first call to the `main` function (which is the first function written in C) from [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c).
|
|
|
|
|
|
+We started to dive into linux kernel insides in the previous [part](linux-bootstrap-1.md) and saw the initial part of the kernel setup code. We stopped at the first call to the `main` function (which is the first function written in C) from [arch/x86/boot/main.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/main.c).
|
|
|
|
|
|
-In this part we will continue to research the kernel setup code and
|
|
|
|
|
|
+In this part, we will continue to research the kernel setup code and
|
|
* see what `protected mode` is,
|
|
* see what `protected mode` is,
|
|
* some preparation for the transition into it,
|
|
* some preparation for the transition into it,
|
|
* the heap and console initialization,
|
|
* the heap and console initialization,
|
|
-* memory detection, cpu validation, keyboard initialization
|
|
|
|
|
|
+* memory detection, CPU validation, keyboard initialization
|
|
* and much much more.
|
|
* and much much more.
|
|
|
|
|
|
So, Let's go ahead.
|
|
So, Let's go ahead.
|
|
@@ -24,14 +24,14 @@ What is [protected mode](https://en.wikipedia.org/wiki/Protected_mode)? Protecte
|
|
|
|
|
|
The main reason to move away from [Real mode](http://wiki.osdev.org/Real_Mode) is that there is very limited access to the RAM. As you may remember from the previous part, there is only 2<sup>20</sup> bytes or 1 Megabyte, sometimes even only 640 Kilobytes of RAM available in the Real mode.
|
|
The main reason to move away from [Real mode](http://wiki.osdev.org/Real_Mode) is that there is very limited access to the RAM. As you may remember from the previous part, there is only 2<sup>20</sup> bytes or 1 Megabyte, sometimes even only 640 Kilobytes of RAM available in the Real mode.
|
|
|
|
|
|
-Protected mode brought many changes, but the main one is the difference in memory management. The 20-bit address bus was replaced with a 32-bit address bus. It allowed access to 4 Gigabytes of memory vs 1 Megabyte of real mode. Also [paging](http://en.wikipedia.org/wiki/Paging) support was added, which you can read about in the next sections.
|
|
|
|
|
|
+Protected mode brought many changes, but the main one is the difference in memory management. The 20-bit address bus was replaced with a 32-bit address bus. It allowed access to 4 Gigabytes of memory vs 1 Megabyte of real mode. Also, [paging](http://en.wikipedia.org/wiki/Paging) support was added, which you can read about in the next sections.
|
|
|
|
|
|
Memory management in Protected mode is divided into two, almost independent parts:
|
|
Memory management in Protected mode is divided into two, almost independent parts:
|
|
|
|
|
|
* Segmentation
|
|
* Segmentation
|
|
* Paging
|
|
* Paging
|
|
|
|
|
|
-Here we will only see segmentation. Paging will be discussed in the next sections.
|
|
|
|
|
|
+Here we will only see segmentation. Paging will be discussed in the next sections.
|
|
|
|
|
|
As you can read in the previous part, addresses consist of two parts in real mode:
|
|
As you can read in the previous part, addresses consist of two parts in real mode:
|
|
|
|
|
|
@@ -83,13 +83,13 @@ Don't worry, I know it looks a little scary after real mode, but it's easy. For
|
|
|
|
|
|
So, it means that if
|
|
So, it means that if
|
|
* if G is 0, Limit is interpreted in terms of 1 Byte and the maximum size of the segment can be 1 Megabyte.
|
|
* if G is 0, Limit is interpreted in terms of 1 Byte and the maximum size of the segment can be 1 Megabyte.
|
|
- * if G is 1, Limit is interpreted in terms of 4096 Bytes = 4 KBytes = 1 Page and the maximum size of the segment can be 4 Gigabytes. Actually when G is 1, the value of Limit is shifted to the left by 12 bits. So, 20 bits + 12 bits = 32 bits and 2<sup>32</sup> = 4 Gigabytes.
|
|
|
|
|
|
+ * if G is 1, Limit is interpreted in terms of 4096 Bytes = 4 KBytes = 1 Page and the maximum size of the segment can be 4 Gigabytes. Actually, when G is 1, the value of Limit is shifted to the left by 12 bits. So, 20 bits + 12 bits = 32 bits and 2<sup>32</sup> = 4 Gigabytes.
|
|
|
|
|
|
2. Base[32-bits] is at (0-15, 32-39 and 56-63 bits). It defines the physical address of the segment's starting location.
|
|
2. Base[32-bits] is at (0-15, 32-39 and 56-63 bits). It defines the physical address of the segment's starting location.
|
|
|
|
|
|
-3. Type/Attribute (40-47 bits) defines the type of segment and kinds of access to it.
|
|
|
|
|
|
+3. Type/Attribute (40-47 bits) defines the type of segment and kinds of access to it.
|
|
* `S` flag at bit 44 specifies descriptor type. If `S` is 0 then this segment is a system segment, whereas if `S` is 1 then this is a code or data segment (Stack segments are data segments which must be read/write segments).
|
|
* `S` flag at bit 44 specifies descriptor type. If `S` is 0 then this segment is a system segment, whereas if `S` is 1 then this is a code or data segment (Stack segments are data segments which must be read/write segments).
|
|
-
|
|
|
|
|
|
+
|
|
To determine if the segment is a code or data segment we can check its Ex(bit 43) Attribute marked as 0 in the above diagram. If it is 0, then the segment is a Data segment otherwise it is a code segment.
|
|
To determine if the segment is a code or data segment we can check its Ex(bit 43) Attribute marked as 0 in the above diagram. If it is 0, then the segment is a Data segment otherwise it is a code segment.
|
|
|
|
|
|
A segment can be of one of the following types:
|
|
A segment can be of one of the following types:
|
|
@@ -119,7 +119,7 @@ A segment can be of one of the following types:
|
|
```
|
|
```
|
|
|
|
|
|
As we can see the first bit(bit 43) is `0` for a _data_ segment and `1` for a _code_ segment. The next three bits (40, 41, 42) are either `EWA`(*E*xpansion *W*ritable *A*ccessible) or CRA(*C*onforming *R*eadable *A*ccessible).
|
|
As we can see the first bit(bit 43) is `0` for a _data_ segment and `1` for a _code_ segment. The next three bits (40, 41, 42) are either `EWA`(*E*xpansion *W*ritable *A*ccessible) or CRA(*C*onforming *R*eadable *A*ccessible).
|
|
- * if E(bit 42) is 0, expand up other wise expand down. Read more [here](http://www.sudleyplace.com/dpmione/expanddown.html).
|
|
|
|
|
|
+ * if E(bit 42) is 0, expand up otherwise expand down. Read more [here](http://www.sudleyplace.com/dpmione/expanddown.html).
|
|
* if W(bit 41)(for Data Segments) is 1, write access is allowed otherwise not. Note that read access is always allowed on data segments.
|
|
* if W(bit 41)(for Data Segments) is 1, write access is allowed otherwise not. Note that read access is always allowed on data segments.
|
|
* A(bit 40) - Whether the segment is accessed by processor or not.
|
|
* A(bit 40) - Whether the segment is accessed by processor or not.
|
|
* C(bit 43) is conforming bit(for code selectors). If C is 1, the segment code can be executed from a lower level privilege e.g. user level. If C is 0, it can only be executed from the same privilege level.
|
|
* C(bit 43) is conforming bit(for code selectors). If C is 1, the segment code can be executed from a lower level privilege e.g. user level. If C is 0, it can only be executed from the same privilege level.
|
|
@@ -131,7 +131,7 @@ As we can see the first bit(bit 43) is `0` for a _data_ segment and `1` for a _c
|
|
|
|
|
|
6. AVL flag(bit 52) - Available and reserved bits. It is ignored in Linux.
|
|
6. AVL flag(bit 52) - Available and reserved bits. It is ignored in Linux.
|
|
|
|
|
|
-7. L flag(bit 53) - indicates whether a code segment contains native 64-bit code. If 1 then the code segment executes in 64 bit mode.
|
|
|
|
|
|
+7. L flag(bit 53) - indicates whether a code segment contains native 64-bit code. If 1 then the code segment executes in 64-bit mode.
|
|
|
|
|
|
8. D/B flag(bit 54) - Default/Big flag represents the operand size i.e 16/32 bits. If it is set then 32 bit otherwise 16.
|
|
8. D/B flag(bit 54) - Default/Big flag represents the operand size i.e 16/32 bits. If it is set then 32 bit otherwise 16.
|
|
|
|
|
|
@@ -152,7 +152,7 @@ Where,
|
|
Every segment register has a visible and hidden part.
|
|
Every segment register has a visible and hidden part.
|
|
* Visible - Segment Selector is stored here
|
|
* Visible - Segment Selector is stored here
|
|
* Hidden - Segment Descriptor(base, limit, attributes, flags)
|
|
* Hidden - Segment Descriptor(base, limit, attributes, flags)
|
|
-
|
|
|
|
|
|
+
|
|
The following steps are needed to get the physical address in the protected mode:
|
|
The following steps are needed to get the physical address in the protected mode:
|
|
|
|
|
|
* The segment selector must be loaded in one of the segment registers
|
|
* The segment selector must be loaded in one of the segment registers
|
|
@@ -205,7 +205,7 @@ GLOBAL(memcpy)
|
|
ENDPROC(memcpy)
|
|
ENDPROC(memcpy)
|
|
```
|
|
```
|
|
|
|
|
|
-Yeah, we just moved to C code and now assembly again :) First of all we can see that `memcpy` and other routines which are defined here, start and end with the two macros: `GLOBAL` and `ENDPROC`. `GLOBAL` is described in [arch/x86/include/asm/linkage.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/linkage.h) which defines `globl` directive and the label for it. `ENDPROC` is described in [include/linux/linkage.h](https://github.com/torvalds/linux/blob/master/include/linux/linkage.h) which marks the `name` symbol as a function name and ends with the size of the `name` symbol.
|
|
|
|
|
|
+Yeah, we just moved to C code and now assembly again :) First of all, we can see that `memcpy` and other routines which are defined here, start and end with the two macros: `GLOBAL` and `ENDPROC`. `GLOBAL` is described in [arch/x86/include/asm/linkage.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/linkage.h) which defines `globl` directive and the label for it. `ENDPROC` is described in [include/linux/linkage.h](https://github.com/torvalds/linux/blob/master/include/linux/linkage.h) which marks the `name` symbol as a function name and ends with the size of the `name` symbol.
|
|
|
|
|
|
Implementation of `memcpy` is easy. At first, it pushes values from the `si` and `di` registers to the stack to preserve their values because they will change during the `memcpy`. `memcpy` (and other functions in copy.S) use `fastcall` calling conventions. So it gets its incoming parameters from the `ax`, `dx` and `cx` registers. Calling `memcpy` looks like this:
|
|
Implementation of `memcpy` is easy. At first, it pushes values from the `si` and `di` registers to the stack to preserve their values because they will change during the `memcpy`. `memcpy` (and other functions in copy.S) use `fastcall` calling conventions. So it gets its incoming parameters from the `ax`, `dx` and `cx` registers. Calling `memcpy` looks like this:
|
|
|
|
|
|
@@ -218,14 +218,14 @@ So,
|
|
* `dx` will contain the address of `hdr`
|
|
* `dx` will contain the address of `hdr`
|
|
* `cx` will contain the size of `hdr` in bytes.
|
|
* `cx` will contain the size of `hdr` in bytes.
|
|
|
|
|
|
-`memcpy` puts the address of `boot_params.hdr` into `di` and saves the size on the stack. After this it shifts to the right on 2 size (or divide on 4) and copies from `si` to `di` by 4 bytes. After this we restore the size of `hdr` again, align it by 4 bytes and copy the rest of the bytes from `si` to `di` byte by byte (if there is more). Restore `si` and `di` values from the stack in the end and after this copying is finished.
|
|
|
|
|
|
+`memcpy` puts the address of `boot_params.hdr` into `di` and saves the size on the stack. After this it shifts to the right on 2 size (or divide on 4) and copies from `si` to `di` by 4 bytes. After this, we restore the size of `hdr` again, align it by 4 bytes and copy the rest of the bytes from `si` to `di` byte by byte (if there is more). Restore `si` and `di` values from the stack in the end and after this copying is finished.
|
|
|
|
|
|
Console initialization
|
|
Console initialization
|
|
--------------------------------------------------------------------------------
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
After `hdr` is copied into `boot_params.hdr`, the next step is console initialization by calling the `console_init` function which is defined in [arch/x86/boot/early_serial_console.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/early_serial_console.c).
|
|
After `hdr` is copied into `boot_params.hdr`, the next step is console initialization by calling the `console_init` function which is defined in [arch/x86/boot/early_serial_console.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/early_serial_console.c).
|
|
|
|
|
|
-It tries to find the `earlyprintk` option in the command line and if the search was successful, it parses the port address and baud rate of the serial port and initializes the serial port. Value of `earlyprintk` command line option can be one of these:
|
|
|
|
|
|
+It tries to find the `earlyprintk` option in the command line and if the search was successful, it parses the port address and baud rate of the serial port and initializes the serial port. The value of `earlyprintk` command line option can be one of these:
|
|
|
|
|
|
* serial,0x3f8,115200
|
|
* serial,0x3f8,115200
|
|
* serial,ttyS0,115200
|
|
* serial,ttyS0,115200
|
|
@@ -334,7 +334,7 @@ Then there is the `heap_end` calculation:
|
|
```
|
|
```
|
|
which means `heap_end_ptr` or `_end` + `512`(`0x200h`). The last check is whether `heap_end` is greater than `stack_end`. If it is then `stack_end` is assigned to `heap_end` to make them equal.
|
|
which means `heap_end_ptr` or `_end` + `512`(`0x200h`). The last check is whether `heap_end` is greater than `stack_end`. If it is then `stack_end` is assigned to `heap_end` to make them equal.
|
|
|
|
|
|
-Now the heap is initialized and we can use it using the `GET_HEAP` method. We will see how it is used, how to use it and how the it is implemented in the next posts.
|
|
|
|
|
|
+Now the heap is initialized and we can use it using the `GET_HEAP` method. We will see how it is used, how to use it and how it is implemented in the next posts.
|
|
|
|
|
|
CPU validation
|
|
CPU validation
|
|
--------------------------------------------------------------------------------
|
|
--------------------------------------------------------------------------------
|
|
@@ -349,12 +349,12 @@ if (cpu_level < req_level) {
|
|
return -1;
|
|
return -1;
|
|
}
|
|
}
|
|
```
|
|
```
|
|
-`check_cpu` checks the cpu's flags, presence of [long mode](http://en.wikipedia.org/wiki/Long_mode) in case of x86_64(64-bit) CPU, checks the processor's vendor and makes preparation for certain vendors like turning off SSE+SSE2 for AMD if they are missing, etc.
|
|
|
|
|
|
+`check_cpu` checks the CPU's flags, the presence of [long mode](http://en.wikipedia.org/wiki/Long_mode) in the case of x86_64(64-bit) CPU, checks the processor's vendor and makes preparation for certain vendors like turning off SSE+SSE2 for AMD if they are missing, etc.
|
|
|
|
|
|
Memory detection
|
|
Memory detection
|
|
--------------------------------------------------------------------------------
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
-The next step is memory detection by the `detect_memory` function. `detect_memory` basically provides a map of available RAM to the cpu. It uses different programming interfaces for memory detection like `0xe820`, `0xe801` and `0x88`. We will see only the implementation of **0xE820** here.
|
|
|
|
|
|
+The next step is memory detection by the `detect_memory` function. `detect_memory` basically provides a map of available RAM to the CPU. It uses different programming interfaces for memory detection like `0xe820`, `0xe801` and `0x88`. We will see only the implementation of **0xE820** here.
|
|
|
|
|
|
Let's look into the `detect_memory_e820` implementation from the [arch/x86/boot/memory.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/memory.c) source file. First of all, the `detect_memory_e820` function initializes the `biosregs` structure as we saw above and fills registers with special values for the `0xe820` call:
|
|
Let's look into the `detect_memory_e820` implementation from the [arch/x86/boot/memory.c](https://github.com/torvalds/linux/blob/master/arch/x86/boot/memory.c) source file. First of all, the `detect_memory_e820` function initializes the `biosregs` structure as we saw above and fills registers with special values for the `0xe820` call:
|
|
|
|
|
|
@@ -416,7 +416,7 @@ After this it calls [0x16](http://www.ctyme.com/intr/rb-1757.htm) again to set r
|
|
Querying
|
|
Querying
|
|
--------------------------------------------------------------------------------
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
-The next couple of steps are queries for different parameters. We will not dive into details about these queries, but will get back to it in later parts. Let's take a short look at these functions:
|
|
|
|
|
|
+The next couple of steps are queries for different parameters. We will not dive into details about these queries but will get back to it in later parts. Let's take a short look at these functions:
|
|
|
|
|
|
The [query_mca](https://github.com/torvalds/linux/blob/master/arch/x86/boot/mca.c#L18) routine calls the [0x15](http://www.ctyme.com/intr/rb-1594.htm) BIOS interrupt to get the machine model number, sub-model number, BIOS revision level, and other hardware-specific attributes:
|
|
The [query_mca](https://github.com/torvalds/linux/blob/master/arch/x86/boot/mca.c#L18) routine calls the [0x15](http://www.ctyme.com/intr/rb-1594.htm) BIOS interrupt to get the machine model number, sub-model number, BIOS revision level, and other hardware-specific attributes:
|
|
|
|
|
|
@@ -444,7 +444,7 @@ int query_mca(void)
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
-It fills the `ah` register with `0xc0` and calls the `0x15` BIOS interruption. After the interrupt execution it checks the [carry flag](http://en.wikipedia.org/wiki/Carry_flag) and if it is set to 1, the BIOS doesn't support [**MCA**](https://en.wikipedia.org/wiki/Micro_Channel_architecture). If carry flag is set to 0, `ES:BX` will contain a pointer to the system information table, which looks like this:
|
|
|
|
|
|
+It fills the `ah` register with `0xc0` and calls the `0x15` BIOS interruption. After the interrupt execution it checks the [carry flag](http://en.wikipedia.org/wiki/Carry_flag) and if it is set to 1, the BIOS doesn't support [**MCA**](https://en.wikipedia.org/wiki/Micro_Channel_architecture). If carry flag is set to 0, `ES:BX` will contain a pointer to the system information table, which looks like this:
|
|
|
|
|
|
```
|
|
```
|
|
Offset Size Description
|
|
Offset Size Description
|
|
@@ -473,7 +473,7 @@ Offset Size Description
|
|
13h 3 BYTEs "JPN"
|
|
13h 3 BYTEs "JPN"
|
|
```
|
|
```
|
|
|
|
|
|
-Next we call the `set_fs` routine and pass the value of the `es` register to it. The implementation of `set_fs` is pretty simple:
|
|
|
|
|
|
+Next, we call the `set_fs` routine and pass the value of the `es` register to it. The implementation of `set_fs` is pretty simple:
|
|
|
|
|
|
```c
|
|
```c
|
|
static inline void set_fs(u16 seg)
|
|
static inline void set_fs(u16 seg)
|
|
@@ -486,11 +486,11 @@ This function contains inline assembly which gets the value of the `seg` paramet
|
|
|
|
|
|
At the end of `query_mca` it just copies the table pointed to by `es:bx` to the `boot_params.sys_desc_table`.
|
|
At the end of `query_mca` it just copies the table pointed to by `es:bx` to the `boot_params.sys_desc_table`.
|
|
|
|
|
|
-The next step is getting [Intel SpeedStep](http://en.wikipedia.org/wiki/SpeedStep) information by calling the `query_ist` function. First of all it checks the CPU level and if it is correct, calls `0x15` for getting info and saves the result to `boot_params`.
|
|
|
|
|
|
+The next step is getting [Intel SpeedStep](http://en.wikipedia.org/wiki/SpeedStep) information by calling the `query_ist` function. First of all, it checks the CPU level and if it is correct, calls `0x15` for getting info and saves the result to `boot_params`.
|
|
|
|
|
|
The following [query_apm_bios](https://github.com/torvalds/linux/blob/master/arch/x86/boot/apm.c#L21) function gets [Advanced Power Management](http://en.wikipedia.org/wiki/Advanced_Power_Management) information from the BIOS. `query_apm_bios` calls the `0x15` BIOS interruption too, but with `ah` = `0x53` to check `APM` installation. After the `0x15` execution, `query_apm_bios` functions check the `PM` signature (it must be `0x504d`), carry flag (it must be 0 if `APM` supported) and value of the `cx` register (if it's 0x02, protected mode interface is supported).
|
|
The following [query_apm_bios](https://github.com/torvalds/linux/blob/master/arch/x86/boot/apm.c#L21) function gets [Advanced Power Management](http://en.wikipedia.org/wiki/Advanced_Power_Management) information from the BIOS. `query_apm_bios` calls the `0x15` BIOS interruption too, but with `ah` = `0x53` to check `APM` installation. After the `0x15` execution, `query_apm_bios` functions check the `PM` signature (it must be `0x504d`), carry flag (it must be 0 if `APM` supported) and value of the `cx` register (if it's 0x02, protected mode interface is supported).
|
|
|
|
|
|
-Next it calls `0x15` again, but with `ax = 0x5304` for disconnecting the `APM` interface and connecting the 32-bit protected mode interface. In the end it fills `boot_params.apm_bios_info` with values obtained from the BIOS.
|
|
|
|
|
|
+Next, it calls `0x15` again, but with `ax = 0x5304` for disconnecting the `APM` interface and connecting the 32-bit protected mode interface. In the end, it fills `boot_params.apm_bios_info` with values obtained from the BIOS.
|
|
|
|
|
|
Note that `query_apm_bios` will be executed only if `CONFIG_APM` or `CONFIG_APM_MODULE` was set in the configuration file:
|
|
Note that `query_apm_bios` will be executed only if `CONFIG_APM` or `CONFIG_APM_MODULE` was set in the configuration file:
|
|
|
|
|
|
@@ -502,7 +502,7 @@ Note that `query_apm_bios` will be executed only if `CONFIG_APM` or `CONFIG_APM_
|
|
|
|
|
|
The last is the [`query_edd`](https://github.com/torvalds/linux/blob/master/arch/x86/boot/edd.c#L122) function, which queries `Enhanced Disk Drive` information from the BIOS. Let's look into the `query_edd` implementation.
|
|
The last is the [`query_edd`](https://github.com/torvalds/linux/blob/master/arch/x86/boot/edd.c#L122) function, which queries `Enhanced Disk Drive` information from the BIOS. Let's look into the `query_edd` implementation.
|
|
|
|
|
|
-First of all it reads the [edd](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt#L1023) option from the kernel's command line and if it was set to `off` then `query_edd` just returns.
|
|
|
|
|
|
+First of all, it reads the [edd](https://github.com/torvalds/linux/blob/master/Documentation/kernel-parameters.txt#L1023) option from the kernel's command line and if it was set to `off` then `query_edd` just returns.
|
|
|
|
|
|
If EDD is enabled, `query_edd` goes over BIOS-supported hard disks and queries EDD information in the following loop:
|
|
If EDD is enabled, `query_edd` goes over BIOS-supported hard disks and queries EDD information in the following loop:
|
|
|
|
|
|
@@ -515,7 +515,7 @@ for (devno = 0x80; devno < 0x80+EDD_MBR_SIG_MAX; devno++) {
|
|
}
|
|
}
|
|
...
|
|
...
|
|
...
|
|
...
|
|
- ...
|
|
|
|
|
|
+ ...
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
@@ -524,7 +524,7 @@ where `0x80` is the first hard drive and the value of `EDD_MBR_SIG_MAX` macro is
|
|
Conclusion
|
|
Conclusion
|
|
--------------------------------------------------------------------------------
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
-This is the end of the second part about Linux kernel insides. In the next part we will see video mode setting and the rest of preparations before transition to protected mode and directly transitioning into it.
|
|
|
|
|
|
+This is the end of the second part about Linux kernel insides. In the next part, we will see video mode setting and the rest of preparations before the transition to protected mode and directly transitioning into it.
|
|
|
|
|
|
If you have any questions or suggestions write me a comment or ping me at [twitter](https://twitter.com/0xAX).
|
|
If you have any questions or suggestions write me a comment or ping me at [twitter](https://twitter.com/0xAX).
|
|
|
|
|
|
@@ -546,4 +546,3 @@ Links
|
|
* [EDD specification](http://www.t13.org/documents/UploadedDocuments/docs2004/d1572r3-EDD3.pdf)
|
|
* [EDD specification](http://www.t13.org/documents/UploadedDocuments/docs2004/d1572r3-EDD3.pdf)
|
|
* [TLDP documentation for Linux Boot Process](http://www.tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/setup.html) (old)
|
|
* [TLDP documentation for Linux Boot Process](http://www.tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/setup.html) (old)
|
|
* [Previous Part](linux-bootstrap-1.md)
|
|
* [Previous Part](linux-bootstrap-1.md)
|
|
-
|
|
|