本文分析基于mips架构,linux-3.3.8
一.kernel入口
首先通过链接脚本找到kernel的入口
1.内核是压缩过的
可见在压缩是入口为/arch/mips/boot/compressed/head.s
2.内核是未压缩的
定义makefile指定了vmlinux的链接脚本:
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds, 由vmlinux.lds.S编译得到
可见入口为kernel_entry
这里只讲解未压缩的情况,因为压缩情况解压之后的跳转到内核执行过程是一样的。
setup_c0_status_pri的定义位于:
current表示当前进程,我们来看看它的定义:
所以这里PTR_LA $28, init_thread_union 实际上就是设置为0号进程,
PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE
PTR_ADDU sp, $28
sp= $28 + _THREAD_SIZE - 32 - PT_SIZE=$28 +8192-32-sizeof(struct pt_regs)
union thread_union {
#ifndef CONFIG_X86
struct thread_info thread_info;
#endif
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long tp_value; /* thread pointer */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
*/
struct restart_block restart_block;
struct pt_regs *regs;
};
$28---------→+----------------+ low address
| *task |
| ... |
| *regs | ----------------------------------+
grow ↑ | xxxxxxxxx | |
| | xxxxxxxxx | |
sp---------> | xxxxxxxxx | |
| 32 byte | |
| sizeof(pt_regs)| ← ------------------------------ +
这样sp就切换到了0号进程的内核栈上,设置好栈之后就可以执行c定义的函数了
二.进入start_kernel
asmlinkage void __init start_kernel(void)
{
boot_init_stack_canary();
local_irq_disable();
tick_init();
setup_arch(&command_line);
build_all_zonelists(NULL);
page_alloc_init();
trap_init();
mm_init();
sched_init();
init_IRQ();
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
local_irq_enable();
...
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
static noinline void __init_refok rest_init(void)
{
int pid;
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //创建init进程,即1号进程
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
/* Call into cpu_idle with preempt disabled */
preempt_disable();
cpu_idle(); //0号进程
}
static int __init kernel_init(void * unused)
{
...
smp_init();
sched_init_smp();
do_basic_setup();
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __force_user *) "/dev/console", O_RDWR, 0) < 0) //打开标准终端0
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0); //打开标准终端1
(void) sys_dup(0); //打开标准终端2
...
init_post();
return 0;
}
static noinline int init_post(void)
{
...
我们现在来看看0号进程即swapper进程都在干些啥:
void __noreturn cpu_idle(void)
{
int cpu;
/* CPU is going idle. */
cpu = smp_processor_id();
/* endless idle loop with no priority at all */
while (1) {
tick_nohz_idle_enter();
rcu_idle_enter();
while (!need_resched() && cpu_online(cpu)) {
if (cpu_wait) {
/* Don't trace irqs off for idle */
stop_critical_timings();
(*cpu_wait)();
start_critical_timings();
}
}
rcu_idle_exit();
tick_nohz_idle_exit();
preempt_enable_no_resched();
schedule();
preempt_disable();
}
}
它是一个while(1)循环,在没有进程可运行时,swapper进程开始运行,一旦其他的进程准备就绪,就切换到其他进程去运行,那么其他进程从阻塞到准备就绪是在哪来切换的呢,当然是在swapper进程执行过程中发生的中断,在中断处理函数中将等待资源的进程设置为就绪态。
通过以上分析代码可以看出,mips架构内核初始化是非常简单的,汇编代码要做的工作非常少,内核的入口地址为0x80001000,整个内核代码的地址空间在KSEG0的覆盖范围了,进行直接地址转换而不需要MMU的设置,整个分页系统的初始都可以用C语言来完成。