Hi,大家好!我是CrazyCatJack。最近在學習Linux核心的設定、編譯及Makefile檔案。今天總結學習成果,分享給大家^_^
1.解壓縮打補丁
首先是解壓縮你獲取到的Linux核心。這裡我用到的是linux.2.22.6版本的核心。在Linux下命令列透過tar xjf linux.2.22.6.tar.bz2解壓縮內核。然後,如果你需要對這個核心打補丁的話,用patch指令:patch -px <..>
--- linux-2.6.22.6/arch/arm/configs/s3c2410_defconfig+++ linux-2.6.22.6_jz2440/arch/arm/configs/s3c2.6.22.6_jz2440/arch/arm/configs/s3c2410_definc2410_L3c2410_L3c2410_deflin. -2.6.22.6下,也就是說打補丁需要忽略掉一個斜線的目錄。那麼打補丁的指令就是patch -p1 <..>
2.配置核心 現在補丁已經打好了,接下來就是配置核心了。這裡配置有3種方法: 1>直接進行make menuconfig。這是最麻煩的一種方法,所有的配置都需要你來操作。 2>在預設設定上自己修改,也就是修改defconfig檔。使用 find -name "*defconfig*"尋找你的架構對應的預設設定檔。我是在arch/arm/configs找到自己板子的預設設定檔。執行defconfig檔: make XXX_defconfig。 XXX是你具體使用的板子型號。執行此操作後,結果會儲存在.config檔。然後再執行make menuconfig指令。這時的配置就是在預設配置上稍加修改就可以了。 3>使用廠商的設定檔。如果你的硬體有廠商提供的config檔那是最輕鬆的。直接cp XXX .config。然後執行make menuconfig。 這裡詳細跟大家講一下內核的配置。 Linux的核心配置,就是為了產生.config檔。因為在編譯時需要用.config檔產生其他相關設定檔。我們的配置項目大多是例如CONFIG_XXXDRIVER,這裡的XXXDRIVER指的是各種驅動。我們需要告訴內核,這些驅動是編譯進內核,還是編譯成模組。透過尋找CONFIG_XXXDRIVER,我們可以發現,它出現在四個地方: 1>C原始碼 2>子目錄Makefile:drivers/XXX/Makefile 〜『34d/po『 include/linux/autoconf.h 這裡首先說明:.config檔在進行核心編譯時(make uImage)產生了include/config/auto.conf和include/linux/autoconf.h。透過查看C原始碼我們發現CONFIG_XXXDRIVER是一個巨集定義,等於一個常數。在include/linux/autoconf.h中宏定義CONFIG_XXXDRIVER為常數,可能是0或1。那現在有個問題,就是CONFIG_XXXDRIVER到底被編譯進核心還是編譯成模組呢?這在C語言中是無法區分的,而這種區分體現在哪裡呢?這種區分體現在子目錄的Makefile檔案中。在子目錄的Makefile中,若有 obj -y += XXX.o則表示XXX.c被編譯進核心;obj -m +=XXX.o則表示XXX被編譯成模組,為XXX.ko。 include/config/auto.conf檔則是對CONFIG_XXXDRIVER進行賦值,為y時表示編譯進內核,為m時表示編譯成獨立模組。#这里是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
透過上面的描述,我們可以知道,在每個driver下,都有一個Makefile檔。來定義這個驅動是編譯進核心還是編譯成模組。這裡稍稍提一下。上面我們講到了在Makefile中單一檔案怎麼編譯進核心和編譯成模組。但是如果有兩個以上的文件該如何書寫呢?這裡舉個例子:obj -y += a.o b.o就表示將a.c和b.c編譯進核心。
obj -m += ab.o
ab -objs := a.o b.o
顶层目录下Makefile中相关命令: include $(srctree)/arch/$(ARCH)/Makefile 架构目录下Makefile相关命令: zImage Image xipImage bootpImage uImage: 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内核的从配置到编译的全部分析了^_^