1. 程式人生 > >linux kernel(二)原始碼淺析

linux kernel(二)原始碼淺析

目錄


title: kernel(二)原始碼淺析
tags: linux
date: 2018-11-08 18:02:34
---

kernel(二)原始碼淺析

建立工程

  1. 移除所有Arch,新增Arch/arm 下除了 Mach_xxx 開頭的,Mach_xxx 表示機器型號,新增2410,2440,剔除 Plat_xxx,加入plat-s3c24xx

    Arch/arm/
    
    boot
    common
    configs
    kernel
    lib
    
    mach-s3c2410
    mach-s3c2440
    plat-s3c24xx
    
    mm
    nwfpe
    oprofile
    tools
    vfp
  2. 移除include目錄,先排除所有Asm相關,只加入asm-arm頂層檔案以及2440相關的如下

    include/
    排除所有 asm-xxx
    
    include/asm-arm/下新增 所有頂層以及以下目錄
    arch-s3c2410
    hardware
    mach
    plat-s3c24xx

啟動簡析

uboot啟動通過theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

中的第二個引數是機器ID,核心通過比對機器ID判斷是否支援啟動.gd->bd->bi_arch_number = MACH_TYPE_S3C2440;linux會這麼做:

  1. 處理uboot傳入的引數
  2. 掛接根檔案系統
  3. 最終目的:執行應用程式(在根檔案系統上)

核心跳轉之前,Uboot設定核心的啟動引數.核心的引數是按照tag組織的.也就是在某個地址(0x30000100,在100ask24x0.c中定義),按照某種格式儲存,這種格式具體為【size....tagid....tag值】

mark

mark

head.s

我們發現在arch\arm\boot\compressed也存在一個head.S的檔案,有些核心編譯出來比較大,他會以壓縮的形式存在也就是包含了自解壓的程式碼,這個檔案就是講壓縮的檔案解壓,在這裡不做分析。我們的入口為arch\arm\kernel\head.S

入口點

連結指令碼有寫,也就是說_stext段為最早的入口點,搜尋下head.S中的入口點.text.head

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

查詢處理器

檢視是否支援__lookup_processor_type

    .section ".text.head", "ax"
    .type   stext, %function
ENTRY(stext)
    msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
                        @ and irqs disabled
    mrc p15, 0, r9, c0, c0      @ get processor id
    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    movs    r10, r5             @ invalid processor (r5=0)?
    beq __error_p           @ yes, error 'p'
    bl  __lookup_machine_type       @ r5=machinfo
    movs    r8, r5              @ invalid machine (r5=0)?
    beq __error_a           @ yes, error 'a'
    bl  __create_page_tables

核心能夠支援哪些處理器,是在編譯核心時定義下來的。核心啟動時去讀暫存器:獲取 ID。看核心是否可以支援這個處理器。若能支援則繼續執行,不支援則跳到_error_p中去,這是個死迴圈

mrc p15, 0, r9, c0, c0      @ get processor id
bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
movs    r10, r5             @ invalid processor (r5=0)?
beq __error_p           @ yes, error 'p'

查詢機器ID

如果不支援這個機器ID則跳轉到__error_a,這也是死迴圈

bl  __lookup_machine_type       @ r5=machinfo
movs    r8, r5              @ invalid machine (r5=0)?
beq __error_a           @ yes, error 'a'

機器ID是存在R1的,因為theKernel (0, bd->bi_arch_number, bd->bi_boot_params)

3:  .long   .
    .long   __arch_info_begin
    .long   __arch_info_end


@  __arch_info_begin 和 __arch_info_end 是在連結指令碼中定義的
@   __arch_info_begin = .;
@   *(.arch.info.init)
@  __arch_info_end = .;
 

/*
 * Lookup machine architecture in the linker-build list of architectures.
 * Note that we can't use the absolute addresses for the __arch_info
 * lists since we aren't running with the MMU on (and therefore, we are
 * not in the correct address space).  We have to calculate the offset.
 *
 *  r1 = machine architecture number
 * Returns:
 *  r3, r4, r6 corrupted
 *  r5 = mach_info pointer in physical address space
 */
    .type   __lookup_machine_type, %function
