1. 程式人生 > >linux kernel(一)編譯體驗

linux kernel(一)編譯體驗

目錄


title: kernel(一)編譯體驗
tags: linux
date: 2018-11-06 17:27:22
---

打補丁

  1. 解壓 tar xjf linux-2.6.22.6.tar.bz2
  2. 打補丁,cat下補丁檔案知道需要忽略第一個/ patch -p1 < linux-2.6.22.6_jz2440.patch
  3. 打包下生成的檔案tar cjvf linux2.6.22_ok.tar.bz2 linux-2.6.22.6

配置

總結

  1. .config通過make自動生成autoconf.hauto.conf
  2. 原始碼中通過autoconf.h決定程式碼編譯,無論配置為m或者y都編譯,因為標頭檔案都定義為1
  3. 子目錄的makefile通過auto.conf中的y/m決定編譯為模組.ko還是編譯到核心.這個檔案是被頂層的Makefile包含

配置方式

配置有三種方法:

  1. make menuconfig 每一項都配置,非常多的選項,圖形介面配置

    mark

  2. 使用預設配置後執行make menuconfig

    1. 檢視下預設配置,使用find -name "*defconfig*"搜尋,找到很多配置檔案,比如在arch/arm下有資料夾configs儲存預設配置

      ./arch/arm/configs/realview_defconfig
      ./arch/arm/configs/hackkit_defconfig
      ./arch/arm/configs/lpd270_defconfig
      .......
      ./arch/arm/configs/s3c2410_defconfig
    2. 進入這個目錄檢視下相關的config,發現 s3c2410_defconfig與我們的單板最為接近

      cd arch/arm/configs/
      ls
      make s3c2410_defconfig
    3. 使用對應的最接近的預設配置make s3c2410_defconfig

    4. 執行 make menuconfig.最後提示所有配置專案寫入.config

      [email protected]:~/stu/kernel/linux2.6/linux-2.6.22.6$ make s3c2410_defconfig
      drivers/serial/Kconfig:235:warning: multi-line strings not supported
      .......
      #
      # configuration written to .config
      #
    5. 如果報錯,則是因為make版本過新

      [email protected]:~/work/linux-2.6.22.6$ make s3c2410_defconfig
      Makefile:416: *** mixed implicit and normal rules: deprecated syntax
      Makefile:1449: *** mixed implicit and normal rules: deprecated syntax
      make: *** No rule to make target 's3c2410_defconfig'。 停止。
      
      原因:是由於我的系統的make工具太新,make的舊版規則已經無法相容新版。
      
      1在makefile中將416行程式碼
      config %config: scripts_basic outputmakefile FORCE
      改為
      %config: scripts_basic outputmakefile FORCE
      2在makefile中將1449行程式碼
      / %/: prepare scripts FORCE
      改為
      %/: prepare scripts FORCE
  3. 廠家提供配置,直接複製為名為.configcp config_ok .config,再執行 make menuconfig實際上方式2最終也是產生這個.config檔案

配置體驗

輸入Y編譯進核心
輸入N 不包含
輸入M編譯為模組,所謂模組就類似於驅動,可以後置載入
輸入?進入檢視具體的配置項
輸入 / 搜尋

比如我們配置網路DM9000,按下圖進入配置

mark

mark

mark

mark

配置詳解

配置的最終目的,是生成了.config檔案,檢視下這個檔案,

#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.22.6
# Wed Dec 30 18:30:33 2009
#
CONFIG_ARM=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
CONFIG_GENERIC_GPIO=y
# CONFIG_GENERIC_TIME is not set
# CONFIG_GENERIC_CLOCKEVENTS is not set
CONFIG_MMU=y
CONFIG_NO_IOPORT=y
CONFIG_GENERIC_HARDIRQS=y
.....
CONFIG_DM9000=y                 #來分析這個配置專案

取其中一行 CONFIG_DM9000=y來分析,搜尋下檔案內容

grep "CONFIG_DM9000" * -nwR
  1. 忽略掉預設配置檔案(以defconfig命名的),可以看到程式碼中有以下.c檔案中的巨集肯定是源自於標頭檔案,也就是4中的auto.conf
