mirror of
https://github.com/0xAX/linux-insides.git
synced 2024-12-22 06:38:07 +00:00
Merge pull request #817 from renaudgermain/copyedit-theory
copyedit: theory chapter
This commit is contained in:
commit
7a51d543fc
@ -8,7 +8,7 @@ In the fifth [part](https://0xax.gitbook.io/linux-insides/summary/booting/linux-
|
|||||||
|
|
||||||
Yeah, there will be many different things, but many many and once again many work with **memory**.
|
Yeah, there will be many different things, but many many and once again many work with **memory**.
|
||||||
|
|
||||||
In my view, memory management is one of the most complex parts of the Linux kernel and in system programming in general. This is why we need to get acquainted with paging, before we proceed with the kernel initialization stuff.
|
In my view, memory management is one of the most complex parts of the Linux kernel and system programming in general. This is why we need to get acquainted with paging, before we proceed with the kernel initialization stuff.
|
||||||
|
|
||||||
`Paging` is a mechanism that translates a linear memory address to a physical address. If you have read the previous parts of this book, you may remember that we saw segmentation in real mode when physical addresses are calculated by shifting a segment register by four and adding an offset. We also saw segmentation in protected mode, where we used the descriptor tables and base addresses from descriptors with offsets to calculate the physical addresses. Now we will see paging in 64-bit mode.
|
`Paging` is a mechanism that translates a linear memory address to a physical address. If you have read the previous parts of this book, you may remember that we saw segmentation in real mode when physical addresses are calculated by shifting a segment register by four and adding an offset. We also saw segmentation in protected mode, where we used the descriptor tables and base addresses from descriptors with offsets to calculate the physical addresses. Now we will see paging in 64-bit mode.
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ There are three paging modes:
|
|||||||
* PAE paging;
|
* PAE paging;
|
||||||
* IA-32e paging.
|
* IA-32e paging.
|
||||||
|
|
||||||
We will only explain the last mode here. To enable the `IA-32e paging` paging mode we need to do following things:
|
We will only explain the last mode here. To enable the `IA-32e paging` paging mode we need to do the following things:
|
||||||
|
|
||||||
* set the `CR0.PG` bit;
|
* set the `CR0.PG` bit;
|
||||||
* set the `CR4.PAE` bit;
|
* set the `CR4.PAE` bit;
|
||||||
@ -211,7 +211,7 @@ Usually kernel's `.text` starts here with the `CONFIG_PHYSICAL_START` offset. We
|
|||||||
|
|
||||||
```
|
```
|
||||||
readelf -s vmlinux | grep ffffffff81000000
|
readelf -s vmlinux | grep ffffffff81000000
|
||||||
1: ffffffff81000000 0 SECTION LOCAL DEFAULT 1
|
1: ffffffff81000000 0 SECTION LOCAL DEFAULT 1
|
||||||
65099: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 _text
|
65099: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 _text
|
||||||
90766: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 startup_64
|
90766: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 startup_64
|
||||||
```
|
```
|
||||||
|
@ -13,7 +13,7 @@ Now let's have a closer look on these components.
|
|||||||
|
|
||||||
**ELF header**
|
**ELF header**
|
||||||
|
|
||||||
The ELF header is located at the beginning of the object file. Its main purpose is to locate all other parts of the object file. The File header contains the following fields:
|
The ELF header is located at the beginning of the object file. Its main purpose is to locate all other parts of the object file. The file header contains the following fields:
|
||||||
|
|
||||||
* ELF identification - array of bytes which helps identify the file as an ELF object file and also provides information about general object file characteristic;
|
* ELF identification - array of bytes which helps identify the file as an ELF object file and also provides information about general object file characteristic;
|
||||||
* Object file type - identifies the object file type. This field can describe that ELF file is a relocatable object file, an executable file, etc...;
|
* Object file type - identifies the object file type. This field can describe that ELF file is a relocatable object file, an executable file, etc...;
|
||||||
@ -109,12 +109,12 @@ The ELF object file also contains other fields/structures which you can find in
|
|||||||
vmlinux
|
vmlinux
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
`vmlinux` is also a relocatable ELF object file . We can take a look at it with the `readelf` util. First of all let's look at the header:
|
`vmlinux` is also a relocatable ELF object file . We can take a look at it with the `readelf` utility. First of all let's look at the header:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ readelf -h vmlinux
|
$ readelf -h vmlinux
|
||||||
ELF Header:
|
ELF Header:
|
||||||
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
|
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
|
||||||
Class: ELF64
|
Class: ELF64
|
||||||
Data: 2's complement, little endian
|
Data: 2's complement, little endian
|
||||||
Version: 1 (current)
|
Version: 1 (current)
|
||||||
@ -147,7 +147,7 @@ We can then look this address up in the `vmlinux` ELF object with:
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ readelf -s vmlinux | grep ffffffff81000000
|
$ readelf -s vmlinux | grep ffffffff81000000
|
||||||
1: ffffffff81000000 0 SECTION LOCAL DEFAULT 1
|
1: ffffffff81000000 0 SECTION LOCAL DEFAULT 1
|
||||||
65099: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 _text
|
65099: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 _text
|
||||||
90766: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 startup_64
|
90766: ffffffff81000000 0 NOTYPE GLOBAL DEFAULT 1 startup_64
|
||||||
```
|
```
|
||||||
@ -205,9 +205,9 @@ Program Headers:
|
|||||||
Segment Sections...
|
Segment Sections...
|
||||||
00 .text .notes __ex_table .rodata __bug_table .pci_fixup .builtin_fw
|
00 .text .notes __ex_table .rodata __bug_table .pci_fixup .builtin_fw
|
||||||
.tracedata __ksymtab __ksymtab_gpl __kcrctab __kcrctab_gpl
|
.tracedata __ksymtab __ksymtab_gpl __kcrctab __kcrctab_gpl
|
||||||
__ksymtab_strings __param __modver
|
__ksymtab_strings __param __modver
|
||||||
01 .data .vvar
|
01 .data .vvar
|
||||||
02 .data..percpu
|
02 .data..percpu
|
||||||
03 .init.text .init.data .x86_cpu_dev.init .altinstructions
|
03 .init.text .init.data .x86_cpu_dev.init .altinstructions
|
||||||
.altinstr_replacement .iommu_table .apicdrivers .exit.text
|
.altinstr_replacement .iommu_table .apicdrivers .exit.text
|
||||||
.smp_locks .data_nosave .bss .brk
|
.smp_locks .data_nosave .bss .brk
|
||||||
|
@ -57,7 +57,7 @@ __asm__ [volatile] [goto] (AssemblerTemplate
|
|||||||
[ : GotoLabels ]);
|
[ : GotoLabels ]);
|
||||||
```
|
```
|
||||||
|
|
||||||
All parameters which are marked with squared brackets are optional. You may notice that if we skip the optional parameters and the modifiers `volatile` and `goto` we obtain the `basic` form.
|
All parameters which are marked with squared brackets are optional. You may notice that if we skip the optional parameters and the modifiers `volatile` and `goto` we obtain the `basic` form.
|
||||||
|
|
||||||
Let's start to consider this in order. The first optional `qualifier` is `volatile`. This specifier tells the compiler that an assembly statement may produce `side effects`. In this case we need to prevent compiler optimizations related to the given assembly statement. In simple terms the `volatile` specifier instructs the compiler not to modify the statement and place it exactly where it was in the original code. As an example let's look at the following function from the [Linux kernel](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/desc.h):
|
Let's start to consider this in order. The first optional `qualifier` is `volatile`. This specifier tells the compiler that an assembly statement may produce `side effects`. In this case we need to prevent compiler optimizations related to the given assembly statement. In simple terms the `volatile` specifier instructs the compiler not to modify the statement and place it exactly where it was in the original code. As an example let's look at the following function from the [Linux kernel](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/desc.h):
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ These are input operands - variables `a` and `b`. We already know what the `r` q
|
|||||||
|
|
||||||
First of all our values `5` and `10` will be put at the stack and then these values will be moved to the two general purpose registers: `%rdx` and `%rax`.
|
First of all our values `5` and `10` will be put at the stack and then these values will be moved to the two general purpose registers: `%rdx` and `%rax`.
|
||||||
|
|
||||||
This way the `%rax` register is used for storing the value of the `b` as well as storing the result of the calculation. **NOTE** that I've used `gcc 6.3.1` version, so the resulted code of your compiler may differ.
|
This way the `%rax` register is used for storing the value of the `b` as well as storing the result of the calculation. **NOTE** that I've used `gcc 6.3.1` version, so the resulted code of your compiler may differ.
|
||||||
|
|
||||||
We have looked at input and output parameters of an inline assembly statement. Before we move on to other constraints supported by `gcc`, there is one remaining part of the inline assembly statement we have not discussed yet - `clobbers`.
|
We have looked at input and output parameters of an inline assembly statement. Before we move on to other constraints supported by `gcc`, there is one remaining part of the inline assembly statement we have not discussed yet - `clobbers`.
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ int main(void)
|
|||||||
{
|
{
|
||||||
unsigned long a[3] = {10000000000, 0, 1};
|
unsigned long a[3] = {10000000000, 0, 1};
|
||||||
unsigned long b = 5;
|
unsigned long b = 5;
|
||||||
|
|
||||||
__asm__ volatile("incq %0" :: "m" (a[0]));
|
__asm__ volatile("incq %0" :: "m" (a[0]));
|
||||||
|
|
||||||
printf("a[0] - b = %lu\n", a[0] - b);
|
printf("a[0] - b = %lu\n", a[0] - b);
|
||||||
@ -288,12 +288,12 @@ Now the result is correct. If we look at the assembly output again:
|
|||||||
```assembly
|
```assembly
|
||||||
00000000004004f6 <main>:
|
00000000004004f6 <main>:
|
||||||
400404: 48 b8 00 e4 0b 54 02 movabs $0x2540be400,%rax
|
400404: 48 b8 00 e4 0b 54 02 movabs $0x2540be400,%rax
|
||||||
40040b: 00 00 00
|
40040b: 00 00 00
|
||||||
40040e: 48 89 04 24 mov %rax,(%rsp)
|
40040e: 48 89 04 24 mov %rax,(%rsp)
|
||||||
400412: 48 c7 44 24 08 00 00 movq $0x0,0x8(%rsp)
|
400412: 48 c7 44 24 08 00 00 movq $0x0,0x8(%rsp)
|
||||||
400419: 00 00
|
400419: 00 00
|
||||||
40041b: 48 c7 44 24 10 01 00 movq $0x1,0x10(%rsp)
|
40041b: 48 c7 44 24 10 01 00 movq $0x1,0x10(%rsp)
|
||||||
400422: 00 00
|
400422: 00 00
|
||||||
400424: 48 ff 04 24 incq (%rsp)
|
400424: 48 ff 04 24 incq (%rsp)
|
||||||
400428: 48 8b 04 24 mov (%rsp),%rax
|
400428: 48 8b 04 24 mov (%rsp),%rax
|
||||||
400431: 48 8d 70 fb lea -0x5(%rax),%rsi
|
400431: 48 8d 70 fb lea -0x5(%rax),%rsi
|
||||||
@ -306,14 +306,14 @@ we will see one difference here which is in the last two lines:
|
|||||||
400431: 48 8d 70 fb lea -0x5(%rax),%rsi
|
400431: 48 8d 70 fb lea -0x5(%rax),%rsi
|
||||||
```
|
```
|
||||||
|
|
||||||
Instead of constant folding, `GCC` now preserves calculations in the assembly and places the value of `a[0]` in the `%rax` register afterwards. In the end it just subtracts the constant value of `b` from the `%rax` register and puts result to the `%rsi`.
|
Instead of constant folding, `GCC` now preserves calculations in the assembly and places the value of `a[0]` in the `%rax` register afterwards. In the end it just subtracts the constant value of `b` from the `%rax` register and puts the result to the `%rsi`.
|
||||||
|
|
||||||
Besides the `memory` specifier, we also see a new constraint here - `m`. This constraint tells the compiler to use the address of `a[0]`, instead of its value. So, now we are finished with `clobbers` and we may continue by looking at other constraints supported by `GCC` besides `r` and `m` which we have already seen.
|
Besides the `memory` specifier, we also see a new constraint here - `m`. This constraint tells the compiler to use the address of `a[0]`, instead of its value. So, now we are finished with `clobbers` and we may continue by looking at other constraints supported by `GCC` besides `r` and `m` which we have already seen.
|
||||||
|
|
||||||
Constraints
|
Constraints
|
||||||
---------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------
|
||||||
|
|
||||||
Now that we are finished with all three parts of an inline assembly statement, let's return to constraints. We already saw some constraints in the previous parts, like `r` which represents a `register` operand, `m` which represents a memory operand and `0-9` which represent an reused, indexed operand. Besides these `GCC` provides support for other constraints. For example the `i` constraint represents an `immediate` integer operand with know value:
|
Now that we are finished with all three parts of an inline assembly statement, let's return to constraints. We already saw some constraints in the previous parts, like `r` which represents a `register` operand, `m` which represents a memory operand and `0-9` which represent a reused, indexed operand. Besides these `GCC` provides support for other constraints. For example the `i` constraint represents an `immediate` integer operand with known value:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -388,7 +388,7 @@ int main(void)
|
|||||||
{
|
{
|
||||||
static unsigned long arr[3] = {0, 1, 2};
|
static unsigned long arr[3] = {0, 1, 2};
|
||||||
static unsigned long element;
|
static unsigned long element;
|
||||||
|
|
||||||
__asm__ volatile("movq 16+%1, %0" : "=r"(element) : "o"(arr));
|
__asm__ volatile("movq 16+%1, %0" : "=r"(element) : "o"(arr));
|
||||||
printf("%lu\n", element);
|
printf("%lu\n", element);
|
||||||
return 0;
|
return 0;
|
||||||
@ -434,7 +434,7 @@ That's about all of the commonly used constraints in inline assembly statements.
|
|||||||
Architecture specific constraints
|
Architecture specific constraints
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Before we finish, let's look at the set of special constraints. These constrains are architecture specific and as this book is specific to the [x86_64](https://en.wikipedia.org/wiki/X86-64) architecture, we will look at constraints related to it. First of all the set of `a` ... `d` and also `S` and `D` constraints represent [generic purpose](https://en.wikipedia.org/wiki/Processor_register) registers. In this case the `a` constraint corresponds to `%al`, `%ax`, `%eax` or `%rax` register depending on instruction size. The `S` and `D` constraints are `%si` and `%di` registers respectively. For example let's take our previous example. We can see in its assembly output that value of the `a` variable is stored in the `%eax` register. Now let's look at the assembly output of the same assembly, but with other constraint:
|
Before we finish, let's look at the set of special constraints. These constrains are architecture specific and as this book is specific to the [x86_64](https://en.wikipedia.org/wiki/X86-64) architecture, we will look at constraints related to it. First of all the set of `a` ... `d` and also `S` and `D` constraints represent [generic purpose](https://en.wikipedia.org/wiki/Processor_register) registers. In this case the `a` constraint corresponds to `%al`, `%ax`, `%eax` or `%rax` register depending on instruction size. The `S` and `D` constraints are `%si` and `%di` registers respectively. For example let's take our previous example. We can see in its assembly output that value of the `a` variable is stored in the `%eax` register. Now let's look at the assembly output of the same assembly, but with other constraint:
|
||||||
|
|
||||||
```C
|
```C
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -464,7 +464,7 @@ Links
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
* [Linux kernel source code](https://github.com/torvalds/linux)
|
* [Linux kernel source code](https://github.com/torvalds/linux)
|
||||||
* [assembly programming language](https://en.wikipedia.org/wiki/Assembly_language)
|
* [assembly programming language](https://en.wikipedia.org/wiki/Assembly_language)
|
||||||
* [GCC](https://en.wikipedia.org/wiki/GNU_Compiler_Collection)
|
* [GCC](https://en.wikipedia.org/wiki/GNU_Compiler_Collection)
|
||||||
* [GNU extension](https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html)
|
* [GNU extension](https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html)
|
||||||
* [Global Descriptor Table](https://en.wikipedia.org/wiki/Global_Descriptor_Table)
|
* [Global Descriptor Table](https://en.wikipedia.org/wiki/Global_Descriptor_Table)
|
||||||
|
Loading…
Reference in New Issue
Block a user