__lookup_machine_type:
    adr r3, 3b                  @ r3= address of 3b,這個時候mmu還沒有啟動,是實體地址
    ldmia   r3, {r4, r5, r6}    @ r4=.,r5=__arch_info_begin,r6=__arch_info_end
                                @ 這個.代表了3這個標號的虛擬地址
        
    sub r3, r3, r4              @ get offset between virt&phys 虛擬地址與實體地址的偏差 
                                @將r5,r6轉換為實際的實體地址
    add r5, r5, r3              @ convert virt addresses to    
    add r6, r6, r3              @ physical address space

    
1:  ldr r3, [r5, #MACHINFO_TYPE]    @ get machine type
    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

首先是將虛擬地址轉換為實體地址,因為這個時候UBOOT 啟動核心時,MMU 還沒啟動,r3 這是實際存在的地址

3:  .long   .
    .long   __arch_info_begin
    .long   __arch_info_end
    
adr r3, 3b  

接下來的r4, r5, r6都是虛擬地址了. .代表虛擬地址。是標號為3的指令的虛擬地址.可以通過r3.(虛擬地址)來計算偏差.

sub r3, r3, r4  @ r3=r3-r4,也就r3=實體地址-虛擬地址的偏差
實際實體地址=虛擬地址+r3即可

add r5, r5, r3              @ convert virt addresses to    
add r6, r6, r3              @ physical address space

檢視下__arch_info_begin和__arch_info_end具體是什麼,這個是在連結指令碼定義如下的,也就是代表了一個段

__arch_info_begin = .;
 *(.arch.info.init)
__arch_info_end = .;

.arch.info.initarch.h 中有定義 ,定義某個結構體(machine_desc)的段屬性

#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(S3C2440, "SMDK2440")
    /* Maintainer: Ben Dooks <[email protected]> */
    .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,
  .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 這個結構體內容,可以發現支援多少單板,就有多少這個巨集的使用

 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);
};

接下去就是從這個結構體讀取第一個引數nr也就是ID來逐個比較了.這個在核心中定義與uboot定義是一致的

#define MACH_TYPE_S3C2440              362

啟動MMU

bl  __create_page_tables    @建立頁表
ldr r13, __switch_data      @ address to jump to after,這是使能mmu後的跳轉地址
                            @ mmu has been enabled
adr lr, __enable_mmu        @ return (PIC) address  使能mmu
add pc, r10, #PROCINFO_INITFUNC

@__enable_mmu 中會呼叫 __turn_mmu_on,最後 mov pc, r13
b   __turn_mmu_on
    .align  5
    .type   __turn_mmu_on, %function
__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=__switch_data

其他操作

複製資料段,清bss段等操作

start_kernel

啟動mmu後會跳轉到__switch_data,如何跳到 __switch_data,在__enable_mmu 中會呼叫 __turn_mmu_on這個函式最後 mov pc, r13,在呼叫__enable_mmu 前是先賦值的ldr r13, __switch_data

ldr r13, __switch_data      @ address to jump to after
....
    b   start_kernel

注意 這是核心的第一個 C 函式,接下來要處理UBOOT 傳輸的第三個啟動引數bd->bi_boot_params.這個檔案在init/main.c,在以下函式處理引數

setup_arch(&command_line);
setup_command_line(command_line);

一覽流程如下

start_kernel
    setup_arch          //解析uboot傳入的引數,只是先存起來字串
    setup_command_line  //只是先存起來字串
    parse_args
        do_early_param
            從__setup_start 中呼叫early函式
    unknown_bootoption
        obsolete_checksetup
            從__setup_start 中呼叫非early函式 段屬性
    rest_init
        kernel_init
            prepare_namespace
                mount_root  //根檔案系統,
               init_post    // 執行應用程式

setup_arch(解析tag)

這裡是先查詢mdesc這個結構體,這個結構體在上面分析機器ID的時候已經發現他儲存了一系列引數.boot_params就是uboot存放參數的地址.然後在parse_tags(tags)處理具體的tag

if (mdesc->boot_params)
    tags = phys_to_virt(mdesc->boot_params);
    
