Hallo zusammen! Ich bin CrazyCatJack. Vor kurzem lerne ich die Konfiguration, Kompilierung und Makefile des Linux-Kernels. Heute werde ich die Lernergebnisse zusammenfassen und mit Ihnen teilen ^_^
1 Dekomprimieren und patchen
Dekomprimieren Sie zunächst den erhaltenen Linux-Kernel. Hier verwende ich die Linux.2.22.6-Version des Kernels. Verwenden Sie die Befehlszeile unter Linux, um den Kernel über tar xjf linux.2.22.6.tar.bz2 zu dekomprimieren. Wenn Sie dann diesen Kernel patchen müssen, verwenden Sie den Patch-Befehl: patch -px <../linux.2.22.6.patch. Der px bezieht sich hier auf das Ignorieren der in der Patch-Datei beschriebenen Schrägstriche. Ignorieren Sie also die ersten x-Verzeichnisse.
--- linux-2.6.22.6/arch/arm/configs/s3c2410_defconfig+++ linux-2.6.22.6_jz2440/arch/arm/configs/s3c2410_defconfig
Wenn Sie sich derzeit im Stammverzeichnis des Kernels befinden, also Linux-2.6.22.6, bedeutet das, dass Sie beim Patchen ein Schrägstrichverzeichnis ignorieren müssen. Dann lautet der Patch-Befehl patch -p1 <../linux.2.22.6.patch.
2. Konfigurieren Sie den Kernel
Nachdem der Patch angewendet wurde, besteht der nächste Schritt darin, den Kernel zu konfigurieren. Hier gibt es drei Konfigurationsmethoden:
1> Dies ist die aufwändigste Methode und alle Konfigurationen müssen von Ihnen vorgenommen werden.
2> Ändern Sie die Standardkonfiguration selbst, dh ändern Sie die Defconfig-Datei. Verwenden Sie find -name „*defconfig*“, um die Standardkonfigurationsdatei für Ihre Architektur zu finden. Ich habe die Standardkonfigurationsdatei meines Boards in arch/arm/configs gefunden. Führen Sie die Defconfig-Datei aus: make XXX_defconfig. XXX ist das spezifische Board-Modell, das Sie verwenden. Nach Durchführung dieses Vorgangs werden die Ergebnisse in der .config-Datei gespeichert. Führen Sie dann den Befehl make menuconfig aus. Bei der aktuellen Konfiguration handelt es sich lediglich um eine geringfügige Änderung der Standardkonfiguration.
3>Verwenden Sie die Konfigurationsdatei des Herstellers. Am einfachsten ist es, wenn Ihre Hardware über eine vom Hersteller bereitgestellte Konfigurationsdatei verfügt. Direkt cp XXX.config. Führen Sie dann make menuconfig aus.
Hier erzähle ich Ihnen ausführlich über die Kernel-Konfiguration. Bei der Linux-Kernel-Konfiguration werden .config-Dateien generiert. Weil Sie die .config-Datei verwenden müssen, um während der Kompilierung andere verwandte Konfigurationsdateien zu generieren. Die meisten unserer Konfigurationselemente sind beispielsweise CONFIG_XXXDRIVER, wobei sich XXXDRIVER auf verschiedene Treiber bezieht. Wir müssen dem Kernel mitteilen, ob diese Treiber in den Kernel oder in Module kompiliert sind. Wenn wir nach CONFIG_XXXDRIVER suchen, können wir feststellen, dass es an vier Stellen erscheint:
1>C-Quellcode
2>Unterverzeichnis Makefile:drivers/XXX/Makefile
3> ; include/config/auto.conf
4>include/linux/autoconf.h
Hier ist die erste Erklärung: Die .config-Datei generiert include/config während der Kernel-Kompilierung (make uImage) / auto.conf und include/linux/autoconf.h. Bei der Betrachtung des C-Quellcodes haben wir festgestellt, dass CONFIG_XXXDRIVER eine Makrodefinition ist, die einer Konstante entspricht. Die Makrodefinition CONFIG_XXXDRIVER in include/linux/autoconf.h ist eine Konstante, die 0 oder 1 sein kann. Nun stellt sich die Frage: Wird CONFIG_XXXDRIVER in den Kernel oder in ein Modul kompiliert? Dies kann in der C-Sprache nicht unterschieden werden. Wo spiegelt sich diese Unterscheidung wider? Diese Unterscheidung spiegelt sich in der Makefile-Datei im Unterverzeichnis wider. Wenn im Makefile des Unterverzeichnisses obj -y += XXX.o vorhanden ist, bedeutet dies, dass XXX.c in den Kernel kompiliert wird. obj -m +=XXX.o bedeutet, dass XXX in ein Modul kompiliert wird XXX.ko. Die Datei include/config/auto.conf weist CONFIG_XXXDRIVER einen Wert zu. Wenn es y ist, bedeutet es, dass es in den Kernel kompiliert wird, und wenn es m ist, bedeutet es, dass es in ein unabhängiges Modul kompiliert wird.
#这里是include/config/auto.conf的部分内容 # Automatically generated make config: don't edit # Linux kernel version: 2.6.22.6 # Sun Nov 27 18:34:38 2016 # CONFIG_CPU_S3C244X=y CONFIG_CPU_COPY_V4WB=y CONFIG_CRYPTO_CBC=y CONFIG_CPU_S3C2410_DMA=y CONFIG_CRYPTO_ECB=m CONFIG_SMDK2440_CPU2440=y
#这里是drivers/i2c/Makefile # Makefile for the i2c core. # obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o obj-$(CONFIG_I2C) += i2c-core.o obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o obj-y += busses/ chips/ algos/ ifeq ($(CONFIG_I2C_DEBUG_CORE),y) EXTRA_CFLAGS += -DDEBUG endif
3. Kompilieren Sie den Kernel
Aus der obigen Beschreibung können wir erkennen, dass sich unter jedem Treiber eine Makefile-Datei befindet. Um zu definieren, ob dieser Treiber in den Kernel oder in ein Modul kompiliert wird. Eine kleine Erwähnung hier. Oben haben wir darüber gesprochen, wie eine einzelne Datei im Makefile in den Kernel und in ein Modul kompiliert wird. Aber wie schreibt man es, wenn es mehr als zwei Dateien gibt? Hier ist ein Beispiel: obj -y += a.o b.o bedeutet, dass a.c und b.c in den Kernel kompiliert werden.
obj -m += ab.o
ab -objs := a.o b.o
kann bedeuten, a.c und b.c in einem Modul zusammenzustellen. Der Prozess besteht darin, dass a.c a.o generiert und b.c b.o generiert. a.o und b.o erzeugen zusammen ab.ko. In der vorherigen Codeanalyse des Uboot-Startkernels haben wir erwähnt, dass das durch die Kompilierung des Kernels generierte uImage aus zwei Teilen besteht: Header + Linux-Kernel. Dieser Header enthält viele Initialisierungsparameterinformationen, wie z. B. die Ladeadresse und die Eintrittsadresse des Kernels. Beim Kompilieren des Kernels können wir make uImage direkt ausführen. Wie ist uImage in der Datei definiert? Wie erstelle ich uImage?
Zuerst haben wir beim Durchsuchen des Makefiles festgestellt, dass sich uImage in arch/arm/Makefile befindet und das Makefile in diesem Architekturverzeichnis im Makefile im Verzeichnis der obersten Ebene enthalten ist. Wenn wir also make uImage ausführen, kann das Makefile im obersten Verzeichnis das Makefile im Architektur-Unterverzeichnis aufrufen, um den Kernel zu kompilieren und uImage zu generieren.
顶层目录下Makefile中相关命令: include $(srctree)/arch/$(ARCH)/Makefile 架构目录下Makefile相关命令: zImage Image xipImage bootpImage uImage: vmlinux
Das habe ich gerade gesagt: Das Makefile der obersten Ebene ruft das Makefile im Architekturverzeichnis auf. Das Makefile im Architekturverzeichnis generiert uImage und hängt von der vmlinux-Datei ab. Als nächstes erklären wir, wie man vmlinux-Dateien generiert. Im Makefile der obersten Ebene finden wir die meisten Befehle zum Generieren von vmlinux.
顶层目录Makefile: init-y := init/ init-y := $(patsubst %/, %/built-in.o, $(init-y)) core-y := usr/ core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ core-y := $(patsubst %/, %/built-in.o, $(core-y)) libs-y := lib/ libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) libs-y := $(libs-y1) $(libs-y2) drivers-y := drivers/ sound/ drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) net-y := net/ net-y := $(patsubst %/, %/built-in.o, $(net-y)) = net/built-in.o vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE vmlinux-init := $(head-y) $(init-y) vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) vmlinux-all := $(vmlinux-init) $(vmlinux-main) vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds export KBUILD_VMLINUX_OBJS := $(vmlinux-all) 架构目录Makefile: zImage Image xipImage bootpImage uImage: vmlinux head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
我已经把顶层目录和架构目录下生成vmlinux的命令摘选出来。首先,我们看要想生成vmlinux,需要vmlinux-lds文件、vmlinux-init文件、vmlinux-main文件。其中,vmlinux-lds是链接脚本文件,定义了代码段,数据段的存放位置。这里我们接着往下看,vmlinux-init需要head-y和init-y,通过查看两个Makefile,我们可以得到经过转换后的结果:
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o init-y := $(patsubst %/, %/built-in.o, $(init-y)) = init/built-in.o core-y := $(patsubst %/, %/built-in.o, $(core-y)) = usr/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o libs-y := $(libs-y1) $(libs-y2) =lib/lib.a lib/built-in.o drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) = drivers/built-in.o sound/built-in.o net-y := $(patsubst %/, %/built-in.o, $(net-y)) = net/built-in.o
现在已经分析了内核编译的全部过程。那怎样知道我们分析的到底对不对,通过实际执行make uImage我们就可以看到执行过程。这是执行make uImage过程中的部分相关命令:
arm-linux-ld -EL -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o
可以看到,首先目标要生成vmlinux,然后是链接脚本为vmlinux.lds。开始生成第一个文件:head.o,第二个文件:init_task.o。这和我们分析的完全一致。接下来以此类推,和我们分析的相同,也就是说我们分析的是正确的。
SECTIONS { . = (0xc0000000) + 0x00008000; .text.head : { _stext = .; _sinittext = .; *(.text.head) } .init : { /* Init code and data */ *(.init.text) _einittext = .; __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .; __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; __tagtable_begin = .; *(.taglist.init) __tagtable_end = .; . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; __early_begin = .; *(.early_param.init) __early_end = .; __initcall_start = .;
这是链接脚本vmlinux.lds中的部分内容。首先定义了虚拟地址:(0xc0000000) + 0x00008000。 然后是首先执行头部文件,这与我们分析的完全一致。代码段,初始化代码段等等。
这就是Linux内核的从配置到编译的全部分析了^_^