内核启动过程(根据自己心得所整理最全).doc_第1页
内核启动过程(根据自己心得所整理最全).doc_第2页
内核启动过程(根据自己心得所整理最全).doc_第3页
内核启动过程(根据自己心得所整理最全).doc_第4页
内核启动过程(根据自己心得所整理最全).doc_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

内核启动过程总结之前配置编译过内核源代码,在交叉编译源代码后产生了三个文件(还有其他文件)分别是vmlinuz、vmlinux、vmlinux32,其中vmlinuz是可引导的、压缩了的内核,将该内核拷贝到系统文件/boot目录下,再配置下/boot/boot.cfg文件,将启动时选择内核的信息和加载内核的地方写入就可以实现内核的移植。其实移植过程和正常内核启动过程的原理是一样的。系统加电启动后,MIPS处理器默认的程序入口时0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是0x1FC00000,即CPU从0x1FC00000开始取第一条指令,内核是系统引导程序把内核加载到内存中的,如果内核是经过压缩的,那么首先执行/arch/mips/boot/compressed的head.S文件去建立堆栈并解压内核映像文件,然后去执行/arch/mips/kernel下的head.S,如果是没有压缩的内核则直接去执行该head.S。linux内核启动的第一阶段就是从kernel文件夹下的head.S开始的,kernel_entry()函数就是内核启动的入口函数,这个函数是与体系结构相关的汇编语言编写的,它首先初始化内核堆栈段,来为创建系统的第一个进程0进程作准备,接着用一段循环将内核映像的未初始化数据段bss段清零,最后跳转到/init/main.c中的start_kernel()初始化硬件平台相关的代码。kernel_entry() - arch/mips/kernel/head.S TLB初始化,Cache初始化清除BSS段准备参数 argc/argp/envp设置栈jal start_kernel (init/main.c)怎么为第一个进程0进程作准备?第一个进程涉及到init_thread_union,这个结构体在include/linux/sched.h得到定义extern union thread_union init_thread_union;union thread_unionstruct thread_info thread_info;unsigned long stackTHREAD_SIZE/sizeof(long); THREAD_SIZE是一个宏定义#define THREAD_SIZE (2*PAGE_SIZE)#define PAGE_SIZE (_AC(1,UL)PAGE_SHIFT) PAGE_SHIFT=13 算出PAGE_SIZE=212=4096;则THREAD_SIZE=8192内核把进程存放在任务队列的双向循环链表中,链表中的每一项都是类型为task_struct、称为进程描述符的结构,进程描述符中包含一个具体进程的所有信息,linux通过slab分配器分配task_struct结构,每个任务都有一个thread_info结构,它在内核栈的尾部分配,结构中的task域中存放的是指向该任务实际task_struct的指针,thread_info结构在文件中定义。struct thread_info struct task_struct *task; /* main task structure */ struct exec_domain *exec_domain; /* execution domain */ unsigned long flags; /* low level flags */ unsigned long tp_value; /* thread pointer */ _u32 cpu; /* current CPU */ int preempt_count; /* 0 = preemptable, BUG */ mm_segment_t addr_limit; /* thread address space: 0-0xBFFFFFFF for user-thead 0-0xFFFFFFFF for kernel-thread */ struct restart_block restart_block; struct pt_regs *regs;/* * Initial thread structure. * * We need to make sure that this is 8192-byte aligned due to the * way process stacks are handled. This is done by making sure * the linker maps this in the .text segment right after head.S, * and making head.S ensure the proper alignment. * * The things we do for performance. */union thread_union init_thread_union _init_task_data _attribute_(_aligned_(THREAD_SIZE) = INIT_THREAD_INFO(init_task) ;_init_task_data是一个宏,在include/linux/init_task.h中这样定义的#define _init_task_data _attribute_(_section_(“.data.init_task”)这是一条赋值语句,对init_thread_union赋初值,并且把数据放在指定的数据段.data.init_task中。具体如何赋值,看看init_task这个全局变量/* * Initial task structure. * * All other task structs will be allocated on slabs in fork.c */struct task_struct init_task = INIT_TASK(init_task);0号进程的task_struct出现了,就是init_task。在对init_thread_union赋初值的时候,同时也通过调用INIT_TASK宏对init_task赋初值:#define INIT_TASK(tsk) .state = 0, .stack = &init_thread_info, .usage = ATOMIC_INIT(2), .flags = PF_KTHREAD, .lock_depth = -1, .prio = MAX_PRIO-20, .static_prio = MAX_PRIO-20, .normal_prio = MAX_PRIO-20, .policy = SCHED_NORMAL, .cpus_allowed = CPU_MASK_ALL, .mm = NULL, .active_mm = &init_mm, .se = .group_node = LIST_HEAD_INIT(tsk.se.group_node), , .rt = .run_list = LIST_HEAD_INIT(tsk.rt.run_list), .time_slice = HZ, .nr_cpus_allowed = NR_CPUS, , .tasks = LIST_HEAD_INIT(tsk.tasks), .pushable_tasks = PLIST_NODE_INIT(tsk.pushable_tasks, MAX_PRIO), .ptraced = LIST_HEAD_INIT(tsk.ptraced), .ptrace_entry = LIST_HEAD_INIT(tsk.ptrace_entry), .real_parent = &tsk, .parent = &tsk, .children = LIST_HEAD_INIT(tsk.children), .sibling = LIST_HEAD_INIT(tsk.sibling), .group_leader = &tsk, .real_cred = &init_cred, .cred = &init_cred, .cred_guard_mutex = _MUTEX_INITIALIZER(tsk.cred_guard_mutex), .comm = swapper, .thread = INIT_THREAD, .fs = &init_fs, .files = &init_files, .signal = &init_signals, .sighand = &init_sighand, .nsproxy = &init_nsproxy, .pending = .list = LIST_HEAD_INIT(tsk.pending.list), .signal = 0, .blocked = 0, .alloc_lock = _SPIN_LOCK_UNLOCKED(tsk.alloc_lock), .journal_info = NULL, .cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), .fs_excl = ATOMIC_INIT(0), .pi_lock = _RAW_SPIN_LOCK_UNLOCKED(tsk.pi_lock), .timer_slack_ns = 50000, /* 50 usec default slack */ .pids = PIDTYPE_PID = INIT_PID_LINK(PIDTYPE_PID), PIDTYPE_PGID = INIT_PID_LINK(PIDTYPE_PGID), PIDTYPE_SID = INIT_PID_LINK(PIDTYPE_SID), , .thread_group = LIST_HEAD_INIT(tsk.thread_group), .dirties = INIT_PROP_LOCAL_SINGLE(dirties), INIT_IDS INIT_PERF_EVENTS(tsk) INIT_TRACE_IRQFLAGS INIT_LOCKDEP INIT_FTRACE_GRAPH INIT_TRACE_RECURSION INIT_TASK_RCU_PREEMPT(tsk) 可以看到0号进程的stack就是刚才init_thread_info的地址;parent是他自己;thread是INIT_THREAD。附上进程描述符的task_struct结构图0号进程task_struct有了,下面就该来讲讲INIT_THREAD_INFO宏了,在arch/mips/include/asm/thread_info.h:/* * macros/functions for gaining access to the thread information structure */#define INIT_THREAD_INFO(tsk) .task = &tsk, .exec_domain = &default_exec_domain, .flags = _TIF_FIXADE, .cpu = 0, .preempt_count = INIT_PREEMPT_COUNT, .addr_limit = KERNEL_DS, .restart_block = .fn = do_no_restart_syscall, , 执行完这个宏以后,init_thread_union就被初始化成以上内容了,至此,0号进程的task_struct和thread_info就初始化完毕了。怎么将未初始化数据段bss段清零呢?看源代码 PTR_LA t0, _bss_start # clear .bss LONG_S zero, (t0) PTR_LA t1, _bss_stop - LONGSIZE1: PTR_ADDIU t0, LONGSIZE LONG_S zero, (t0) bne t0, t1, 1b最后跳转到 /init/main.c中的start_kernel()初始化硬件平台相关的代码。*附上/kernel/head.S源代码及部分注释:.macro setup_c0_status set clr#ifdef CONFIG_MIPS_MT_SMTC.#else mfc0 t0, CP0_STATUS #取CP0 status寄存器的值到临时寄存器to中 or t0, ST0_CU0|set|0x1f|clr xor t0, 0x1f|clr mtc0 t0, CP0_STATUS #将临时寄存器to中的值赋给CP0状态寄存器 .set noreorder #表示禁止为了填充加载指令和分支指令的延迟槽而对代 #码重新排序 sll zero,3 # ehb (exception hazard barrier) .macro setup_c0_status_pri- #ifdef CONFIG_64BIT setup_c0_status ST0_KX 0#else setup_c0_status 0 0#endif .endm-NESTED(kernel_entry, 16, sp) # kernel entry point声明函数 kernel_entry,函数的堆栈为 16 byte,返回地址保存在 $sp 寄存器中。kernel_entry_setup #cpu specific setup,某些MIPS CPU需要额外的设置一些控制寄存器,和具体的平台相关,一般为空宏;某些多核MIPS,启动时所有的core的入口一起指向kernel_entry,然后在该宏里分叉,boot core 继续往下,其它的则不停的判断循环,直到boot core 唤醒之 setup_c0_status_pri #设置cp0_status 寄存器 PTR_LA t0, 0f jr t0PTR_LA t0, _bss_start # clear .bss LONG_S zero, (t0) #变量 _bss_start 和 _bss_stop 在连接文件arch/mips/kernel/vmlinux.lds 中定义。 PTR_LA t1, _bss_stop - LONGSIZE1: PTR_ADDIU t0, LONGSIZE LONG_S zero, (t0) bne t0, t1, 1b LONG_S a0, fw_arg0 # firmware arguments LONG_S a1, fw_arg1 #bootloader会将要传给内核的参数写在 LONG_S a2, fw_arg2 a0a3里,此处为将参数保存在fw_arg0-fw_arg3中 LONG_S a3, fw_arg3 MTC0 zero, CP0_CONTEXT # clear context registerPTR_LA $28, init_thread_union #初始化gp,指向一个 union,THREAD_SIZE大小,最低处是一个thread_info结构体 PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE #_THERAD_SIZE=16384 PT_SIZE=304(sizeof(struct pt_regs) PTR_ADDU sp, $28 back_to_back_c0_hazard set_saved_sp sp, t0, t1 #把这个CPU核的堆栈地址 $sp保存到 kernelspNR_CPUS数组。-如果定义了 CONFIG_SMP 宏,即多 CPU 核。 .macro set_saved_sp stackp temp temp2#ifdef CONFIG_MIPS_MT_SMTC mfc0 temp, CP0_TCBIND#else MFC0 temp, CP0_CONTEXT#endif LONG_SRL temp, PTEBASE_SHIFT LONG_S stackp, kernelsp(temp) .endm如果没有定义 CONFIG_SMP 宏,单 CPU 核。 .macro set_saved_sp stackp temp temp2 LONG_S stackp, kernelsp .endm变量 kernelsp 的定义,在 arch/mips/kernel/setup.c 文件中。unsigned long kernelspNR_CPUS;- PTR_SUBU sp, 4 * SZREG # init stack pointer SZREG=8 j start_kernel #最后跳转到 /init/main.c中的start_kernel()初始化硬件平台相关的代码。 END(kernel_entry)*#define asmlinkage _attribute_(regparm(0) _attribute_是关键字,是gcc的C语言扩展,regparm(0)表示不从寄存器传递参数。如果是_attribute_(regparm(3),那么调用函数的时候参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数函数定义前加宏asmlinkage ,表示这些函数通过堆栈而不是通过寄存器传递参数。gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage*看看init/main.c源代码asmlinkage void _init start_kernel(void) char *command_line; extern const struct kernel_param _start_param,_stop_param;/来自外部的/include/linux/moduleparam.h中的kernel_param结构体 这两个变量为地址指针,指向内核启动参数处理相关结构体段在内存中的位置(虚拟地址)。 smp_setup_processor_id();/当只有一个CPU的时候这个函数什么都不做,但是如果有多个CPU的时候它就返回启动的时候的那个CPU的id号unwind_init(); 在MIPS体系结构中,这个函数是个空函数(可能调用setup_arch,配置核的相关函数)lockdep_init();初始化核依赖关系哈希表 lockdep是一个内核调试模块,用来检查内核互斥机制(尤其是自旋锁)潜在的死锁问题。由于自旋锁以查询方式等待,不释放处理器,比一般互斥机制更容易死锁,故引入lockdep检查以下几种可能的死锁情况:1.同一个进程递归地加锁同一把锁;2.一把锁既在中断(或中断下半部)使能的情况下执行过加锁操作, 又在中断(或中断下半部)里执行过加锁操作。这样该锁有可能在锁定时由于中断发生又试图在同一处理器上加锁;3.加锁后导致依赖图产生成闭环,这是典型的死锁现象。 启动Lock Dependency Validator(内核依赖的关系表),本质上就是建立两个散列表calsshash_table和chainhash_table,并初始化全局变量lockdep_initialized,标志已初始化完成debug_objects_early_init();/在启动早期初始化hash buckets 和链接静态的 pool objects对象到 poll 列表. 在这个调用完成后 object tracker 已经开始完全运作了.boot_init_stack_canary();canary值的是用于防止栈溢出攻击的堆栈的保护字 。参考资料: GCC 中的编译器堆栈保护技术cgroup_init_early();cgroup: 它的全称为control group.即一组进程的行为控制.该函数主要是做数据结构和其中链表的初始化 local_irp_disable(); /关闭系统总中断(底层调用汇编指令)early_boot_irqs_off();/设置系统中断的关闭标志(bool全局变量)early_init_irp_lock_class();每个中断都有一个中断描述符(struct irq_desc)来进行描述,这个函数的作用就是设置所有中断描述符的锁tick_init();如果没有定义 CONFIG_GENERIC_CLOCKEVENTS宏定义,则这个函数为空函数,如果定义了这个宏,这执行初始化 tick控制功能,注册clockevents的框架。boot_cpu_init();对于 CPU 核的系统来说,设置第一个 CPU 核为活跃 CPU 核。对于单 CPU 核系统来说,设置 CPU 核为活跃 CPU 核page_address_init();当定义了CONFIG_HIGHMEM 宏,并且没有定义 WANT_PAGE_VIRTUAL 宏时,非空函数。其他情况为空函数。printk(KERN_NOTICE %s,linux_banner);setup_arch(&command_line);内核构架相关初始化函数,可以说是非常重要的一个初始化步骤。其中包含了处理器相关参数的初始化、内核启动参数(tagged list)的获取和前期处理、内存子系统的早期的初始化(bootmem分配器)。 mm_init_owner(&init_mm,&init_task);初始化代表内核本身内存使用的管理结构体init_mm。ps:每一个任务都有一个mm_struct结构以管理内存空间,init_mm是内核的mm_struct,其中:设置成员变量* mmap指向自己,意味着内核只有一个内存管理结构; setup_command_line(command_line); 保存未改变的 comand_line 到字符数组 static_command_line 中。保存 boot_command_line到字符数组 saved_command_line中。setup_nr_cpu_ids();setup_per_cpu_areas();如果没有定义 CONFIG_SMP宏,则这个函数为空函数。如果定义了CONFIG_SMP宏,则这个 setup_per_cpu_areas()函数给每个CPU分配内存,并拷贝.data.percpu段的数据。smp_prepare_boot_cpu();build_all_zonelists(NULL);建立各个节点的管理区的 zonelist,便于分配内存的 fallback使用。这个链表的作用: 这个链表是为了在一个分配不能够满足时可以考察下一个管理区来设置了。在考察结束时,分配将从 ZONE_HIGHMEM回退到 ZONE_NORMAL,在分配时从 ZONE_NORMAL退回到 ZONE_DMA就不会回退了。page_alloc_init();printk(KERN_NOTICE Kernel command line:%sn,boot_command_line);parse_early_param();parse_args(Booting kernel,static_command_line,_start_param,_stop_param-_start_param,&unknown_bootoption);。此处省略很多行rest_init();*其中有个函数是与自己体系结构相关的函数setup_arch()在kernel/setup.c中在里面看到unsigned long kernelspNR_CPUS;unsigned long fw_arg0, fw_arg1, fw_arg2, fw_arg3;void _init setup_arch(char *cmdline_p) cpu_probe(); 调用函数cpu_probe(),该函数通过MIPS CPU的PRID寄存器来确定CPU类型, 从而确定使用的指令集和其他一些CPU参数,如TLB等 prom_init(); prom_init() 函数是和硬件相关的,做一些低层的初始化,接受引导装载程序传给内核的参数,确定 mips_machgroup,mips_machtype 这两个变量,这两个变量分别对应着相应的芯片组合开发板; 打印cpu_probe() 函数检测到的CPU 的Processor ID。 如果有浮点处理器,也打印浮点处理器的Processor ID。 cpu_report(); 应用程序通过终端接口设备使用特定的接口规程与终端进行交互,与操作系统内核本身交互的终端称为控制台, 它可以是内核本身的内部显示终端,也可以是通过串口连接的外部哑终端。 由于大多数情况下控制台都是内核显示终端,因此内核显示终端也常常直接称为控制台。 内核终端对用户来说具有若干个虚拟终端子设备,它们共享同一物理终端, 但同一时刻只能有一个虚拟终端操作硬件屏幕。 宏 CONFIG_VT 的意思是否支持虚拟终端。 当配置了宏 CONFIG_VGA_CONSOLE 时为内核本身的内部显示终端。 当配置了宏 CONFIG_DUMMY_CONSOLE 时为通过串口连接的外部哑终端。 用变量 conswitchp 来进行指定。#if defined(CONFIG_VT)#if defined(CONFIG_VGA_CONSOLE) conswitchp = &vga_con;#elif defined(CONFIG_DUMMY_CONSOLE) conswitchp = &dummy_con;#endif#endif 对内存进行初始化。 arch_mem_init(cmdline_p);这个函数遍历每一个内存空间范围(物理地址),在资源管理器中进行资源申请,并对内核代码和数据段进行资源申请。 resource_init();#ifdef CONFIG_SMP plat_smp_setup();#endif在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线程initstatic noinline void _init_refok rest_init(void) _releases(kernel_lock) int pid; rcu_scheduler_starting();#内核RCU锁机制调度启动 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);#创建kernel_init内核线程,内核的1号进程!kernel_init 是要执行的函数的指针, NULL 表示传递给该函数的参数为空, CLONE_FS | CLONE_SIGHAND 为 do_fork 产生线程时的标志,表示进程间的 fs 信息共享,信号处理和块信号共享 numa_default_policy(); pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);1 创建kthreadd内核线程,它的作用是管理和调

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论