Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2785553
  • 博文数量: 258
  • 博客积分: 9440
  • 博客等级: 少将
  • 技术积分: 6998
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-03 10:28
个人简介

-- linux爱好者,业余时间热衷于分析linux内核源码 -- 目前主要研究云计算和虚拟化相关的技术,主要包括libvirt/qemu,openstack,opennebula架构和源码分析。 -- 第五届云计算大会演讲嘉宾 微博:@Marshal-Liu

文章分类

全部博文(258)

文章存档

2016年(1)

2015年(4)

2014年(16)

2013年(22)

2012年(41)

2011年(59)

2010年(40)

2009年(75)

分类: LINUX

2009-12-20 13:04:47

                                      lguest上的guest os启动的过程


根据linux启动流程的分析,在执行到jmp *0xc0100000时,系统将会根据是压缩内核还是未压缩的内核来决定跳转的方向:
(1)如果是未压缩的内核,就直接跳到/kernel/head_32.S的入口开始执行
(2)如果是压缩的内核,就要先解压,整个解压的过程在/boot/compressed/head_32.S中,解压完成后跳到解压内核的起始地址开始执行其实解压后的起始地址,也是/kernel/head_32.S的入口。

因此不管是压缩的内核还是未压缩的内核,都会执行/kernel/head_32.S中的代码。这是可以确定0xc0100000处的第一条代码就是startup_32.
现在我们就从/kernel/head_32.S开始分析。
(1)重新加载boot_gdt_desc和段寄存器的值
(2)清空bss段
(3)把实模式下的boot_params,拷贝到保护模式下的boot_params结构体中
(4)movl pa(boot_params) + NEW_CL_POINTER,%esi                          // %esi指向了 setup_header.cmd_line_ptr
判断一下命令行参数指针是否为空,如果不为空,就要把命令行的参数拷贝过来,拷贝到boot_command_line数组中。
下面就开始判断,如果定义了CONFIG_PARAVIRT的话,即支持虚拟化对的话,就要选择到底执行哪条内核路径,这里有三条路径:
1 default_entry:这是默认的系统启动路径
2 bad_subarch: 当前内核不支持的启动路径
3当前内核支持的两种虚拟化启动路径:lguest_entry 和 xen_entry

相当于定义了一个内核路径数组,数组名是subarch_entries,元素个数是num_subarch_entries,然后在寻址的时候是这样的:

eax = 0 + eax*4 + pa(subarch_entries)。因为我们分析的是lguest , 因此这里的跳到lguest_entry。

有个很重要的数据结构,在这里必须要介绍一下: boot_params 结构体,它就是传说中的"zero page".


这个数据结构几乎保存了启动过程中所需要的所有的信息,比如屏幕显示信息,hdr,e820返回的内存信息等等,在后面的启动程序中,很多地方都用到了这个数据结构中的参数,

很明显这是个循环复制的一组指令,目的地址就是boot_params, 那么源地址是哪里呢?其实,当跳到startup_32时,%esi还是执行实模式下的数据boot_params。应该豁然开朗了,

实模式下的数据在保护模式下是不可用的,因此要拷贝过来。为了验证esi到底是不是指向boot_params,看下面的代码:

但是,boot_params到底是什么时候被初始化的呢?这也是我们比较关心的一个问题。

大部分的初始化的代码包含在arch\x86\boot\Main.c中
(1)copy_boot_params(); 初始化的boot_params.hdr
(2)detect_memory(void);
               初始化了boot_params.e820_map 和boot_params.e820_entries
(3)query_apm_bios(); 初始化了apm_bios_info
(4)query_apm_bios(); 初始化了screen_info

到此,boot_params这个数据结构介绍完了,后面很多代码都会从这个结构体里面取数据。继续分析lguest执行流程lguest_entry.

在arch\x86\lguest\i386_head.S中找到了ENTRY(lguest_entry)

       我们把这段代码总整体上来看,就是实现了这么一个操作,%eax = $LHCALL_LGUEST_INIT   %ebx=lguest_data 数据结构的物理地址
