Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1081656
  • 博文数量: 104
  • 博客积分: 3715
  • 博客等级: 中校
  • 技术积分: 1868
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-30 08:38
文章分类

全部博文(104)

文章存档

2013年(1)

2012年(9)

2011年(41)

2010年(3)

2009年(3)

2008年(47)

分类: 系统运维

2011-02-21 17:24:10

    本文分析Minix3内核的初始化过程。如Boot系列中所说的,内核启动并作了少量初始化之后,会
开始执行main.c文件中的main()函数,本文就是从这个函数开始看的。

* big_kernel_lock
    目前,Minix3支持SMP。在SMP的情况下,big_kernel_lock是一个自旋锁,用于防止多个CPU同时执行某
段Kernel代码,也就是说,用于多个CPU之间的互斥。在main()函数开始时,会抢占这个锁。

* proc_init()
    main()会调用这个函数(定义在proc.c文件中)来初始化全局的进程表和特权表,以及各CPU对应的IDLE任务。
    对于进程表:
    1.  初始化标识字段,表示slot空闲,初始化magic字段;
    2.  初始化进程号,从-NR_TASKS开始,依此递增;
    3.  初始化slot对应的消息地址endpoint号;
    4.  初始化调度器为null,优先级和时间片都是0;
    对于权限表:
    1.  初始化对应的进程字段为NONE,表示slot为空闲;
    2.  初始化自己的id为自己的下标;
    3.  初始化ppriv_addr数组;
    4.  初始化信号处理器为null;

** Idle任务
    在Minix3中,每个CPU都有一个对应的idle任务,所有的idle任务共享一个权限结构:定义在proc.c中
的idle_priv。这个结构体仅仅设置了其s_flags成员为(SYS_PROC|BILLABLE)。
    idle任务的p_rts_flags中设置了RTS_PROC_STOP的标志,使其不能被调度执行。
    idle任务的名字是"idle" + XXX,其中,XXX是CPU的编号。

** 其它
    人工对齐每个进程表项中的fpu_state_s结构体。
    这个要求16字节对齐,此处用一段代码对它们进行对齐操作。

    下面,一大段代码主要的工作是为boot image中的进程设置进程表项。
* 栈
    所有内核任务(不包括kernel任务,它的栈是汇编分配的)的栈都分配在一个数组中,该数组是t_stack,
定义在table.c中。
    boot image中其它进程的栈已经被boot monitor分配在它们的数据空间中了,所以,它们的栈指针指向其
数据空间的末尾。
    这里有个问题,如果这样的话,这些进程的栈大小在编译时就是知道的。难道是最大栈大小?

    如前面的文章所述,每个boot image中进程的某些属性定义在表image中。
