Linux内核-操作系统启动引导

操作系统启动引导

操作系统的起点:

磁盘引导程序,需要将内核等移入内存进行运行,并初始化多种模块和硬件

终点:运行第一个应用程序,系统的根文件系统

大致流程

BIOS/bootloader

由PC机的BIOS(0xFFFF0是BIOS存储的总线地址)把bootsect.s从某个固定的地址拿到了内存中某个固定的地址(0x90000),并且进行了一系列的硬件初始化和参数设置

bootsect.s

磁盘引导块程序,在磁盘的第一个扇区中的程序(0磁道 0磁头 1扇区)

bootsect.s被bios加载到0x7c00(31KB)处,并将自己移到了0x90000(576KB)处,并跳转到那里

然后使用BIOS中断将setup.s直接加载到自己的后面(0x90200)(576.5KB),并将system加载到地址0x10000处

作用:

  1. 首先将后续的setup.s代码从磁盘中加载到紧接着bootsect.s的地方
  2. 在显示屏上显示loading system,再将system(操作系统)模块加载到0x10000的地方
  3. 最后跳转到setup.s中运行
! 定义的SEG后都需要加0
SETUPLEN = 4                ! nr of setup-sectors
BOOTSEG  = 0x07c0            ! original address of boot-sector
INITSEG  = 0x9000            ! we move boot here - out of the way
SETUPSEG = 0x9020            ! setup starts here
SYSSEG   = 0x1000            ! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE        ! where to stop loading

setup.s

setup.s是一个操作系统加载程序,它的主要作用时利用ROM BIOS中断读取及其系统数据,并将这些数据保存到0x90000开始的位置(覆盖掉了bootsect程序所在的地方),所取得的参数和保留的内存位置如图:

内存地址长度(字节)名称描述
0x900002光标位置列号(0x00最左端,0x00最顶端)
0x900022扩展内存数系统从1MB开始的拓展内存数值(KB)
0x900042显示页面当前显示页面
0x900061显示模式
0x9008016硬盘参数表第一个硬盘的参数表
0x9009016硬盘参数表第二个硬盘的参数表(没有则清零)
0x901FC2根设备号根文件系统所在的设备号(bootsect.s中设置)

设置系统内核运行的LDT(局部描述符寄存器),IDT(中断描述符寄存器),GDT(全局描述符寄存器)

设置中断控制芯片,进入保护模式运行(svc32保护模式,设置寄存器中的值)

跳转到system模块的最前面的代码运行(head.s)

head.s

加载内核运行时的各数据段寄存器,并且重新设置一个中断描述符表

开启内核正常运行时的协处理器等资源

设置内存管理的分页机制

跳转到main.c开始运行

main.c

main.c文件开始有一段宏定义

/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002)   //1M 以后的扩展内存大小
#define DRIVE_INFO (*(struct drive_info *)0x90080) // 来自BIOS信息,硬盘参数表(机器系统数据)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)  // 来自BIOS信息,根设备号(软盘)

这里的地址正是对应上面setup.s中保存的读取的系统数据

在main函数开始有一段保存系统数据的代码

1.设置操作系统的根设备号

     ROOT_DEV = ORIG_ROOT_DEV;

2.设置操作系统驱动参数

     drive_info = DRIVE_INFO;

3.获取系统内存参数,设置系统的内存大小

本身内存(1MB)+扩展内存大小

    memory_end = (1<<20) + (EXT_MEM_K<<10);        // 内存大小=1Mb 字节+扩展内存(k)*1024 字节
    memory_end &= 0xfffff000;                      // 忽略不到 4Kb(1 页)的内存数,内存对齐4k
    // 假设物理内存为16M,进入第二个分支
    // 主内存开始地址和缓冲区结束地址相同,均为4M位置
    if (memory_end > 16*1024*1024)
        memory_end = 16*1024*1024;    // 如果内存超过 16Mb,则按 16Mb 计
    if (memory_end > 12*1024*1024) 
        buffer_memory_end = 4*1024*1024; //设置高速缓冲区大小
    else if (memory_end > 6*1024*1024)
        buffer_memory_end = 2*1024*1024;
    else
        buffer_memory_end = 1*1024*1024;
    main_memory_start = buffer_memory_end;
#ifdef RAMDISK
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);  //虚拟磁盘,把内存一部分做磁盘用
#endif

EXT_MEM_K的单位是KB,所以是左移10位

memory_end &= 0xfffff000; 这个代码是让系统内存大小内存对齐4KB

memory_end = 16*1024*1024;这个代码是设置内存最大为16MB

buffer_memory_end = 4*1024*1024; //设置高速缓冲区大小根据总的内存大小设置高速缓冲区

#ifdef RAMDISK这个是虚拟盘是否启用

Linux内核识别不同的板子

这里以linux内核3.4.2版本的为例,这时候还没有设备树

一种高效的编程结构

内核链接脚本写法

