内核启动过程根据自己心得所整理 最全.docx
- 文档编号:18339087
- 上传时间:2023-08-15
- 格式:DOCX
- 页数:21
- 大小:42.03KB
内核启动过程根据自己心得所整理 最全.docx
《内核启动过程根据自己心得所整理 最全.docx》由会员分享,可在线阅读,更多相关《内核启动过程根据自己心得所整理 最全.docx(21页珍藏版)》请在冰点文库上搜索。
内核启动过程根据自己心得所整理最全
内核启动过程总结
之前配置编译过内核源代码,在交叉编译源代码后产生了三个文件(还有其他文件)分别是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
设置栈
jalstart_kernel(init/main.c)
怎么为第一个进程0进程作准备?
第一个进程涉及到init_thread_union,这个结构体在include/linux/sched.h得到定义
externunionthread_unioninit_thread_union;
unionthread_union{
structthread_infothread_info;
unsignedlongstack[THREAD_SIZE/sizeof(long)];
};
THREAD_SIZE是一个宏定义#defineTHREAD_SIZE(2*PAGE_SIZE)
#definePAGE_SIZE(_AC(1,UL)< 内核把进程存放在任务队列的双向循环链表中,链表中的每一项都是类型为task_struct、称为进程描述符的结构,进程描述符中包含一个具体进程的所有信息,linux通过slab分配器分配task_struct结构,每个任务都有一个thread_info结构,它在内核栈的尾部分配,结构中的task域中存放的是指向该任务实际task_struct的指针,thread_info结构在文件 structthread_info{ structtask_struct*task;/*maintaskstructure*/ structexec_domain*exec_domain;/*executiondomain*/ unsignedlongflags;/*lowlevelflags*/ unsignedlongtp_value;/*threadpointer*/ __u32cpu;/*currentCPU*/ intpreempt_count;/*0=>preemptable,<0=>BUG*/ mm_segment_taddr_limit;/*threadaddressspace: 0-0xBFFFFFFFforuser-thead 0-0xFFFFFFFFforkernel-thread*/ structrestart_blockrestart_block; structpt_regs*regs; }; /* *Initialthreadstructure. * *Weneedtomakesurethatthisis8192-bytealignedduetothe *wayprocessstacksarehandled.Thisisdonebymakingsure *thelinkermapsthisinthe.textsegmentrightafterhead.S, *andmakinghead.Sensuretheproperalignment. * *Thethingswedoforperformance.. */ unionthread_unioninit_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这个全局变量 /* *Initialtaskstructure. * *Allothertaskstructswillbeallocatedonslabsinfork.c */ structtask_structinit_task=INIT_TASK(init_task); 0号进程的task_struct出现了,就是init_task。 在对init_thread_union赋初值的时候,同时也通过调用INIT_TASK宏对init_task赋初值: #defineINIT_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,/*50usecdefaultslack*/\ .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/functionsforgainingaccesstothethreadinformationstructure */ #defineINIT_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_LAt0,__bss_start#clear.bss LONG_Szero,(t0) PTR_LAt1,__bss_stop-LONGSIZE 1: PTR_ADDIUt0,LONGSIZE LONG_Szero,(t0) bnet0,t1,1b 最后跳转到/init/main.c中的start_kernel()初始化硬件平台相关的代码。 ********************************************************************* 附上/kernel/head.S源代码及部分注释: .macrosetup_c0_statussetclr #ifdefCONFIG_MIPS_MT_SMTC ........... #else mfc0t0,CP0_STATUS#取CP0status寄存器的值到临时寄存器to中 ort0,ST0_CU0|\set|0x1f|\clr xort0,0x1f|\clr mtc0t0,CP0_STATUS#将临时寄存器to中的值赋给CP0状态寄存器 .setnoreorder#表示禁止为了填充加载指令和分支指令的延迟槽而对代#码重新排序 sllzero,3#ehb(exceptionhazardbarrier) .macrosetup_c0_status_pri ----------------------------------------------------- #ifdefCONFIG_64BIT setup_c0_statusST0_KX0 #else setup_c0_status00 #endif .endm ------------------------------------------------------------------------------------------ NESTED(kernel_entry,16,sp)#kernelentrypoint声明函数kernel_entry,函数的堆栈为16byte,返回地址保存在$sp寄存器中。 kernel_entry_setup#cpuspecificsetup,某些MIPSCPU需要额外的设置一些 控制寄存器,和具体的平台相关,一般为空宏;某些多 核MIPS,启动时所有的core的入口一起指向kernel_entry, 然后在该宏里分叉,bootcore继续往下,其它的则不停 的判断循环,直到bootcore唤醒之 setup_c0_status_pri#设置cp0_status寄存器 PTR_LAt0,0f jrt0 PTR_LAt0,__bss_start#clear.bss LONG_Szero,(t0)#变量__bss_start和__bss_stop在连接文件arch/mips/kernel/vmlinux.lds中定义。 PTR_LAt1,__bss_stop-LONGSIZE 1: PTR_ADDIUt0,LONGSIZE LONG_Szero,(t0) bnet0,t1,1b LONG_Sa0,fw_arg0#firmwarearguments LONG_Sa1,fw_arg1#bootloader会将要传给内核的参数写在 LONG_Sa2,fw_arg2a0~a3里,此处为将参数保存在fw_arg0-fw_arg3中 LONG_Sa3,fw_arg3 MTC0zero,CP0_CONTEXT#clearcontextregister PTR_LA$28,init_thread_union#初始化gp,指向一个union,THREAD_SIZE大小,最低处是一个thread_info结构体 PTR_LIsp,_THREAD_SIZE-32-PT_SIZE#_THERAD_SIZE=16384 PT_SIZE=304(sizeof(structpt_regs)) PTR_ADDUsp,$28 back_to_back_c0_hazard set_saved_spsp,t0,t1#把这个CPU核的堆栈地址$sp保存到kernelsp[NR_CPUS]数组。 ---------------------------------------------------------------- 如果定义了CONFIG_SMP宏,即多CPU核。 .macroset_saved_spstackptemptemp2 #ifdefCONFIG_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核。 .macroset_saved_spstackptemptemp2 LONG_S\stackp,kernelsp .endm 变量kernelsp的定义,在arch/mips/kernel/setup.c文件中。 unsignedlongkernelsp[NR_CPUS]; ------------------------------------------------------------------------------------------------------- PTR_SUBUsp,4*SZREG#initstackpointerSZREG=8 jstart_kernel#最后跳转到/init/main.c中的start_kernel()初始化硬件平台相关的代码。 END(kernel_entry) ********************************************************************* #defineasmlinkage__attribute__((regparm(0))) _attribute__是关键字,是gcc的C语言扩展,regparm(0)表示不从寄存器传递参数。 如果是__attribute__((regparm(3))), 那么调用函数的时候参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数 函数定义前加宏asmlinkage,表示这些函数通过堆栈而不是通过寄存器传递参数。 gcc编译器在汇编过程中调用c语言函数时 传递参数有两种方法: 一种是通过堆栈,另一种是通过寄存器。 缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数, 并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage ********************************************************************* 看看init/main.c源代码 asmlinkagevoid__initstart_kernel(void){ char*command_line; externconststructkernel_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.加锁后导致依赖图产生成闭环,这是典型的死锁现象。 启动LockDependencyValidator(内核依赖的关系表),本质上就是建立两个散列表calsshash_table和chainhash_table,并初始化全局变量lockdep_initialized,标志已初始化完成 debug_objects_early_init();//在启动早期初始化hashbuckets和链接静态的poolobjects对象到poll列表.在这个调用完成后objecttracker已经开始完全运作了. boot_init_stack_canary();canary值的是用于防止栈溢出攻击的堆栈的保护字。 参考资料: GCC中的编译器堆栈保护技术 cgroup_init_early();cgroup: 它的全称为controlgroup.即一组进程的行为控制.该函数主要是做数据结构和其中链表的初始化 local_irp_disable();//关闭系统总中断(底层调用汇编指令) early_boot_irqs_off();//设置系统中断的关闭标志(bool全局变量) early_init_irp_lock_class();每个中断都有一个中断描述符(structirq_desc)来进行描述,这
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 内核启动过程根据自己心得所整理 最全 内核 启动 过程 根据 自己 心得 整理