您尚未登录。

楼主 #1 2018-08-21 20:22:10

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

Linux_kernel 简单跟踪分析001

内核启动流程分析
1. 内核打补丁、配置、编译、烧写、实验
    a. 解压缩、打补丁
        tar xjf linux-2.6.22.6.tar.bz2
        cd linux-2.6.22.6
        patch -p1 <../linux-2.6.22.6_jz2440.patch
    b. 配置
        . make menuconfig // 自己根据菜单配置
            配置项很多,比较麻烦。
        . 使用默认配置,并在上面修改   
            find -name "*defconfig*"    // 查找所有默认配置文件 
            cd ./arch/arm/configs        // ARM 架构
            找到相似的配置文件        s3c2410_defconfig
            cd linux-2.6.22.6/
            make s3c2410_defconfig    //所有的配置项被写入 .config 文件中去
            make menuconfig            // 读取 .config 以菜单形式显示
            教程中用的Ubuntu9.10
            实际用的ubuntu 16.04 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’。 停止。
           
            https://blog.csdn.net/u013944565/article/details/77686569

        . 使用厂家提供的配置文件
            将厂家提供的配置文件 命名为 .config
            make menuconfig
           
            对应开发板 cp config_ok .config 即可
        make menuconfig 报错
        `Symbol ‘acs_map’ has different size in shared object, consider re-linking
        解决方法:
        sudo apt-get install libncurses5-dev libncursesw5-dev
        https://blog.csdn.net/czg13548930186/article/details/79851149
       
        make menuconfig 菜单操作
       
        上下左右移动光标。
        回车进入子菜单
        Y 选中,编译进内核
        N 不选中
        M 编译为模块
        加粗字体为相应的快捷键
        ? 为帮助
        / 搜索
        esc 两次退出
    c. 编译
        make uImage    
            1> 根据.config 生成 autoconfig.h 文件,供原代码使用
            2> 根据.config 生成 auto.config 文件,供子目录 Makefile 使用,在顶层Makefile中包含。
       
       
        make uImage时出现错误:
        UIMAGE arch/arm/boot/uImage
        “mkimage” command not found - U-Boot images will not be built
        Image arch/arm/boot/uImage is ready
       
        解决方法:
        安装u-boot-tools软件包:
        sudo apt-get install u-boot-tools
       
    d. 下载内核
        在uboot菜单选择k
        在电脑上用dnw.exe -> USB Poart -> Transmit 选择编译好的内核文件 uImage
        生成的内核文件在 /work/kernel/linux-2.6.22.6/arch/arm/boot 文件下
        根文件系统的删除:
        在uboot 下执行 nand erase root
       
        没有根文件时,内核初始化后就卡死了。
       
2. 内核功能、结构,结合Makefile、Kconfig 进行分析

因为配置完后会生成一个.config 文件,所以我们分析 .config 文件中去

.config 里面都是配置项,我们以CONFIG_DM9000 为例分析。

grep "CONFIG_DM9000" * -nwR

1> arch/arm/plat-s3c24xx/common-smdk.c 源码用到        来自头文件 autoconfig.h
2> drivers/net/Makefile 子目录 Makefile 用到    // y 或 m 在 Makefile 体现    obj-$(CONFIG_DM9000) += dm9dev9000c.o  Makefile中的CONFIG_DM9000 来自于 auto.config
    如果是 y 则被编译进内核
    如果是 m 则被编译成 .ko 模块
3> include/config/auto.conf    // 来自.config自动生成 CONFIG_DM9000=y   auto.conf 中的内容要被别的Makefile使用,所以它被包含在顶层的Makefile中
    在顶层Makefile中查找 /auto.conf
    -include include/config/auto.conf   Makefile:443
4> include/linux/autoconfig.h    // 来自.config自动生成   CONFIG_DM9000 = 1 不管是y 或 m 在头文件里都是1

分析Makefile和链接脚本
Makefile的介绍看韦老师书本 16.2.2 表 16.3
关于Makefile的文档请看: 源码下的Documentation/kbuid/Makefile.txt

子目录下的Makefile很简单
obj-y += a.o
obj-m += b.o

a.c b.c
怎么将两个源文件编译进内核
obj-y += a.o b.o
怎么将两个文件编译成模块    Makefile.txt->Line:190
obj-m += ab.o
ab-obj := a.o b.o

我们编译内核是使用 make uImage

所以我们查找 uImage 
grep "uImage" * -nwR

uImage 在架构相关的文件中定义
arch/arm/Makefile:227:zImage Image xipImage bootpImage uImage: vmlinux

uImage 并没有在顶层Makefile中定义,所以 顶层文件应该包含 arch/arm/Makefile 文件

在顶层文件中搜索 include

/include n 下一个

Makefile:413
include $(srctree)/arch/$(ARCH)/Makefile


arch/arm/Makefile:227:zImage Image xipImage bootpImage uImage: vmlinux
继续分析 uImage 依赖于 vmlinux uImage = 头 + 真正的内核(vmlinux)

继续搜索 vmlinux 不在架构 Makefile 中,在顶层 Makefile 中有定义
grep "vmlinux" Makefile -nwR
745:vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE

展开:
    $(vmlinux-lds) 是 链接脚本  arch/arm/kernel/vmlinux.lds

608:vmlinux-init := $(head-y) $(init-y)
    /* 第一个文件 */
    grep "head-y" arch/arm/Makefile -nwR    // 架构 Makefile 中
    94:head-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o    // 最初始的代码 第一个链接的应该是 arch/arm/kernel/head$(MMUEXT).o
    /* 初始化 */
    grep "init-y" Makefile -nwR                // 顶层 Makefile 中
    434:init-y        := init/
    573:init-y        := $(patsubst %/, %/built-in.o, $(init-y))    //  init-y = init/built-in.o 最终init 目录下的文件会被编译成一个built-in.o文件 (patsubst 是一个Makefile 函数) 
609:vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
    /* 核心 */
    grep "core-y" Makefile -nwR   
    438:core-y        := usr/
    562:core-y        += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
    574:core-y        := $(patsubst %/, %/built-in.o, $(core-y))
    最终 core-y := 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
   
    /* 库 */
    grep "libs-y" Makefile -nwR
    437:libs-y        := lib/
    577:libs-y1        := $(patsubst %/, %/lib.a, $(libs-y))
    578:libs-y2        := $(patsubst %/, %/built-in.o, $(libs-y))
    579:libs-y        := $(libs-y1) $(libs-y2)
    最终 libs-y := lib/lib.a lib/built-in.o
   
    /* 驱动 */
    grep "drivers-y" Makefile -nwR
    435:drivers-y    := drivers/ sound/
    575:drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y))
    最终 drivers-y := drivers/built-in.o sound/built-in.o
   
    /* 网络 */
    grep "net-y" Makefile -nwR
    436:net-y        := net/
    576:net-y        := $(patsubst %/, %/built-in.o, $(net-y))
    最终 net-y := net/built-in.o
   

610:vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
611:vmlinux-lds  := arch/$(ARCH)/kernel/vmlinux.lds

手工展开太过复杂,所以我查看编译的最后一条信息:
rm vmlinux    // 删除 vmlinux 重新编译
make uImage V=1    // V=1 显示详细的编译信息

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
通过Makefile的分析我们大概能知道内核的结构,看韦老师书本 16.2.2 表 16.2

确定链接脚本:arch/arm/kernel/vmlinux.lds // 根据 vmlinux.lds.S 生成的 链接脚本中的链接地址是虚拟地址
确定第一个文件:arch/arm/kernel/head.o
然后就可以根据第一个文件,一路跟踪下去了。

3. 内核的启动流程分析 详细请看 韦老师书本 16.3.1 图 16.7
    a. __lookup_processor_type 确定内核是否支持该架构
    b. __lookup_machine_type 确定内核时候支持该单板 u-boot 传入的第二个参数
    c. __create_page_tables 建立一级页表
    d. _arm920_setup 禁止ICache、DCache
    e. __enable_mmu 使能MMU
    f. __mmap_switched 复制数据段,清除BSS段,设置栈指针,保存CPU ID 到processor_id 变量、保存机器类型ID到_machine_arch_type变量,调用start_kernel
   




分析 arch/arm/kernel/head.S 文件

    bl    __lookup_processor_type        @ r5=procinfo r9=cpuid // 协处理器获取处理器类型
    bl    __lookup_machine_type        @ r5=machinfo    // 判断传入的机器ID  在head-common.S 文件中去
   
    __lookup_machine_type:        @ 查找机器类型 u-boot  传入的di 2 个参数
    adr    r3, 3b                @ r3 = address of 3b ,real address, phy address  3:    .long    .   qian mian
    ldmia    r3, {r4, r5, r6}    @ r4 ="." virtual address of 3b, r5 = __arch_info_begin , r6 = __arch_info_end  r5 r6 在链接脚本中定义,中间是架构信息初始化段 .arch.info.init
    sub    r3, r3, r4            @ get offset between virt&phys
    add    r5, r5, r3            @ convert virt addresses to
    add    r6, r6, r3            @ physical address space  转换虚拟地址到物理地址(当前的虚拟地址只是链接时候用的虚拟地址,因为MMU还没有启动)
1:    ldr    r3, [r5, #MACHINFO_TYPE]    @ get machine type
    teq    r3, r1                @ matches loader number?  r1 di 2 个参数
    beq    2f                @ found        2f  hou mian       2:    mov    pc, lr
    add    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    cmp    r5, r6
    blo    1b
    mov    r5, #0                @ unknown machine
2:    mov    pc, lr

在链接脚本中:
arch/arm/kernel/vmlinux.lds    
  __arch_info_begin = .;
   *(.arch.info.init)
  __arch_info_end = .;
搜索 .arch.info.init

grep ".arch.info.init" * -nR

Binary file arch/arm/mach-s3c2412/mach-smdk2413.o matches
Binary file arch/arm/mach-s3c2412/built-in.o matches
Binary file arch/arm/mach-s3c2412/mach-vstms.o matches
Binary file arch/arm/mach-s3c2410/mach-qt2410.o matches
Binary file arch/arm/mach-s3c2410/mach-smdk2410.o matches
Binary file arch/arm/mach-s3c2410/built-in.o matches
Binary file arch/arm/mach-s3c2440/mach-smdk2440.o matches
Binary file arch/arm/mach-s3c2440/built-in.o matches
Binary file arch/arm/mach-s3c2443/mach-smdk2443.o matches
Binary file arch/arm/mach-s3c2443/built-in.o matches        // 内核也支持上面这些单板
arch/arm/kernel/vmlinux.lds.S:39:            *(.arch.info.init)                                // 生成的链接脚本
arch/arm/kernel/vmlinux.lds:306:   *(.arch.info.init)                                        // 真实的链接脚本
include/asm-arm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {    \        // 文件夹链接
include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {\            // 真实的文件

arch.h 找到定义

#define MACHINE_START(_type,_name)            \
static const struct machine_desc __mach_desc_##_type    \
__used                            \
__attribute__((__section__(".arch.info.init"))) = {    \
    .nr        = MACH_TYPE_##_type,        \
    .name        = _name,

#define MACHINE_END                \
};

搜索这个宏 MACHINE_START 谁在使用 ,选择其中一个单板 Mach-smdk2440.c (arch\arm\mach-s3c2440):MACHINE_START(S3C2440, "SMDK2440")

MACHINE_START(S3C2440, "SMDK2440")
    /* Maintainer: Ben Dooks <ben@fluff.org> */
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,

    .init_irq    = s3c24xx_init_irq,
    .map_io        = smdk2440_map_io,
    .init_machine    = smdk2440_machine_init,
    .timer        = &s3c24xx_timer,
MACHINE_END

展开:

static const struct machine_desc __mach_desc_S3C2440    \
__used                            \
__attribute__((__section__(".arch.info.init"))) = {    \
    .nr        = MACH_TYPE_S3C2440,        \
    .name        "SMDK2440",
        /* Maintainer: Ben Dooks <ben@fluff.org> */
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,

    .init_irq    = s3c24xx_init_irq,
    .map_io        = smdk2440_map_io,
    .init_machine    = smdk2440_machine_init,
    .timer        = &s3c24xx_timer,
};

相当于定义了一个结构体 machine_desc 这个结构体被放在 .arch.info.init 段,查看结构体 machine_desc

struct machine_desc {
    /*
     * Note! The first four elements are used
     * by assembler code in head-armv.S
     */
    unsigned int        nr;        /* architecture number    */
    unsigned int        phys_io;    /* start of physical io    */
    unsigned int        io_pg_offst;    /* byte offset for io
                         * page tabe entry    */

    const char        *name;        /* architecture name    */
    unsigned long        boot_params;    /* tagged list        */

    unsigned int        video_start;    /* start of video RAM    */
    unsigned int        video_end;    /* end of video RAM    */

    unsigned int        reserve_lp0 :1;    /* never has lp0    */
    unsigned int        reserve_lp1 :1;    /* never has lp1    */
    unsigned int        reserve_lp2 :1;    /* never has lp2    */
    unsigned int        soft_reboot :1;    /* soft reboot        */
    void            (*fixup)(struct machine_desc *,
                     struct tag *, char **,
                     struct meminfo *);
    void            (*map_io)(void);/* IO mapping function    */
    void            (*init_irq)(void);
    struct sys_timer    *timer;        /* system tick timer    */
    void            (*init_machine)(void);
};

继续分析 :arch/arm/kernel/head.S

bl    __create_page_tables // 创建一级页表
ldr    r13, __switch_data        @ address to jump to after mmu has been enabled   当MMU使能之后 跳转到 __mmap_switched
adr    lr, __enable_mmu        @ return (PIC) address 使能MMU

ldr    r13, __switch_data 的意思:
r13 = __switch_data的地址 查看 __switch_data:head-common.S (arch\arm\kernel):__switch_data:
__switch_data:
    .long    __mmap_switched     // 相当于 r13 中保存了 __mmap_switched 函数的入口地址,这个地址是运行时地址 (虚拟地址,因为链接脚本中用的是虚拟地址)
    .long    __data_loc            @ r4
    .long    __data_start            @ r5
    .long    __bss_start            @ r6
    .long    _end                @ r7
    .long    processor_id            @ r4
    .long    __machine_arch_type        @ r5
    .long    cr_alignment            @ r6
    .long    init_thread_union + THREAD_START_SP @ sp


adr    lr, __enable_mmu 的意思:
相对地址adr 当前地址 与链接位置无关 。查看 __enable_mmu

__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
    orr    r0, r0, #CR_A
#else
    bic    r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
    bic    r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
    bic    r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
    bic    r0, r0, #CR_I
#endif
    mov    r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    mcr    p15, 0, r5, c3, c0, 0        @ load domain access register
    mcr    p15, 0, r4, c2, c0, 0        @ load page table pointer
    b    __turn_mmu_on                // MMU结尾调用了  __turn_mmu_on
   
分析 __turn_mmu_on 代码:

__turn_mmu_on:
    mov    r0, r0
    mcr    p15, 0, r0, c1, c0, 0        @ write control reg
    mrc    p15, 0, r3, c0, c0, 0        @ read id reg
    mov    r3, r3
    mov    r3, r3
    mov    pc, r13        // 将PC 指针指向 R13 的运行地址 即  __mmap_switched 函数的入口地址
   
再分析__mmap_switched 代码:
__mmap_switched:
    adr    r3, __switch_data + 4

    ldmia    r3!, {r4, r5, r6, r7}
    cmp    r4, r5                @ Copy data segment if needed
1:    cmpne    r5, r6
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b

    mov    fp, #0                @ Clear BSS (and zero fp)
1:    cmp    r6, r7
    strcc    fp, [r6],#4
    bcc    1b

    ldmia    r3, {r4, r5, r6, sp}
    str    r9, [r4]            @ Save processor ID
    str    r1, [r5]            @ Save machine type
    bic    r4, r0, #CR_A            @ Clear 'A' bit
    stmia    r6, {r0, r4}            @ Save control register values
    b    start_kernel            // 最终调用 start_kernel C 函数   到这个位置,第一阶段代码就算完毕了,更复杂的功能在C语言中实现。
   
   
第一阶段并没有使用到u-boot 传递进来的参数,所以应该是在C函数中实现的,我们继续分析 start_kernel

一些初始化函数
printk(linux_banner);    // 打印内核信息
setup_arch(&command_line);// u-boot 传入进来的命令行
setup_command_line(command_line);


分析 setup_arch(&command_line) 函数:

    struct tag *tags = (struct tag *)&init_tags;    // 标签
    struct machine_desc *mdesc;                        // 架构信息的结构 machine_desc
    char *from = default_command_line;                // 默认命令行参数 , 如果u-boot 没有传入参数会执行默认命令行参数。
   
    // 物理地址转虚拟地址
    tags = phys_to_virt(mdesc->boot_params);    // mdesc->boot_params = S3C2410_SDRAM_PA + 0x100 = 物理地址+0x100 刚好等于 u-boot 参数的存放地址 0x30000100  theKernel (0, bd->bi_arch_number, bd->bi_boot_params)
    parse_tags(tags);                            // 解析 tags
   
    parse_cmdline(cmdline_p, from);                // 单独解析命令行
    ....
   
返回 start_kernel 函数继续分析:
start_kernel()
    ...
    setup_arch(&command_line);            // 解析 u-boot 传入进来的参数
    setup_command_line(command_line);    // 解析 u-boot 传入进来的参数
    ...
    parse_early_param();
        do_early_param
            从 __setup_start 扫描到 __setup_end  调用 early 函数
    unknown_bootoption
        obsolete_checksetup
            从 __setup_start 扫描到 __setup_end  调用 非 early 函数
    ...
    rest_init();
        创建 kernel_init 线程 :kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
            prepare_namespace();
                mount_root();// 我们的最终目的是挂接根文件系统
            init_post();
                sys_open((const char __user *) "/dev/console", O_RDWR, 0)// 打开控制台
                // 执行应用程序
                run_init_process("/sbin/init");
                run_init_process("/etc/init");
                run_init_process("/bin/init");
                run_init_process("/bin/sh");

挂载根文件系统是哪个盘/分区?

传递进来的参数是 char *commandline = getenv ("bootargs");// 来自于环境变量 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
root=/dev/mtdblock3 根文件放在第四个分区上
prepare_namespace(); 函数中 根文件挂载 mount_root(); 之前肯定要确定挂接的是哪个文件系统

进入 prepare_namespace
if (saved_root_name[0]) {
    root_device_name = saved_root_name;    //    设备名字在  saved_root_name[0] 数组中
    if (!strncmp(root_device_name, "mtd", 3)) {
        mount_block_root(root_device_name, root_mountflags);
        goto out;
    }
    ROOT_DEV = name_to_dev_t(root_device_name); // ROOT_DEV 根文件设备  root_device_name 设备名字
    if (strncmp(root_device_name, "/dev/", 5) == 0)
        root_device_name += 5;
}

搜索 saved_root_name

Do_mounts.c (init):static char __initdata saved_root_name[64];
Do_mounts.c (init):    strlcpy(saved_root_name, line, sizeof(saved_root_name));
Do_mounts.c 中 下面这两段代码与 u-boot 中的命令行相似 一个结构对应一个函数,我们可以猜测,先检测到 "root=" 这个字符串,然后执行对应的函数 root_dev_setup

static int __init root_dev_setup(char *line) 
{
    strlcpy(saved_root_name, line, sizeof(saved_root_name));// root_dev_setup 把/dev/mtdblock3 保存到 saved_root_name 中去
    return 1;
}

__setup("root=", root_dev_setup);    // 这里有 "root="

分析 __setup 宏,这个宏是一个结构体变量,它被强制定义在某一个段内,然后会有其他程序从这个段的起始地址轮训到结束地址去执行这些 __setup 命令。
搜索 __setup 搜索结果中搜索 define

Init.h (include\linux):#define __setup(str, fn)                    \

#define __setup(str, fn)                    \
    __setup_param(str, fn, fn, 0)
   
查找 __setup_param 的定义

#define __setup_param(str, unique_id, fn, early)            \
    static char __setup_str_##unique_id[] __initdata = str;    \
    static struct obs_kernel_param __setup_##unique_id    \
        __attribute_used__                \
        __attribute__((__section__(".init.setup")))    \
        __attribute__((aligned((sizeof(long)))))    \
        = { __setup_str_##unique_id, fn, early }

根据上面的信息对 __setup("root=", root_dev_setup) 进行展开

#define __setup_param(str, unique_id, fn, early)            \
    static char __setup_str_root_dev_setup[] __initdata = "root=";    \    // 定义一个字符串 __setup_str_root_dev_setup
    static struct obs_kernel_param __setup_root_dev_setup    \
        __attribute_used__                \
        __attribute__((__section__(".init.setup")))    \
        __attribute__((aligned((sizeof(long)))))    \
        = { __setup_str_root_dev_setup, root_dev_setup, 0 }                // __setup_str_root_dev_setup 对应的函数 root_dev_setup
       
.init.setup 在链接脚本的定义 :
  __setup_start = .;
   *(.init.setup)
  __setup_end = .;
 
搜索 __setup_start __setup_end 看谁调用扫描了这些段,以及执行了这些对应函数

Main.c (init):extern struct obs_kernel_param __setup_start[], __setup_end[];    // 链接脚本地址声明
Main.c (init):    p = __setup_start;                                                // static int __init obsolete_checksetup(char *line) 函数
Main.c (init):    for (p = __setup_start; p < __setup_end; p++) {                    // static int __init do_early_param(char *param, char *val) 函数

分析这两个函数
--------------------------------------------------

static int __init obsolete_checksetup(char *line)
{
    struct obs_kernel_param *p;
    int had_early_param = 0;

    p = __setup_start;
    do {
        int n = strlen(p->str);
        if (!strncmp(line, p->str, n)) {
            if (p->early) {                                // p->early = 1
                /* Already done in parse_early_param?
                 * (Needs exact match on param part).
                 * Keep iterating, as we can have early
                 * params and __setups of same names 8( */
                if (line[n] == '\0' || line[n] == '=')
                    had_early_param = 1;
            } else if (!p->setup_func) {                // 对应的函数为空
                printk(KERN_WARNING "Parameter %s is obsolete,"
                       " ignored\n", p->str);
                return 1;
            } else if (p->setup_func(line + n))        // 执行 __setup 中字符串对应的函数  p->early = 0 且 对应的函数不为空  上面 __setup("root=", root_dev_setup) 展开中 p->early = 0 所以 __setup("root=", root_dev_setup) 在这里面调用
                return 1;
        }
        p++;
    } while (p < __setup_end);

    return had_early_param;
}

obsolete_checksetup 谁来调用的

static int __init unknown_bootoption(char *param, char *val)
{
    /* Change NUL term back to "=", to make "param" the whole string. */
    if (val) {
        /* param=val or param="val"? */
        if (val == param+strlen(param)+1)
            val[-1] = '=';
        else if (val == param+strlen(param)+2) {
            val[-2] = '=';
            memmove(val-1, val, strlen(val)+1);
            val--;
        } else
            BUG();
    }

    /* Handle obsolete-style parameters */
    if (obsolete_checksetup(param))    // 传入参数,调用 obsolete_checksetup
        return 0;
    ......    // 略
    ......
}

unknown_bootoption 由 C 入口函数 asmlinkage void __init start_kernel(void) 调用

parse_args("Booting kernel", static_command_line, __start___param,
           __stop___param - __start___param,
           &unknown_bootoption);    // 调用 unknown_bootoption   parse_args 不在细分 应该是给 unknown_bootoption 传递参数 并调用

--------------------------------------------------
/* Check for early params. */
static int __init do_early_param(char *param, char *val)
{
    struct obs_kernel_param *p;

    for (p = __setup_start; p < __setup_end; p++) {
        if (p->early && strcmp(param, p->str) == 0) {        // 判断传入的参数 param 是否 等于 p->str 等于是在.init.setup 段里面扫描匹配字符串 且 p->early = 1 执行  上面 __setup("root=", root_dev_setup) 展开中 p->early = 0 所以 __setup("root=", root_dev_setup) 并不在这里面调用
            if (p->setup_func(val) != 0)                    // p->setup_func(val) 执行对应的函数
                printk(KERN_WARNING
                       "Malformed early option '%s'\n", param);
        }
    }
    /* We accept everything at this stage. */
    return 0;
}

do_early_param 谁来调用的

void __init parse_early_param(void)
{
    static __initdata int done = 0;
    static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

    if (done)
        return;

    /* All fall through to do_early_param. */
    strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
    parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);    // 这里调用 do_early_param    parse_args 有时间自己分析 应该是给 do_early_param 传递参数并调用
    done = 1;
}

parse_early_param 由 C 入口函数 asmlinkage void __init start_kernel(void) 调用
        parse_early_param();

-------------------------------------------------------------------

分区的概念:

root=/dev/mtdblock3 根文件放在第四个分区上,我们的NAND Flash 没有分区表,所以怎么确认第四个分区的?

由于没有分区表,所以只能在代码里面写死了
以u-boot 为例

bootloader||环境变量(参数)||kernel||文件系统(root)
可以输入 u-boot 命令 mtd 查看
#: name                size        offset            mask_flags
0: boot_loader    0x00040000        0x00000000        0
1: params        0x00020000        0x00040000        0
2: kernel        0x00200000        0x00060000        0
3: root            0x0fda0000        0x00260000        0

怎么修改分区,参考韦老师书本 16.3.3 MTD 分区

如果换一个单板,或内核,不知道这些分区信息怎么办?
可以查看内核启动时的分区打印信息
然后在代码中搜索对应的字符串如:
grep "\"bootloader\"" * -nR

arch/arm/plat-s3c24xx/common-smdk.c:120:        .name   = "bootloader",

static struct mtd_partition smdk_default_nand_part[] = {
    [0] = {
        .name   = "bootloader",
        .size   = 0x00040000,
        .offset    = 0,
    },
    [1] = {
        .name   = "params",
        .offset = MTDPART_OFS_APPEND, // 表示接着上一个分区
        .size   = 0x00020000,
    },
    [2] = {
        .name   = "kernel",
        .offset = MTDPART_OFS_APPEND,
        .size   = 0x00200000,
    },
    [3] = {
        .name   = "root",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
    }
};

更多请看 韦老师文本第 16 章

离线

#2 2018-08-21 21:26:25

晕哥
管理员
注册时间: 2017-09-06
已发帖子: 9,348
积分: 9202

Re: Linux_kernel 简单跟踪分析001

https://whycan.cn/t_1075.html

xiaoci 真是一个努力的朋友,挖坑网见证小白到大神的过程!





离线

楼主 #3 2018-08-22 08:28:21

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

Re: Linux_kernel 简单跟踪分析001

晕哥 说:

https://whycan.cn/t_1075.html

xiaoci 真是一个努力的朋友,挖坑网见证小白到大神的过程!

谢谢晕哥!最近的事情有点忙,耽误太多时间,前同事离职,几个烂尾项目都压在我手上,老板一直也在催进度!大女儿刚幼儿园报名要适应节奏,小孩要适应,家长也要适应。二孩还有一个月左右出生,老婆的脾气也是被现实生活折磨的非常敏感易怒,我也非常能理解她的心情,感觉自己在感情上成熟了不少(怕老婆了!!)。总之一言难尽,生活本就如此,有苦有甜吧。然后就是自己时间也安排的不是太合理,学习被一些乱七八糟的事情打乱,有点浮躁。但我不会放弃,最近可能会优先清理手头的烂尾工作,晚上抽时间出来看书学习,论坛进度更新。死磕到底,不会放弃。

离线

#4 2018-08-22 08:34:06

jumiao
会员
注册时间: 2018-08-22
已发帖子: 2
积分: 2

Re: Linux_kernel 简单跟踪分析001

刚刚百度到这里,看到楼主的发言,感觉一把辛酸泪,和我太相似了,都快哭出来了,生活不易, 且行且珍惜!

离线

#5 2018-08-22 08:38:28

jumiao
会员
注册时间: 2018-08-22
已发帖子: 2
积分: 2

Re: Linux_kernel 简单跟踪分析001

面对这已经不再年轻的自己,现实中各种压力,再加上今年第一批00后上大学,三年后面临着和第一批00后抢饭碗, 心中也是五味杂陈.

离线

#6 2018-08-22 08:48:45

晕哥
管理员
注册时间: 2017-09-06
已发帖子: 9,348
积分: 9202

Re: Linux_kernel 简单跟踪分析001

xiaoxiaoci 的压力我感同身受, 不过现在两个孩子都大了,
比以前轻松很多了, 但是仍然面临各种问题和压力,
我想只要自己不放弃自己,未来就会充满希望.

未来你和你的家庭, 一定会感谢现在努力的你!





离线

#7 2018-08-22 09:23:54

Jin劲
会员
注册时间: 2018-04-06
已发帖子: 217
积分: 217

Re: Linux_kernel 简单跟踪分析001

刚出来工作 迷茫中...

离线

#8 2018-08-22 09:26:08

晕哥
管理员
注册时间: 2017-09-06
已发帖子: 9,348
积分: 9202

Re: Linux_kernel 简单跟踪分析001

还要面临和小劲这样的实力派竞争 ...





离线

#9 2019-01-19 20:40:05

jw__liu
会员
注册时间: 2019-01-18
已发帖子: 40
积分: 40

Re: Linux_kernel 简单跟踪分析001

mark一下

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn