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):
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
- Popular Resources
- Popular amplifiers
- Learn ARM development(16)
- Learn ARM development(17)
- Learn ARM development(18)
- Embedded system debugging simulation tool
- A small question that has been bothering me recently has finally been solved~~
- Learn ARM development (1)
- Learn ARM development (2)
- Learn ARM development (4)
- Learn ARM development (6)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- CGD and Qorvo to jointly revolutionize motor control solutions
- CGD and Qorvo to jointly revolutionize motor control solutions
- Keysight Technologies FieldFox handheld analyzer with VDI spread spectrum module to achieve millimeter wave analysis function
- Infineon's PASCO2V15 XENSIV PAS CO2 5V Sensor Now Available at Mouser for Accurate CO2 Level Measurement
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- A new chapter in Great Wall Motors R&D: solid-state battery technology leads the future
- Naxin Micro provides full-scenario GaN driver IC solutions
- Interpreting Huawei’s new solid-state battery patent, will it challenge CATL in 2030?
- Are pure electric/plug-in hybrid vehicles going crazy? A Chinese company has launched the world's first -40℃ dischargeable hybrid battery that is not afraid of cold
- Today's live broadcast: Battery management chip solution design and precautions
- [Repost] 7 PCB layout principles, good PCB design depends on it
- It’s terrible. Two manufacturers in the PCB industry are engaged in a price war. Did you get any benefits yesterday?
- 【McQueen Trial】Python Programming (4)
- Crazy Shell AI open source drone interruption (key detection)
- Data flow modeling
- [Top Micro Smart Display Screen Review] 4. Network port remote update and communication function test and questions
- IAR for MSP430 Two methods of adding custom header files
- CC2640R2F supports Alibaba Cloud Link IoT platform
- [Mill MYB-YT507 development board trial experience] + unboxing experience