//arch/arm/kernel/vmlinux.lds.S
    .init.arch.info : {
        __arch_info_begin = .;
        *(.arch.info.init)
        __arch_info_end = .;
    }

基础语法含义

  • SECTION_NAME : { ... }:定义一个输出段(Output Section),告诉链接器将所有符合规则的输入段(Input Sections)合并到这里。
  • *(.name)* 是通配符,表示将所有目标文件(.o)中名为 .name 的段全部收集进来。
  • 符号 = .;. 代表当前地址计数器(Location Counter)。这里定义了全局可见的链接器符号(如 __arch_info_begin),C 代码可以通过 extern 声明这些符号来获取该段在内存中的起始/结束地址(用于遍历表格)。 .init.arch.info(机器描述符表)
  • *(.arch.info.init) 收集所有使用 MACHINE_START/MACHINE_END 宏定义的 struct machine_desc 结构体。
  • __arch_info_begin__arch_info_end 围成了这个结构体数组。内核启动的汇编头阶段head.S)会遍历这个表,根据硬件 ID(或设备树)匹配当前单板,然后调用对应的初始化函数。这是 ARM 32 位时代支持多平台(Multi-platform)的核心机制。

arch.h中的宏定义

这里的##是一种连接符

//arch/arm/include/asm/mach/arch.h
/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#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                \
};

__attribute__GCC 编译器(及 Clang)的一个特性,用于向编译器传递“特殊指令”,告诉编译器对某个变量、函数或类型进行特定的处理。

语法格式为:__attribute__((属性列表))

在这段代码里,它起了两个关键作用:

  • __attribute__((__section__(".arch.info.init")))
    这是定位指令。它强制编译器将这个 machine_desc 结构体变量生成的二进制数据,放到目标文件(.o)中名为 .arch.info.init 的节(Section)里,而不是默认的 .data.bss 段。这样就方便后续链接器按名称把这些零散的数据“黏合”在一起。

这个 machine_desc 结构体虽然在 C 代码层面是“静态”的,也没有直接的 C 函数去调用它,但它需要被链接器脚本通过地址(__arch_info_begin)去隐式访问。编译器感知不到这种隐式访问。

  • 如果没有 __used:编译器会把它当成死代码直接删除,导致内核启动时遍历表格找不到对应的机器 ID,最终无法启动(挂死)。
  • 加了 __used:等于告诉编译器:“这变量虽然看着没人用,但你别动它,必须给我保留在目标文件里!

实际使用案例

以smdk6440的一个使用MACHINE_START为示例

MACHINE_START(SMDK6440, "SMDK6440")
    /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
    .atag_offset    = 0x100,

    .init_irq    = s5p6440_init_irq,
    .handle_irq    = vic_handle_irq,
    .map_io        = smdk6440_map_io,
    .init_machine    = smdk6440_machine_init,
    .timer        = &s5p_timer,
    .restart    = s5p64x0_restart,
MACHINE_END

宏展开

结合上面两个文件我们可以得到最终宏定义展开的结果:

static const struct machine_desc __mach_desc_SMDK6440
 __used                            
 __attribute__((__section__(".arch.info.init"))) = {
     .nr        = MACH_TYPE_SMDK6440,        
    .name        = "SMDK6440",
    /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
    .atag_offset    = 0x100,

    .init_irq    = s5p6440_init_irq,
    .handle_irq    = vic_handle_irq,
    .map_io        = smdk6440_map_io,
    .init_machine    = smdk6440_machine_init,
    .timer        = &s5p_timer,
    .restart    = s5p64x0_restart,
};

通过宏定义灵活的定义了一个专属于SMDK6440的machine_desc结构体,并且这个结构体在编译后被存放在.arch.info.init段里面而不是默认的.data或者.bss

machine_desc结构体

machine_desc结构体是linux用于设备板子识别的结构体

struct machine_desc {
    unsigned int        nr;        /* architecture number    */
    const char        *name;        /* architecture name    */
    unsigned long        atag_offset;    /* tagged list (relative) */
    const char *const     *dt_compat;    /* array of device tree
                         * 'compatible' strings    */

    unsigned int        nr_irqs;    /* number of IRQs */

#ifdef CONFIG_ZONE_DMA
    unsigned long        dma_zone_size;    /* size of DMA-able area */
#endif

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

    unsigned char        reserve_lp0 :1;    /* never has lp0    */
    unsigned char        reserve_lp1 :1;    /* never has lp1    */
    unsigned char        reserve_lp2 :1;    /* never has lp2    */
    char            restart_mode;    /* default restart mode    */
    void            (*fixup)(struct tag *, char **,
                     struct meminfo *);
    void            (*reserve)(void);/* reserve mem blocks    */
    void            (*map_io)(void);/* IO mapping function    */
    void            (*init_early)(void);
    void            (*init_irq)(void);
    struct sys_timer    *timer;        /* system tick timer    */
    void            (*init_machine)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
    void            (*handle_irq)(struct pt_regs *);
#endif
    void            (*restart)(char, const char *);
};