arch/arm/plat-s3c24xx/common-smdk.c:46:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
arch/arm/plat-s3c24xx/common-smdk.c:162:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
arch/arm/plat-s3c24xx/common-smdk.c:200:#endif /* CONFIG_DM9000 */
arch/arm/plat-s3c24xx/common-smdk.c:250:#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
  1. 子目錄下的Makefile中有以下,這裡的會被區分是編譯為模組還是編譯為核心,參考子目錄的Makefile
drivers/net/Makefile:197:obj-$(CONFIG_DM9000) += dm9dev9000c.o
drivers/net/Makefile:198:#obj-$(CONFIG_DM9000) += dm9000.o
drivers/net/Makefile:199:#obj-$(CONFIG_DM9000) += dm9ks.o
  1. make之後還會在include下有以下,這個檔案很明顯就是.config產生的,2中根據這個編譯模組還是核心.這個會被頂層的makefile包含
include/config/auto.conf:144:CONFIG_DM9000=y
  1. 還有標頭檔案自動產生的,這個給1中的原始碼使用.不論配置為Y或者為M模組,在這個標頭檔案都被定義為1
include/linux/autoconf.h:145:#define CONFIG_DM9000 1
//該檔案下的內容都是類似的如下
#define CONFIG_DM9000 1
#define CONFIG_SOLARIS_X86_PARTITION 1
#define CONFIG_SERIAL_NONSTANDARD 1
#define CONFIG_BLK_DEV_RAM_BLOCKSIZE 1024

至於是M與Y的區別,是在makefile中體現的

Makefile解析

詳細的解釋可以看/Documentation/kbuild/makefiles.txt

子目錄的Makefile

子目錄的makefile形式簡單,諸如下:

obj-$(CONFIG_DM9000) += dm9dev9000c.o

然後 CONFIG_DM9000是在auto.conf中定義,他是由.config中定義為y(核心檔案)或者m(編譯為.ko模組),所以也就是形如

obj-y += xxx.o
obj-m += xxx.o
如果沒有被定義則是
obj - +=xxx.o 不被處理

架構下面的Makefile

arch/arm/Makefile

我們執行命令make uImage並不在頂層的makefile,而是在架構下面的makefile,所以它一定會被頂層的makefile包含

zImage Image xipImage bootpImage uImage: vmlinux
    $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/[email protected]

頂層Makefile

搜尋下arch,可以下頂層的Makefile中找到包含了架構的Makefile

include $(srctree)/arch/$(ARCH)/Makefile
export KBUILD_DEFCONFIG

可以繼續搜發現同時定義了arm架構,這是在補丁檔案修改的

#ARCH       ?= $(SUBARCH)
ARCH        ?= arm
CROSS_COMPILE   ?= arm-linux-

同時搜尋下.config檔案生成的auto.conf,也在頂層包含

ifeq ($(dot-config),1)
# Read in config
-include include/config/auto.conf

Make解析

總結:最終在各個目錄下生成built-in.o,根據arch/$(ARCH)/kernel/vmlinux.lds的連結指令碼連結

uiamge依賴於vmlinux,uImage實際就是頭部資訊加上一個真正的核心,也就是vmlinux就是真正的核心

zImage Image xipImage bootpImage uImage: vmlinux
    $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/[email protected]

可以發現在頂層makefile存在以下,也就是說這也是預設的目標檔案

all: vmlinux

繼續搜尋目標vmlinux的依賴

# vmlinux image - including updated kernel symbols
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
ifdef CONFIG_HEADERS_CHECK
    $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
    $(call if_changed_rule,vmlinux__)
    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost [email protected]
    $(Q)rm -f .old_version  

其中相關變數繼續搜尋如下

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
# vmlinux-lds:連結指令碼。
# vmlinux-init:一些初始化程式碼。
# vmlinux-main:一些主要的程式碼(與核心核心相關的)。

vmlinux-init

#頂層 /makefile
init-y      := init/
init-y      := $(patsubst %/, %/built-in.o, $(init-y))
# 相當於 init-y =  init/built-in.o

#架構 arch/arm/makefile
head-y      := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
#這裡MMUEXT 並沒有被定義,也就是最終就是 arch/arm/kernel/head.o  arch/arm/kernel/init_task.o

patsubst分析

格式:$(patsubst <pattern>,<replacement>,<text> ) 
名稱:模式字串替換函式——patsubst。
功能:查詢<text>中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>,如果匹配的話,則以<replacement>替換。
  • pattern=%/在這裡應該是匹配所有的意思
  • replacement=%/built-in.o
  • text=init/

也就是說在init/下的所有檔名都被替換為+built-in.o,視訊講的是最後會被編譯為built-in.o,也就是相當於init-y = init/built-in.o

vmlinux-main

core-y 核心

vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
# core-y
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/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-y      := lib/
# 在lib/中查詢 替換為lib.a
libs-y1     := $(patsubst %/, %/lib.a, $(libs-y))
# 在lib/中查詢 替換為built-in.o
libs-y2     := $(patsubst %/, %/built-in.o, $(libs-y))
# 最終生成 built-in.o 和 lib.a
libs-y      := $(libs-y1) $(libs-y2)

同樣的,也就是在最終生成built-in.o和 替換為lib.a

drivers-y 驅動

drivers-y := drivers/ sound/ #(依賴了這兩個目錄)
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))

同樣的,也就是在最終生成built-in.o

net-y 網路

net-y := net/
net-y := $(patsubst %/, %/built-in.o, $(net-y))

同樣的替換,生成built-in.o

編譯

uboot啟動的是uImage格式的linux kernel, 就是帶有頭部資訊的程式檔案

make uImage

從編譯流程分析下編譯過程

rm vmlinux #先刪除
make uImage V=1  # V=1 表示更加詳細顯示命令

摘錄下最後的日誌記錄

連結

檔案的順序在外部定義.第一個檔案為arch/arm/kernel/head.o,具體的段由連結指令碼決定.這個連結命令實際也是存在.vmlinux.cmd中的

  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  arch/arm/common/built-in.o  arch/arm/mach-s3c2410/built-in.o  arch/arm/mach-s3c2400/built-in.o  arch/arm/mach-s3c2412/built-in.o  arch/arm/mach-s3c2440/built-in.o  arch/arm/mach-s3c2442/built-in.o  arch/arm/mach-s3c2443/built-in.o  arch/arm/nwfpe/built-in.o  arch/arm/plat-s3c24xx/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  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  net/built-in.o --end-group .tmp_kallsyms2.o


#-T arch/arm/kernel/vmlinux.lds 
arch/arm/kernel/head.o 
arch/arm/kernel/init_task.o
#這個與makefile中的是對應的
vmlinux-init := $(head-y) $(init-y)
head-y      := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o

連結指令碼

arch/arm/kernel/,檔案裡面的段按照順序,比如程式碼段是先放.text.head,**一開始是放“ *”(指所有檔案)的 “ .text.head”段。**再放init,檔案的順序由外部決定. 連結指令碼 vmlinux.lds 是由 vmlinux.lds.S 檔案生成的。

SECTIONS
{

 . = (0xc0000000) + 0x00008000;     #這個是虛擬地址

 .text.head : {                     #先放所有檔案的 .text.head 段
  _stext = .;
  _sinittext = .;
  *(.text.head)
 }

 .init : { /* Init code and data        */ #再接著是放所有檔案的“ .init.text”段。
   *(.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 = .;
   *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
  __initcall_end = .;
  __con_initcall_start = .;
   *(.con_initcall.init)
  __con_initcall_end = .;
  __security_initcall_start = .;
   *(.security_initcall.init)
  __security_initcall_end = .;

  . = ALIGN(32);
  __initramfs_start = .;
   usr/built-in.o(.init.ramfs)
  __initramfs_end = .;

  . = ALIGN(4096);
  __per_cpu_start = .;
   *(.data.percpu)
  __per_cpu_end = .;

  __init_begin = _stext;
  *(.init.data)
  . = ALIGN(4096);
  __init_end = .;

 }

燒寫核心

在uboot下,輸入k進入燒寫,使用dnw燒寫程式,具體的k實現了什麼命令,檢視下uboot程式碼,在cmd_menu.c定義

strcpy(cmd_buf, 

"usbslave 1 0x30000000; nand erase kernel; 
nand write.jffs2 0x30000000 kernel $(filesize)");

run_command(cmd_buf, 0);

然後就可以通過b啟動