Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15530519
  • 博文数量: 2005
  • 博客积分: 11986
  • 博客等级: 上将
  • 技术积分: 22535
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-17 13:56
文章分类

全部博文(2005)

文章存档

2014年(2)

2013年(2)

2012年(16)

2011年(66)

2010年(368)

2009年(743)

2008年(491)

2007年(317)

分类: LINUX

2008-01-03 11:37:49

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

阅读(1804) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~