重点关注虚拟内存的切换,pgd,mm_struct, start_code;理清楚这三个概念。
pgd不是在这里设置的遵循COW,是在写时缺页中断填充的。
start_code只是一个段开始的标记;真正的进程上下文,存在tast_struct->thread_inf->cpu_context中。
mm_struct:是进程的整个内存空间描述。
进程内核栈示意图
首先看task内核栈的分布图
task_struct->stack;即为进程的内核栈,指向栈底。
栈底,为magic word,进程调度时用来检测是否栈溢出的。然后是task_struct结构体,task_struct里面包含thread_info结构。
thread_info->保存进程切换两个进程的寄存器现场。
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu 寄存器上下文*/
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value[2]; /* TLS registers */
#ifdef CONFIG_CRUNCH
struct crunch_state crunchstate;
#endif
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
};
cpu_context_save结构体:
struct cpu_context_save {
__u32 r4;
__u32 r5;
__u32 r6;
__u32 r7;
__u32 r8;
__u32 r9;
__u32 sl;
__u32 fp;
__u32 sp; /*栈指针*/
__u32 pc; /*pc值*/
__u32 extra[2]; /* Xscale 'acc' register, etc */
};
通过宏TI_CPU_SAVE可以访问到这个成员的偏移量。
文件:arch\arm\kernel\asm-offsets.c
DEFINE(TI_CPU_SAVE, offsetof(struct thread_info, cpu_context));
TI是thread_info的缩写。
进程上下文切换寄存器环境的切换
这些就能解释进程调度中上下文切换的pc的跳了。
来看__switch_to函数,汇编代码
文件arch\arm\kernel\entry-v7m.S
/*
* Register switch for ARMv7-M processors.
* r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
* previous and next are guaranteed not to be the same.
*/
ENTRY(__switch_to)
.fnstart
.cantunwind
/*获取old进程cpu_context值*/
add ip, r1, #TI_CPU_SAVE
/*保存r4-r11寄存器*/
stmia ip!, {r4 - r11} @ Store most regs on stack
/*保存sp寄存器*/
str sp, [ip], #4
/*保存pc寄存器*/
str lr, [ip], #4
mov r5, r0
/*获取新进程的cpu_context*/
add r4, r2, #TI_CPU_SAVE
ldr r0, =thread_notify_head
mov r1, #THREAD_NOTIFY_SWITCH
bl atomic_notifier_call_chain
mov ip, r4
mov r0, r5
/*j加载r4-r11寄存器值*/
ldmia ip!, {r4 - r11} @ Load all regs saved previously
/*加载sp*/
ldr sp, [ip]
/*加载pc,完成进程的切换*/
ldr pc, [ip, #4]!
.fnend
ENDPROC(__switch_to)
问:进程切换为什么不保护寄存器r0-r3的值?
答: r0,r1,r2用来做__switch_to传参用,r0:old task_struct的指针;r1是新进程指针,r2 old thread_info。
内栈顶的pt_regs是作甚的呢?
答:是系统调用保存用户侧上下文环境的寄存器值
#define current_pt_regs() task_pt_regs(current)
arch\arm\include\asm\processor.h中
/*THREAD_START_SP 为内核栈大小-8*/
#define task_pt_regs(p) \
((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)
#define task_stack_page(task) ((void *)(task)->stack)
elf文件的加载
直接分析函数load_elf_binary