本文主要探讨210内核启动过程。
主Makefile
定义kernel版本号(2.6.35.7)
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 35
EXTRAVERSION = .7
指定编译文件生成目录
make O=/tmp
定义交叉编译工具链
CROSS_COMPILE ?= /root/arm-2009q3/bin/arm-none-linux-gnueabi-
指定架构
ARCH ?= arm
链接脚本
kernel链接脚本需要条件编译,lds格式不支持条件编译
kernel链接接脚由汇编文件vmlinux.lds.S编译生成vmlinux.lds(arch/arm/kernel/)
vmlinux.lds中ENTRY(stext)为链接地址入口
head.S和head-nommu.S都包含ENTRY(stext),head.S使用用MMU,head-nommu.S未使用mmu
head.S
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
内核运行的虚拟地址(KERNEL_RAM_VADDR)为0xC0008000,内核运行的物理地址(KERNEL_RAM_PADDR)为0x30008000
(arch/arm/kernel/.head.o.cmd)
cmd_arch/arm/kernel/head.o := /root/arm-2009q3/bin/arm-none-linux-gnueabi-gcc -Wp,-MD,arch/arm/kernel/.head.o.d -nostdinc -isystem /root/arm-2009q3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.1/include -I/root/kernel/arch/arm/include -Iinclude -include include/generated/autoconf.h -D__KERNEL__ -mlittle-endian -Iarch/arm/mach-s5pv210/include -Iarch/arm/plat-s5p/include -Iarch/arm/plat-samsung/include -D__ASSEMBLY__ -mabi=aapcs-linux -mno-thumb-interwork -D__LINUX_ARM_ARCH__=7 -march=armv7-a -include asm/unified.h -msoft-float -gdwarf-2 -DTEXT_OFFSET=0x00008000 -c -o arch/arm/kernel/head.o arch/arm/kernel/head.S
(arch/arm/include/asm/memory.h)#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
(.config)CONFIG_PAGE_OFFSET=0xC0000000
(arch/arm/mach-s5pv210/include/mach/memory.h)
#if defined(CONFIG_MACH_SMDKV210)
#define PHYS_OFFSET UL(0x30000000)
#else
#define PHYS_OFFSET UL(0x30000000)
#endif
(include/generated/autoconf.h)
#define CONFIG_MACH_SMDKV210 1
uboot启动内核后,内核运行zImage前解压代码解压zImage,再运行内核入口代码
uboot启动内核(theKernel(0,machid,bd->bi_boot_params))运行时把0写入r0,machid写入r1,bd->bi_boot_params写入r2
kernel启动时MMU是关闭的,硬件需要的物理地址,zImage是整体不能分散加载,需使用位置无关码(物理地址)
__HEAD
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 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 __vet_atags
bl __create_page_tables
__HEAD定义后面代码属于.head.text段
__HEAD
ENTRY(stext)
(include/linux/init.h)
#define __HEAD .section ".head.text","ax"
cp15协处理器c0读取出硬件CPU ID号,用于合法性检验,合法则继续启动,不合法则停止启动,转向__error_p启动失败
内核包含CPU ID号码数组,该函数从硬件中读取的cpu id号码和数组中的对比,相同则不合法
__lookup_processor_type
(System.map)
c0008168 t __lookup_processor_type
c00081a4 T lookup_processor_type
(arch/arm/kernel/setup.c)
list = lookup_processor_type(read_cpuid_id());
if (!list) {
printk("CPU configuration botched (ID %08x), unable "
"to continue.\n", read_cpuid_id());
while (1);
}
(arch/arm/kernel/include/asm/cputype.h)
#define CPUID_ID 0
static inline unsigned int __attribute_const__ read_cpuid_id(void)
{
return read_cpuid(CPUID_ID);
}
__lookup_machine_type函数用于校验机器码,类似cpu id校验
__vet_atags函数校验uboot给内核传参(ATAGS格式)
__create_page_tables
函数用于建立页表,内核链接在虚拟地址处,kernel需要启动MMU
kernel建立页表:kernel先建立段式页表(同uboot建立页相同页,大小为1MB,1MB映射4GB需4096页表项,每个页表项4字节,共需16KB做页表),建立细页表(细页表项大小为4kb)
__switch_data (arch/arm/kernel/head-common.S)
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long _data @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
__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
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel
ENDPROC(__mmap_switched)
建立段式页表后执行__switch_data,__switch_data中执行__mmap_switched函数
__mmap_switched函数复制数据段、清除bss段(构建C环境),保存cpu id、机器码、tag首地址
start_kernel跳转到C阶段
内核启动的C阶段(start_kernel,init/main.c)
smp_setup_processor_id()设置对称多处理器(多核CPU)
lockdep_init内核调试模块处理内核自旋锁死锁
cgroup_init_early处理进程组
printk(KERN_NOTICE "%s", linux_banner);
const char linux_banner[] =
"Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
(include/generated/utsrelease.h)
#define UTS_RELEASE "2.6.35.7"
(include/generated/compile.h)
#define UTS_VERSION "#1 PREEMPT Tue Dec 12 11:46:41 CST 2023"
#define LINUX_COMPILE_BY "root"
#define LINUX_COMPILE_HOST "kax-virtual-machine"
#define LINUX_COMPILER "gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67) "
include/generated/下的文件为编译过程中生成的头文件
printk定义打印级别(0-7)来过滤显示机制
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
setup_arch
用来确定内核机器的arch和machine
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
unwind_init();
setup_processor();
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");
if (__atags_pointer)
{
tags = phys_to_virt(__atags_pointer);
printk("@@@@@@@ atags_pointer not null\n");
}
else if (mdesc->boot_params)
{
tags = phys_to_virt(mdesc->boot_params);
printk("@@@@@@@ boot params not null\n");
}
printk("@@@@@@@linter#####boot_params:%p,mdesc->boot_params:%p\n",tags);
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
printk("$$$$$$$$$cmdline:%s\n",cmd_line);
parse_early_param();
paging_init(mdesc);
request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
cpu_init();
tcm_init();
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
setup_processor();
setup_processor用来查找CPU信息并打印信息
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_name = list->cpu_name;
#ifdef MULTI_CPU
processor = *list->proc;
#endif
#ifdef MULTI_TLB
cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
cpu_cache = *list->cache;
#endif
printk("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
proc_arch[cpu_architecture()], cr_alignment);
sprintf(init_utsname()->machine, "%s%c", list->arch_name, ENDIANNESS);
sprintf(elf_platform, "%s%c", list->elf_name, ENDIANNESS);
elf_hwcap = list->elf_hwcap;
#ifndef CONFIG_ARM_THUMB
elf_hwcap &= ~HWCAP_THUMB;
#endif
cacheid_init();
cpu_proc_init();
}
setup_machine的参数machine_arch_type是机器码编号(2456)
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
(include/generated/mach-types.h)
#define MACH_TYPE_SMDKV210 2456
#ifdef CONFIG_MACH_SMDKV210
# ifdef machine_arch_type
# undef machine_arch_type
# define machine_arch_type __machine_arch_type
# else
# define machine_arch_type MACH_TYPE_SMDKV210
# endif
# define machine_is_smdkv210() (machine_arch_type == MACH_TYPE_SMDKV210)
#else
# define machine_is_smdkv210() (0)
#endif
(arch/arm/kernel/setup.c)
struct machine_desc *mdesc;
(arch/arm/include/asm/mach/arch.h)
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.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);
};
ENTRY(lookup_machine_type)
stmfd sp!, {r4 - r6, lr}
mov r1, r0
bl __lookup_machine_type
mov r0, r5
ldmfd sp!, {r4 - r6, pc}
ENDPROC(lookup_machine_type)
setup_machine调用lookup_machine_type调用__lookup_machine_type
内核把各种CPU架构信息组成machine_desc实例并赋予段属性(.arch.info.init)保证链接在一起,__lookup_machine_type遍历各描述符
setup_arch函数处理cmdline(初步bootargs参数)
char *from = default_command_line;
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
printk("$$$$$$$$$cmdline:%s\n",cmd_line);
parse_early_param();
default_command_line(CONFIG_CMDLINE)是默认命令行参数
uboot通过tag给kernel传递cmdline为空则使用kernel默认命令行参数
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
(include/generated/autoconf.h)
#define CONFIG_CMDLINE "console=ttySAC2,115200"
setup_command_line(cmdline解析,kernel/init/init)
cmdline:console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3
解析为
console=ttySAC2,115200
root=/dev/mmcblk0p2 rw
init=/linuxrc
rootfstype=ext3
cmdline参数
root=/dev/xxx指定根文件系统位置
rootfstype=xxx指定根文件系统的文件系统类型
console=指定控制台信息(串口,波特率)
mem=xxx指定内核系统内存大小
init=指定init进程
start_kernel其他函数
trap_init 设置异常向量表
mm_init 内存管理模块初始化
scheduler_init 内核调度系统初始化
early_irq_init&init_IRQ 中断初始化
console_init 控制台初始化
rest_init(init文件)
start_kernel调用许多xx_init函数为初始化内核模块函数,rest_init用于串联已初始化的模块
static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
unlock_kernel();
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}
调用kernel_thread启动2个内核线程:kernel_init和kthreadd
调用schedule函数开启内核调度系统,linux系统运行
调用cpu_idle函数结束内核启动进入死循环
内核调度系统里的进程需要运行,调度系统终止cpu_idle死循环进程(空闲进程),执行该进程
进程0:cpu_idle()函数,idle进程(死循环)
进程1:kernel_init函数为init进程
进程2:kthreadd函数是进程2,是linux内核守护进程,保证linux内核正常工作的
init进程挂载根文件系统并运行根文件系统下的init程序转变为用户态应用程序,脱离内核态,转为用户态
init进程
init进程构建交互界面,创建其他进程(login、命令行、shell...),shell进程启动其他用户进程,shell工作后就可运行其他命令和程序
打开控制台
打开/dev/console文件(210)并复制2次文件描述符,三个文件描述符分别是0(标准输入)、1(标准输出)、2(标准错误)
进程1打开三个文件描述符,进程1的子进程都有3个三描述符
挂载根文件系统
prepare_namespace函数中挂载根文件系统(init/do_mounts.c)
uboot传参中root=/dev/mmcblk0p2 rw定义根文件系统位置和权限
uboot传参中rootfstype=ext3定义rootfs类型。
执行用户态下(init进程)
uboot传参init=/linuxrc指定rootfs中的init程序
其他备用init进程:/sbin/init,/etc/init,sbin/init,/bin/sh
start_kernel函数打印了内核初始化信息、内核模块初始化(譬如内存管理、调度系统、异常处理···)
setup_arch:机器码架构查找,架构相关硬件初始化,uboot传参cmdline给内核
rest_init:构建进程0,1,2,挂载根文件系统,进程1转为用户态进程
内核框架图