Analysis of the bootloader execution flow after the system is powered on and the startup process of ARM Linux

Publisher:翅膀小鹰Latest update time:2018-02-14 Source: eefocus Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

    1 Introduction

    Linux was originally developed by Linus Torvalds, a student at the University of Helsinki in Sweden, in 1991. Later, with the support of GNU, Linux has achieved tremendous development. Although Linux is not as popular as Microsoft's  Windows  operating system on desktop PCs, its rapid development and increasing number of users are also something that Microsoft cannot underestimate. In recent years, the rapid development of Linux in the embedded field has injected new vitality into Linux.

    From a software perspective, an embedded Linux system can be divided into four parts[1]: bootloader,

    Linux kernel, file system, applications.

    The bootloader is the first code executed after the system is started or reset. It is mainly used to initialize the processor and peripherals, and then call the Linux kernel. After completing the system initialization, the Linux kernel needs to mount a file system as the root file system (Root Filesystem). The root file system is the core component of the Linux system. It can be used as a storage area for files and data in the Linux system. It usually also includes system configuration files and libraries required to run application software. The application can be said to be the "soul" of the embedded system. The functions it implements are usually the goals of designing the embedded system. Without the support of the application, any well-designed embedded system on hardware has no practical significance.

    From the above analysis, we can see the relationship and role of bootloader and Linux kernel in embedded systems. Although bootloader has the functions of initializing the system and executing commands input by users during operation, its most fundamental function is to start the Linux kernel. In the process of embedded system development, a large part of the energy is spent on the development or transplantation of bootloader and Linux kernel. If you can clearly understand the execution process of bootloader and the startup process of Linux, it will help to clarify the work required in the development process, thereby accelerating the development process of embedded systems. This is exactly what this article is going to study.

    2. Bootloader

    2.1 Concept and Function of Bootloader Bootloader is the boot loader of embedded system. It is the first program to run after the system is powered on. Its function is similar to  BIOS on PC . After completing the initialization task of the system, it will copy the Linux kernel in the non-volatile memory (usually Flash or DOC, etc.) to RAM, and then jump to the first instruction of the kernel to continue execution, so as to start the Linux kernel. It can be seen that the bootloader and the Linux kernel are closely related. In order to clearly understand the boot process of the Linux kernel, we must first understand the execution process of the bootloader, so that we can have a clear grasp of the entire boot process of the embedded system.

    2.2 Bootloader Execution Process The first instruction address executed after power-on or reset of different processors is different. For ARM processors, the address is 0x00000000. For general embedded systems, non-volatile memory such as Flash is usually mapped to this address, and the bootloader is located at the front end of the memory, so the first program executed after the system is powered on or reset is the bootloader. Because the memory storing the bootloader is different, the execution process of the bootloader is also different, which will be analyzed in detail below.

    The non-volatile memory widely used in embedded systems is usually Flash, which is divided into Nor Flash and Nand Flash. The difference between them is that Nor Flash supports on-chip execution (XIP, eXecute In Place), so the code can be executed directly on the Flash without having to be copied to RAM for execution. Nand Flash does not support XIP, so in order to execute the code on Nand Flash, it must first be copied to RAM and then jumped to RAM for execution. In actual applications, the bootloader can be designed to be very complex depending on the required functions. In addition to completing basic tasks such as initializing the system and calling the Linux kernel, it can also execute many user-entered commands, such as setting Linux startup parameters and partitioning Flash. It can also be designed to be very simple and only complete the most basic functions. However, in order to achieve the purpose of booting the Linux kernel, all bootloaders must have the following functions [2]:

    1) Initialize RAM

    Because the Linux kernel usually runs in RAM, the bootloader must set up and initialize RAM before calling the Linux kernel to prepare for calling the Linux kernel. The tasks of initializing RAM include setting the control register parameters of the CPU so that the RAM can be used normally and detecting the RAM size.

    2) Initialize the serial port The serial port plays a very important role in the Linux boot process. It is one of the ways for the Linux kernel and the user to interact. Linux can output information through the serial port during the boot process, so that you can clearly understand the Linux boot process. Although it is not a task that the bootloader must complete, outputting information through the serial port is a powerful tool for debugging the bootloader and the Linux kernel, so general bootloaders will initialize a serial port as a debugging port during execution.

    3) Detect processor type

    Before calling the Linux kernel, the bootloader must detect the system's processor type and save it in a constant to provide to the Linux kernel. During the boot process, the Linux kernel will call the corresponding initialization program according to the processor type.

    4) Set Linux boot parameters

    The bootloader must set and initialize the Linux kernel startup parameters during execution. Currently, there are two main ways to pass startup parameters: struct param_struct and struct tag (tagged list). struct param_struct is an older parameter passing method and is used more frequently in kernels prior to version 2.4.

    Since version 2.4, the Linux kernel basically uses the tag list method. However, in order to maintain compatibility with previous versions, it still supports the struct param_struct parameter passing method, but it will be converted into the tag list method during the kernel startup process.

    The tag list method is a relatively new parameter passing method. It must start with ATAG_CORE and end with ATAG_NONE. Other lists can be added in the middle as needed. The Linux kernel will perform corresponding initialization work according to the startup parameters during the startup process.

    5) Calling the Linux kernel image

    The last task of the bootloader is to call the Linux kernel. If the Linux kernel is stored in Flash and can be run directly on it (Flash here refers to Nor Flash), then you can jump directly to the kernel for execution. However, since there are various restrictions on executing code in Flash, and the speed is far slower than RAM, general embedded systems copy the Linux kernel to RAM and then jump to RAM for execution. In either case, before jumping to the Linux kernel for execution, the CPU registers must meet the following conditions: r0 = 0, r1 = processor type, r2 = address of the tag list in RAM.


    3. Linux kernel startup process

    After the bootloader copies the Linux kernel image to RAM, the Linux kernel can be started by the following code: call_linux(0, machine_type, kernel_params_base).

    Among them, machine_tpye is the processor type detected by the bootloader, and kernel_params_base is the address of the boot parameters in RAM. In this way, the parameters required for Linux startup are passed from the bootloader to the kernel. There are two images of the Linux kernel: one is the uncompressed kernel, called Image, and the other is its compressed version, called zImage. Depending on the kernel image, the startup of the Linux kernel is also different at the beginning. zImage is formed by compressing Image, so its size is smaller than Image. But in order to use zImage, you must add decompression code at the beginning of it, and zImage can only be executed after decompression, so its execution speed is slower than Image. But considering that the storage capacity of embedded systems is generally small, using zImage can occupy less storage space, so it is worth sacrificing a little performance. Therefore, general embedded systems all use compressed kernels.

    For ARM series processors, the entry program of zImage is arch/arm/boot/compressed/head.S. It completes the following tasks in sequence: turns on MMU and Cache, calls decompress_kernel() to decompress the kernel, and finally starts the uncompressed kernel image by calling call_kernel(). The following will analyze the Linux kernel startup process after this.

    3.1 Linux kernel entry

    The entry point of the Linux uncompressed kernel is located in the stext segment of the file /arch/arm/kernel/head-armv.S. The base address of this segment is the jump address of the compressed kernel after decompression. If the kernel loaded in the system is an uncompressed image, the bootloader will jump directly to this address after copying the kernel from Flash to RAM, thereby starting the Linux kernel. The entry point files of Linux systems with different architectures are different, and because the file is related to the specific architecture, it is generally written in assembly language [3]. For Linux systems based on ARM processors, this file is head-armv.S. The program searches for the processor core type and the processor type, calls the corresponding initialization function, then creates a page table, and finally jumps to the start_kernel() function to start the kernel initialization work.

    The processor core type detection is completed in the assembly sub-function __lookup_processor_type. It can be called by the following code: bl __lookup_processor_type. When the __lookup_processor_type call ends and returns to the original program, the return result will be saved in the register. Among them, r8 saves the page table flag, r9 saves the processor ID number, and r10 saves the address of the struproc_info_list structure related to the processor.

    The processor type detection is completed in the assembly sub-function __lookup_architecture_type. Similar to __lookup_processor_type, it is called through the code: "bl __lookup_processor_type". When the function returns, the return structure will be saved in the three registers r5, r6 and r7. Among them, r5 saves the starting base address of RAM, r6 saves the I/O base address, and r7 saves the I/O page table offset address. After the detection of the processor core and processor type is completed, the __create_page_tables sub-function will be called to create the page table. What it has to do is to map the physical address of the 4M space starting from the RAM base address to the virtual address starting from 0xC0000000. For my S3C2410 development board, the RAM is connected to the physical address 0x30000000. After calling __create_page_tables, the physical addresses 0x30000000 to 0x30400000 will be mapped to the virtual addresses 0xC0000000 to 0xC0400000.

    When all initialization is completed, use the following code to jump to the entry function start_kernel() of the C program to start the subsequent kernel initialization work:

    b SYMBOL_NAME(start_kernel)

    3.2 start_kernel function

    start_kernel is the entry function for all Linux platforms after entering the system kernel initialization. It mainly completes the remaining initialization work related to the hardware platform. After a series of kernel-related initializations, it calls the first user process - the init process and waits for the execution of the user process. In this way, the entire Linux kernel is started. The specific work done by this function is [4][5]

    :

    1) Call the setup_arch() function to perform the first initialization work related to the architecture;

    This function has different definitions for different architectures. For the ARM platform, this function is defined in arch/arm/kernel/Setup.c. It first initializes the processor core by detecting the processor type, then initializes the memory structure according to the system-defined EMI nfo structure through the bootmem_init() function, and finally calls paging_init() to turn on the MMU, create the kernel page table, and map all physical memory and IO space.

    2) Create an exception vector table and initialize the interrupt handling function;

    3) Initialize the system core process scheduler and clock interrupt processing mechanism;

    4) Initialize the serial console (serial-console);

    During the initialization process, ARM-Linux usually initializes a serial port as the kernel console, so that the kernel can output information through the serial port during the startup process so that developers or users can understand the system startup process.

    5) Create and initialize the system cache to provide cache for various memory call mechanisms, including dynamic memory allocation, virtual file system (Virtual File System) and page cache.

    6) Initialize memory management, detect memory size and memory occupied by the kernel;

    7) Initialize the system's inter-process communication mechanism (IPC);

    When all the above initialization work is completed, the start_kernel() function will call the rest_init() function to perform the final initialization, including creating the first process of the system - the init process to end the kernel startup. The init process first performs a series of hardware initializations, and then mounts the root file system through the parameters passed by the command line. Finally, the init process will execute the "init=" startup parameter passed by the user to execute the command specified by the user, or execute one of the following processes:

    execve("/sbin/init",argv_init,envp_init);

    execve("/etc/init",argv_init,envp_init);

    execve("/bin/init",argv_init,envp_init);

    execve("/bin/sh",argv_init,envp_init).

    When all initialization work is completed, the cpu_idle() function will be called to put the system in an idle state and wait for the execution of user programs. At this point, the entire Linux kernel is started.

    4 Conclusion

    The Linux kernel is a very large project. After more than ten years of development, it has grown from a few hundred KB to several hundred megabytes. It is very difficult to clearly understand every process it executes. However, in the process of embedded development, we do not need to know the internal working mechanism of Linux very well. As long as we modify the hardware-related parts of the Linux kernel appropriately, we can port Linux to other target platforms. By analyzing the boot process of Linux, we can see which parts are related to hardware and which are the functions that have been implemented in the Linux kernel, so that we can target them in the process of porting Linux. The layered design of the Linux kernel will make Linux porting easier.

    references

    [1] Zhan Rongkai. Insider information of embedded system bootloader technology [EB/OL]. /index.html, 2003.12.

    [2] Russell King. Booting ARM Linux[Z]. Linux Documentation. May 2002

    [3] Liu Miao. Embedded System Interface Design and Linux Driver Development[M]. Beijing University of Aeronautics and Astronautics Press. June 2006

    [4] William Gatliff. The Linux 2.4 Kernel's Startup Procedure[DB/CD]. 2002 Embedded System Conference San Francisco, March..2002

    [5] Claudia Salzberg Rodriguez, Gordon Fischer, Steven Smolski. Linux Kernel Programming [M]. Chen Lijun, He Yan, Liu Xialin. Machinery Industry Press. 2006.7 Paper source (author):


Reference address:Analysis of the bootloader execution flow after the system is powered on and the startup process of ARM Linux

Previous article:Discussion on the porting of μC/OS-II to LPC2119 and the basic knowledge needed before porting
Next article:A method for realizing a unified interface of multiple serial buses based on Linux system

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号