再浅析armlinux 2.4.19第1个内核线程init的建立全流程
文章来源:http://gliethttp.cublog.cn
调用kernel_thread的父进程执行完sys_clone()后直接执行ret_fast_syscall(),最后返回到arch_kernel_thread的movs %0, r0语句处继续执行,新创建出来的子进程被调度器调度后直接执行ret_from_fork()函数,最后也返回到arch_kernel_thread的movs %0, r0语句处继续往下执行,所以sys_clone调用一次,会分别从父进程和新创建的子进程各返回一次,并且返回到同一语句点[发生系统调用swi处的下一语句]各自独立的继续往下执行,也就是父进程返回到哪个空间,新建的子进程也返回到那个空间,因为新建的子进程的pt_regs结构体拷贝的是父进程的pt_regs结构体,所以内核空间调用sys_clone(),那么父、子进程都会返回到内核空间swi执行处的下一语句,如果是用户空间程序调用了sys_clone(),那么父、子进程都会返回到用户空间swi执行处的下一语句,至于实际运行过程中是返回到内核空间还是用户空间则由pt_regs结构体的spsr数值全权决定[gliethttp_20080103]! //-------------------------------------------------- //init/main.c->start_kernel()->rest_init() static void rest_init(void) { //动态创建第1个核心线程 kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); unlock_kernel(); current->need_resched = 1; cpu_idle(); } //-------------------------------------------------- pid_t arch_kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) { pid_t __ret;
__asm__ __volatile__( "orr r0, %1, %2 @ kernel_thread sys_clone \n\ mov r1, #0 \n\
//执行软中断swi 0x00900078进入系统调用,调用第0x78=120号软中断处理函数,即:arch/arm/kernel/calls.S //文件中的sys_clone_wapper函数 /* .align 5 ENTRY(vector_swi)//软中断入口 //具体对该结构体的分析可以参看: //《浅析arm-linux系统调用的流程》http://blog.chinaunix.net/u1/38994/showart_331915.html //《浅析armlinux 2.4.19中断irq分发例程的派发流程之根基》 // http://blog.chinaunix.net/u1/38994/showart_449653.html save_user_regs //用户user模式下的寄存器,swi软中断和irq中断不一样,因为swi软中断使用的就是svc模式堆栈,所以 //只是简单的保存用户模式,即便是在svc内核空间调用了系统调用,也同样保存user用户空间的寄存器, //因为spsr的值会区分开之前的模式是svc还是user,也就决定了restore_user_regs执行完成之后, //程序在svc内核空间继续执行还是回到user用户空间 zero_fp get_scno arm710_bug_check scno, ip ...略... */ //swi中断使用cpu的svc模式,所以swi软中断发生时,I中断位由cpu自动进行中断禁止, //之后arm处理器自动将cpsr切换到svc模式[gliethttp_20080103]
"__syscall(clone)" \n\
//对于刚启动的arm处理器spsr和cpsr都是svc模式 //所以如果前面没有将svc下的spsr改成其他值,那么init核心线程就会 //在在svc模式下继续运行,不过上电之后arm的cpsr和spsr都是irq和fiq禁用的 //但是在restore_user_regs调用的时候msr spsr, r1将svc模式的spsr设置成了当前的cpsr,所以movs执行之前arm内核的spsr和cpsr数值已经相等. movs %0, r0 @ if we are the child \n\ bne 1f /*非0表示返回到调用arch_kernel_thread的父进程rest_init()*/\n\ mov fp, #0 @ ensure that fp is zero \n\ mov r0, %4 \n\ mov lr, pc \n\ mov pc, %3 /*跳转到init核心子线程执行*/ \n\ b sys_exit /*init核心子线程退出 */ \n\ 1: " //注:%0=__ret;%1=flags;%2=CLONE_VM;%3=fn;%4=arg [gliethttp_20080103] : "=&r" (__ret) : "Ir" (flags), "I" (CLONE_VM), "r" (fn), "r" (arg) : "r0", "r1", "lr"); return __ret; } //-------------------------------------------------- //对于堆栈空间分布图可以参考 //1.《copy_thread()中8k空间及其中task_struct结构填充图》 // http://blog.chinaunix.net/u1/38994/showart_344665.html //2.《浅析armlinux-init核心线程创建流程梗概描述和图解》 // http://blog.chinaunix.net/u1/38994/showart_344277.html .macro save_user_regs sub sp, sp, #S_FRAME_SIZE stmia sp, {r0 - r12} @ Calling r0 - r12 add r8, sp, #S_PC stmdb r8, {sp, lr}^ @ Calling sp, lr //对于init内核线程的创建该spsr的数值为svc模式值,而非user模式值;这样就决定了创建的新 //子线程init会继续停留在svc内核空间,而不会回到user用户空间[gliethttp_20080103] mrs r8, spsr @ called from non-FIQ mode, so ok. str lr, [sp, #S_PC] @ Save calling PC str r8, [sp, #S_PSR] @ Save CPSR str r0, [sp, #S_OLD_R0] @ Save OLD_R0 .endm //-------------------------------------------------- .macro restore_user_regs ldr r1, [sp, #S_PSR] ldr lr, [sp, #S_PC]! msr spsr, r1 //对于kernel_thread()函数下生成r1的数值为svc模式值 ldmdb sp, {r0 - lr}^//将数据恢复到user的r0-r14这15个寄存器中 mov r0, r0 add sp, sp, #S_FRAME_SIZE - S_PC //跳转到lr同时将cpu恢复到spsr模式下,对于在内核模式调用的kernel_thread()也spsr仍为svc //也就是还在svc模式下,对于user那么spsr对应的就是user模式值了,这也就决定了kernel_thread() //函数创建的新pid内核线程不会回到user用户空间,仍在svc内核空间运行[gliethttp_20080103] movs pc, lr//创建出来的核心子线程init跳转到arch_kernel_thread()的movs %0, r0处继续执行 .endm //-------------------------------------------------- sys_clone_wapper: //因为现在sp的真实位置在前面的str r4, [sp, #-S_OFF]!压栈之后,改为了sp-#S_OFF值, //所以经过调整之后的r2指向struct pt_regs结构体起始地址 add r2, sp, #S_OFF b SYMBOL_NAME(sys_clone) //-------------------------------------------------- asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp, struct pt_regs *regs) { //对于内核子线程init的创建r1=newsp=0,而regs->ARM_sp为用户空间的sp值 //不过因为内核线程的spsr为svc模式,所以最后restore_user_regs时, //并不会恢复到user用户空间执行所以newsp对于内核线程也就没有什么意义了 if (!newsp) newsp = regs->ARM_sp; return do_fork(clone_flags, newsp, regs, 0); } //-------------------------------------------------- //do_fork()通过拷贝或者共享父线程的各种资源的过程中,会调用copy_thread() int copy_thread(int nr, unsigned long clone_flags, unsigned long esp, unsigned long unused, struct task_struct * p, struct pt_regs * regs) { struct pt_regs *childregs; struct context_save_struct * save;
atomic_set(&p->thread.refcount, 1); //esp就是用户空间堆栈newsp //对于堆栈空间分布图可以参考 //《copy_thread()中8k空间及其中task_struct结构填充图》 // http://blog.chinaunix.net/u1/38994/showart_344665.html childregs = ((struct pt_regs *)((unsigned long)p + 8192 - 8)) - 1; *childregs = *regs;//将父线程的swi软中断入栈的pt_regs结构体数据复制给该新子线程 childregs->ARM_r0 = 0; childregs->ARM_sp = esp;
//调度器进行__switch_to新线程切换恢复时使用的特有堆栈数据结构体context_save_struct save = ((struct context_save_struct *)(childregs)) - 1; //#define INIT_CSS (struct context_save_struct){ SVC_MODE, 0, 0, 0, 0, 0, 0, 0, 0, 0 } //显然新创建的task进程首先返回到该task进程对应的svc内核空间,然后进一步根据spsr的值 //来决定新进程最终返回到user用户空间还是继续在svc内核空间执行[gliethttp_20080103]. *save = INIT_CSS; save->pc |= (unsigned long)ret_from_fork;//swi返回执行函数
p->thread.save = save;//保存切换数据指针,将有ldr sp, [r1, #TSS_SAVE]使用
return 0; } //对于给新创建的内核线程分配pid号,我有这样的一些感触: //linux 2.4.19内核的get_pid()获取函数存在缺陷,如果通过若干天的连续运行,程序们不停的 //释放、创建pid,那么当小于0x8000次时,get_pid()函数获取pid的速度很快,当大于0x8000次时, //会调用for_each_task(p)从第1个进程开始扫描所有进程,因此get_pid()函数获取pid的 //速度大大降低.linux 2.6内核使用hash表改进了get_pid()函数[gliethttp_20080103]. //-------------------------------------------------- //新创建的核心子线程迟早会通过schedule()被调度执行 schedule()->switch_to()->__switch_to() ENTRY(__switch_to) //保存调度结构体context_save_struct内容 //对于堆栈空间分布图可以参考 //《copy_thread()中8k空间及其中task_struct结构填充图》 // http://blog.chinaunix.net/u1/38994/showart_344665.html stmfd {r4 - sl, fp, lr} @ Store most regs on stack mrs ip, cpsr str ip, [sp, #-4]! @ Save cpsr_SVC str sp, [r0, #TSS_SAVE] @ Save sp_SVC ldr sp, [r1, #TSS_SAVE] @ Get saved sp_SVC ldr r2, [r1, #TSS_DOMAIN] ldr ip, [sp], #4 mcr p15, 0, r2, c3, c0 @ Set domain register msr spsr, ip @ Save tasks CPSR into SPSR for this return ldmfd {r4 - sl, fp, pc}^ @ Load all regs saved previously //对于sys_clone出来的子进程来说,就是跳转到copy_thread()中 //save->pc |= (unsigned long)ret_from_fork; //从ret_from_fork继续执行新创建的线程剩余部分. //-------------------------------------------------- ENTRY(ret_from_fork) bl SYMBOL_NAME(schedule_tail) get_current_task tsk ldr ip, [tsk, #TSK_PTRACE] @ check for syscall tracing mov why, #1 tst ip, #PT_TRACESYS @ are we tracing syscalls? beq ret_disable_irq //如果没有进行strace操作,那么调用ret_disable_irq继续返回 mov r1, sp mov r0, #1 @ trace exit [IP = 1] bl SYMBOL_NAME(syscall_trace) b ret_disable_irq //-------------------------------------------------- //entry-common.S reschedule: bl SYMBOL_NAME(schedule) ret_disable_irq: disable_irq r1 @ ensure IRQs are disabled ENTRY(ret_to_user) ret_slow_syscall: ldr r1, [tsk, #TSK_NEED_RESCHED] ldr r2, [tsk, #TSK_SIGPENDING] teq r1, #0 @ need_resched => schedule() bne reschedule 1: teq r2, #0 @ sigpending => do_signal() bne __do_signal restore: //不需要reschedule重新调度,也不需要do_signal做信号处理,那么 //子线程创建完成,恢复子线程的寄存器数值,跳转到子线程执行 restore_user_regs //对restore_user_regs的分析见上面 __do_signal: enable_irq r1 mov r0, #0 @ NULL 'oldset' mov r1, sp @ 'regs' mov r2, why @ 'syscall' bl SYMBOL_NAME(do_signal) @ note the bl above sets lr disable_irq r1 @ ensure IRQs are disabled b restore //-------------------------------------------------- init()函数中的execve()定义在include/asm-arm/unistd.h中,具体定义如下: static inline _syscall3(int,execve,const char *,file,char **,argv,char **,envp);
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \ type name(type1 arg1,type2 arg2,type3 arg3) { \ register long __r0 __asm__("r0") = (long)arg1; \ register long __r1 __asm__("r1") = (long)arg2; \ register long __r2 __asm__("r2") = (long)arg3; \ register long __res __asm__("r0"); \ __asm__ __volatile__ ( \ __syscall(name) \ : "=r" (__res) \ : "r" (__r0),"r" (__r1),"r" (__r2) \ : "lr"); \ __syscall_return(type,__res); \ } 所以拆解合成之后就是 static inline int execve(const char *file,char **argv,char **envp) { ... swi 0x0090000b//也就是执行系统调用sys_execve_wrapper()->sys_execve() ... }
|