//在上面定義機器id結構體的時候,.boot_params  = S3C2410_SDRAM_PA + 0x100,=0x30000100
#define S3C2410_CS6 (0x30000000)
#define S3C2410_SDRAM_PA    (S3C2410_CS6)
//我們在uboot的時候儲存引數的地址也是這個
board_init -----gd->bd->bi_boot_params = 0x30000100;
然後開始處理tags


  static const struct machine_desc __mach_desc_S3C2440  
  __used                            
  __attribute__((__section__(".arch.info.init"))) = {   
  .nr       = MACH_TYPE_S3C2440,        
  .name     = SMDK2440,
  .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,
 };

setup_command_line

所謂命令列,就是uboot設定的bootargs,linux通過getenv("bootargx")獲取引數,如果沒有設定這個引數,內部有一個預設引數. 這裡只是將命令行復制到指定的陣列,並沒有處理

char *from = default_command_line;      //這是預設的命令列引數

static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
#define CONFIG_CMDLINE "root=/dev/hda1 ro init=/bin/bash console=ttySAC0"

memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from); //      
// cmdline_p 是 傳遞的引數,用作拷貝
// from 是預設引數

掛載根檔案系統

建立一個執行緒,可以理解為執行程式kernel_init

rest_init
        kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    kernel_init
        >prepare_namespace
            >mount_root 掛在根檔案系統
        >init_post(); 執行應用程式

處理命令列

uboot設定命令tag,多了引數commandline,源自環境變數bootargs檢視下環境變數bootargs,使用print檢視,也可搜尋下程式碼

"bootargs=" CONFIG_BOOTARGS         "\0"
//include/configs/100ask24x0.h
#define CONFIG_BOOTARGS "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"
  • root=/dev/mtdblock3表示根檔案系統從第四個FLASH分割槽開始(從0開始計數)可以往上看分割槽空間
  • init=/linuxrc指示第一個應用程式
  • console=ttySAC0,核心列印資訊從串列埠0 列印

mark

我們需要知道ROOT_DEV是什麼,可以看到在函式prepare_namespace中有saved_root_name儲存這個這個陣列.

if (saved_root_name[0]) {
    root_device_name = saved_root_name;
    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);
    if (strncmp(root_device_name, "/dev/", 5) == 0)
        root_device_name += 5;
}

搜尋下saved_root_nameroot_dev_setup對齊賦值,再繼續查詢函式的引用,只有一個巨集使用了它

static int __init root_dev_setup(char *line)
{
    strlcpy(saved_root_name, line, sizeof(saved_root_name));
    return 1;
}

解析下這個巨集__setup("root=", root_dev_setup);

#define __setup(str, fn)                    \
    __setup_param(str, fn, fn, 0)

#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 }




static char __setup_str_root_dev_setup[] __initdata = "root=";
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,root_dev_setup,0
    }
    
這個結構體的原型如下
struct obs_kernel_param 
{
    const char *str;
    int (*setup_func)(char *);
    int early;
};

最終大概分析一下也就是定義了一個char陣列和一個有特殊段屬性.init.setup的結構體.注意這裡的early是0.這個段屬性肯定是在lds中定義,搜尋下這個段的起始和結束地址的呼叫情況.

  __setup_start = .;
   *(.init.setup)
  __setup_end = .;
  
  obsolete_checksetup
  do_early_param
  
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) {
            if (p->setup_func(val) != 0)
                printk(KERN_WARNING
                       "Malformed early option '%s'\n", param);
        }
    }
    /* We accept everything at this stage. */
    return 0;
}

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) {
                /* 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))
                return 1;
        }
        p++;
    } while (p < __setup_end);

    return had_early_param;
}

可以看出obsolete_checksetup先判斷這個結構的early屬性,為0則執行setup_func方法,這符合我們的這個巨集__setup("root=", root_dev_setup);

結論:掛接根檔案系統的引數是由命令列給出的,核心函式去分析這個命令列,去賦值ROOT_DEV

mark

分割槽

分割槽表是沒有的,是程式碼裡面寫死的,我們可以啟動核心的時候發現有以下輸出

Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit":
0x00000000-0x00040000 : "bootloader"
0x00040000-0x00060000 : "params"
0x00060000-0x00260000 : "kernel"
0x00260000-0x10000000 : "root"

可以搜尋這個"bootloader"發現在arm/plat-s3c24xx/common-smdk.c下面定義

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,
    }
};

MTDPART_OFS_APPEND 這個 offset 意思是緊接著上面一個分割槽的意思