* 主要流程
    对于每个在image中的进程,找到其对应的进程表入口地址,做如下操作(如果没有特别指出,都是指对
进程表项的设置操作):
    1.  根据进程表中的endpoint设置image表中的字段;
    2.  清0剩余CPU时间;
    3.  设置进程的名字;
    4.  清空account;
    5.  此时,检查当前进程是不是应该被立刻调度运行。如果是,则设置它对应的特权,使得它立刻可以被
        调度执行,否则,不设置它的特权,而是设置其RTS标志RTS_NO_PRIV,使得它暂时不能运行。从条件
        可以看到,只有内核任务和root system process(也就是再生服务器RS)是立刻可以运行的。
        设置特权的主要流程如下:
        a.  调用get_priv()函数为当前进程分配并设置对应的特权表项。对于这些进程,其对应的特权ID是
            NR_TASKS + proc_nr,对于内核任务而言,分别是0,1,2,3。事实上,get_priv()支持动态
            或者静态地分配特权表项,这里,传递进去的特权id是大于等于0的,表示静态分配指定的特权表
            项,如果传递进来的id是-1,则表示动态地查找一个空闲表项并分配。
        b.  设置进程特权表项的内容:
    6.  如果当前任务是内核任务,设置它的栈,这些栈都是分配在t_stack中的;
    7.  读取当前进程的头信息,放在局部的一个struct exec结构体中;
    8.  构造当前进程的内存映射,即p_memmap数组,下面都是以click为单位的:
        a.  代码段的mem_phys设置为aout中符号表的大小;
        b.  代码段的mem_len设置为aout中代码段的大小;
        c.  数据段的mem_phys设置为代码段的base+length;
        d.  数据段的mem_len设置为aout中数据段加bss段的大小;
        e.  栈段的mem_phys设置为代码段的base+length;
        f.  栈段的mem_len设置为0;
        g.  栈段的mem_vir设置为aout中a_total的大小;
    9.  设置进程表项中寄存器的值:
        a.  pc设置为0;
        b.  设置psw;
        c.  设置用户进程的栈指针sp,它指向p_memmap[S].mem_vir对应的字节地址 - 3*sizeof(reg_t)的位置,
            之所以空出3个字长的空间,是用于放置进程main函数的argc、argv和envp三个参数。
    10. 在Minix内核中,有一个CPU-LOCAL全局变量proc_ptr,它指向当前正在运行的进程的进程表项。调度器
        依赖于这个变量。此时,我们检查一下当前CPU的proc_ptr是否为空,如果为空,则将它设置为当前进程。
    11. 如果当前进程的运行需要虚拟地址,设置其RTS标志的RTS_VMINHIBIT位,表示,在VM为它设置好页表
        之前,该进程不能被调度执行;
    12. 设置当前进程的RTS标志的RTS_PROC_STOP位,表示不能运行;
    13. 调用alloc_segments()函数,为该进程分配段(段寄存器,在386上使用);
    至此,一个进程表项就初始化结束了。
* 后续初始化流程
    1.  设置ipc_call_names数组,包含SEND, RECEIVE, SENDREC, NOTIFY, SENDNB, SENDA。
    2.  调用arch_init()完成体系结构相关的初始化;
        对于i386而言,这里主要做设置好内核栈环境,初始化acpi、idt、tss等工作。
    3.  调用system_init()完成系统任务的初始化;
        该函数主要在初始化一个call_vec数组,该数组中保存了kernel-call的句柄。该数组将
        将SYS_XXX号对应到do_xxx()函数上。例如,SYS_FORK对应do_fork()函数。
        除上面之外,该函数还做了另外两件事:
        a.  将irq_hooks表项的proc_nr_e清成NONE;
        b.  初始化特权表中的s_alarm_timer成员,设置为永不超时,没有next;
    4.  调用bsp_finish_booting(),该函数从不返回,进入系统正常运行过程。

    现在,内核最原始的初始化过程已经结束了,后续的工作交给了bsp_finish_booting()函数。

* bsp_finish_booting()
    该函数执行结束后,系统就开始正常运行并调度各个进程了。该函数主要完成以下操作:
    1.  调用cpu_identify()获取CPU的相关信息,保存在全局变量cpu_info中;
    2.  设置全局标志vm_running为0,初始化全局变量krandom,设置随机种子信息;
    此时,Minix已经基本ready了,所有boot image中的进程也都处于ready队列中了,后面的主要动作及时
    启动调度并启动用户态的进程了。
    3.  设置当前CPU的计费和进程,都指向当前cpu的idle_proc;
    4.  打印minix启动时的信息;
    5.  清楚用户态进程中的RTS_PROC_STOP标志,使得它们可以开始执行(至少不被这个标志阻挡);
    6.  调用boot_cpu_init_timer函数,启用时钟中断,并设置中断处理函数;
    7.  初始化fpu;
    8.  调用switch_to_user()函数,开始调度用户态进程运行,该函数不会返回。

    至此,Minix内核所有的启动初始化完成了,开始调度用户态进程并进入正常执行的状态。

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