When compiling the Linux kernel, we often execute some commands in the top-level directory of the Linux kernel. Taking RK3288 as an example, commands include: make firefly-rk3288-linux_defconfig, make menuconfig, make firefly-rk3288.img, make zImage, etc. Without delving into the specific meanings yet, I would like to raise a few questions.
(1) The Linux kernel is so large (tens of thousands of files) and the directories are divided into many layers; how does it associate files across these directories?
(2) Linux supports so many architectures (X86, ARM, AVR, MIPS, etc.); why is it that when using a chip of a certain architecture, such as RK3288, the code for other architectures is not compiled? And why is the code related to other SOC architectures under the ARM architecture not compiled?
(3) What is the significance of executing the command: make menuconfig before compiling the kernel?
(4) What is the significance of executing the command: make zImage during kernel compilation?
(5) How are the Makefile and Kconfig files written in each layer of the Linux kernel?
I believe that once we understand and resolve the above questions, the question posed in the title of this article will naturally be answered. Below, I will address these questions one by one:
Question 1: How does the Linux kernel associate files across its many directories?
To accurately answer this question, only true experts might be able to do so, but I will share my understanding. The code management of the Linux kernel source code is very scientific. In the top-level directory of the Linux kernel source code, there are corresponding directories allocated, representing clusters of certain functions or attributes, thus achieving modularity for easier management. For instance, the arch directory is related to platform architecture, the include directory stores a large number of kernel header files, the drivers directory contains various driver codes such as graphics cards, network cards, USB buses, PCI buses, etc., the kernel directory stores implementations of architecture-specific features like semaphore handling and SMP, and the mm directory contains implementations of architecture-specific memory management programs, and so forth. Additionally, each subdirectory is further subdivided; for example, under the arch directory, there are directories related to the x86 architecture (x86), ARM architecture (arm), MIPS architecture (mips), and so on. This forms a tree structure.
Those who have studied data structures should know that there is a way to traverse a non-standard tree, although the algorithm may be more complex. In the Linux kernel source code, the connections between the various subdirectories are established through Kconfig files, while the Makefile files are used to determine whether the corresponding files in each directory should be compiled. The .config file acts like a master control panel, directing the Makefile to compile specified program code files (mainly C and assembly). All these control relationships are established by the Kconfig file.
In my understanding, this is essentially a method used for traversing a tree structure, akin to asking for directions. Whenever we ask for directions in an unfamiliar place, let’s say from point A to point E, the scenario usually goes like this:
Typically, we follow the directions given by the passerby to accurately find our destination E. Similarly, in a program or code architecture, the .config file acts like a passerby, Kconfig is the person asking for directions, and Makefile is the pair of legs doing the hard work! Haha!
Question 2: Why, when using a chip of a certain architecture like RK3288, is the code for other architectures not compiled, and why is related code for other SOC architectures under the ARM architecture not compiled?
This question is actually related to the commands we execute when compiling the Linux kernel: make firefly-rk3288-linux_defconfig or make menuconfig. The purpose of executing these two commands is to generate the .config file. Often, when we execute the command make menuconfig, we only modify some driver modules and the programs to be compiled; we typically do not select architecture-related options. For instance, we might not specifically choose RK3288 SOC or select a Firefly platform board; while it is possible to do so, we usually first execute make firefly-rk3288-linux_defconfig to generate a .config file based on Firefly platform-related RK3288 configurations, and then execute make menuconfig to select some module codes for compilation. This approach reduces a lot of workload. However, there is an issue: what if the kernel to be compiled does not support the existing development board? For example, if the original Linux kernel source downloaded from the official website only supports RK3288 SOC but does not support the Firefly development board for RK3288? This involves the porting of the Linux kernel, which I will not elaborate on here. One point to note is that you can look for a configuration for a development board based on RK3288 SOC (in the official initial Linux kernel source, for each supported SOC, there is usually a corresponding official development board as a reference) for porting and simulation, ultimately generating a configuration file similar to firefly-rk3288-linux_defconfig to generate a basic .config file.
Understanding the above relationships makes it easy to grasp the reason behind question 1. Indeed, it is based on the firefly-rk3288-linux_defconfig file that the default .config configuration file is generated, which contains architecture-related information. Therefore, when compiling the Linux kernel source code, the compilation of architecture-related code is determined based on the basic configuration of the .config file. In summary, everything is based on this control console, the .config file.
Question 3: What is the significance of executing the command: make menuconfig before compiling the kernel?
This question has essentially been answered in the previous two questions. make menuconfig opens the tree structure of the kernel source code in a menu format, allowing programmers to configure and select the module codes they wish to compile based on the default configuration.
There are several commands that can accomplish this task, such as:
• make config: A traditional text-based configuration interface, too complex and not intuitive, not recommended.
• make xconfig: A graphical window-based configuration interface, intuitive and clear, recommended for use in Xwindow environments.
• make oldconfig: If you just want to modify a few small details based on the original kernel configuration, this can save a lot of trouble.
• make menuconfig: A text menu-based configuration interface, intuitive and clear, recommended for use in character terminal environments.
There are a few other methods, but manually modifying the .config file is something that basically no one would do.
Question 4: What is the significance of executing the command: make zImage during kernel compilation?
Typically, during compilation, this command or the make uImage command is executed. What is its significance? It generates a kernel image named zImage or uImage. Of course, some might wonder, when compiling for the Firefly RK3288 development board, the command is make firefly-rk3288.img instead of either of the above; this is actually a modified method by Firefly, and in essence, it is just a name. Don’t get too hung up on this; if you want to understand more, the answer lies in the kernel source provided by Firefly.
After executing the compilation command, the source code can be compiled in a systematic and selective manner using the .config file, Kconfig file, and Makefile.
Question 5: How are the Makefile and Kconfig files written in each layer of the kernel?
Actually, the first four questions are just foundational groundwork; this is the main focus, but it must rely on the principles discussed earlier.
First, we need to confirm that in each layer of the Linux kernel source code, there exists a Kconfig file and a Makefile, while the .config file exists in the top-level directory. As shown in the image:
The image essentially proves everything.
To better illustrate, I created a my_dr directory under the drivers directory, mainly to store my own kernel driver code. The other directories under this directory are all my written driver codes. Now I need to connect them so that when the make menuconfig command is executed, it can find my own kernel driver code. Here, I will use a hello program as an example.
So how should this be done? The answer is simple: “Imitate”!
As shown in the image:
The image above illustrates the directory structure I want to demonstrate, where the hello directory contains the hello.c program, and the my_dr directory is where I want to store my own driver program. The drivers directory is the Linux kernel driver directory, and the firefly-rk3288-kernel directory is the top-level directory of the Linux kernel source code. Therefore, following the reasoning from earlier, each layer directory will have a Makefile and Kconfig file. Now let’s analyze the Makefile and Kconfig files from the lowest hello directory to the drivers directory.
(1) hello directory
As shown in the image, the left side is the Makefile and the right side is the Kconfig file. In the Makefile, if the variable CONFIG_HELLO is true or false, it determines whether to compile the hello.c file in this directory into hello.o. The value of the CONFIG_HELLO variable comes from the configuration in the .config file. The configuration of .config is derived from the explicit selection in the Kconfig file (i.e., through menu selection). Now looking at the Kconfig file, as shown in the image:
Config is the configuration keyword; HELLO is the configuration item; tristate is a tri-state selector, which provides three choices when presenting the menu configuration to the upper layer: do not compile, compile into the kernel image, and compile as a module driver. In addition to this keyword, there is also another keyword, bool, which is easier to understand, offering two choices: compile or do not compile; help provides help prompts. This is the classic format. Therefore, when writing such configurations, the usual practice is to refer to the existing Kconfig and Makefile files in the Linux kernel source code and imitate their formats.
(2) my_dr directory
The image above shows the contents of the Makefile and Kconfig files in the my_dr directory. In the Makefile, the directive points to the hello directory. For the Kconfig file, since the my_dr directory may contain many driver programs, it is necessary to establish a menu for selecting each driver program. Thus, the first line of code establishes a menu named my Drivers; the fifth line indicates that the Kconfig resource can be found at the relative path drivers/my_dr/hello/Kconfig (note that this path is relative to the top-level directory of the Linux kernel source). The last line, line 7, uses the endmenu keyword to end the file.
(3) drivers directory
The image above shows the Makefile and Kconfig files in the drivers directory. Similarly, in the Makefile, we add the directory path of the menu resource; in the Kconfig file, we also add the path of the Kconfig resources in this directory. They are used in conjunction. Additionally, in both documents, there are numerous reference examples that can be directly referenced! One question that arises is, in the Makefile, for example, line 158, as shown in the image:
There is a variable CONFIG_GATOR; what does this signify? It indicates that in order to display the selection for the gator directory menu in the menu, the value of the variable CONFIG_GATOR must be y. This way, when using the make menuconfig command, the gator menu will be visible, and the value of CONFIG_GATOR is dependent on certain code choices in the upper directory, which set its value to y. Conversely, I directly used the statement obj-y, indicating the variable value is y (essentially yes), thus it becomes visible without relying on other code.
The entire process is as described above. Now, execute the make menuconfig command to see if the previously established menu is visible. As shown in the image:
As shown in the image, you can see the menu I created named my Drivers.
The two images above demonstrate the results of using the tristate keyword, showing the three-state results, which can be selected according to needs.
Regularly share embedded knowledge in an easy-to-understand manner. Follow our public account, star it, and make a little progress every day.
Disclaimer:
All original and reprinted articles and images belong to the original authors. If there is any infringement, please contact us for deletion.

