
再浅析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()
...
}
|