通过上面使用MACHINE_START和链接脚本的写法,将这些不同板子的结构体都限定在了内存的某一区域,并且通过uboot传递过来的参数对结构体进行配置。在之后的启动或其他函数中对该结构体进行调用

移植linux时候也要对这些结构体的变量进行赋值

识别CPU信息

获取芯片ID的指令

    mrc    p15, 0, r9, c0, c0        @ get processor id
    bl    __lookup_processor_type

lookup_processor_type

//arch/arm/kernel/head-common.S
__lookup_processor_type:
    adr    r3, __lookup_processor_type_data   //将__lookup_processor_type_data地址载入r3

lookup_processor_type_data

//arch/arm/kernel/head-common.S
/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
    .align    2
    .type    __lookup_processor_type_data, %object
__lookup_processor_type_data:
    .long    .
    .long    __proc_info_begin
    .long    __proc_info_end
    .size    __lookup_processor_type_data, . - __lookup_processor_type_data

对应链接脚本

 //arch/arm/kernel/vmlinux.lds.S
#define PROC_INFO                            \
    . = ALIGN(4);                            \
    VMLINUX_SYMBOL(__proc_info_begin) = .;                \
    *(.proc.info.init)                        \
    VMLINUX_SYMBOL(__proc_info_end) = .;

.proc.info.init描述的是CPU(缓存大小、TLB类型、MMU属性、是否支持Thumb指令等)。

这段 proc.info.init 段就是内核的 “CPU 自检 ROM 表”。硬件上电后,内核完全不知道自己在什么 CPU 上跑,必须靠这张表来识别自己,并获取对应的缓存/TLB 操作方法,这是 ARM Linux 启动流程中最底层的硬件抽象层(HAL)

具体芯片示例

以arm925示例

    //arch/arm/mm/proc-arm925.S
    .section ".proc.info.init", #alloc, #execinstr

.macro arm925_proc_info name:req, cpu_val:req, cpu_mask:req, cpu_name:req, cache
    .type    __\name\()_proc_info,#object
__\name\()_proc_info:
    .long    \cpu_val
    .long    \cpu_mask
    .long   PMD_TYPE_SECT | \
        PMD_BIT4 | \
        PMD_SECT_AP_WRITE | \
        PMD_SECT_AP_READ
    .long   PMD_TYPE_SECT | \
        PMD_BIT4 | \
        PMD_SECT_AP_WRITE | \
        PMD_SECT_AP_READ
    b    __arm925_setup
    .long    cpu_arch_name
    .long    cpu_elf_name
    .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
    .long    cpu_arm925_name
    .long    arm925_processor_functions
    .long    v4wbi_tlb_fns
    .long    v4wb_user_fns
    .long    arm925_cache_fns
    .size    __\name\()_proc_info, . - __\name\()_proc_info
.endm

    arm925_proc_info arm925, 0x54029250, 0xfffffff0, cpu_arm925_name
    arm925_proc_info arm915, 0x54029150, 0xfffffff0, cpu_arm925_name

设置架构信息setup_arch

在内核main函数有一个设置架构信息的函数setup_arch

void __init setup_arch(char **cmdline_p)
{
    struct machine_desc *mdesc;

    setup_processor();
    mdesc = setup_machine_fdt(__atags_pointer);
    if (!mdesc)
        mdesc = setup_machine_tags(machine_arch_type);
    machine_desc = mdesc;
    machine_name = mdesc->name;
    ....
}

这个函数开始完成了两步:

  1. 获取CPU信息并且保存到全局变量
  2. 获取设备信息并且保存到全局变量

setup_processor

static void __init setup_processor(void)
{
    struct proc_info_list *list;

    /*
     * locate processor in the list of supported processor
     * types.  The linker builds this table for us from the
     * entries in arch/arm/mm/proc-*.S
     */
    list = lookup_processor_type(read_cpuid_id());
    if (!list) {
        printk("CPU configuration botched (ID %08x), unable "
               "to continue.\n", read_cpuid_id());
        while (1);
    }

首先创建一个CPU指令集结构体proc_info_list,然后从制定的内存中(lookup_processor_type,对应上文的.proc.info.init段)获取到该描述结构体。最后将获取到的CPU信息存放到全局变量中,在末尾初始化CPU相关信息

setup_machine_fdt

setup_machine_fdt根据uboot传递来的设备数参数信息去寻找最匹配的设备信息,并且 保存到全局变量中以便后续调用

struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
    struct machine_desc *mdesc, *mdesc_best = NULL;
    for_each_machine_desc(mdesc) {
               score = of_flat_dt_match(dt_root, mdesc->dt_compat);
               if (score > 0 && score < mdesc_score) {
                       mdesc_best = mdesc;
                       mdesc_score = score;
               }
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