• Linux内核初探


    在uboot学习的时候, 我们知道了一个庞大的程序,感觉无从下手,但其实,通过韦老师和一些老手的经验告诉我,如果我们不是专门弄uboot的,一般只用知道怎么用就行了。确实这个东西太大了而且花那么多时间去弄这个也不值得。同理,uboot的终极奥义是启动内核,现在uboot的简单应用我们已经会了,内核是一个比uboot更加庞大的家伙,只是2440相关的文件都1w多个,要想一接触就弄清楚每一步是干什么的也几乎不可能,但是内核和uboot不同,里面需要细细研究和借鉴的东西很多,只是刚开始,我们还是跟着师傅学一点皮毛,能跑上应用程序再说。显而易见的是现在没有什么教程能教会你linux内核的全部知识,除非linus来教你,^_^。

    内核操作方式如同uboot一样,依旧先下载内核源码,然后解压缩,然后打补丁。接着就是配置了。

    以下部分内容借鉴韦老师一学员的笔记,我太懒了,有现成的决不自己来。

     搜索到了目录部分截图如下:

     然后我们到arm构架下查看有没有我们熟悉的配置项,结果是有的,2410:

     现在执行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 'menuconfig'.  Stop

    因为我的ubuntu是16.04新版的make编译器:

    新版Makefile不支持这样的组合目标:config %config(一个有通配符,另一个没有通配符)

    解决方法:

    修改linux-2.6.22.6 顶层Makefile 416行:

    config %config: scripts_basic outputmakefile FORCE

    改为:

    %config: scripts_basic outputmakefile FORCE

     

    修改linux-2.6.22.6 顶层 Makefile 1449行:

    / %/: prepare scripts FORCE

    改为:

    %/: prepare scripts FORCE

    可以看到生成了.config:

    然后make menuconfig,这个会去调用刚才生成的.config

     像上面内核配置的界面,高亮显示的第一个字母是这个选项的快捷键(不区分大小写),按下  / 建进入搜索模式,具体介绍上面图片的英文有说明。

    上面是默认配置,和默认配置同等级别或者更高级别的是使用厂家提供的config文件,这里使用韦老师的config_ok文件试试:

    使用厂家的配置文件覆盖了.config之后,执行make menuconfig:

     

     如果根文件系统没有下载到内存,启动内核的时候的会失败。

    等于m代表的是编译或模块。看看.config中网卡DM9000的配置:

    我们在内核文件全局搜索一下:grep -w选项表示精确查找

    需要注意的是,能搜素到DM9000是我们要执行make或者make uImage之后,否则搜索不到哦。

    可以看看autocnf.h看看自动配置头文件。

    还有 Makefile 中有:
    drivers etMakefile
    #obj-$(CONFIG_DM9000) += dm9000.o
    #obj-$(CONFIG_DM9000) += dm9ks.o
    则:
    a. C 源代码中用到 CONFIG_DM9000.C 语言语法看肯定是个宏。宏只能在 C 文件或是
    头文件中定义。
    依照这里的情况,应该是在“include/linux/autoconf.h ”这个头文件中定义。
    b. 子目录下的 Makefile 中有 CONFIG_DM9000 配置项(如 drivers/net/Makefile
    c. include/config/auto.conf 中有。
    d. include/linux/autoconf.h 中有。从 autoconf.h 可猜测这个文件是自动生成的。它里面的内
    容来源于
    make 内核时, make 机制会自动根据生成的 “.config” 配置文件,生成 autconf.h 这个文
    件。
    autoconf.h 中搜索 DM9000 得到: #define CONFIG_DM9000 1
    可见 CONFIG_DM9000 被定义成一个宏,为“1”。
    整个文件 autoconfi.h 中的宏基本都是定义为“1”,就是说不管在 .config 配置文件中,
    配置项=y,还是=m
    在这个由 .config 生成的头文件 autoconf.h 中都被定义成“1”。
    C 语言源码中就只使用这些 宏 了。这些等于 y 等于 m 的在 autoconf.h 中定义宏时都定
    义为“1”,它们的区别则在
    使用这些宏的 C 语言中体现不出来了。这些区别是在 Makefile 中定义。看子目录下的
    Makefile.
    看子目录 dervice/net/Makefile 文件。搜索其中 CONFIG_DM9000 文件。
    对于内核的 Makefile,它的子目录的 Makefie 很简单。
    2. 内核子目录 Makefile:
    1.格式比较简单:内核子目录下的 Makefile
    obj-y += xxx.o
    xxx.c 的文件最后会被编译进内核中去。
    obj-m += yyy.o
    yyy.c 文件最后会编译成 可加载 的模块 yyy.ko
    这两种格式。
    如下示例:
    obj-$(CONFIG_DM9000) += dm9000.o
    意思:若 CONFIG_DM9000 这个变量被定义为 y 的话,这个 dm9000.c 就会被编译进内核
    中去。
    若这个配 置 项 CONFIG_DM9000 被 定义为 m 的话 ,这 个 dm9000.c 会被编译成
    dm9000.ko 模块
    y m 的区别就是在内核的子目录下的 Makefile 中体现的。子目录下的 Makefile
    中的 CONFIG_DM9000 是谁来定义的呢?
    也是由其他来定义的。由 "include/config/auto.conf " 来定义(这个文件也是来源于 make
    内核时的 .config)。从 auto.conf
    可知它也是自动生成的。 里面的内核和 .config 文件很像。其中的项要么等于 y,要么等于
    m,或其他值。
    显然这个 include/config/auto.conf 文件是会被别人包含进去。(被顶层的 Makefile 包含)

    配置内核时生成了 .config 文件。然后 make menuconfig 或是 make uImage 时:
    1.config 被自动来创建生成了一个 include/linux/autoconfig.h 文件 .这个头文件被 C
    代码去对应里面的配置。
    2.config 也被自动来生成一个 include/config/auto.conf 文件。这个文件由顶层 Makefile
    来包含。由子目录下的 Makefile 来用它。

    三、 内核 Makefile
    分析 Makefile:找到第一个目标文件和链接文件。

    对于 Makefile 的文档在 Documentationkbuild 下的 makefiles.txt 对内核的 makefile 讲的很透彻。 

    子目录下的 Makefile 很简单,就只有几条格式:
    obj-y += a.o b.o
    obj-m += a.o

    2. 架构相关的 Makefile 。(arch/$(ARCH)/Makefile
    分析一个 Makefile 时,从它的命令开始分析。编译内核时是直接 make make uImage
    从顶层 Makefile 一直往下走时会涉及到所有的东西。
    <1> make uImage 时这个目标 uImage 不在顶层的 Makefile 中,在 arch/arm/Makefile
    定义了这个目标。
    我们是在顶层目录 make uImage 的,则可知顶层 Makefile 会包含 arch/arm/Makefile

     

    3. 顶层目录的 Makefile.
    make uImage 命令往下分析。
    <1> 目标 uImage 定义在 arch/arm/Makefile 中,找到 uImage 目标所在行,查看它相关
    的依赖。

    在顶层目录直接输入 make ,默认就是执行第一个目标, "all"就是第一个目标。这个目标也
    是依赖于 vmlinux 。即都是要先生成 vmlinux .

    init-y := $(patsubst %/, %/built-in.o, $(init-y))
    这是一个 Makefile 的函数。
    %/ 代表的是 init/目录下的所有文件。
    %/built-in.o 相当于在 init/下的文件全部编译成 built-in.o
    这个函数的意思是: init-y := $(patsubst %/, %/built-in.o, $(init-y)) = init/built-in.o
    init-y 等于 init 目录下所有涉及的那些文件,这些文件会被编译成一个 built-in.o
    patsubst :替换通配符。
    $(patsubst %.c%.o$(dir) )中, patsubst $(dir)中的变量符合后缀是.c 的全部替换成.o
    任何输出。
    或者可以使用
    obj=$(dir:%.c=%.o)
    效果也是一样的。

    vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
    core-y :核心 libs-y:库 drivers-y:驱动 net-y:网络
    Makefile 中搜索 core-y 有如下依赖:
    core-y := usr/
    core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
    core-y := $(patsubst %/, %/built-in.o, $(core-y))
    意思是最后 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
    就是将这些目录(usrkernelmmfsipcsecuritycryptoblock)下涉及的文件分别
    编译成 built-in.o
    不是所有文件,而是涉及到的文件。

    libs-y : 依赖
    libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
    libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
    libs-y := $(libs-y1) $(libs-y2)
    最后 libs-y = lib/lib.a
    += lib/built-in.o
    drivers-y : 驱动
    drivers-y := drivers/ sound/ (依赖了这两个目录)
    drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
    意思是最后 drivers-y = drivers/built-in.o (drivers 目录下所有涉及的文件编译成
    built-in.o 文件)
    += sound/built-in.o (sound 目录下所有涉及的编译成 built-in.o 文件)
    net-y : 网络
    net-y := net/
    net-y := $(patsubst %/, %/built-in.o, $(net-y))
    意思是最后,将 net/目录下的所有涉及到的文件编译 built-in.o 这个文件。
    从面的依赖文件展开来看,源材料就是上面这一大堆东西。这些东西如何组合成一个内核
    (链接成在一块),要看 vmlinux 如何编译的。

    4. vmlinux 如何编译

    编译时是通过这些命令来编译的。这些命令最终会生成什么东西?可以通过这里一一分析下
    去。这里涉及的脚本、函数太庞大了。没精
    力去做。
    想知道上在的源材料如何编译成内核:
    方法 1:分析 Makefile .
    方法 2:直接编译内核。 看编译过程。
    a.rm vmlinux 先删除原来编译得到的内核。
    b.make uImage V=1 (V=1 是更加详细的列出那些命令。 )
    我们关心详细命令的最后一条。

    分析后确定两个方面:
    第一个文件是谁:从上面 make uImage 最后一条命令可知,内核第一个文件是
    arch/arm/kernel/head.ohead.S
    链接脚本: arch/arm/kernel/vmlinux.lds (决定内核如何排布).

    再接着是放所有文件的“.init.text”段。
    等等、、、
    这些所有文件排放在相应的 “段” 中,排放的顺序就是如下“链接脚本”后面“.o”文件
    的排布顺序:

    四、 机器 ID,启动参数
    1. 建立 SI 工程:先是添加所有的代码,再去除不相关的代码。
    添加完所有的文件代码后,再移除 ARCH 目录(因为里面有不需要的代码),移除后再进到
    ARCH 目录重新
    添加 ARM 相关的代码(因为这里处理的是 ARM 平台)

    最后添加上平台: Plat-s3c24xx 平台,其他平台不需要加。
    <2> include 目录。里面也有很多东西,先“Remove Tree”后,再挑选我们需要的加进工
    程。
    include 目录中, asm 开头的目录显然是 架构 相关的头文件。我们只关心 Asm-arm
    arm 架构的头文件。

    head.S 做的事情:
    0.判断是否支持此 CPU
    1.如何比较 机器 ID 是:(判断是否支持单板)
    3.创建页表。
    4.使能 MMU
    5.跳转到 start_kernel (它就是内核的第一个 C 函数)
    2. 分析内核源代码:
    <1> 通过 make uImage V=1 详细查看内核编译时的最后一条命令可知。
    内核中排布的第一个文件是: arch/arm/kernel/head.S
    链接脚本: arch/arm/kernel/vmlinux.lds
    UBOOT 启动时首先在内存里设置了一大堆参数。

    3. 内核启动:最终目标是就运行应用程序。对于 Linux 来说应用程序在 根文件系统中。需要挂接文件系统。
     
    1)处理 UBOOT 传入的参数。
    内核中排布的第一个文件是: arch/arm/kernel/head.S

    4. 内核:
    处理 UBOOT 传入的参数
    1.首先判断是否支持这个 CPU
    2.判断是否支持这个单板。(UBOOT 启动内核时传进来的:机器 ID bd->bi_arch_number
    UBOOT 代码,对于这块开发板是: gd->bd->bi_arch_number = MACH_TYPE_SMDK2410

    这个 362 这个值存在哪里?从汇编的 C 语言交互规则知道。这个参数 bi_arch_number 是存在r1寄存器中的。

    24102440qt2410 的单板代码都强制放在这个地方。
    内核启动时,会从 __arch_info_begin = . 开始读,读到 __arch_info_end = . 一个一个的将
    单板
    信息取出来。将里面的机器 ID UBOOT 传进来的机器 ID 比较。相同则表示内核支持这个
    单板。

    下面就是比较机器 ID了。(内核中的和 UBOOT传进来的)archarmkernelhead-common.S
    从“__lookup_machine_type:”中可知 r5=__arch_info_begin
    1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
    r5 是: __arch_info_begin
    r1 UBOOT 传来的参数: bi_arch_number
    teq r3, r1 @ matches loader number?
    beq 2f @ found
    add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
    cmp r5, r6

    blo 1b
    mov r5, #0 @ unknown machine
    2: mov pc, lr
    最后比较成功后,会回到: head.S
    以上 单板机器 ID 比较完成。

     

     

    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);创建内核线程。暂且
    认为它是调用 kernel_init 这个函数。这个函数中又有一个: prepare_namespace(),它其中
    又有一个: mount_root();挂接根文件系统。

     

    缩进表示调用关系, prepare_namespace 中执行完了 挂接根文件系统 后,会执行 init_post
    函数,

    在这个函数中会打开 /dev/console 和 执行应用程序。

    __setup(“root=,root_dev_setup):其中 __setup 是一个宏。
    大 概 意 思 是 发 现 在 命 令 行 参 数 : bootargc=ninitrd root=/dev/mtdblock3 init=/linuxrc
    console=ttySAC0
    中的 root= 时,就以这个 root= 来找到“root_dev_setup”这个函数。然后调用这个函数。
    这个函数将:
    /dev/mtdblock3 init=/linuxrc console=ttySAC0 保存到
    strlcpy(saved_root_name, line, sizeof(saved_root_name));”中的变量 saved_root_name 中。
    这个变量是个数组。
    static int __init root_dev_setup(char *line)
    {
    strlcpy(saved_root_name, line, sizeof(saved_root_name));
    return 1;
    }
    __setup("root=", root_dev_setup);
    __setup 这也是个宏,这个宏也是定义一个结构体。经过分析 UBOOT 和内核可知,这个宏
    是个 结构体 。结构体里面有“root=
    root_dev_setup 函数指针(结构体中有“名字”和“函数指针”)。

    这个结构体中的 段属性强制将其放一个段.init.setup 中,这个段在链接脚本中。

    **********************************************************************
    内核启动流程:
    arch/arm/kernel/head.S
    start_kernel
    setup_arch //解析 UBOOT 传入的启动参数
    setup_command_line //解析 UBOOT 传入的启动参数
    parse_early_param
    do_early_param
    __setup_start __setup_end,调用 early 函数
    unknow_bootoption
    obsolete_checksetup
    __setup_start __setup_end,调用非 early 函数
    rest_init
    kernel_init
    prepare_namespace
    mount_root //挂接根文件系统
    init_post
    //执行应用程序
    early”“非 early”是:
    __setup("root=",root_dev_setup)
    #define __setup(str, fn)
    __setup_param(str,fn,fn,0)中参数"0".
    **********************************************************************
    从代码里知道,这里 early 这个成员为 0.则没“do_early_param” 和“parse_early_param”,

    现在是 root=/dev/mtdblock3 ,我们说过在 FLASH 中没有分区表。那这个分区 mtdblock3
    体现在写死的
    代码。和 UBOOT 一样: “bootloader|参数|内核|文件系统”在代码中写死。也是用这个分
    区,在代码里也要写死。
    启动内核时,会打印出这些“分区信息”。

    MTDPART_OFS_APPEND 这个 offset 意思是紧接着上面一个分区的意思。 

  • 相关阅读:
    0x1L
    失败全是无能,成功多是侥幸。
    也谈不甘
    维护网站小笔记
    C#反射(二) 【转】
    C#反射(一) 【转】
    短期学习目标
    局域网手机遥控关机
    密码验证
    字符串反转
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/8612063.html
Copyright © 2020-2023  润新知