みなさん、こんにちは!私はクレイジーキャットジャックです。最近、Linux カーネルの設定、コンパイル、Makefile を勉強しています。今日は学習結果をまとめて皆さんと共有します^_^
1.解凍してパッチを当てます
1つ目は、入手したLinuxカーネルを解凍することです。ここでは、linux.2.22.6 バージョンのカーネルを使用しています。 Linux でコマンド ラインを使用し、tar xjf linux.2.22.6.tar.bz2 を通じてカーネルを解凍します。次に、このカーネルにパッチを適用する必要がある場合は、patch コマンド: patch -px <../linux.2.22.6.patch を使用します。ここでの px は、パッチ ファイルに記述されているスラッシュを無視することを指します。つまり、最初の x ディレクトリは無視されます。
--- linux-2.6.22.6/arch/arm/configs/s3c2410_defconfig++ linux-2.6.22.6_jz2440/arch/arm/configs/s3c2410_defconfig
現時点でカーネルのルートディレクトリにいる場合、つまり linux -2.6.22.6 です。これは、パッチではスラッシュの付いたディレクトリを無視する必要があることを意味します。次に、パッチ適用コマンドは patch -p1 <../linux.2.22.6.patch です。
2. カーネルを構成する
パッチが適用されたので、次のステップはカーネルを構成することです。ここで設定するには 3 つの方法があります:
1> 直接 menuconfig を作成します。これは最も面倒な方法であり、すべての設定を自分で行う必要があります。
2> デフォルト設定を自分で変更します。つまり、defconfig ファイルを変更します。 find -name "*defconfig*" を使用して、アーキテクチャのデフォルト構成ファイルを見つけます。ボードのデフォルト設定ファイルは、arch/arm/configs にありました。 defconfig ファイル make XXX_defconfig を実行します。 XXX は、使用している特定のボード モデルです。この操作を実行すると、結果が .config ファイルに保存されます。次に、make menuconfig コマンドを実行します。この時点の構成は、デフォルトの構成を少し変更しただけです。
3>メーカーの設定ファイルを使用します。ハードウェアに製造元から提供された構成ファイルがある場合、これが最も簡単です。 XXX.config を直接 cp します。次に、make menuconfig を実行します。
ここではカーネル構成について詳しく説明します。 Linux カーネル構成では、.config ファイルが生成されます。コンパイル中に .config ファイルを使用して他の関連構成ファイルを生成する必要があるためです。たとえば、構成項目のほとんどは CONFIG_XXXDRIVER です。XXXDRIVER はさまざまなドライバーを指します。これらのドライバーがカーネルにコンパイルされるか、モジュールにコンパイルされるかをカーネルに伝える必要があります。 CONFIG_XXXDRIVER を探すと、次の 4 つの場所にあることがわかります。
1>C ソースコード
2>サブディレクトリ Makefile: drivers/XXX/Makefile
3>include/config/auto.conf
4>include /linux/autoconf.h
最初の説明は次のとおりです。カーネルのコンパイル (make uImage) 時に、.config ファイルは 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
3. カーネルをコンパイルします
上記の説明から、各ドライバーの下に Makefile ファイルがあることがわかります。このドライバーをカーネルにコンパイルするか、モジュールにコンパイルするかを定義します。ここで少し言及。上記では、Makefile 内の単一のファイルがどのようにカーネルにコンパイルされ、モジュールにコンパイルされるかについて説明しました。しかし、ファイルが 3 つ以上ある場合はどうやって書くのでしょうか?以下に例を示します。 obj -y += a.o b.o は、a.c と b.c をカーネルにコンパイルすることを意味します。
obj -m += ab.o
ab -objs := a.o b.o
は、 a.c と b.c をモジュールにコンパイルすることを意味します。そのプロセスは、a.c が a.o を生成し、b.c が b.o を生成するというものです。 a.o と b.o が一緒になって ab.ko を生成します。前回の uboot 起動カーネルのコード分析では、カーネルのコンパイルによって生成される uImage は、ヘッダーと Linux カーネルの 2 つの部分で構成されていると述べました。このヘッダーには、カーネルのロード アドレスやエントリ アドレスなど、多くの初期化パラメーター情報が含まれています。カーネルをコンパイルするときに、make uImage を直接実行できます。では、uImage はファイル内でどのように定義されているのでしょうか? uImageを生成するにはどうすればよいですか?
まず、Makefile を検索すると、uImage は Arch/arm/Makefile にあり、このアーキテクチャ ディレクトリの Makefile は最上位ディレクトリの Makefile に含まれていることがわかりました。このようにして、make uImage を実行すると、トップ ディレクトリの Makefile がアーキテクチャ サブディレクトリの Makefile を呼び出して、カーネルをコンパイルして uImage を生成できます。
顶层目录下Makefile中相关命令: include $(srctree)/arch/$(ARCH)/Makefile 架构目录下Makefile相关命令: zImage Image xipImage bootpImage uImage: vmlinux
これは先ほど言った通りで、トップレベルの Makefile はアーキテクチャ ディレクトリの Makefile を呼び出し、アーキテクチャ ディレクトリの Makefile が uImage を生成し、それは vmlinux ファイルに依存します。次に、vmlinux ファイルを生成する方法について説明します。最上位の Makefile には、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内核的从配置到编译的全部分析了^_^