From a9f44b0341ebbd3430b478fab8c6bf81d7cf9318 Mon Sep 17 00:00:00 2001 From: Neo Date: Fri, 27 Oct 2017 07:10:41 -0400 Subject: [PATCH 1/7] upload the linux-initialization-5 Chinese version --- Initialization/linux-initialization-5.md | 1070 +++++++++++----------- 1 file changed, 556 insertions(+), 514 deletions(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index 4e4d8b0..8146f7c 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -1,514 +1,556 @@ -Kernel initialization. Part 5. -================================================================================ - -Continue of architecture-specific initialization -================================================================================ - -In the previous [part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html), we stopped at the initialization of an architecture-specific stuff from the [setup_arch](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/setup.c#L856) function and now we will continue with it. As we reserved memory for the [initrd](http://en.wikipedia.org/wiki/Initrd), next step is the `olpc_ofw_detect` which detects [One Laptop Per Child support](http://wiki.laptop.org/go/OFW_FAQ). We will not consider platform related stuff in this book and will skip functions related with it. So let's go ahead. The next step is the `early_trap_init` function. This function initializes debug (`#DB` - raised when the `TF` flag of rflags is set) and `int3` (`#BP`) interrupts gate. If you don't know anything about interrupts, you can read about it in the [Early interrupt and exception handling](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-2.html). In `x86` architecture `INT`, `INTO` and `INT3` are special instructions which allow a task to explicitly call an interrupt handler. The `INT3` instruction calls the breakpoint (`#BP`) handler. You may remember, we already saw it in the [part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-2.html) about interrupts: and exceptions: - -``` ----------------------------------------------------------------------------------------------- -|Vector|Mnemonic|Description |Type |Error Code|Source | ----------------------------------------------------------------------------------------------- -|3 | #BP |Breakpoint |Trap |NO |INT 3 | ----------------------------------------------------------------------------------------------- -``` - -Debug interrupt `#DB` is the primary method of invoking debuggers. `early_trap_init` defined in the [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c). This functions sets `#DB` and `#BP` handlers and reloads [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table): - -```C -void __init early_trap_init(void) -{ - set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK); - set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK); - load_idt(&idt_descr); -} -``` - -We already saw implementation of the `set_intr_gate` in the previous part about interrupts. Here are two similar functions `set_intr_gate_ist` and `set_system_intr_gate_ist`. Both of these two functions take three parameters: - -* number of the interrupt; -* base address of the interrupt/exception handler; -* third parameter is - `Interrupt Stack Table`. `IST` is a new mechanism in the `x86_64` and part of the [TSS](http://en.wikipedia.org/wiki/Task_state_segment). Every active thread in kernel mode has own kernel stack which is `16` kilobytes. While a thread in user space, this kernel stack is empty. - -In addition to per-thread stacks, there are a couple of specialized stacks associated with each CPU. All about these stack you can read in the linux kernel documentation - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/x86_64/kernel-stacks). `x86_64` provides feature which allows to switch to a new `special` stack for during any events as non-maskable interrupt and etc... And the name of this feature is - `Interrupt Stack Table`. There can be up to 7 `IST` entries per CPU and every entry points to the dedicated stack. In our case this is `DEBUG_STACK`. - -`set_intr_gate_ist` and `set_system_intr_gate_ist` work by the same principle as `set_intr_gate` with only one difference. Both of these functions checks -interrupt number and call `_set_gate` inside: - -```C -BUG_ON((unsigned)n > 0xFF); -_set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); -``` - -as `set_intr_gate` does this. But `set_intr_gate` calls `_set_gate` with [dpl](http://en.wikipedia.org/wiki/Privilege_level) - 0, and ist - 0, but `set_intr_gate_ist` and `set_system_intr_gate_ist` sets `ist` as `DEBUG_STACK` and `set_system_intr_gate_ist` sets `dpl` as `0x3` which is the lowest privilege. When an interrupt occurs and the hardware loads such a descriptor, then hardware automatically sets the new stack pointer based on the IST value, then invokes the interrupt handler. All of the special kernel stacks will be set in the `cpu_init` function (we will see it later). - -As `#DB` and `#BP` gates written to the `idt_descr`, we reload `IDT` table with `load_idt` which just calss `ldtr` instruction. Now let's look on interrupt handlers and will try to understand how they works. Of course, I can't cover all interrupt handlers in this book and I do not see the point in this. It is very interesting to delve in the linux kernel source code, so we will see how `debug` handler implemented in this part, and understand how other interrupt handlers are implemented will be your task. - -#DB handler --------------------------------------------------------------------------------- - -As you can read above, we passed address of the `#DB` handler as `&debug` in the `set_intr_gate_ist`. [lxr.free-electrons.com](http://lxr.free-electrons.com/ident) is a great resource for searching identifiers in the linux kernel source code, but unfortunately you will not find `debug` handler with it. All of you can find, it is `debug` definition in the [arch/x86/include/asm/traps.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps.h): - -```C -asmlinkage void debug(void); -``` - -We can see `asmlinkage` attribute which tells to us that `debug` is function written with [assembly](http://en.wikipedia.org/wiki/Assembly_language). Yeah, again and again assembly :). Implementation of the `#DB` handler as other handlers is in this [arch/x86/kernel/entry_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/entry_64.S) and defined with the `idtentry` assembly macro: - -```assembly -idtentry debug do_debug has_error_code=0 paranoid=1 shift_ist=DEBUG_STACK -``` - -`idtentry` is a macro which defines an interrupt/exception entry point. As you can see it takes five arguments: - -* name of the interrupt entry point; -* name of the interrupt handler; -* has interrupt error code or not; -* paranoid - if this parameter = 1, switch to special stack (read above); -* shift_ist - stack to switch during interrupt. - -Now let's look on `idtentry` macro implementation. This macro defined in the same assembly file and defines `debug` function with the `ENTRY` macro. For the start `idtentry` macro checks that given parameters are correct in case if need to switch to the special stack. In the next step it checks that give interrupt returns error code. If interrupt does not return error code (in our case `#DB` does not return error code), it calls `INTR_FRAME` or `XCPT_FRAME` if interrupt has error code. Both of these macros `XCPT_FRAME` and `INTR_FRAME` do nothing and need only for the building initial frame state for interrupts. They uses `CFI` directives and used for debugging. More info you can find in the [CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html). As comment from the [arch/x86/kernel/entry_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/entry_64.S) says: `CFI macros are used to generate dwarf2 unwind information for better backtraces. They don't change any code.` so we will ignore them. - -```assembly -.macro idtentry sym do_sym has_error_code:req paranoid=0 shift_ist=-1 -ENTRY(\sym) - /* Sanity check */ - .if \shift_ist != -1 && \paranoid == 0 - .error "using shift_ist requires paranoid=1" - .endif - - .if \has_error_code - XCPT_FRAME - .else - INTR_FRAME - .endif - ... - ... - ... -``` - -You can remember from the previous part about early interrupts/exceptions handling that after interrupt occurs, current stack will have following format: - -``` - +-----------------------+ - | | -+40 | SS | -+32 | RSP | -+24 | RFLAGS | -+16 | CS | -+8 | RIP | - 0 | Error Code | <---- rsp - | | - +-----------------------+ -``` - -The next two macro from the `idtentry` implementation are: - -```assembly - ASM_CLAC - PARAVIRT_ADJUST_EXCEPTION_FRAME -``` - -First `ASM_CLAC` macro depends on `CONFIG_X86_SMAP` configuration option and need for security reason, more about it you can read [here](https://lwn.net/Articles/517475/). The second `PARAVIRT_ADJUST_EXCEPTION_FRAME` macro is for handling handle Xen-type-exceptions (this chapter about kernel initialization and we will not consider virtualization stuff here). - -The next piece of code checks if interrupt has error code or not and pushes `$-1` which is `0xffffffffffffffff` on `x86_64` on the stack if not: - -```assembly - .ifeq \has_error_code - pushq_cfi $-1 - .endif -``` - -We need to do it as `dummy` error code for stack consistency for all interrupts. In the next step we subtract from the stack pointer `$ORIG_RAX-R15`: - -```assembly - subq $ORIG_RAX-R15, %rsp -``` - -where `ORIRG_RAX`, `R15` and other macros defined in the [arch/x86/include/asm/calling.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/calling.h) and `ORIG_RAX-R15` is 120 bytes. General purpose registers will occupy these 120 bytes because we need to store all registers on the stack during interrupt handling. After we set stack for general purpose registers, the next step is checking that interrupt came from userspace with: - -```assembly -testl $3, CS(%rsp) -jnz 1f -``` - -Here we checks first and second bits in the `CS`. You can remember that `CS` register contains segment selector where first two bits are `RPL`. All privilege levels are integers in the range 0–3, where the lowest number corresponds to the highest privilege. So if interrupt came from the kernel mode we call `save_paranoid` or jump on label `1` if not. In the `save_paranoid` we store all general purpose registers on the stack and switch user `gs` on kernel `gs` if need: - -```assembly - movl $1,%ebx - movl $MSR_GS_BASE,%ecx - rdmsr - testl %edx,%edx - js 1f - SWAPGS - xorl %ebx,%ebx -1: ret -``` - -In the next steps we put `pt_regs` pointer to the `rdi`, save error code in the `rsi` if it has and call interrupt handler which is - `do_debug` in our case from the [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c). `do_debug` like other handlers takes two parameters: - -* pt_regs - is a structure which presents set of CPU registers which are saved in the process' memory region; -* error code - error code of interrupt. - -After interrupt handler finished its work, calls `paranoid_exit` which restores stack, switch on userspace if interrupt came from there and calls `iret`. That's all. Of course it is not all :), but we will see more deeply in the separate chapter about interrupts. - -This is general view of the `idtentry` macro for `#DB` interrupt. All interrupts are similar to this implementation and defined with idtentry too. After `early_trap_init` finished its work, the next function is `early_cpu_init`. This function defined in the [arch/x86/kernel/cpu/common.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/cpu/common.c) and collects information about CPU and its vendor. - -Early ioremap initialization --------------------------------------------------------------------------------- - -The next step is initialization of early `ioremap`. In general there are two ways to communicate with devices: - -* I/O Ports; -* Device memory. - -We already saw first method (`outb/inb` instructions) in the part about linux kernel booting [process](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-3.html). The second method is to map I/O physical addresses to virtual addresses. When a physical address is accessed by the CPU, it may refer to a portion of physical RAM which can be mapped on memory of the I/O device. So `ioremap` used to map device memory into kernel address space. - -As i wrote above next function is the `early_ioremap_init` which re-maps I/O memory to kernel address space so it can access it. We need to initialize early ioremap for early initialization code which needs to temporarily map I/O or memory regions before the normal mapping functions like `ioremap` are available. Implementation of this function is in the [arch/x86/mm/ioremap.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/mm/ioremap.c). At the start of the `early_ioremap_init` we can see definition of the `pmd` point with `pmd_t` type (which presents page middle directory entry `typedef struct { pmdval_t pmd; } pmd_t;` where `pmdval_t` is `unsigned long`) and make a check that `fixmap` aligned in a correct way: - -```C -pmd_t *pmd; -BUILD_BUG_ON((fix_to_virt(0) + PAGE_SIZE) & ((1 << PMD_SHIFT) - 1)); -``` - -`fixmap` - is fixed virtual address mappings which extends from `FIXADDR_START` to `FIXADDR_TOP`. Fixed virtual addresses are needed for subsystems that need to know the virtual address at compile time. After the check `early_ioremap_init` makes a call of the `early_ioremap_setup` function from the [mm/early_ioremap.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/mm/early_ioremap.c). `early_ioremap_setup` fills `slot_virt` array of the `unsigned long` with virtual addresses with 512 temporary boot-time fix-mappings: - -```C -for (i = 0; i < FIX_BTMAPS_SLOTS; i++) - slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); -``` - -After this we get page middle directory entry for the `FIX_BTMAP_BEGIN` and put to the `pmd` variable, fills `bm_pte` with zeros which is boot time page tables and call `pmd_populate_kernel` function for setting given page table entry in the given page middle directory: - -```C -pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)); -memset(bm_pte, 0, sizeof(bm_pte)); -pmd_populate_kernel(&init_mm, pmd, bm_pte); -``` - -That's all for this. If you feeling puzzled, don't worry. There is special part about `ioremap` and `fixmaps` in the [Linux Kernel Memory Management. Part 2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) chapter. - -Obtaining major and minor numbers for the root device --------------------------------------------------------------------------------- - -After early `ioremap` was initialized, you can see the following code: - -```C -ROOT_DEV = old_decode_dev(boot_params.hdr.root_dev); -``` - -This code obtains major and minor numbers for the root device where `initrd` will be mounted later in the `do_mount_root` function. Major number of the device identifies a driver associated with the device. Minor number referred on the device controlled by driver. Note that `old_decode_dev` takes one parameter from the `boot_params_structure`. As we can read from the x86 linux kernel boot protocol: - -``` -Field name: root_dev -Type: modify (optional) -Offset/size: 0x1fc/2 -Protocol: ALL - - The default root device device number. The use of this field is - deprecated, use the "root=" option on the command line instead. -``` - -Now let's try to understand what `old_decode_dev` does. Actually it just calls `MKDEV` inside which generates `dev_t` from the give major and minor numbers. It's implementation is pretty simple: - -```C -static inline dev_t old_decode_dev(u16 val) -{ - return MKDEV((val >> 8) & 255, val & 255); -} -``` - -where `dev_t` is a kernel data type to present major/minor number pair. But what's the strange `old_` prefix? For historical reasons, there are two ways of managing the major and minor numbers of a device. In the first way major and minor numbers occupied 2 bytes. You can see it in the previous code: 8 bit for major number and 8 bit for minor number. But there is a problem: only 256 major numbers and 256 minor numbers are possible. So 16-bit integer was replaced by 32-bit integer where 12 bits reserved for major number and 20 bits for minor. You can see this in the `new_decode_dev` implementation: - -```C -static inline dev_t new_decode_dev(u32 dev) -{ - unsigned major = (dev & 0xfff00) >> 8; - unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); - return MKDEV(major, minor); -} -``` - -After calculation we will get `0xfff` or 12 bits for `major` if it is `0xffffffff` and `0xfffff` or 20 bits for `minor`. So in the end of execution of the `old_decode_dev` we will get major and minor numbers for the root device in `ROOT_DEV`. - -Memory map setup --------------------------------------------------------------------------------- - -The next point is the setup of the memory map with the call of the `setup_memory_map` function. But before this we setup different parameters as information about a screen (current row and column, video page and etc... (you can read about it in the [Video mode initialization and transition to protected mode](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-3.html))), Extended display identification data, video mode, bootloader_type and etc...: - -```C - screen_info = boot_params.screen_info; - edid_info = boot_params.edid_info; - saved_video_mode = boot_params.hdr.vid_mode; - bootloader_type = boot_params.hdr.type_of_loader; - if ((bootloader_type >> 4) == 0xe) { - bootloader_type &= 0xf; - bootloader_type |= (boot_params.hdr.ext_loader_type+0x10) << 4; - } - bootloader_version = bootloader_type & 0xf; - bootloader_version |= boot_params.hdr.ext_loader_ver << 4; -``` - -All of these parameters we got during boot time and stored in the `boot_params` structure. After this we need to setup the end of the I/O memory. As you know one of the main purposes of the kernel is resource management. And one of the resource is memory. As we already know there are two ways to communicate with devices are I/O ports and device memory. All information about registered resources are available through: - -* /proc/ioports - provides a list of currently registered port regions used for input or output communication with a device; -* /proc/iomem - provides current map of the system's memory for each physical device. - -At the moment we are interested in `/proc/iomem`: - -``` -cat /proc/iomem -00000000-00000fff : reserved -00001000-0009d7ff : System RAM -0009d800-0009ffff : reserved -000a0000-000bffff : PCI Bus 0000:00 -000c0000-000cffff : Video ROM -000d0000-000d3fff : PCI Bus 0000:00 -000d4000-000d7fff : PCI Bus 0000:00 -000d8000-000dbfff : PCI Bus 0000:00 -000dc000-000dffff : PCI Bus 0000:00 -000e0000-000fffff : reserved - 000e0000-000e3fff : PCI Bus 0000:00 - 000e4000-000e7fff : PCI Bus 0000:00 - 000f0000-000fffff : System ROM -``` - -As you can see range of addresses are shown in hexadecimal notation with its owner. Linux kernel provides API for managing any resources in a general way. Global resources (for example PICs or I/O ports) can be divided into subsets - relating to any hardware bus slot. The main structure `resource`: - -```C -struct resource { - resource_size_t start; - resource_size_t end; - const char *name; - unsigned long flags; - struct resource *parent, *sibling, *child; -}; -``` - -presents abstraction for a tree-like subset of system resources. This structure provides range of addresses from `start` to `end` (`resource_size_t` is `phys_addr_t` or `u64` for `x86_64`) which a resource covers, `name` of a resource (you see these names in the `/proc/iomem` output) and `flags` of a resource (All resources flags defined in the [include/linux/ioport.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/ioport.h)). The last are three pointers to the `resource` structure. These pointers enable a tree-like structure: - -``` -+-------------+ +-------------+ -| | | | -| parent |------| sibling | -| | | | -+-------------+ +-------------+ - | - | -+-------------+ -| | -| child | -| | -+-------------+ -``` - -Every subset of resources has root range resources. For `iomem` it is `iomem_resource` which defined as: - -```C -struct resource iomem_resource = { - .name = "PCI mem", - .start = 0, - .end = -1, - .flags = IORESOURCE_MEM, -}; -EXPORT_SYMBOL(iomem_resource); -``` - -TODO EXPORT_SYMBOL - -`iomem_resource` defines root addresses range for io memory with `PCI mem` name and `IORESOURCE_MEM` (`0x00000200`) as flags. As i wrote above our current point is setup the end address of the `iomem`. We will do it with: - -```C -iomem_resource.end = (1ULL << boot_cpu_data.x86_phys_bits) - 1; -``` - -Here we shift `1` on `boot_cpu_data.x86_phys_bits`. `boot_cpu_data` is `cpuinfo_x86` structure which we filled during execution of the `early_cpu_init`. As you can understand from the name of the `x86_phys_bits` field, it presents maximum bits amount of the maximum physical address in the system. Note also that `iomem_resource` is passed to the `EXPORT_SYMBOL` macro. This macro exports the given symbol (`iomem_resource` in our case) for dynamic linking or in other words it makes a symbol accessible to dynamically loaded modules. - -After we set the end address of the root `iomem` resource address range, as I wrote above the next step will be setup of the memory map. It will be produced with the call of the `setup_ memory_map` function: - -```C -void __init setup_memory_map(void) -{ - char *who; - - who = x86_init.resources.memory_setup(); - memcpy(&e820_saved, &e820, sizeof(struct e820map)); - printk(KERN_INFO "e820: BIOS-provided physical RAM map:\n"); - e820_print_map(who); -} -``` - -First of all we call look here the call of the `x86_init.resources.memory_setup`. `x86_init` is a `x86_init_ops` structure which presents platform specific setup functions as resources initialization, pci initialization and etc... initialization of the `x86_init` is in the [arch/x86/kernel/x86_init.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/x86_init.c). I will not give here the full description because it is very long, but only one part which interests us for now: - -```C -struct x86_init_ops x86_init __initdata = { - .resources = { - .probe_roms = probe_roms, - .reserve_resources = reserve_standard_io_resources, - .memory_setup = default_machine_specific_memory_setup, - }, - ... - ... - ... -} -``` - -As we can see here `memry_setup` field is `default_machine_specific_memory_setup` where we get the number of the [e820](http://en.wikipedia.org/wiki/E820) entries which we collected in the [boot time](http://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-2.html), sanitize the BIOS e820 map and fill `e820map` structure with the memory regions. As all regions are collected, print of all regions with printk. You can find this print if you execute `dmesg` command and you can see something like this: - -``` -[ 0.000000] e820: BIOS-provided physical RAM map: -[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009d7ff] usable -[ 0.000000] BIOS-e820: [mem 0x000000000009d800-0x000000000009ffff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved -[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000be825fff] usable -[ 0.000000] BIOS-e820: [mem 0x00000000be826000-0x00000000be82cfff] ACPI NVS -[ 0.000000] BIOS-e820: [mem 0x00000000be82d000-0x00000000bf744fff] usable -[ 0.000000] BIOS-e820: [mem 0x00000000bf745000-0x00000000bfff4fff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000bfff5000-0x00000000dc041fff] usable -[ 0.000000] BIOS-e820: [mem 0x00000000dc042000-0x00000000dc0d2fff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000dc0d3000-0x00000000dc138fff] usable -[ 0.000000] BIOS-e820: [mem 0x00000000dc139000-0x00000000dc27dfff] ACPI NVS -[ 0.000000] BIOS-e820: [mem 0x00000000dc27e000-0x00000000deffefff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000defff000-0x00000000deffffff] usable -... -... -... -``` - -Copying of the BIOS Enhanced Disk Device information --------------------------------------------------------------------------------- - -The next two steps is parsing of the `setup_data` with `parse_setup_data` function and copying BIOS EDD to the safe place. `setup_data` is a field from the kernel boot header and as we can read from the `x86` boot protocol: - -``` -Field name: setup_data -Type: write (special) -Offset/size: 0x250/8 -Protocol: 2.09+ - - The 64-bit physical pointer to NULL terminated single linked list of - struct setup_data. This is used to define a more extensible boot - parameters passing mechanism. -``` - -It used for storing setup information for different types as device tree blob, EFI setup data and etc... In the second step we copy BIOS EDD information from the `boot_params` structure that we collected in the [arch/x86/boot/edd.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/edd.c) to the `edd` structure: - -```C -static inline void __init copy_edd(void) -{ - memcpy(edd.mbr_signature, boot_params.edd_mbr_sig_buffer, - sizeof(edd.mbr_signature)); - memcpy(edd.edd_info, boot_params.eddbuf, sizeof(edd.edd_info)); - edd.mbr_signature_nr = boot_params.edd_mbr_sig_buf_entries; - edd.edd_info_nr = boot_params.eddbuf_entries; -} -``` - -Memory descriptor initialization --------------------------------------------------------------------------------- - -The next step is initialization of the memory descriptor of the init process. As you already can know every process has its own address space. This address space presented with special data structure which called `memory descriptor`. Directly in the linux kernel source code memory descriptor presented with `mm_struct` structure. `mm_struct` contains many different fields related with the process address space as start/end address of the kernel code/data, start/end of the brk, number of memory areas, list of memory areas and etc... This structure defined in the [include/linux/mm_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/mm_types.h). As every process has its own memory descriptor, `task_struct` structure contains it in the `mm` and `active_mm` field. And our first `init` process has it too. You can remember that we saw the part of initialization of the init `task_struct` with `INIT_TASK` macro in the previous [part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html): - -```C -#define INIT_TASK(tsk) \ -{ - ... - ... - ... - .mm = NULL, \ - .active_mm = &init_mm, \ - ... -} -``` - -`mm` points to the process address space and `active_mm` points to the active address space if process has no address space such as kernel threads (more about it you can read in the [documentation](https://www.kernel.org/doc/Documentation/vm/active_mm.txt)). Now we fill memory descriptor of the initial process: - -```C - init_mm.start_code = (unsigned long) _text; - init_mm.end_code = (unsigned long) _etext; - init_mm.end_data = (unsigned long) _edata; - init_mm.brk = _brk_end; -``` - -with the kernel's text, data and brk. `init_mm` is the memory descriptor of the initial process and defined as: - -```C -struct mm_struct init_mm = { - .mm_rb = RB_ROOT, - .pgd = swapper_pg_dir, - .mm_users = ATOMIC_INIT(2), - .mm_count = ATOMIC_INIT(1), - .mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem), - .page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock), - .mmlist = LIST_HEAD_INIT(init_mm.mmlist), - INIT_MM_CONTEXT(init_mm) -}; -``` - -where `mm_rb` is a red-black tree of the virtual memory areas, `pgd` is a pointer to the page global directory, `mm_users` is address space users, `mm_count` is primary usage counter and `mmap_sem` is memory area semaphore. After we setup memory descriptor of the initial process, next step is initialization of the Intel Memory Protection Extensions with `mpx_mm_init`. The next step is initialization of the code/data/bss resources with: - -```C - code_resource.start = __pa_symbol(_text); - code_resource.end = __pa_symbol(_etext)-1; - data_resource.start = __pa_symbol(_etext); - data_resource.end = __pa_symbol(_edata)-1; - bss_resource.start = __pa_symbol(__bss_start); - bss_resource.end = __pa_symbol(__bss_stop)-1; -``` - -We already know a little about `resource` structure (read above). Here we fills code/data/bss resources with their physical addresses. You can see it in the `/proc/iomem`: - -```C -00100000-be825fff : System RAM - 01000000-015bb392 : Kernel code - 015bb393-01930c3f : Kernel data - 01a11000-01ac3fff : Kernel bss -``` - -All of these structures are defined in the [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/setup.c) and look like typical resource initialization: - -```C -static struct resource code_resource = { - .name = "Kernel code", - .start = 0, - .end = 0, - .flags = IORESOURCE_BUSY | IORESOURCE_MEM -}; -``` - -The last step which we will cover in this part will be `NX` configuration. `NX-bit` or no execute bit is 63-bit in the page directory entry which controls the ability to execute code from all physical pages mapped by the table entry. This bit can only be used/set when the `no-execute` page-protection mechanism is enabled by the setting `EFER.NXE` to 1. In the `x86_configure_nx` function we check that CPU has support of `NX-bit` and it does not disabled. After the check we fill `__supported_pte_mask` depend on it: - -```C -void x86_configure_nx(void) -{ - if (cpu_has_nx && !disable_nx) - __supported_pte_mask |= _PAGE_NX; - else - __supported_pte_mask &= ~_PAGE_NX; -} -``` - -Conclusion --------------------------------------------------------------------------------- - -It is the end of the fifth part about linux kernel initialization process. In this part we continued to dive in the `setup_arch` function which makes initialization of architecture-specific stuff. It was long part, but we have not finished with it. As i already wrote, the `setup_arch` is big function, and I am really not sure that we will cover all of it even in the next part. There were some new interesting concepts in this part like `Fix-mapped` addresses, ioremap and etc... Don't worry if they are unclear for you. There is a special part about these concepts - [Linux kernel memory management Part 2.](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md). In the next part we will continue with the initialization of the architecture-specific stuff and will see parsing of the early kernel parameters, early dump of the pci devices, direct Media Interface scanning and many many more. - -If you have any questions or suggestions write me a comment or ping me at [twitter](https://twitter.com/0xAX). - -**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-insides).** - -Links --------------------------------------------------------------------------------- - -* [mm vs active_mm](https://www.kernel.org/doc/Documentation/vm/active_mm.txt) -* [e820](http://en.wikipedia.org/wiki/E820) -* [Supervisor mode access prevention](https://lwn.net/Articles/517475/) -* [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/x86_64/kernel-stacks) -* [TSS](http://en.wikipedia.org/wiki/Task_state_segment) -* [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table) -* [Memory mapped I/O](http://en.wikipedia.org/wiki/Memory-mapped_I/O) -* [CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html) -* [PDF. dwarf4 specification](http://dwarfstd.org/doc/DWARF4.pdf) -* [Call stack](http://en.wikipedia.org/wiki/Call_stack) -* [Previous part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html) +Part 5 +=========================================================== + +与系统架构有关的初始化后续分析 +=========================================================== + +在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html) 中, +我们讲到了与系统架构有关的 [setup_arch](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c#L856) 函数部分,本文会继续从这里开始。 +为 [initrd](http://en.wikipedia.org/wiki/Initrd) 预留了内存之后,下一步是执行 `olpc_ofw_detect` 函数检测系统是否支持 [One Laptop Per Child](http://wiki.laptop.org/go/OFW_FAQ)。 +我们不会考虑与平台有关的东西,因此会忽略与平台有关的内容。所以我们继续往下看。 +下一步是执行 `early_trap_init` 函数。这个函数会初始化调试功能 (#DB -当TF标志位和rflags被设置时会被使用)和 `int3` (`#BP`)中断门。 +如果你不了解中断,你可以从 [Early interrupt and exception handling](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html) 中学习有关中断的内容。 +在 `x86` 架构中,`INT`,`INT0` 和 `INT3` 是支持任务显式调用中断处理函数的特殊指令。`INT3` 指令调用断点(`#BP`)处理函数。 +你如果记得,我们在这[部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-2.html) 看到过中断和异常概念: + +``` +---------------------------------------------------------------------------------------------- +|Vector|Mnemonic|Description |Type |Error Code|Source | +---------------------------------------------------------------------------------------------- +|3 | #BP |Breakpoint |Trap |NO |INT 3 | +---------------------------------------------------------------------------------------------- +``` + +调试中断 `#DB` 是激活调试器的重要方法。在 [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c) 中定义的 `early_trap_init` 函数设置了 `#DB` 和 `#BP` 处理函数, +并且重新加载了 [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table): + +```C +void __init early_trap_init(void) +{ + set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK); + set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK); + load_idt(&idt_descr); +} +``` + +我们之前已经看到过 `set_intr_gate` 中关于中断的实现。这里的 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 也是类似的实现。 +这两个函数都需要三个参数: + +* 中断号 +* 中断/异常处理函数的基地址 +* 第三个参数是 `Interrupt Stack Table`。 `IST` 是 [TSS](http://en.wikipedia.org/wiki/Task_state_segment) 的部分内容,是 `x86_64` 引入的新机制。 +在内核态处于活跃状态的线程拥有 `16kb` 的内核栈空间。但是在用户空间的线程的内核栈是空的。 +除了线程栈,还有一些与每个 `CPU` 有关的特殊栈。你可以查阅 `linux` 内核文档 - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/x86_64/kernel-stacks) 部分了解这些栈信息。 +`x86_64` 提供了像在非屏蔽中断等类似事件中切换新的特殊栈的特性支持。这个特性的名字是 `Interrupt Stack Table`。 +每个CPU最多可以有7个 `IST` 条目,每个条目有自己特定的栈。在我们的案例中使用的是 `DEBUG_STACK`。 +`set_intr_gate_ist` 和 `set_system_intr_gate_ist` 和 `set_intr_gate` 的工作原理几乎一样,只有一个区别。 +这些函数检查中断号并在内部调用 `_set_gate` : + +```C +BUG_ON((unsigned)n > 0xFF); +_set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); +``` + +其中, `set_intr_gate` 把 [dpl](http://en.wikipedia.org/wiki/Privilege_level) 和 `ist` 置为0来调用 `_set_gate`。 +但是 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 把 `ist` 设置为 `DEBUG_STACK`,并且 `set_system_intr_gate_ist` 把 `dpl` 设置为优先级最低的 `0x3`。 +当中断发生时,硬件加载这个描述符,然后硬件根据 `IST` 的值自动设置新的栈指针。 +之后激活对应的中断处理函数。所有的特殊内核栈会在 `cpu_init` 函数中设置好(我们会在后文中提到)。 +当 `#DB` 和 `#BP` 门向 `idt_descr` 有写操作,我们会调用 `load_idt` 函数来执行 `ldtr` 指令来重新加载 `IDT` 表。 +现在我们来了解下中断处理函数并尝试理解它的工作原理。当然,我们不可能在这本书中讲解所有的中断处理函数。 +深入学习linux的内核源码是很有意思的事情,我们会在这里讲解 `debug` 处理函数的实现。请自行学习其他的中断处理函数实现。 + +`#DB` 处理函数 +像上文中提到的,我们在 `set_intr_gate_ist` 中通过 `&debug` 的地址传送 `#DB` 处理函数。[lxr.free-electorns.com](http://lxr.free-electrons.com/ident) 是用来搜索 `linux` 源代码中标识符的很好的资源。 +遗憾的是,你在其中找不到 `debug` 处理函数。你只能在 [arch/x86/include/asm/traps.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps.h) 中找到 `debug` 的定义: + +```C +asmlinkage void debug(void); +``` + +从 `asmlinkage` 属性我们可以知道 `debug` 是由 [assembly](http://en.wikipedia.org/wiki/Assembly_language)语言实现的函数。是的,又是汇编语言 :)。 +和其他处理函数一样,`#DB` 处理函数的实现可以在 [arch/x86/kernel/entry_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/entry_64.S) 文件中找到。 +都是由 `idtentry` 汇编宏定义的: + +```assembly +idtentry debug do_debug has_error_code=0 paranoid=1 shift_ist=DEBUG_STACK +``` + +`idtentry` 是一个定义中断/异常指令入口点的宏。它需要五个参数: +* 中断条目点的名字 +* 中断处理函数的名字 +* 是否有中断错误码 +* paranoid - 如果这个参数置为1,则切换到特殊栈 +* shift_ist - 支持中断期间切换栈 + +现在我们来看下 `idtentry` 宏的实现。这个宏的定义也在相同的汇编文件中,并且定义了有 `ENTRY` 宏属性的 `debug` 函数。 +首先,`idtentry` 宏检查所有的参数是否正确,是否需要切换到特殊栈。接下来检查中断返回的错误码。例如本案例中的 `#DB` 不会返回错误码。 +如果有错误码返回,它会调用 `INTR_FRAME` 或者 `XCPT_FRAM` 宏。其实 `XCPT_FRAME` 和 `INTR_FRAME` 宏什么也不会做,只是对中断初始状态编译的时候有用。 +它们使用 `CFI` 指令用来调试。你可以查阅更多有关 `CFI` 指令的信息[CFI](https://sourceware.org/binutils/docs/as/CFI-directives.html)。 +就像 [arch/x86/kernel/entry_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/entry_64) 中解释:`CFI` 宏是用来产生更好的回溯的 `dwarf2` 的解开信息。 +它们不会改变任何代码。因此我们可以忽略它们。 + +```assembly +.macro idtentry sym do_sym has_error_code:req paranoid=0 shift_ist=-1 +ENTRY(\sym) + /* Sanity check */ + .if \shift_ist != -1 && \paranoid == 0 + .error "using shift_ist requires paranoid=1" + .endif + + .if \has_error_code + XCPT_FRAME + .else + INTR_FRAME + .endif + ... + ... + ... +``` + +当中断发生后经过初期的中断/异常处理,我们可以知道栈内的格式是这样的: + +``` + +-----------------------+ + | | ++40 | SS | ++32 | RSP | ++24 | RFLAGS | ++16 | CS | ++8 | RIP | + 0 | Error Code | <---- rsp + | | + +-----------------------+ +``` + +`idtentry` 实现中的另外两个宏分别是 + +```assembly + ASM_CLAC + PARAVIRT_ADJUST_EXCEPTION_FRAME +``` + +第一个 `ASM_CLAC` 宏依赖于 `CONFIG_X86_SMAP` 这个配置项和考虑安全因素,你可以从[这里](https://lwn.net/Articles/517475)了解更多内容。 +第二个 `PARAVIRT_EXCEPTION_FRAME` 宏是用来处理 `Xen` 类型异常(这章只讲解内核初始化,不会考虑虚拟化的内容)。 +下一段代码会检查中断是否有错误码。如果没有则会把 `$-1`(在 `x86_64` 架构下值为 `0xffffffffffffffff`)压入栈: + +```assembly + .ifeq \has_error_code + pushq_cfi $-1 + .endif +``` + +为了保证对于所有中断的栈的一致性,我们会把它处理为 `dummy` 错误码。下一步我们从栈指针中减去 `$ORIG_RAX-R15`: + +```assembly + subq $ORIG_RAX-R15, %rsp +``` + +其中,`ORIG_RAX`,`R15` 和其他宏都定义在[arch/x86/include/asm/calling.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/calling.h) 中。`ORIG_RAX-R15` 是120字节。 +我们在中断处理过程中需要把所有的寄存器信息存储在栈中,所有通用寄存器会占用这个120字节。 +为通用寄存器设置完栈之后,下一步是检查从用户空间产生的中断: + +```assembly +testl $3, CS(%rsp) +jnz 1f +``` + +我们查看段寄存器 `CS` 的前两个比特位。你应该记得 `CS` 寄存器包含段选择器,它的前两个比特是 `RPL`。所有的权限等级是0-3范围内的整数。 +数字越小代表权限越高。因此当中断来自内核空间,我们会调用 `save_paranoid`,如果不来自内核空间,我们会跳转到标签 `1` 处处理。 +在 `save_paranoid` 函数中,我们会把所有的通用寄存器存储到栈中,如果需要的话会用户态 `gs` 切换到内核态 `gs`: + +```assembly +movl $1,%ebx + movl $MSR_GS_BASE,%ecx + rdmsr + testl %edx,%edx + js 1f + SWAPGS + xorl %ebx,%ebx +1: ret +``` + +下一步我们把 `pt_regs` 指针存在 `rdi` 中,如果存在错误码就把它存储到 `rsi` 中,然后调用中断处理函数,例如就像[arch/x86/kernel/trap.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps)中的 `do_debug`。 +`do_debug` 像其他处理函数一样需要两个参数: + +* pt_regs - 是一个存储在进程内存区域的一组CPU寄存器 +* error code - 中断错误码 + +中断处理函数完成工作后会调用 `paranoid_exit` 还原栈区。如果中断来自用户空间则切换回用户态并调用 `iret`。我们会在不同的章节继续深入分析中断。 +这是用在 `#DB` 中断中的 `idtentry` 宏的基本介绍。所有的中断都和这个实现类似,都定义在 `idtentry`中。`early_trap_init` 执行完后,下一个函数是 `early_cpu_init`。 +这个函数定义在 [arch/x86/kernel/cpu/common.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/cpu/common.c) 中,负责收集 `CPU` 和其供应商的信息。 + +早期ioremap初始化 +------------------------------------------------------------------------------ + +下一步是初始化早期的 `ioremap`。通常有两种实现与设备通信的方式: + +* I/O端口 +* 设备内存 + +我们在 `linux` 内核启动[过程](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html)中见过第一种方法(通过 `outb/inb` 指令实现)。 +第二种方法是把 `I/O` 的物理地址映射到虚拟地址。当 `CPU` 读取一段物理地址时,它可以读取到映射了 `I/O` 设备的物理 `RAM` 区域。 +`ioremap` 就是用来把设备内存映射到内核地址空间的。像我上面提到的下一个函数时 `early_ioremap_init`,它可以在正常的像 `ioremap` 这样的映射函数可用之前,把 `I/O` 内存映射到内核地址空间以方便读取。 +我们需要在初期的初始化代码中初始化临时的 `ioremap` 来映射 `I/O` 设备到内存区域。初期的 `ioremap` 实现在 [arch/x86/mm/ioremap.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/mm/ioremap.c) 中可以找到。 +在 `early_ioremap_init` 的一开始我们可以看到 `pmd_t` 类型的 `pmd` 指针定义(代表页中间目录条目 `typedef struct {pmdval_t pmd; } pmd_t;` 其中 `pmdval_t` 是无符号长整型)。 +然后检查 `fixmap` 是正确对齐的: + +```C +pmd_t *pmd; +BUILD_BUG_ON((fix_to_virt(0) + PAGE_SIZE) & ((1 << PMD_SHIFT) - 1)); +``` + +`fixmap` - 是一段从 `FIXADDR_START` 到 `FIXADDR_TOP` 的固定虚拟地址映射区域。它在子系统需要知道虚拟地址的编译过程中会被使用。 +之后 `early_ioremap_init` 函数会调用 [mm/early_ioremap.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/mm/early_ioremap.c) 中的 `early_ioremap_setup` 函数。 +`early_ioremap_setup` 会填充512个临时的启动时固定映射表来完成无符号长整型矩阵 `slot_virt` 的初始化: + +```C +for (i = 0; i < FIX_BTMAPS_SLOTS; i++) + slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); +``` + +之后我们就获得了 `FIX_BTMAP_BEGIN` 的页中间目录条目,并把它赋值给了 `pmd` 变量,把启动时间页表 `bm_pte` 写满0。然后调用 `pmd_populate_kernel` 函数设置给定的页中间目录的页表条目: + +```C +pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)); +memset(bm_pte, 0, sizeof(bm_pte)); +pmd_populate_kernel(&init_mm, pmd, bm_pte); +``` + +这就是所有过程。如果你仍然觉得困惑,不要担心。在 [Linux Kernel Memory Managment. Part 2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) 章节会有单独一部分讲解 `ioremap` 和 `fixmaps`。 + +获取根设备的主次设备号 +---------------------------------------------------------------------------- + +经过 `ioremap` 初始化完成,你可以看到下面的代码: + +```C +ROOT_DEV = old_decode_dev(boot_params.hdr.root_dev); +``` + +这段代码用来获取根设备的主次设备号。后面 `initrd` 会通过 `do_mount_root` 函数挂载到这个根设备上。其中主设备号用来识别和这个设备有关的驱动。 +次设备号用来表示使用该驱动的各设备。注意 `old_decode_dev` 函数是从 `boot_params_structure` 中获取了一个参数。我们可以从`x86 linux` 内核启动协议中查到: + +``` +Field name: root_dev +Type: modify (optional) +Offset/size: 0x1fc/2 +Protocol: ALL + + The default root device device number. The use of this field is + deprecated, use the "root=" option on the command line instead +``` + +现在我们来看看 `old_decode_dev` 如何实现的。实际上它只是根据主次设备号调用了 `MKDEV` 来生成一个 `dev_t` 类型的设备。它的实现很简单: + +```C +static inline dev_t old_decode_dev(u16 val) +{ + return MKDEV((val >> 8) & 255, val & 255); +} +``` + +其中 `dev_t` 是用来表示主/次设备号对的一个内核数据类型。但是这个奇怪的 `old` 前缀代表了什么呢?出于历史原因,有两种管理主次设备号的方法。 +第一种方法主次设备号占用2字节。你可以在以前的代码中发现:主设备号占用8bit,次设备号占用8bit。但是这会引入一个问题:最多只能支持256个主设备号和256个次设备号。 +因此后来引入了32bit来表示主次设备号,其中12位用来表示主设备号,20位用来表示次设备号。你可以在 `new_decode_dev` 的实现中找到: + +```C +static inline dev_t new_decode_dev(u32 dev) +{ + unsigned major = (dev & 0xfff00) >> 8; + unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + return MKDEV(major, minor); +} +``` + +如果 `dev` 的值是 `0xffffffff`,经过计算我们可以得到用来表示主设备号的12位值 `0xfff`,表示次设备号的20位值 `0xfffff`。因此经过 `old_decode_dev` 我们最终可以得到在 `ROOT_DEV` 中根设备的主次设备号。 + +Memory Map设置 +----------------------------------------------------------------------- + +下一步是调用 `setup_memory_map` 函数设置内存映射。但是在这之前我们需要设置与显示屏有关的参数(目前有行、列,视频页等,你可以在 [Video mode initialization and transition to protected mode](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html) 中了解), +与拓展显示识别数据,视频模式,引导启动器类型等参数: + +```C +screen_info = boot_params.screen_info; + edid_info = boot_params.edid_info; + saved_video_mode = boot_params.hdr.vid_mode; + bootloader_type = boot_params.hdr.type_of_loader; + if ((bootloader_type >> 4) == 0xe) { + bootloader_type &= 0xf; + bootloader_type |= (boot_params.hdr.ext_loader_type+0x10) << 4; + } + bootloader_version = bootloader_type & 0xf; + bootloader_version |= boot_params.hdr.ext_loader_ver << 4; +``` + +我们可以从启动时候存储在 `boot_params` 结构中获取这些参数信息。之后我们需要设置 `I/O` 内存。众所周知,内核主要做的工作就是资源管理。其中一个资源就是内存。 +我们也知道目前有通过 `I/O` 口和设备内存两种方法实现设备通信。所有有关注册资源的信息可以通过 `/proc/ioports` 和 `/proc/iomem` 获得: +* /proc/ioports - 提供用于设备输入输出通信的一租注册端口区域 +* /proc/iomem - 提供每个物理设备的系统内存映射地址 +我们先来看下 `/proc/iomem`: + +``` +cat /proc/iomem +00000000-00000fff : reserved +00001000-0009d7ff : System RAM +0009d800-0009ffff : reserved +000a0000-000bffff : PCI Bus 0000:00 +000c0000-000cffff : Video ROM +000d0000-000d3fff : PCI Bus 0000:00 +000d4000-000d7fff : PCI Bus 0000:00 +000d8000-000dbfff : PCI Bus 0000:00 +000dc000-000dffff : PCI Bus 0000:00 +000e0000-000fffff : reserved + 000e0000-000e3fff : PCI Bus 0000:00 + 000e4000-000e7fff : PCI Bus 0000:00 + 000f0000-000fffff : System ROM +``` + +可以看到,根据不同属性划分为以十六进制符号表示的一段地址范围。`Linux` 内核提供了用来管理所有资源的一种通用 `API`。全局资源(比如 `PICs` 或者 `I/O` 端口)可以划分为与硬件总线插槽有关的子集。 +`resource` 的主要结构是: + +```C +struct resource { + resource_size_t start; + resource_size_t end; + const char *name; + unsigned long flags; + struct resource *parent, *sibling, *child; +}; +``` + +例如下图中的树形系统资源子集示例。这个结构提供了资源占用的从 `start` 到 `end` 的地址范围(`resource_size_t` 是 `phys_addr_t` 类型,在 `x86_64` 架构上是 `u64`)。 +资源名(你可以在 `/proc/iomem` 输出中看到),资源标记(所有的资源标记定义在 [include/linux/ioport.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/ioport.h) 文件中)。最后三个是资源结构体指针,如下图所示: + +``` ++-------------+ +-------------+ +| | | | +| parent |------| sibling | +| | | | ++-------------+ +-------------+ + | + | ++-------------+ +| | +| child | +| | ++-------------+ +``` + +每个资源子集有自己的根范围资源。`iomem` 的资源 `iomem_resource` 的定义是: + +```C +struct resource iomem_resource = { + .name = "PCI mem", + .start = 0, + .end = -1, + .flags = IORESOURCE_MEM, +}; +EXPORT_SYMBOL(iomem_resource); +TODO EXPORT_SYMBOL +``` + +`iomem_resource` 利用 `PCI mem` 名字和 `IORESOURCE_MEM (0x00000200)` 标记定义了 `io` 内存的根地址范围。就像上文提到的,我们目前的目的是设置 `iomem` 的结束地址。我们需要这样做: + +```C +iomem_resource.end = (1ULL << boot_cpu_data.x86_phys_bits) - 1; +``` + +我们对1左移 `boot_cpu_data.x86_phys_bits`。`boot_cpu_data` 是我们在执行 `early_cpu_init` 的时候初始化的 `cpuinfo_x86` 结构。从字面理解,`x86_phys_bits` 代表系统可达到的最大内存地址时需要的比特数。 +另外,`iomem_resource` 是通过 `EXPORT_SYMBOL` 宏传递的。这个宏可以把指定的符号(例如 `iomem_resource`)做动态链接。换句话说,它可以支持动态加载模块的时候访问对应符号。 +设置完根 `iomem` 的资源地址范围的结束地址后,下一步就是设置内存映射。它通过调用 `setup_memory_map` 函数实现: + +```C +void __init setup_memory_map(void) +{ + char *who; + + who = x86_init.resources.memory_setup(); + memcpy(&e820_saved, &e820, sizeof(struct e820map)); + printk(KERN_INFO "e820: BIOS-provided physical RAM map:\n"); + e820_print_map(who); +} +``` + +首先,我们来看下 `x86_init.resources.memory_setup`。`x86_init` 是一种 `x86_init_ops` 类型的结构体,用来表示项资源初始化,`pci` 初始化平台特定的一些设置函数。 +`x86_init` 的初始化实现在 [arch/x86/kernel/x86_init.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/x86_init.c) 文件中。我不会全部解释这个初始化过程,因为我们只关心一个地方: + +```C +struct x86_init_ops x86_init __initdata = { + .resources = { + .probe_roms = probe_roms, + .reserve_resources = reserve_standard_io_resources, + .memory_setup = default_machine_specific_memory_setup, + }, + ... + ... + ... +} +``` + +我们可以看到,这里的 `memory_setup` 赋值为 `default_machine_specific_memory_setup`,它是我们在对 [boot time](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 过程中的所有 [e820](http://en.wikipedia.org/wiki/E820) 条目经过整理和把内存分区填入 `e820map` 结构体中获得的。 +所有收集的内存分区会用 `printk` 打印出来。你可以通过运行 `dmesg` 命令找到类似于下面的信息: + +``` +[ 0.000000] e820: BIOS-provided physical RAM map: +[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009d7ff] usable +[ 0.000000] BIOS-e820: [mem 0x000000000009d800-0x000000000009ffff] reserved +[ 0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved +[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000be825fff] usable +[ 0.000000] BIOS-e820: [mem 0x00000000be826000-0x00000000be82cfff] ACPI NVS +[ 0.000000] BIOS-e820: [mem 0x00000000be82d000-0x00000000bf744fff] usable +[ 0.000000] BIOS-e820: [mem 0x00000000bf745000-0x00000000bfff4fff] reserved +[ 0.000000] BIOS-e820: [mem 0x00000000bfff5000-0x00000000dc041fff] usable +[ 0.000000] BIOS-e820: [mem 0x00000000dc042000-0x00000000dc0d2fff] reserved +[ 0.000000] BIOS-e820: [mem 0x00000000dc0d3000-0x00000000dc138fff] usable +[ 0.000000] BIOS-e820: [mem 0x00000000dc139000-0x00000000dc27dfff] ACPI NVS +[ 0.000000] BIOS-e820: [mem 0x00000000dc27e000-0x00000000deffefff] reserved +[ 0.000000] BIOS-e820: [mem 0x00000000defff000-0x00000000deffffff] usable +... +... +... +``` + +复制 `BIOS` 增强磁盘设备信息 +-------------------------------------------------------------------------------------------- + +下面两部是通过 `parse_setup_data` 函数解析 `setup_data`,并且把 `BIOS` 的 `EDD` 信息复制到安全的地方。 +`setup_data` 是内核启动头中包含的字段,我们可以在 `x86` 的启动协议中了解: + +``` +Field name: setup_data +Type: write (special) +Offset/size: 0x250/8 +Protocol: 2.09+ + + The 64-bit physical pointer to NULL terminated single linked list of + struct setup_data. This is used to define a more extensible boot + parameters passing mechanism. +``` + +它用来存储不同类型的设置信息,例如设备树 `blob`,`EFI` 设置数据等等。第二步是从 `boot_params` 结构中复制我们在 [arch/x86/boot/edd.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/boot/edd.c) 中 `BIOS` 的 `EDD` 信息到 `edd` 结构中。 + +```C +static inline void __init copy_edd(void) +{ + memcpy(edd.mbr_signature, boot_params.edd_mbr_sig_buffer, + sizeof(edd.mbr_signature)); + memcpy(edd.edd_info, boot_params.eddbuf, sizeof(edd.edd_info)); + edd.mbr_signature_nr = boot_params.edd_mbr_sig_buf_entries; + edd.edd_info_nr = boot_params.eddbuf_entries; +} +``` + +内存描述符初始化 +-------------------------------------------------------------------------------------------------- + +下一步是在初始化阶段完成内存描述符的初始化。我们知道每个进程都有自己的运行内存地址空间。通过调用 `memory descriptor` 可以看到这些特殊数据结构。 +在 `linux` 内核源码中内存描述符是用 `mm_struct` 结构体表示的。`mm_struct` 包含许多不同的与进程地址空间有关的字段,像内核代码/数据段的起始和结束地址, +`brk` 的起始和结束,内存区域的数量,内存区域列表等。这些结构定义在 [include/linux/mm_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/mm_types.h) 中。`task_struct` 结构的 `mm` 和 `active_mm` 字段包含了每个进程自己的内存描述符。 +我们的第一个 `init` 进程也有自己的内存描述符。在之前的[章节](http://0xax.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html) 我们看到过通过 `INIT_TASK` 宏实现 `task_struct` 的部分初始化信息: + +```C +#define INIT_TASK(tsk) \ +{ + ... + ... + ... + .mm = NULL, \ + .active_mm = &init_mm, \ + ... +} +``` + +`mm` 指向进程地址空间,`active_mm` 指向像内核线程这样子不存在地址空间的有效地址空间(你可以在 [documentation](https://www.kernel.org/doc/Documentation/vm/active_mm.txt) 中了解更多内容)。 +接下来我们在初始化阶段完成内存描述符中内核代码段,数据段和 `brk` 段的初始化: + +```C + init_mm.start_code = (unsigned long) _text; + init_mm.end_code = (unsigned long) _etext; + init_mm.end_data = (unsigned long) _edata; + init_mm.brk = _brk_end; +``` + +`init_mm` 是初始化阶段的内存描述符定义: + +```C +struct mm_struct init_mm = { + .mm_rb = RB_ROOT, + .pgd = swapper_pg_dir, + .mm_users = ATOMIC_INIT(2), + .mm_count = ATOMIC_INIT(1), + .mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem), + .page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock), + .mmlist = LIST_HEAD_INIT(init_mm.mmlist), + INIT_MM_CONTEXT(init_mm) +}; +``` + +其中 `mm_rb` 是虚拟内存区域的红黑树结构,`pgd` 是全局页目录的指针,`mm_user` 是使用该内存空间的进程数目,`mm_count` 是主引用计数,`mmap_sem` 是内存区域信号量。 +在初始化阶段完成内存描述符的设置后,下一步是通过 `mpx_mm_init` 完成 `Intel` 内存保护扩展的初始化。下一步是代码/数据/`bss` 资源的初始化: + +```C +code_resource.start = __pa_symbol(_text); + code_resource.end = __pa_symbol(_etext)-1; + data_resource.start = __pa_symbol(_etext); + data_resource.end = __pa_symbol(_edata)-1; + bss_resource.start = __pa_symbol(__bss_start); + bss_resource.end = __pa_symbol(__bss_stop)-1; +``` + +通过上面我们已经知道了一小部分关于 `resource` 结构体的样子。在这里,我们把物理地址段赋值给代码/数据/`bss` 段。你可以在 `/proc/iomem` 中看到: + +``` +00100000-be825fff : System RAM + 01000000-015bb392 : Kernel code + 015bb393-01930c3f : Kernel data + 01a11000-01ac3fff : Kernel bss +在 [arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/setup.c) 中有所有这些结构体的定义: +static struct resource code_resource = { + .name = "Kernel code", + .start = 0, + .end = 0, + .flags = IORESOURCE_BUSY | IORESOURCE_MEM +}; +``` + +本章节涉及的最后一部分就是 `NX` 配置。`NX-bit` 或者 `no-execute` 位是页目录条目的第63比特位。它的作用是控制被映射的物理页面是否具有执行代码的能力。 +这个比特位只会在通过把 `EFER.NXE` 置为1使能 `no-execute` 页保护机制的时候被使用/设置。在 `x86_configure_nx` 函数中会检查 `CPU` 是否支持 `NX-bit`,以及是否被禁用。 +经过检查后,我们会根据结果给 `_supported_pte_mask` 赋值: + +```C +void x86_configure_nx(void) +{ + if (cpu_has_nx && !disable_nx) + __supported_pte_mask |= _PAGE_NX; + else + __supported_pte_mask &= ~_PAGE_NX; +} +``` + +结论 +--------------------------------------------------------------------------------------------------- + +以上是 `linux` 内核初始化过程的第五部分。在这一章我们讲解了有关架构初始化的 `setup_arch` 函数。内容很多,但是我们还没有学习完。其中,`setup_arch` + 是一个很复杂的函数,甚至我不确定我们能在以后的章节中讲完它的所有内容。在这一章节中有一些很有趣的概念像 `Fix-mapped` 地址,`ioremap` 等等。 +如果没听明白也不用担心,在 [Linux kernel memory management Part2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) 还会有更详细的解释。在下一章节我们会继续讲解有关结构初始化的东西, +以及初期内核参数的解析,`pci` 设备的早期转存,直接媒体接口扫描等等。 + +If you have any questions or suggestions write me a comment or ping me at [twitter](https://twitter.com/0xAX). + +**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-insides).** + +Links +-------------------------------------------------------------------------------- + +* [mm vs active_mm](https://www.kernel.org/doc/Documentation/vm/active_mm.txt) +* [e820](http://en.wikipedia.org/wiki/E820) +* [Supervisor mode access prevention](https://lwn.net/Articles/517475/) +* [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/x86_64/kernel-stacks) +* [TSS](http://en.wikipedia.org/wiki/Task_state_segment) +* [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table) +* [Memory mapped I/O](http://en.wikipedia.org/wiki/Memory-mapped_I/O) +* [CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html) +* [PDF. dwarf4 specification](http://dwarfstd.org/doc/DWARF4.pdf) +* [Call stack](http://en.wikipedia.org/wiki/Call_stack) +* [Previous part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html) \ No newline at end of file From 74c1a77fcad8dbbca106cc530ab054a60f27e0e6 Mon Sep 17 00:00:00 2001 From: NeoCui Date: Sun, 29 Oct 2017 15:04:12 +0800 Subject: [PATCH 2/7] Format change --- Initialization/linux-initialization-5.md | 53 +++++++++++++----------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index 8146f7c..a968532 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -4,11 +4,11 @@ Part 5 与系统架构有关的初始化后续分析 =========================================================== -在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html) 中, +在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html)中, 我们讲到了与系统架构有关的 [setup_arch](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c#L856) 函数部分,本文会继续从这里开始。 为 [initrd](http://en.wikipedia.org/wiki/Initrd) 预留了内存之后,下一步是执行 `olpc_ofw_detect` 函数检测系统是否支持 [One Laptop Per Child](http://wiki.laptop.org/go/OFW_FAQ)。 我们不会考虑与平台有关的东西,因此会忽略与平台有关的内容。所以我们继续往下看。 -下一步是执行 `early_trap_init` 函数。这个函数会初始化调试功能 (#DB -当TF标志位和rflags被设置时会被使用)和 `int3` (`#BP`)中断门。 +下一步是执行 `early_trap_init` 函数。这个函数会初始化调试功能 (`#DB` -当 `TF` 标志位和rflags被设置时会被使用)和 `int3` (`#BP`)中断门。 如果你不了解中断,你可以从 [Early interrupt and exception handling](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html) 中学习有关中断的内容。 在 `x86` 架构中,`INT`,`INT0` 和 `INT3` 是支持任务显式调用中断处理函数的特殊指令。`INT3` 指令调用断点(`#BP`)处理函数。 你如果记得,我们在这[部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-2.html) 看到过中断和异常概念: @@ -21,8 +21,7 @@ Part 5 ---------------------------------------------------------------------------------------------- ``` -调试中断 `#DB` 是激活调试器的重要方法。在 [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c) 中定义的 `early_trap_init` 函数设置了 `#DB` 和 `#BP` 处理函数, -并且重新加载了 [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table): +调试中断 `#DB` 是激活调试器的重要方法。用来设置 `#DB` 和 `#BP` 处理函数,并且实现重新加载 [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table) 的 `early_trap_init` 函数的定义在 [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c) 中。 ```C void __init early_trap_init(void) @@ -40,10 +39,11 @@ void __init early_trap_init(void) * 中断/异常处理函数的基地址 * 第三个参数是 `Interrupt Stack Table`。 `IST` 是 [TSS](http://en.wikipedia.org/wiki/Task_state_segment) 的部分内容,是 `x86_64` 引入的新机制。 在内核态处于活跃状态的线程拥有 `16kb` 的内核栈空间。但是在用户空间的线程的内核栈是空的。 -除了线程栈,还有一些与每个 `CPU` 有关的特殊栈。你可以查阅 `linux` 内核文档 - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/x86_64/kernel-stacks) 部分了解这些栈信息。 +除了线程栈,还有一些与每个 `CPU` 有关的特殊栈。你可以查阅 `linux` 内核文档 - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/kernel-stacks) 部分了解这些栈信息。 `x86_64` 提供了像在非屏蔽中断等类似事件中切换新的特殊栈的特性支持。这个特性的名字是 `Interrupt Stack Table`。 每个CPU最多可以有7个 `IST` 条目,每个条目有自己特定的栈。在我们的案例中使用的是 `DEBUG_STACK`。 -`set_intr_gate_ist` 和 `set_system_intr_gate_ist` 和 `set_intr_gate` 的工作原理几乎一样,只有一个区别。 + +`set_intr_gate_ist` 和 `set_system_intr_gate_ist` 与 `set_intr_gate` 的工作原理几乎一样,只有一个区别。 这些函数检查中断号并在内部调用 `_set_gate` : ```C @@ -55,11 +55,14 @@ _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); 但是 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 把 `ist` 设置为 `DEBUG_STACK`,并且 `set_system_intr_gate_ist` 把 `dpl` 设置为优先级最低的 `0x3`。 当中断发生时,硬件加载这个描述符,然后硬件根据 `IST` 的值自动设置新的栈指针。 之后激活对应的中断处理函数。所有的特殊内核栈会在 `cpu_init` 函数中设置好(我们会在后文中提到)。 + 当 `#DB` 和 `#BP` 门向 `idt_descr` 有写操作,我们会调用 `load_idt` 函数来执行 `ldtr` 指令来重新加载 `IDT` 表。 现在我们来了解下中断处理函数并尝试理解它的工作原理。当然,我们不可能在这本书中讲解所有的中断处理函数。 深入学习linux的内核源码是很有意思的事情,我们会在这里讲解 `debug` 处理函数的实现。请自行学习其他的中断处理函数实现。 `#DB` 处理函数 +----------------------------------------------------------------------- + 像上文中提到的,我们在 `set_intr_gate_ist` 中通过 `&debug` 的地址传送 `#DB` 处理函数。[lxr.free-electorns.com](http://lxr.free-electrons.com/ident) 是用来搜索 `linux` 源代码中标识符的很好的资源。 遗憾的是,你在其中找不到 `debug` 处理函数。你只能在 [arch/x86/include/asm/traps.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps.h) 中找到 `debug` 的定义: @@ -67,7 +70,7 @@ _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); asmlinkage void debug(void); ``` -从 `asmlinkage` 属性我们可以知道 `debug` 是由 [assembly](http://en.wikipedia.org/wiki/Assembly_language)语言实现的函数。是的,又是汇编语言 :)。 +从 `asmlinkage` 属性我们可以知道 `debug` 是由 [assembly](http://en.wikipedia.org/wiki/Assembly_language) 语言实现的函数。是的,又是汇编语言 :)。 和其他处理函数一样,`#DB` 处理函数的实现可以在 [arch/x86/kernel/entry_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/entry_64.S) 文件中找到。 都是由 `idtentry` 汇编宏定义的: @@ -125,8 +128,8 @@ ENTRY(\sym) `idtentry` 实现中的另外两个宏分别是 ```assembly - ASM_CLAC - PARAVIRT_ADJUST_EXCEPTION_FRAME + ASM_CLAC + PARAVIRT_ADJUST_EXCEPTION_FRAME ``` 第一个 `ASM_CLAC` 宏依赖于 `CONFIG_X86_SMAP` 这个配置项和考虑安全因素,你可以从[这里](https://lwn.net/Articles/517475)了解更多内容。 @@ -134,18 +137,18 @@ ENTRY(\sym) 下一段代码会检查中断是否有错误码。如果没有则会把 `$-1`(在 `x86_64` 架构下值为 `0xffffffffffffffff`)压入栈: ```assembly - .ifeq \has_error_code - pushq_cfi $-1 - .endif + .ifeq \has_error_code + pushq_cfi $-1 + .endif ``` 为了保证对于所有中断的栈的一致性,我们会把它处理为 `dummy` 错误码。下一步我们从栈指针中减去 `$ORIG_RAX-R15`: ```assembly - subq $ORIG_RAX-R15, %rsp + subq $ORIG_RAX-R15, %rsp ``` -其中,`ORIG_RAX`,`R15` 和其他宏都定义在[arch/x86/include/asm/calling.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/calling.h) 中。`ORIG_RAX-R15` 是120字节。 +其中,`ORIG_RAX`,`R15` 和其他宏都定义在 [arch/x86/include/asm/calling.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/entry/calling.h) 中。`ORIG_RAX-R15` 是120字节。 我们在中断处理过程中需要把所有的寄存器信息存储在栈中,所有通用寄存器会占用这个120字节。 为通用寄存器设置完栈之后,下一步是检查从用户空间产生的中断: @@ -169,7 +172,7 @@ movl $1,%ebx 1: ret ``` -下一步我们把 `pt_regs` 指针存在 `rdi` 中,如果存在错误码就把它存储到 `rsi` 中,然后调用中断处理函数,例如就像[arch/x86/kernel/trap.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps)中的 `do_debug`。 +下一步我们把 `pt_regs` 指针存在 `rdi` 中,如果存在错误码就把它存储到 `rsi` 中,然后调用中断处理函数,例如就像 [arch/x86/kernel/trap.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c)中的 `do_debug`。 `do_debug` 像其他处理函数一样需要两个参数: * pt_regs - 是一个存储在进程内存区域的一组CPU寄存器 @@ -189,7 +192,9 @@ movl $1,%ebx 我们在 `linux` 内核启动[过程](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html)中见过第一种方法(通过 `outb/inb` 指令实现)。 第二种方法是把 `I/O` 的物理地址映射到虚拟地址。当 `CPU` 读取一段物理地址时,它可以读取到映射了 `I/O` 设备的物理 `RAM` 区域。 -`ioremap` 就是用来把设备内存映射到内核地址空间的。像我上面提到的下一个函数时 `early_ioremap_init`,它可以在正常的像 `ioremap` 这样的映射函数可用之前,把 `I/O` 内存映射到内核地址空间以方便读取。 +`ioremap` 就是用来把设备内存映射到内核地址空间的。 + +像我上面提到的下一个函数时 `early_ioremap_init`,它可以在正常的像 `ioremap` 这样的映射函数可用之前,把 `I/O` 内存映射到内核地址空间以方便读取。 我们需要在初期的初始化代码中初始化临时的 `ioremap` 来映射 `I/O` 设备到内存区域。初期的 `ioremap` 实现在 [arch/x86/mm/ioremap.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/mm/ioremap.c) 中可以找到。 在 `early_ioremap_init` 的一开始我们可以看到 `pmd_t` 类型的 `pmd` 指针定义(代表页中间目录条目 `typedef struct {pmdval_t pmd; } pmd_t;` 其中 `pmdval_t` 是无符号长整型)。 然后检查 `fixmap` 是正确对齐的: @@ -221,14 +226,14 @@ pmd_populate_kernel(&init_mm, pmd, bm_pte); 获取根设备的主次设备号 ---------------------------------------------------------------------------- -经过 `ioremap` 初始化完成,你可以看到下面的代码: +`ioremap` 初始化完成后,紧接着是执行下面的代码: ```C ROOT_DEV = old_decode_dev(boot_params.hdr.root_dev); ``` 这段代码用来获取根设备的主次设备号。后面 `initrd` 会通过 `do_mount_root` 函数挂载到这个根设备上。其中主设备号用来识别和这个设备有关的驱动。 -次设备号用来表示使用该驱动的各设备。注意 `old_decode_dev` 函数是从 `boot_params_structure` 中获取了一个参数。我们可以从`x86 linux` 内核启动协议中查到: +次设备号用来表示使用该驱动的各设备。注意 `old_decode_dev` 函数是从 `boot_params_structure` 中获取了一个参数。我们可以从x86 linux内核启动协议中查到: ``` Field name: root_dev @@ -306,7 +311,7 @@ cat /proc/iomem 000f0000-000fffff : System ROM ``` -可以看到,根据不同属性划分为以十六进制符号表示的一段地址范围。`Linux` 内核提供了用来管理所有资源的一种通用 `API`。全局资源(比如 `PICs` 或者 `I/O` 端口)可以划分为与硬件总线插槽有关的子集。 +可以看到,根据不同属性划分为以十六进制符号表示的一段地址范围。linux 内核提供了用来管理所有资源的一种通用 API。全局资源(比如 PICs 或者 I/O 端口)可以划分为与硬件总线插槽有关的子集。 `resource` 的主要结构是: ```C @@ -350,7 +355,7 @@ EXPORT_SYMBOL(iomem_resource); TODO EXPORT_SYMBOL ``` -`iomem_resource` 利用 `PCI mem` 名字和 `IORESOURCE_MEM (0x00000200)` 标记定义了 `io` 内存的根地址范围。就像上文提到的,我们目前的目的是设置 `iomem` 的结束地址。我们需要这样做: +`iomem_resource` 利用 `PCI mem` 名字和 `IORESOURCE_MEM (0x00000200)` 标记定义了 `io` 内存的根地址范围。就像上文提到的,我们目前的目的是设置 `iomem` 的结束地址,我们需要这样做: ```C iomem_resource.end = (1ULL << boot_cpu_data.x86_phys_bits) - 1; @@ -447,7 +452,7 @@ static inline void __init copy_edd(void) 下一步是在初始化阶段完成内存描述符的初始化。我们知道每个进程都有自己的运行内存地址空间。通过调用 `memory descriptor` 可以看到这些特殊数据结构。 在 `linux` 内核源码中内存描述符是用 `mm_struct` 结构体表示的。`mm_struct` 包含许多不同的与进程地址空间有关的字段,像内核代码/数据段的起始和结束地址, `brk` 的起始和结束,内存区域的数量,内存区域列表等。这些结构定义在 [include/linux/mm_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/mm_types.h) 中。`task_struct` 结构的 `mm` 和 `active_mm` 字段包含了每个进程自己的内存描述符。 -我们的第一个 `init` 进程也有自己的内存描述符。在之前的[章节](http://0xax.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html) 我们看到过通过 `INIT_TASK` 宏实现 `task_struct` 的部分初始化信息: +我们的第一个 `init` 进程也有自己的内存描述符。在之前的[章节](http://0xax.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html)我们看到过通过 `INIT_TASK` 宏实现 `task_struct` 的部分初始化信息: ```C #define INIT_TASK(tsk) \ @@ -461,7 +466,7 @@ static inline void __init copy_edd(void) } ``` -`mm` 指向进程地址空间,`active_mm` 指向像内核线程这样子不存在地址空间的有效地址空间(你可以在 [documentation](https://www.kernel.org/doc/Documentation/vm/active_mm.txt) 中了解更多内容)。 +`mm` 指向进程地址空间,`active_mm` 指向像内核线程这样子不存在地址空间的有效地址空间(你可以在这个[文档](https://www.kernel.org/doc/Documentation/vm/active_mm.txt) 中了解更多内容)。 接下来我们在初始化阶段完成内存描述符中内核代码段,数据段和 `brk` 段的初始化: ```C @@ -531,7 +536,7 @@ void x86_configure_nx(void) 结论 --------------------------------------------------------------------------------------------------- -以上是 `linux` 内核初始化过程的第五部分。在这一章我们讲解了有关架构初始化的 `setup_arch` 函数。内容很多,但是我们还没有学习完。其中,`setup_arch` +以上是linux内核初始化过程的第五部分。在这一章我们讲解了有关架构初始化的 `setup_arch` 函数。内容很多,但是我们还没有学习完。其中,`setup_arch` 是一个很复杂的函数,甚至我不确定我们能在以后的章节中讲完它的所有内容。在这一章节中有一些很有趣的概念像 `Fix-mapped` 地址,`ioremap` 等等。 如果没听明白也不用担心,在 [Linux kernel memory management Part2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) 还会有更详细的解释。在下一章节我们会继续讲解有关结构初始化的东西, 以及初期内核参数的解析,`pci` 设备的早期转存,直接媒体接口扫描等等。 @@ -553,4 +558,4 @@ Links * [CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html) * [PDF. dwarf4 specification](http://dwarfstd.org/doc/DWARF4.pdf) * [Call stack](http://en.wikipedia.org/wiki/Call_stack) -* [Previous part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html) \ No newline at end of file +* [Previous part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html) From d27477f1f59cea5d4f7d1539a955e2c4db87112b Mon Sep 17 00:00:00 2001 From: NeoCui Date: Sun, 29 Oct 2017 15:08:48 +0800 Subject: [PATCH 3/7] format change --- Initialization/linux-initialization-5.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index a968532..465ea01 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -39,7 +39,7 @@ void __init early_trap_init(void) * 中断/异常处理函数的基地址 * 第三个参数是 `Interrupt Stack Table`。 `IST` 是 [TSS](http://en.wikipedia.org/wiki/Task_state_segment) 的部分内容,是 `x86_64` 引入的新机制。 在内核态处于活跃状态的线程拥有 `16kb` 的内核栈空间。但是在用户空间的线程的内核栈是空的。 -除了线程栈,还有一些与每个 `CPU` 有关的特殊栈。你可以查阅 `linux` 内核文档 - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/kernel-stacks) 部分了解这些栈信息。 +除了线程栈,还有一些与每个 `CPU` 有关的特殊栈。你可以查阅 linux 内核文档 - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/kernel-stacks) 部分了解这些栈信息。 `x86_64` 提供了像在非屏蔽中断等类似事件中切换新的特殊栈的特性支持。这个特性的名字是 `Interrupt Stack Table`。 每个CPU最多可以有7个 `IST` 条目,每个条目有自己特定的栈。在我们的案例中使用的是 `DEBUG_STACK`。 @@ -58,12 +58,12 @@ _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); 当 `#DB` 和 `#BP` 门向 `idt_descr` 有写操作,我们会调用 `load_idt` 函数来执行 `ldtr` 指令来重新加载 `IDT` 表。 现在我们来了解下中断处理函数并尝试理解它的工作原理。当然,我们不可能在这本书中讲解所有的中断处理函数。 -深入学习linux的内核源码是很有意思的事情,我们会在这里讲解 `debug` 处理函数的实现。请自行学习其他的中断处理函数实现。 +深入学习 linux 的内核源码是很有意思的事情,我们会在这里讲解 `debug` 处理函数的实现。请自行学习其他的中断处理函数实现。 `#DB` 处理函数 ----------------------------------------------------------------------- -像上文中提到的,我们在 `set_intr_gate_ist` 中通过 `&debug` 的地址传送 `#DB` 处理函数。[lxr.free-electorns.com](http://lxr.free-electrons.com/ident) 是用来搜索 `linux` 源代码中标识符的很好的资源。 +像上文中提到的,我们在 `set_intr_gate_ist` 中通过 `&debug` 的地址传送 `#DB` 处理函数。[lxr.free-electorns.com](http://lxr.free-electrons.com/ident) 是用来搜索 linux 源代码中标识符的很好的资源。 遗憾的是,你在其中找不到 `debug` 处理函数。你只能在 [arch/x86/include/asm/traps.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps.h) 中找到 `debug` 的定义: ```C @@ -88,7 +88,7 @@ idtentry debug do_debug has_error_code=0 paranoid=1 shift_ist=DEBUG_STACK 现在我们来看下 `idtentry` 宏的实现。这个宏的定义也在相同的汇编文件中,并且定义了有 `ENTRY` 宏属性的 `debug` 函数。 首先,`idtentry` 宏检查所有的参数是否正确,是否需要切换到特殊栈。接下来检查中断返回的错误码。例如本案例中的 `#DB` 不会返回错误码。 如果有错误码返回,它会调用 `INTR_FRAME` 或者 `XCPT_FRAM` 宏。其实 `XCPT_FRAME` 和 `INTR_FRAME` 宏什么也不会做,只是对中断初始状态编译的时候有用。 -它们使用 `CFI` 指令用来调试。你可以查阅更多有关 `CFI` 指令的信息[CFI](https://sourceware.org/binutils/docs/as/CFI-directives.html)。 +它们使用 `CFI` 指令用来调试。你可以查阅更多有关 `CFI` 指令的信息 [CFI](https://sourceware.org/binutils/docs/as/CFI-directives.html)。 就像 [arch/x86/kernel/entry_64.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/entry_64) 中解释:`CFI` 宏是用来产生更好的回溯的 `dwarf2` 的解开信息。 它们不会改变任何代码。因此我们可以忽略它们。 @@ -190,7 +190,7 @@ movl $1,%ebx * I/O端口 * 设备内存 -我们在 `linux` 内核启动[过程](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html)中见过第一种方法(通过 `outb/inb` 指令实现)。 +我们在 linux 内核启动[过程](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html)中见过第一种方法(通过 `outb/inb` 指令实现)。 第二种方法是把 `I/O` 的物理地址映射到虚拟地址。当 `CPU` 读取一段物理地址时,它可以读取到映射了 `I/O` 设备的物理 `RAM` 区域。 `ioremap` 就是用来把设备内存映射到内核地址空间的。 @@ -233,7 +233,7 @@ ROOT_DEV = old_decode_dev(boot_params.hdr.root_dev); ``` 这段代码用来获取根设备的主次设备号。后面 `initrd` 会通过 `do_mount_root` 函数挂载到这个根设备上。其中主设备号用来识别和这个设备有关的驱动。 -次设备号用来表示使用该驱动的各设备。注意 `old_decode_dev` 函数是从 `boot_params_structure` 中获取了一个参数。我们可以从x86 linux内核启动协议中查到: +次设备号用来表示使用该驱动的各设备。注意 `old_decode_dev` 函数是从 `boot_params_structure` 中获取了一个参数。我们可以从 x86 linux 内核启动协议中查到: ``` Field name: root_dev @@ -450,7 +450,7 @@ static inline void __init copy_edd(void) -------------------------------------------------------------------------------------------------- 下一步是在初始化阶段完成内存描述符的初始化。我们知道每个进程都有自己的运行内存地址空间。通过调用 `memory descriptor` 可以看到这些特殊数据结构。 -在 `linux` 内核源码中内存描述符是用 `mm_struct` 结构体表示的。`mm_struct` 包含许多不同的与进程地址空间有关的字段,像内核代码/数据段的起始和结束地址, +在 linux 内核源码中内存描述符是用 `mm_struct` 结构体表示的。`mm_struct` 包含许多不同的与进程地址空间有关的字段,像内核代码/数据段的起始和结束地址, `brk` 的起始和结束,内存区域的数量,内存区域列表等。这些结构定义在 [include/linux/mm_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/mm_types.h) 中。`task_struct` 结构的 `mm` 和 `active_mm` 字段包含了每个进程自己的内存描述符。 我们的第一个 `init` 进程也有自己的内存描述符。在之前的[章节](http://0xax.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html)我们看到过通过 `INIT_TASK` 宏实现 `task_struct` 的部分初始化信息: @@ -536,7 +536,7 @@ void x86_configure_nx(void) 结论 --------------------------------------------------------------------------------------------------- -以上是linux内核初始化过程的第五部分。在这一章我们讲解了有关架构初始化的 `setup_arch` 函数。内容很多,但是我们还没有学习完。其中,`setup_arch` +以上是 linux 内核初始化过程的第五部分。在这一章我们讲解了有关架构初始化的 `setup_arch` 函数。内容很多,但是我们还没有学习完。其中,`setup_arch` 是一个很复杂的函数,甚至我不确定我们能在以后的章节中讲完它的所有内容。在这一章节中有一些很有趣的概念像 `Fix-mapped` 地址,`ioremap` 等等。 如果没听明白也不用担心,在 [Linux kernel memory management Part2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) 还会有更详细的解释。在下一章节我们会继续讲解有关结构初始化的东西, 以及初期内核参数的解析,`pci` 设备的早期转存,直接媒体接口扫描等等。 From 315eb9e05140745861f9da6ed275221cf6b263ce Mon Sep 17 00:00:00 2001 From: Neo Date: Mon, 30 Oct 2017 02:01:40 -0400 Subject: [PATCH 4/7] update links to Chinese version --- Initialization/linux-initialization-5.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index 465ea01..9579f38 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -9,9 +9,9 @@ Part 5 为 [initrd](http://en.wikipedia.org/wiki/Initrd) 预留了内存之后,下一步是执行 `olpc_ofw_detect` 函数检测系统是否支持 [One Laptop Per Child](http://wiki.laptop.org/go/OFW_FAQ)。 我们不会考虑与平台有关的东西,因此会忽略与平台有关的内容。所以我们继续往下看。 下一步是执行 `early_trap_init` 函数。这个函数会初始化调试功能 (`#DB` -当 `TF` 标志位和rflags被设置时会被使用)和 `int3` (`#BP`)中断门。 -如果你不了解中断,你可以从 [Early interrupt and exception handling](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html) 中学习有关中断的内容。 +如果你不了解中断,你可以从 [初期中断和异常处理](https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html) 中学习有关中断的内容。 在 `x86` 架构中,`INT`,`INT0` 和 `INT3` 是支持任务显式调用中断处理函数的特殊指令。`INT3` 指令调用断点(`#BP`)处理函数。 -你如果记得,我们在这[部分](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-2.html) 看到过中断和异常概念: +你如果记得,我们在这[部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html) 看到过中断和异常概念: ``` ---------------------------------------------------------------------------------------------- @@ -190,7 +190,7 @@ movl $1,%ebx * I/O端口 * 设备内存 -我们在 linux 内核启动[过程](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html)中见过第一种方法(通过 `outb/inb` 指令实现)。 +我们在 linux [内核启动过程](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html)中见过第一种方法(通过 `outb/inb` 指令实现)。 第二种方法是把 `I/O` 的物理地址映射到虚拟地址。当 `CPU` 读取一段物理地址时,它可以读取到映射了 `I/O` 设备的物理 `RAM` 区域。 `ioremap` 就是用来把设备内存映射到内核地址空间的。 @@ -221,7 +221,7 @@ memset(bm_pte, 0, sizeof(bm_pte)); pmd_populate_kernel(&init_mm, pmd, bm_pte); ``` -这就是所有过程。如果你仍然觉得困惑,不要担心。在 [Linux Kernel Memory Managment. Part 2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) 章节会有单独一部分讲解 `ioremap` 和 `fixmaps`。 +这就是所有过程。如果你仍然觉得困惑,不要担心。在 [内核内存管理,第二部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/MM/linux-mm-2.html) 章节会有单独一部分讲解 `ioremap` 和 `fixmaps`。 获取根设备的主次设备号 ---------------------------------------------------------------------------- @@ -272,7 +272,7 @@ static inline dev_t new_decode_dev(u32 dev) Memory Map设置 ----------------------------------------------------------------------- -下一步是调用 `setup_memory_map` 函数设置内存映射。但是在这之前我们需要设置与显示屏有关的参数(目前有行、列,视频页等,你可以在 [Video mode initialization and transition to protected mode](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html) 中了解), +下一步是调用 `setup_memory_map` 函数设置内存映射。但是在这之前我们需要设置与显示屏有关的参数(目前有行、列,视频页等,你可以在 [显示模式初始化和进入保护模式](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-3.html) 中了解), 与拓展显示识别数据,视频模式,引导启动器类型等参数: ```C @@ -393,7 +393,7 @@ struct x86_init_ops x86_init __initdata = { } ``` -我们可以看到,这里的 `memory_setup` 赋值为 `default_machine_specific_memory_setup`,它是我们在对 [boot time](http://0xax.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 过程中的所有 [e820](http://en.wikipedia.org/wiki/E820) 条目经过整理和把内存分区填入 `e820map` 结构体中获得的。 +我们可以看到,这里的 `memory_setup` 赋值为 `default_machine_specific_memory_setup`,它是我们在对 [内核启动](https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-2.html) 过程中的所有 [e820](http://en.wikipedia.org/wiki/E820) 条目经过整理和把内存分区填入 `e820map` 结构体中获得的。 所有收集的内存分区会用 `printk` 打印出来。你可以通过运行 `dmesg` 命令找到类似于下面的信息: ``` @@ -452,7 +452,7 @@ static inline void __init copy_edd(void) 下一步是在初始化阶段完成内存描述符的初始化。我们知道每个进程都有自己的运行内存地址空间。通过调用 `memory descriptor` 可以看到这些特殊数据结构。 在 linux 内核源码中内存描述符是用 `mm_struct` 结构体表示的。`mm_struct` 包含许多不同的与进程地址空间有关的字段,像内核代码/数据段的起始和结束地址, `brk` 的起始和结束,内存区域的数量,内存区域列表等。这些结构定义在 [include/linux/mm_types.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/mm_types.h) 中。`task_struct` 结构的 `mm` 和 `active_mm` 字段包含了每个进程自己的内存描述符。 -我们的第一个 `init` 进程也有自己的内存描述符。在之前的[章节](http://0xax.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html)我们看到过通过 `INIT_TASK` 宏实现 `task_struct` 的部分初始化信息: +我们的第一个 `init` 进程也有自己的内存描述符。在之前的[章节](https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html)我们看到过通过 `INIT_TASK` 宏实现 `task_struct` 的部分初始化信息: ```C #define INIT_TASK(tsk) \ @@ -538,14 +538,14 @@ void x86_configure_nx(void) 以上是 linux 内核初始化过程的第五部分。在这一章我们讲解了有关架构初始化的 `setup_arch` 函数。内容很多,但是我们还没有学习完。其中,`setup_arch` 是一个很复杂的函数,甚至我不确定我们能在以后的章节中讲完它的所有内容。在这一章节中有一些很有趣的概念像 `Fix-mapped` 地址,`ioremap` 等等。 -如果没听明白也不用担心,在 [Linux kernel memory management Part2](https://github.com/0xAX/linux-insides/blob/master/mm/linux-mm-2.md) 还会有更详细的解释。在下一章节我们会继续讲解有关结构初始化的东西, +如果没听明白也不用担心,在 [内核内存管理,第二部分](https://xinqiu.gitbooks.io/linux-insides-cn/content/MM/linux-mm-2.html) 还会有更详细的解释。在下一章节我们会继续讲解有关结构初始化的东西, 以及初期内核参数的解析,`pci` 设备的早期转存,直接媒体接口扫描等等。 -If you have any questions or suggestions write me a comment or ping me at [twitter](https://twitter.com/0xAX). +如果你有任何问题或者建议,你可以留言,也可以直接发送消息给我[twitter](https://twitter.com/0xAX)。 -**Please note that English is not my first language, And I am really sorry for any inconvenience. If you find any mistakes please send me PR to [linux-insides](https://github.com/0xAX/linux-insides).** +**很抱歉,英语并不是我的母语,非常抱歉给您阅读带来不便,如果你发现文中描述有任何问题,清提交一个 PR 到 [linux-insides](https://github.com/MintCN/linux-insides-zh)。** -Links +链接 -------------------------------------------------------------------------------- * [mm vs active_mm](https://www.kernel.org/doc/Documentation/vm/active_mm.txt) From d6c40f111de72815aa7d08d2f1f84ff0e84765a1 Mon Sep 17 00:00:00 2001 From: Neo Date: Mon, 30 Oct 2017 02:08:09 -0400 Subject: [PATCH 5/7] update the link --- Initialization/linux-initialization-5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index 9579f38..492ef36 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -558,4 +558,4 @@ void x86_configure_nx(void) * [CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html) * [PDF. dwarf4 specification](http://dwarfstd.org/doc/DWARF4.pdf) * [Call stack](http://en.wikipedia.org/wiki/Call_stack) -* [Previous part](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html) +* [内核初始化. Part 4.](http://github.com/MintCN/linux-insides-zh/blob/master/initialization/linux-initialization-4.md) From 6ddb6f5e8ff6727e5e7730fdeaf5b53a85a980c5 Mon Sep 17 00:00:00 2001 From: Neo Date: Mon, 30 Oct 2017 02:10:17 -0400 Subject: [PATCH 6/7] translate the title --- Initialization/linux-initialization-5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index 492ef36..015fccf 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -1,4 +1,4 @@ -Part 5 +内核初始化 第五部分 =========================================================== 与系统架构有关的初始化后续分析 From 805c07448297d35b93c2525acbd187a57cda30aa Mon Sep 17 00:00:00 2001 From: NeoCui Date: Sun, 5 Nov 2017 23:03:01 +0800 Subject: [PATCH 7/7] modify linux-initialization-5.md based on mudongliang's suggestion. --- Initialization/linux-initialization-5.md | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Initialization/linux-initialization-5.md b/Initialization/linux-initialization-5.md index 015fccf..5043ddc 100644 --- a/Initialization/linux-initialization-5.md +++ b/Initialization/linux-initialization-5.md @@ -6,8 +6,8 @@ 在之前的[章节](http://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html)中, 我们讲到了与系统架构有关的 [setup_arch](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/setup.c#L856) 函数部分,本文会继续从这里开始。 -为 [initrd](http://en.wikipedia.org/wiki/Initrd) 预留了内存之后,下一步是执行 `olpc_ofw_detect` 函数检测系统是否支持 [One Laptop Per Child](http://wiki.laptop.org/go/OFW_FAQ)。 -我们不会考虑与平台有关的东西,因此会忽略与平台有关的内容。所以我们继续往下看。 +我们为 [initrd](http://en.wikipedia.org/wiki/Initrd) 预留了内存之后,下一步是执行 `olpc_ofw_detect` 函数检测系统是否支持 [One Laptop Per Child support](http://wiki.laptop.org/go/OFW_FAQ)。 +我们不会考虑与平台有关的东西,且会忽略与平台有关的函数。所以我们继续往下看。 下一步是执行 `early_trap_init` 函数。这个函数会初始化调试功能 (`#DB` -当 `TF` 标志位和rflags被设置时会被使用)和 `int3` (`#BP`)中断门。 如果你不了解中断,你可以从 [初期中断和异常处理](https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html) 中学习有关中断的内容。 在 `x86` 架构中,`INT`,`INT0` 和 `INT3` 是支持任务显式调用中断处理函数的特殊指令。`INT3` 指令调用断点(`#BP`)处理函数。 @@ -21,7 +21,7 @@ ---------------------------------------------------------------------------------------------- ``` -调试中断 `#DB` 是激活调试器的重要方法。用来设置 `#DB` 和 `#BP` 处理函数,并且实现重新加载 [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table) 的 `early_trap_init` 函数的定义在 [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c) 中。 +调试中断 `#DB` 是激活调试器的重要方法。`early_trap_init` 函数的定义在 [arch/x86/kernel/traps.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/traps.c) 中。这个函数用来设置 `#DB` 和 `#BP` 处理函数,并且实现重新加载 [IDT](http://en.wikipedia.org/wiki/Interrupt_descriptor_table)。 ```C void __init early_trap_init(void) @@ -32,7 +32,7 @@ void __init early_trap_init(void) } ``` -我们之前已经看到过 `set_intr_gate` 中关于中断的实现。这里的 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 也是类似的实现。 +我们之前中断相关章节中看到过 `set_intr_gate` 的实现。这里的 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 也是类似的实现。 这两个函数都需要三个参数: * 中断号 @@ -41,7 +41,7 @@ void __init early_trap_init(void) 在内核态处于活跃状态的线程拥有 `16kb` 的内核栈空间。但是在用户空间的线程的内核栈是空的。 除了线程栈,还有一些与每个 `CPU` 有关的特殊栈。你可以查阅 linux 内核文档 - [Kernel stacks](https://www.kernel.org/doc/Documentation/x86/kernel-stacks) 部分了解这些栈信息。 `x86_64` 提供了像在非屏蔽中断等类似事件中切换新的特殊栈的特性支持。这个特性的名字是 `Interrupt Stack Table`。 -每个CPU最多可以有7个 `IST` 条目,每个条目有自己特定的栈。在我们的案例中使用的是 `DEBUG_STACK`。 +每个CPU最多可以有 7 个 `IST` 条目,每个条目有自己特定的栈。在我们的案例中使用的是 `DEBUG_STACK`。 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 与 `set_intr_gate` 的工作原理几乎一样,只有一个区别。 这些函数检查中断号并在内部调用 `_set_gate` : @@ -51,7 +51,7 @@ BUG_ON((unsigned)n > 0xFF); _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); ``` -其中, `set_intr_gate` 把 [dpl](http://en.wikipedia.org/wiki/Privilege_level) 和 `ist` 置为0来调用 `_set_gate`。 +其中, `set_intr_gate` 把 [dpl](http://en.wikipedia.org/wiki/Privilege_level) 和 `ist` 置为 0 来调用 `_set_gate`。 但是 `set_intr_gate_ist` 和 `set_system_intr_gate_ist` 把 `ist` 设置为 `DEBUG_STACK`,并且 `set_system_intr_gate_ist` 把 `dpl` 设置为优先级最低的 `0x3`。 当中断发生时,硬件加载这个描述符,然后硬件根据 `IST` 的值自动设置新的栈指针。 之后激活对应的中断处理函数。所有的特殊内核栈会在 `cpu_init` 函数中设置好(我们会在后文中提到)。 @@ -63,7 +63,7 @@ _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS); `#DB` 处理函数 ----------------------------------------------------------------------- -像上文中提到的,我们在 `set_intr_gate_ist` 中通过 `&debug` 的地址传送 `#DB` 处理函数。[lxr.free-electorns.com](http://lxr.free-electrons.com/ident) 是用来搜索 linux 源代码中标识符的很好的资源。 +像上文中提到的,我们在 `set_intr_gate_ist` 中通过 `&debug` 的地址传送 `#DB` 处理函数。[lxr.free-electorns.com](http://lxr.free-electrons.com/ident) 是很好的用来搜索 linux 源代码中标识符的资源。 遗憾的是,你在其中找不到 `debug` 处理函数。你只能在 [arch/x86/include/asm/traps.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/traps.h) 中找到 `debug` 的定义: ```C @@ -82,7 +82,7 @@ idtentry debug do_debug has_error_code=0 paranoid=1 shift_ist=DEBUG_STACK * 中断条目点的名字 * 中断处理函数的名字 * 是否有中断错误码 -* paranoid - 如果这个参数置为1,则切换到特殊栈 +* paranoid - 如果这个参数置为 1,则切换到特殊栈 * shift_ist - 支持中断期间切换栈 现在我们来看下 `idtentry` 宏的实现。这个宏的定义也在相同的汇编文件中,并且定义了有 `ENTRY` 宏属性的 `debug` 函数。 @@ -113,7 +113,7 @@ ENTRY(\sym) 当中断发生后经过初期的中断/异常处理,我们可以知道栈内的格式是这样的: ``` - +-----------------------+ + +-----------------------+ | | +40 | SS | +32 | RSP | @@ -148,8 +148,8 @@ ENTRY(\sym) subq $ORIG_RAX-R15, %rsp ``` -其中,`ORIG_RAX`,`R15` 和其他宏都定义在 [arch/x86/include/asm/calling.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/entry/calling.h) 中。`ORIG_RAX-R15` 是120字节。 -我们在中断处理过程中需要把所有的寄存器信息存储在栈中,所有通用寄存器会占用这个120字节。 +其中,`ORIG_RAX`,`R15` 和其他宏都定义在 [arch/x86/include/asm/calling.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/entry/calling.h) 中。`ORIG_RAX-R15` 是 120 字节。 +我们在中断处理过程中需要把所有的寄存器信息存储在栈中,所有通用寄存器会占用这个 120 字节。 为通用寄存器设置完栈之后,下一步是检查从用户空间产生的中断: ```assembly @@ -213,7 +213,7 @@ for (i = 0; i < FIX_BTMAPS_SLOTS; i++) slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); ``` -之后我们就获得了 `FIX_BTMAP_BEGIN` 的页中间目录条目,并把它赋值给了 `pmd` 变量,把启动时间页表 `bm_pte` 写满0。然后调用 `pmd_populate_kernel` 函数设置给定的页中间目录的页表条目: +之后我们就获得了 `FIX_BTMAP_BEGIN` 的页中间目录条目,并把它赋值给了 `pmd` 变量,把启动时间页表 `bm_pte` 写满 0。然后调用 `pmd_populate_kernel` 函数设置给定的页中间目录的页表条目: ```C pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)); @@ -255,8 +255,8 @@ static inline dev_t old_decode_dev(u16 val) ``` 其中 `dev_t` 是用来表示主/次设备号对的一个内核数据类型。但是这个奇怪的 `old` 前缀代表了什么呢?出于历史原因,有两种管理主次设备号的方法。 -第一种方法主次设备号占用2字节。你可以在以前的代码中发现:主设备号占用8bit,次设备号占用8bit。但是这会引入一个问题:最多只能支持256个主设备号和256个次设备号。 -因此后来引入了32bit来表示主次设备号,其中12位用来表示主设备号,20位用来表示次设备号。你可以在 `new_decode_dev` 的实现中找到: +第一种方法主次设备号占用 2 字节。你可以在以前的代码中发现:主设备号占用 8 bit,次设备号占用 8 bit。但是这会引入一个问题:最多只能支持 256 个主设备号和 256 个次设备号。 +因此后来引入了 32 bit 来表示主次设备号,其中 12 位用来表示主设备号,20 位用来表示次设备号。你可以在 `new_decode_dev` 的实现中找到: ```C static inline dev_t new_decode_dev(u32 dev) @@ -267,7 +267,7 @@ static inline dev_t new_decode_dev(u32 dev) } ``` -如果 `dev` 的值是 `0xffffffff`,经过计算我们可以得到用来表示主设备号的12位值 `0xfff`,表示次设备号的20位值 `0xfffff`。因此经过 `old_decode_dev` 我们最终可以得到在 `ROOT_DEV` 中根设备的主次设备号。 +如果 `dev` 的值是 `0xffffffff`,经过计算我们可以得到用来表示主设备号的 12 位值 `0xfff`,表示次设备号的20位值 `0xfffff`。因此经过 `old_decode_dev` 我们最终可以得到在 `ROOT_DEV` 中根设备的主次设备号。 Memory Map设置 ----------------------------------------------------------------------- @@ -519,7 +519,7 @@ static struct resource code_resource = { }; ``` -本章节涉及的最后一部分就是 `NX` 配置。`NX-bit` 或者 `no-execute` 位是页目录条目的第63比特位。它的作用是控制被映射的物理页面是否具有执行代码的能力。 +本章节涉及的最后一部分就是 `NX` 配置。`NX-bit` 或者 `no-execute` 位是页目录条目的第 63 比特位。它的作用是控制被映射的物理页面是否具有执行代码的能力。 这个比特位只会在通过把 `EFER.NXE` 置为1使能 `no-execute` 页保护机制的时候被使用/设置。在 `x86_configure_nx` 函数中会检查 `CPU` 是否支持 `NX-bit`,以及是否被禁用。 经过检查后,我们会根据结果给 `_supported_pte_mask` 赋值: