Author: Anand K santhanam (asanthan@in.ibm.com), software engineer, IBM Global Services
Vishal Kulkarni, software engineer, IBM Global Services
Linux is developing steadily in the field of embedded development. Because Linux uses GPL (see references later in this article ), therefore, anyone who is interested in customizing Linux to PDA, handheld computers, or wearable devices can download their kernels and applications from the Internet for free and start porting or development. Many Linux improvements cater to the embedded/real-time market. These include RTLinux (Real-Time Linux), uClinux (Linux for non-MMU devices), and montavista Linux (Linux distribution for ARM, MIPS, and PPC), arm-Linux (Linux on ARM) and other Linux systems (see references to link to these terms and other products mentioned in this article .)
Embedded Linux development involves three levels: Boot Loader, Linux kernel, and graphical user interface (or GUI ). In this article, we will focus on some basic concepts related to these three layers, and gain an in-depth understanding of how the boot loader, kernel, and file systems interact; it will also look at part of the many options available for file systems, guis, and boot loaders.
Bootstrap loader
The bootstrap loader is usually the first piece of code executed on any hardware. In conventional systems such as desktops, boot loaders are usually loaded into Master Boot Record (MBR, or load the first sector of the disk where Linux resides. In general, the BIOS transfers control to the boot loader on a desktop or other system. This raises an interesting question: who loads the Boot Loader (in most cases) on an embedded device without a BIOS?
There are two common technologies to solve this problem: specialized software and tiny boot code ).
Dedicated SoftwareYou can directly interact with Flash devices on a remote system and install the boot loader in a given location of flash.Flash DevicesIt is a special chip that functions like a storage device and can store information permanently-that is, it will not erase its content during reboot.
This software usage goal (in embedded development, embedded devices are usually calledTarget). It is an interface used to execute commands from external input (usually from host machines. Jflash-Linux is a popular tool for writing flash memory directly. It supports a large number of Flash chips. In this article, an i386 machine is calledHost. Of course, this means that the target needs a parallel interface to enable it to communicate with the host. Jflash-Linux can be used in both Linux and Windows versions. You can run the following command on the command line to start it:
Some types of embedded devices haveTiny Boot Code-Based on several bytes of instruction-It initializes some DRAM settings and enables a serial (or USB, or Ethernet) port on the target to communicate with the Host Program. Then, the host program or loader can use this connection to send the bootstrap loader to the target and write it into flash memory.
After installing it and giving it control, the bootstrap loader performs the following functions:
- CPU initialization speed
- Initializes the memory, including enabling the memory library and initializing the memory configuration registers.
- Initialize the serial port (if any)
- Enable command/data cache
- Set Stack pointer
- Set the parameter area and construct the parameter structure and tag (this is an important step because the kernel uses the bootstrap parameter to identify the root device, page size, memory size, and more)
- Perform post (power-on self-check) to identify the existing device and report any problems
- Support pending/restoring power management
- Jump to the start of the kernel
The typical memory layout of a system with boot loader, parameter structure, kernel, and file system may be as follows:
Listing 1. Typical memory Layout
/* Top of memory */
Bootloader
Parameter Area
Kernel
Filesystem
/* End of memory */
/* End of memory */
Some popular and free Linux boot loaders on embedded devices include blob, Redboot, and bootldr (see references for links ). All these boot loaders are used in Linux based on ARM devices, and jflash-Linux tools are required for installation.
Once the boot loader is installed in the target flash memory, it will execute all the initialization work we mentioned above. Then, it is ready to receive the kernel and file system from the host. Once the kernel is loaded, the boot loader transfers the control to the kernel.
Set tool chains
Set the toolchain to create a Build Environment on the host machine for compiling the kernel and applications that will run on the target-this is because the target hardware may not have a host-compatible binary execution level.
A tool chain consists of a set of components used to compile, compile, and link the kernel and applications. These components include:
Binutils-A set of utilities used to operate binary files. They include AR, As, objdump, objcopy
- Sample utility.
- Gcc-Gnu c compiler.
- Glibc-All user applications are linked to the C library. Avoid using any c-library function kernel and other applications to compile without this library.
Build a tool chain to establish a cross-compiler environment.Local CompilerCompile commands of the same processor as the local machine.Cross CompilerIt runs on a certain processor, but can compile the commands of another processor. Setting the cross-compiler toolchain with repeated headers is not a simple task: it includes downloading source code, patching, configuration, compilation, setting header files, installation, and many other operations. In addition, such a thorough construction process has a huge demand for memory and hard disks. If there is not enough memory and hard disk space, many problems will pop up during the build phase due to problems such as relevance, configuration or header file settings.
Therefore, obtaining pre-compiled binary files from the Internet is a good thing (but it is not good that most of them are currently limited to ARM-based systems, but will change sooner or later ). Some popular pre-compiled tool chains include those from Compaq (familiar Linux), lart (lart Linux), and embedian (based on Debian but not related to it) tool chain-all these tool chains are used for ARM-based platforms.
Kernel settings
The Linux community is actively adding feature components and support for new hardware, correcting errors in the kernel, and making regular improvements in a timely manner. This results in a stable new release of the Linux tree every six months (or less than six months. Different maintainers maintain different kernel trees and patches for specific architectures. When you select a kernel for a project, you need to evaluate the stability of the latest release, whether it meets project requirements and hardware platforms, the degree of comfort from a programming perspective, and other uncertain aspects. It is also important to find all the patches that need to be applied to the basic kernel to adjust the kernel for a specific architecture.
Kernel Layout
The kernel layout is divided into the architecture-specific part and the architecture-independent part. In the kernel, the architecture-specific part is first executed. Hardware registers are configured, memory ing is configured, architecture-specific Initialization is executed, and control is transferred to the architecture-independent part of the kernel. The rest of the system is initialized during this second phase. The directory ARCH/Under the kernel tree is composed of different sub-directories, each sub-directory is used for a different architecture (MIPs, arm, i386, iSCSI, PPC, etc ). Each of these subdirectories contains the kernel/and mm/subdirectories, they contain system-specific code to perform operations such as initializing memory, setting IRQ, enabling cache, and setting kernel page tables. Once the kernel is loaded and controlled, these functions are called first, and the rest of the system is initialized.
Based on available system resources and boot loader functions, the kernel can be compiled into vmlinux, image, or zimage. The main difference between vmlinux and zimage is thatVmlinuxIs an actual (uncompressed) executable file, andZimageIt is a compressed self-decompressed file that contains more or less the same information-only compression of it to process (usually forced by Intel) The 640 kb boot time limit. For more information about these authoritative interpretations, seeLinux magazine"Kernel configuration: dealing with the unexpected" (see references ).
Kernel link and Mount
Once the kernel is compiled for the target system, it is loaded into the target flash memory by using the boot loader ), the kernel is loaded into the memory of the target system (in DRAM or flash memory ). The boot loader communicates with the host by using a serial, USB, or Ethernet port to transmit the kernel to the target flash memory or dram. After
After the target is fully loaded, the boot loader passes control to the address where the kernel is loaded.
The kernel executable file consists of many object files linked together. Object files have many sections, such as text, Data, init data, and bass. These object files are calledLinker script. The function of this linker script is to map the sections of the input object file to the output file. In other words, it links all input object files to a single executable file, load the sections of the executable file to the specified address.Vmlinux. LDSIs the kernel linker script that exists in the arch/<target>/directory. It is responsible for linking each section of the kernel and loading them to a specific offset in the memory. The typical vmlinux. LDS looks like this:
List 2.Typical vmlinux. LDSFile
Output_arch (<arch>)/* <arch> includes architecture type */
Entry (stext)/* stext is the kernel entry point */ Sections/* Sections command describes the Layout Of the output file */ { . = Textaddr;/* textaddr is LMA for the kernel */ . Init: {/* init code and Data */ _ Stext =.;/* first section is stext followed By _ init data section */ _ Init_begin = .; * (. Text. init) _ Init_end = .; } . Text: {/* Real Text Segment follows _ init_data Section */ _ Text = .; * (. Text) _ Etext =.;/* end of text section */ } . Data :{ _ DATA =.;/* Data section comes after text section */ * (. Data) _ Edata = .; }/* Data section ends here */ . BSS: {/* BSS section follows symbol table section */ _ Bss_start = .; * (. BSS) _ End =.;/* BSS section ends here */ } }
|
LMA is the address of the mounted module. It indicates the address in the target virtual memory of the kernel to be loaded. Textaddr is the virtual starting address of the kernel, and its value is specified in makefile under ARCH/<target>. This address must match the address used by the boot loader.
Once the boot loader copies the kernel to flash memory or dram, the kernel is relocated to textaddr-it passes
It is often in dram. Then, the pilot loader transfers the control to this address so that the inner nuclear energy starts to execute.
Parameter transfer and kernel boot
Stext is the kernel entry point, which means that the code in this section will be executed first during kernel boot. It is usually written in an assembly language and usually under the arch/<target>/kernel directory. This Code sets the kernel page Directory, creates the identity kernel ing, identifies the architecture and processor, and executes the branch start_kernel (initialize the main routine of the system ).
Start_kernel calls setup_arch as the first step of execution, in which the system structure-specific settings are completed. This includes initializing hardware registers, identifying the number of DRAM and flash memory available in the root device and system, specifying the number of available pages in the system, and the size of the file system. All this information is transmitted from the boot loader to the kernel in the form of parameters.
There are two methods to pass parameters from the boot loader to the kernel: parameter_structure and the tag list. In both methods, the parameter structure is not supported because it imposes a limit: specify that in memory, each parameter must be located at a specific offset in param_struct. The latest kernel expected parameters are passed as the tag List format and converted to the marked format. Param_struct is defined in include/ASM/setup. h. Some of its important fields are:
Listing 3.Sample parameter structure
Struct param_struct {
Unsigned long page_size;/* 0: size of the page */ Unsigned long nr_pages;/* 4: Number of pages in the system */ Unsigned long ramdisk/* 8: ramdisk size */ Unsigned long rootdev;/* 16: number representing the root device */ Unsigned long initrd_start;/* 64: Starting address of initial ramdisk */ /* This can be either in Flash/DRAM */ Unsigned long initrd_size;/* 68: size of initial ramdisk */ }
|
Note: These numbers indicate the offset in the parameter structure of the defined field. This means that if the boot loader places the parameter structure in the address 0xc0000100, then the rootdev parameter will be placed in 0xc0000100 + 16, initrd_start will be placed in 0xc0000100 + 64, and so on-otherwise, the kernel will encounter difficulties in interpreting correct parameters.
As mentioned above, because there are some constraints for passing parameters from the boot loader to the kernel, most 2.4.x series kernel expect parameters to be passed in the marked List format. In the marked list, each tag consists of the tag_header that identifies the passed parameter and its subsequent parameter values. The general format marked in the tag list can be as follows:
Listing 4.Sample tag format. The kernel uses <atag_tagname>Header to identify each tag.
# Define <atag_tagname> <some magic number>
Struct <tag_tagname> { U32 <tag_param>; U32 <tag_param>; }; /* Example tag for passing memory information */ # Define atag_mem 0x54410002/* Magic Number */ Struct tag_mem32 { U32 size;/* size of memory */ U32 start;/* Physical start address of memory */ };
Struct tag_mem32 { U32 size;/* size of memory */ U32 start;/* Physical start address of memory */ };
/* Example tag for passing memory information */ # Define atag_mem 0x54410002/* Magic Number */ Struct tag_mem32 { U32 size;/* size of memory */ U32 start;/* Physical start address of memory */ };
Struct tag_mem32 { U32 size;/* size of memory */ U32 start;/* Physical start address of memory */ };
|
Setup_arch also needs to perform memory ing on the flash repository, system registers, and other specific devices. Once the system structure-specific settings are completed, the control will return to the start_kernel function for initializing the rest of the system. These additional initialization tasks include:
- Set traps
- Initialization interruption
- Initialization Timer
- Initialize the Console
- Call mem_init to calculate the number of pages in various regions and high memory areas.
- Initialize the slab distributor and create slab cache for VFS and buffer cache.
- Create various file systems, such as proc, ext2, and jffs2
- Create kernel_thread, which runs the init command in the file system and displays the Lign prompt. If no INIT program exists in/bin,/sbin, or/etc, the kernel runs the shell in/bin of the file system.
Device Driver
Embedded systems usually have many devices used to interact with users, such as touch screen, keypad, roller wheel, sensor, ra232 interface, and LCD. In addition to these devices, there are many other dedicated devices, including flash, USB, and GSM. The kernel controls these devices through their own device drivers, including GUI user applications that also access these drivers. This section focuses on the device drivers of important devices that are usually used in almost every embedded environment.
Frame Buffer driver
This is one of the most important drivers, because this driver enables the system to display content on the screen. The frame buffer driver usually has three layers. The underlying layer is the basic console driver Drivers/Char/console. C, which provides some of the common interfaces of the text console. By using the console driver function, we can print text to the screen-But graphics or animations are not available (this requires the video mode function, which usually appears on the middle layer, that is, drivers/Video/fbcon. c ). The second-level driver provides a common interface for plotting in video mode.
The frame buffer is the memory on the video card. You need to map the memory to the user space so that the image and text can be written to this memory segment: Then This information will be reflected on the screen. Frame Buffer increases the drawing speed and overall performance. This is also an eye-catching topic DRIVER: The top layer is a hardware-specific driver, it must support different hardware aspects of the video card, such as enable/disable the video card controller, support for depth and mode, and color palette. All three layers depend on each other to implement the correct video function. The devices related to the frame buffer are/dev/fb0 (primary device number 29, secondary device number 0 ).
Enter the device driver
Touchpad is one of the most basic user interaction devices for embedded devices-keypad, sensor, and scroll wheel are also included in many different devices for different purposes.
The main function of the touchpad device is to report the user's touch at any time and identify the coordinates of the touch. This is usually implemented by generating an interrupt every time a touch occurs.
Then, the role of the device driver is to query the touch screen controller whenever there is an interruption and request the Controller to send the coordinates of the touch. Once the driver receives the coordinates, it sends signals about touch and any available data to the user application and sends the data to the application (if possible ). The user application then processes the data according to its needs.
Almost all input devices-including keypad-work in a similar way.
Flash MTD driver
MTD devices are devices such as flash chips, small flash cards, and memory sticks. They are increasingly used in embedded devices.
The MTD driver is a new type of driver specially developed for the embedded environment in Linux. Compared with conventional block device drivers, MTD drivers are designed for flash-based devices, therefore, they usually have better support, better management, and better interface for sector-based erasure and read/write operations. MTD driver interfaces in Linux are divided into two types: User Module and hardware module.
User Module
These modules provide interfaces directly used from the user space: original character access, original block access, FTL (flash conversion layer, flash transition layer-a file system used on flash memory) and JFS (that is, the log file system, journaled File System-provides the file system directly on flash memory instead of simulating Block devices ). The current version of JFS for flash memory is jffs2 (which will be described later in this article ).
Hardware module
These modules provide physical access to memory devices, but do not directly use them. Use the above user modules to access them. These modules provide actual routines for read, erase, and write operations on flash memory.
MTD driver settings
To access a specific flash device and place the file system on it, you need to compile the MTD subsystem into the kernel. This includes selecting the appropriate MTD hardware and user module. Currently, the MTD subsystem supports a large number of Flash devices-and more drivers are being added for different flash chips.
There are two popular user modules that enable access to flash memory: mtd_char and mtd_block.
Mtd_char provides access to the original characters in the flash memory, while mtd_block designs the flash memory as a general block device (such as an IDE disk) that can create a file system on it ). Devices associated with mtd_char are/dev/mtd0, mtd1, mtd2 (and so on), while devices associated with mtd_block are/dev/mtdblock0 and mtdblock1 (and so on ). Because mtd_block devices provide simulation like Block devices, it is generally better to create file systems like FTL and jffs2 Based on the simulation.
To perform this operation, you may need to create a partition table to split the flash device into the boot loader, kernel, and file system sections. The sample partition table may contain the following information:
Listing 5. MTDSimple flash device partitioning
Struct mtd_partition sample_partition = {
{ /* First partition */ Name: bootloader,/* bootloader Section */ Size: 0x00010000,/* size */ Offset: 0,/* offset from start of flash-location 0x0 */ Mask_flags: mtd_writeable/* This partition is not writable */ }, {/* Second partition */ Name: kernel,/* kernel Section */ Size: 0x00100000,/* size */ Offset: mtdpart_ofs_append,/* append after bootloader Section */ Mask_flags: mtd_writeable/* This partition is not writable */ }, {/* Third partition */ Name: jffs2,/* jffs2 filesystem */ Size: mtdpart_siz_full,/* occupy rest of Flash */ Offset: mtdpart_ofs_append/* append after kernel Section */ } }
|
The preceding Partition Table uses the mtd_block interface to partition flash devices. The device nodes in these partitions are:
Device node in simple flash memory Partition
User device node major number minor number
Bootloader/dev/mtdblock0 31 0 Kernel/dev/mtdblock1 31 1 Filesystem/dev/mtdblock2 31 2
|
In this example, the boot loader must set the node related to the root device (/dev/mtdblock2) and you can find the correct parameter of the file system address (flash_base_address + 0x04000000 in this example) in flash memory and pass it to the kernel. After partitioning, the flash device is ready to mount or mount the file system.
In Linux, the main objective of the MTD subsystem is to provide common interfaces between the hardware driver and the upper layer of the system or between user modules. Hardware drivers do not need to know the methods used by user modules such as jffs2 and FTL. All they really need to provide is a set of simple routines that perform read, write, and erase operations on the underlying flash system.