后面又创建了一个堆栈,然后又调用了一个c函数lguest_init .把以上的信息串联起来,岂不就相当于在汇编里调用c函数的整个准备过程,

先设置好参数(%eax,%ebx),然后设置好堆栈。

    LHCALL_LGUEST_INIT是 lguest实现的hypercall 调用号,就想我们熟悉的linux的其他的系统调用,只不过是lguest的系统调用而已。在调用

系统调用前,要把系统调用号保存在eax中,把参数保存在ebx等其他的几个寄存器中。然后.byte 0×0f ,0×01,0xc1就是执行系统调用,相当于

int $0×80,这么做的目的无非就是通知host os,当前运行的是一个guest os.

    这里也涉及到一个非常重要的数据结构lguest_data, 分析下这个结构体。

这个数据结构实现了Host和Guest之间进行交流的一种方法
/*G:032 The second method of communicating with the Host is to via "struct
 * lguest_data".  Once the Guest's initialization hypercall tells the Host where
 * this is, the Guest and Host both publish information in it. :*/
详细分析一下每一个数据成员的含义:
(1)irq_enabled
/* 512 == enabled (same as eflags in normal hardware).  The Guest
     * changes interrupts so often that a hypercall is too slow. */
相当于Host里面的eflags, 512=2^9,即第10位置1,就表示开中断。这么做的原因是:如果通过hypercall 来实现中断的使能的话,太慢了!

(2)DECLARE_BITMAP(blocked_interrupts, LGUEST_IRQS);

定义了一个bitmap,用来做中断屏蔽的???
(3)CR2 :Guest缺页中断时会在CR2中保存一个hypercall,Host在这里写上一次page fault的虚拟地址
(4)time : Host设置的时间
(5)hcall_status[LHCALL_RING_SIZE]   LHCALL_RING_SIZE=64
    /* Async hypercall ring.  Instead of directly making hypercalls, we can
     * place them in here for processing the next time the Host wants.
     * This batching can be quite efficient. */
    /* 0xFF == done (set by Host), 0 == pending (set by Guest). */
并不是每产生一个hypercall,Host就对它进行处理,可以先让这些hypercall 排队,主机在某一个时间来对他们进行处理。0xFF表示Host处理完了所有的pending的hypercall, 0表示有Guest的hypercall在pending.
(6)reserve_mem: 指明给switcher保留的空间的大小(主机初始化)
(7)设置TSC的 频率(Host初始化)
(以下变量guest 在初始化时设置)
(8)noirq_start,noirq_end:  不允许中断的一段指令的范围,即使此时是开中断的;
(9)kernel_address = PAGE_OFFSET
(10)syscall_vec: 系统调用号0×80

下面我们就跳到arch\x86\lguest\boot.c 中的lguest_init()函数来执行。
这个函数主要是对内核的一些敏感操作进行的封装或这说是替换,主要有一下几个方面的封装:
(1)中断相关的操作
(2)cpu指令的封装
(3)页表管理
(4)apic的读写操作
(5)时间相关操作
对以上部分封装之后,接着又执行了下面一系列的操作:
(1)reserve_top_address(lguest_data.reserve_mem);
为Host<->Guest Switcher 保留一段内存空间,这段内存空间的大小由lguest_data.reserve_mem 指定。
(2)lockdep_init()
这个函数分别申请4096个classhash_table和8192个chainhash_table.这两个hashtable到底是用来he干什么的,现在还不清楚?
(3)para_virt_disable_iospace()
禁止所有的非虚拟驱动程序去扫描它所支持的硬件设备,减少启动时间
(4)cpu_detect(&new_cpu_data)
(5)add_preferred_console(“hvc”,0,NULL);
注册hvc这个虚拟终端的驱动
(6)virtio_cons_early_init(early_put_chars);
(7)pm_power_off = lguest_power_off
   machine_ops.restart=lguest_restart
(9)i386_start_kernel

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