免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2376 | 回复: 0
打印 上一主题 下一主题

qemu源代码分析 (一) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-06-15 22:26 |只看该作者 |倒序浏览

                                                                                 qemu源代码分析
          第一部分 qemu oldworld mac(heathrow)的初始化
约定:
qemu版本: 0.10.5
分析的目标机器: heathrow (oldmac)
目标处理器架构: PPC(包括7x0/750, BookE, FSL-BookE)
我们从hw/ppc_oldworld.c开始,函数:
static void ppc_heathrow_init(...);
其中的一个重要的函数:
void cpu_register_physical_memory(target_phys_addr_t start_addr, ram_addr_t size, ram_addr_t phys_offset);
/* register physical memory. 'size' must be a multiple of the target
   page size. If (phys_offset & ~TARGET_PAGE_MASK) != 0, then it is an
   io memory page.  The address used when calling the IO function is
   the offset from the start of the region, plus region_offset.  Both
   start_region and regon_offset are rounded down to a page boundary
   before calculating this offset.  This should not be a problem unless
   the low bits of start_addr and region_offset differ.  */
void cpu_register_physical_memory_offset(target_phys_addr_t start_addr,
                                         ram_addr_t size,
                                         ram_addr_t phys_offset,
                                         ram_addr_t region_offset)
{
    target_phys_addr_t addr, end_addr;
    PhysPageDesc *p;
    CPUState *env;
    ram_addr_t orig_size = size;
    void *subpage;
#ifdef USE_KQEMU
    /* XXX: should not depend on cpu context */
    env = first_cpu;
    if (env->kqemu_enabled) {
        kqemu_set_phys_mem(start_addr, size, phys_offset);
    }
#endif
    if (kvm_enabled())
        kvm_set_phys_mem(start_addr, size, phys_offset);
    if (phys_offset == IO_MEM_UNASSIGNED) {
        region_offset = start_addr;
    }
    region_offset &= TARGET_PAGE_MASK;
    size = (size + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK;
    end_addr = start_addr + (target_phys_addr_t)size;
    for(addr = start_addr; addr != end_addr; addr += TARGET_PAGE_SIZE) {
        p = phys_page_find(addr >> TARGET_PAGE_BITS);
        if (p && p->phys_offset != IO_MEM_UNASSIGNED) {
            ram_addr_t orig_memory = p->phys_offset;
            target_phys_addr_t start_addr2, end_addr2;
            int need_subpage = 0;
            CHECK_SUBPAGE(addr, start_addr, start_addr2, end_addr, end_addr2,
                          need_subpage);
            if (need_subpage || phys_offset & IO_MEM_SUBWIDTH) {
                if (!(orig_memory & IO_MEM_SUBPAGE)) {
                    subpage = subpage_init((addr & TARGET_PAGE_MASK),
                                           &p->phys_offset, orig_memory,
                                           p->region_offset);
                } else {
                    subpage = io_mem_opaque[(orig_memory & ~TARGET_PAGE_MASK)
                                            >> IO_MEM_SHIFT];
                }
                subpage_register(subpage, start_addr2, end_addr2, phys_offset,
                                 region_offset);
                p->region_offset = 0;
            } else {
                p->phys_offset = phys_offset;
                if ((phys_offset & ~TARGET_PAGE_MASK) > TARGET_PAGE_BITS, 1);
            p->phys_offset = phys_offset;
            p->region_offset = region_offset;
            if ((phys_offset & ~TARGET_PAGE_MASK) phys_offset, IO_MEM_UNASSIGNED,
                                           addr & TARGET_PAGE_MASK);
                    subpage_register(subpage, start_addr2, end_addr2,
                                     phys_offset, region_offset);
                    p->region_offset = 0;
                }
            }
        }
        region_offset += TARGET_PAGE_SIZE;
    }
    /* since each CPU stores ram addresses in its TLB cache, we must
       reset the modified entries */
    /* XXX: slow ! */
    for(env = first_cpu; env != NULL; env = env->next_cpu) {
        tlb_flush(env, 1);
    }
}
==> phys_page_find() ==> phys_page_find_alloc(target_phys_addr_t index, int alloc);
static PhysPageDesc *phys_page_find_alloc(target_phys_addr_t index, int alloc)
{
    void **lp, **p;
    PhysPageDesc *pd;
    p = (void **)l1_phys_map;
#if TARGET_PHYS_ADDR_SPACE_BITS > 32
#if TARGET_PHYS_ADDR_SPACE_BITS > (32 + L1_BITS)
#error unsupported TARGET_PHYS_ADDR_SPACE_BITS
#endif
    lp = p + ((index >> (L1_BITS + L2_BITS)) & (L1_SIZE - 1));
    p = *lp;
    if (!p) {
        /* allocate if not found */
        if (!alloc)
            return NULL;
        p = qemu_vmalloc(sizeof(void *) * L1_SIZE);
        memset(p, 0, sizeof(void *) * L1_SIZE);
        *lp = p;
    }
#endif
    lp = p + ((index >> L2_BITS) & (L1_SIZE - 1));
    pd = *lp;
    if (!pd) {
        int i;
        /* allocate if not found */
        if (!alloc)
            return NULL;
        pd = qemu_vmalloc(sizeof(PhysPageDesc) * L2_SIZE);
        *lp = pd;
        for (i = 0; i > L2_BITS) & (L1_SIZE - 1));
    pd = *lp;
lp为一级页表的offset,pd则为pgd[offset]。如果alloc==0,这个函数将返回:
    return ((PhysPageDesc *)pd) + (index & (L2_SIZE - 1));
也就是二级页表的地址,也就是pte entry的地址;否则当搜索的pte entry不存在是,自动分配内存给要求的pte entry,并返回这个pte entry。
需要注意的是当TARGET_PHYS_ADDR_SPACE_BITS > 32时,通常我们需要三级页表,如对ppc64,TARGET_PHYS_ADDR_SPACE_BITS == 42,因此我们需要多做一个页表搜索才能得到pte entry的地址。
    lp = p + ((index >> (L1_BITS + L2_BITS)) & (L1_SIZE - 1));
    p = *lp;
L1_BITS,L2_BITS这里其实相当于第二级与第三级页表的bits。
index传进来的时候是一个地址右移了PAGE_BITS,经过上面的操作后就变成了三级页表的第一级的offset了(由于代码里写的是L1_BITS, L2BITS,我们可以假定第一级为L0_BITS)。
如果不考虑特殊情况,函数cpu_register_physical_memory()做的事情到此已经结束,即:搜索(分配)根据addr与size相关参数确定的pte entry。其中最重要的是我们知道qemu模拟了目标架构的MMU(softmmu).
// 考虑特殊情况(FIXME)
当ppc_heathrow_init()时,我们分别注册了以下内存区域:
    ram_offset = qemu_ram_alloc(ram_size);
    cpu_register_physical_memory(0, ram_size, ram_offset);
    bios_offset = qemu_ram_alloc(BIOS_SIZE);
    cpu_register_physical_memory(PROM_ADDR, BIOS_SIZE, bios_offset | IO_MEM_ROM);
重要函数:
/* return  load_elf_32() or load_elf_64()
我们以load_elf_32为例(ppc heathrow),这个函数在文件elf_ops.h中定义;实际上这个文件做了一点小小的手段,导致我们定义SZ=32或SZ=64是,#include这个文件两次,我们就生成了需要的相关_xxx_32或是_xxx_64函数(都是静态的)。如:
static int glue(load_elf, SZ)(int fd, int64_t address_offset,
                              int must_swab, uint64_t *pentry,
                              uint64_t *lowaddr, uint64_t *highaddr)
==>
static int load_elf_32(...) or
static int load_elf_64(...)
为了便于阅读,我们假定SZ=32,将上面的函数略做替代之后,得到:
static int load_elf_32(int fd, int64_t address_offset,
                              int must_swab, uint64_t *pentry,
                              uint64_t *lowaddr, uint64_t *highaddr)
{
    struct elfhdr ehdr;
    struct elf_phdr *phdr = NULL, *ph;
    int size, i, total_size;
    elf_word mem_size;
    uint64_t addr, low = 0, high = 0;
    uint8_t *data = NULL;
    if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
        goto fail;
    if (must_swab) {
        bswap_ehdr_32(&ehdr);
    }
    if (ELF_MACHINE != ehdr.e_machine)
        goto fail;
    if (pentry)
       *pentry = (uint64_t)(elf_sword)ehdr.e_entry;
    load_symbols_32(&ehdr, fd, must_swab);
    size = ehdr.e_phnum * sizeof(phdr[0]);
    lseek(fd, ehdr.e_phoff, SEEK_SET);
    phdr = qemu_mallocz(size);
    if (!phdr)
        goto fail;
    if (read(fd, phdr, size) != size)
        goto fail;
    if (must_swab) {
        for(i = 0; i p_type == PT_LOAD) {
            mem_size = ph->p_memsz;
            /* XXX: avoid allocating */
            data = qemu_mallocz(mem_size);
            if (ph->p_filesz > 0) {
                if (lseek(fd, ph->p_offset, SEEK_SET) p_filesz) != ph->p_filesz)
                    goto fail;
            }
            /* address_offset is hack for kernel images that are
               linked at the wrong physical address.  */
            addr = ph->p_paddr + address_offset;
            cpu_physical_memory_write_rom(addr, data, mem_size);
            total_size += mem_size;
            if (!low || addr  high)
                high = addr + mem_size;
            qemu_free(data);
            data = NULL;
        }
    }
    qemu_free(phdr);
    if (lowaddr)
        *lowaddr = (uint64_t)(elf_sword)low;
    if (highaddr)
        *highaddr = (uint64_t)(elf_sword)high;
    return total_size;
fail:
    qemu_free(data);
    qemu_free(phdr);
    return -1;
}
了解一点loader与ELF文件格式的都会知道,ELF文件的PT_LOAD字段要求被载入内存,这通常包含.text与.data字段,对于我们比较了解的x86来说,.text字段通常要求在0x0804_8000加载,因此loader会在loader这个ELF文件(executable)的时候请求在0x0804_8000映射内存,这个通常由一个mmap()调用达到目的。对于PPC来说,这个地址通常是0x1000_0000,.data字段通常在.text字段(连续)之后,当然ELF文件会要求load的时候被对齐(如对其到系统的页面大小4kb)。我们可以通过readelf -l /path/to/executable来获取相关信息,ELF entry通常就是_start函数开始的地址(当然是链接后的)。通过load_symbols_32(),qemu还加载了相应的.sym, .strtab字段。
函数load_elf() (load_elf_32)返回了@lowaddr与@highaddr,当一个ELF文件有多个PT_LOAD字段时,@lowaddr为所有映射字段地址里最低的,同理@highaddr为所有映射字段里面地址最高的结束为止 (addr + mem_size)。
与普通的loader不同的是,qemu loader无法将ELF文件要求映射的地址按其要求映射。qemu是一个普通的进程,可能无法再将ELF可执行文件映射到默认的entry地址(如主机为x86, 0x0804_8000已无法继续映射)。上面我们提到qemu模拟了目标机器的MMU(softmmu, 具有分页机制),因此类似一个正常的loader(通过mmap要求os映射到指定地址),我们需要请求qemu的MMU将目标ELF文件要求映射的地址做好映射。相关的工作由以下函数完成:
void cpu_physical_memory_write_rom(target_phys_addr_t addr,
                                   const uint8_t *buf, int len)
{
    int l;
    uint8_t *ptr;
    target_phys_addr_t page;
    unsigned long pd;
    PhysPageDesc *p;
    while (len > 0) {
        page = addr & TARGET_PAGE_MASK;
        l = (page + TARGET_PAGE_SIZE) - addr;
        if (l > len)
            l = len;
        p = phys_page_find(page >> TARGET_PAGE_BITS);
        if (!p) {
            pd = IO_MEM_UNASSIGNED;
        } else {
            pd = p->phys_offset;
        }
        if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM &&
            (pd & ~TARGET_PAGE_MASK) != IO_MEM_ROM &&
            !(pd & IO_MEM_ROMD)) {
            /* do nothing */
        } else {
            unsigned long addr1;
            addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK);
            /* ROM/RAM case */
            ptr = phys_ram_base + addr1;
            memcpy(ptr, buf, l);
        }
        len -= l;
        buf += l;
        addr += l;
    }
}
注意qemu仅仅映射了PT_LOAD字段,解析了符号表,返回了entry函数地址(以及设定@lowaddr/highaddr),但目前为止并未执行任何动作。
函数QEMUMachine->init (heathrow_init)在文件vl.c:main()中被调用(qemu 0.10.5, #5599):
    machine->init(ram_size, vga_ram_size, boot_devices,
                  kernel_filename, kernel_cmdline, initrd_filename, cpu_model);
    current_machine = machine;
我们可能有点奇怪qemu加载的ELF文件(可能为linux kernel或是firmware)是在哪里执行的?ppc_oldworld.c:ppc_heathrow_init.c()中有两处调用了load_elf(),第一次加载firmware,第二次加载linux kernel(如果有-kernel选项的话):
    /* Load OpenBIOS (ELF) */
    bios_size = load_elf(buf, 0, NULL, NULL, NULL);
    ...
    kernel_size = load_elf(kernel_filename, kernel_base, NULL, &lowaddr, NULL);
两次加载的entry_point都没有指定(同加载一个正常的用户空间ELF文件不一样)。那heathrow是如何启动起来的?回答这个问题之前我们需要了解一点CPU初始化的知识。由于不了解heathrow(oldmac, 740?),以Freescale MPC85xx为例,CPU(指MPC85xx)上电的一刻,ip指向的位置为0xFFFF_FFFC(当然是虚拟地址,BookE PPC并没有所谓的“实”模式);CPU默认的ROM地址为0xFF80_0000,也就是4G的最后8M,我们可以不全部使用这8M,如u-boot就只使用了最后的512KB。但如果想让firmware工作正常,我们需要在0xFFFF_FFFC(在boot rom中)放一条跳转指令。以u-boot为例,这个指令将跳转至0xFFFF_E000,即最后一页。这里我们需要特别小心:由于CPU刚上电,几乎一切都没有被初始化;并且对于BookE(如MPC85xx, PPC 44x)的PPC,默认地址转换(即MMU)处于开启状态,因此CPU必须(默认行为)在TLB寄存器里在存储最后一页的映射。我们开始能使用的地址空间(包括指令,数据)就只有这一页(4KB)。在正确的初始化完CPU之前,试图访问这一页之外的数据会触发Data TLB Error或Instruction TLB Error异常,但这时候异常向量表并未被正确的设置,结果是很显然的(更多信息会在后续的u-boot初始化分析中给出)。
回到ppc_oldworld.c,ppc_heathrow_init()中有如下调用:
    /* init CPUs */
    if (cpu_model == NULL)
        cpu_model = "G3";
    for (i = 0; i osi_call = vga_osi_call;
        qemu_register_reset(&cpu_ppc_reset, env);
        envs = env;
    }
注意函数ppc_init(const char* model),这将调用cpu_ppc_init():
CPUPPCState *cpu_ppc_init (const char *cpu_model)
{
    CPUPPCState *env;
    const ppc_def_t *def;
    def = cpu_ppc_find_by_name(cpu_model);
    if (!def)
        return NULL;
    env = qemu_mallocz(sizeof(CPUPPCState));
    cpu_exec_init(env);
    ppc_translate_init();
    env->cpu_model_str = cpu_model;
    cpu_ppc_register_internal(env, def);
    cpu_ppc_reset(env);
    if (kvm_enabled())
        kvm_init_vcpu(env);
    return env;
}
cpu_ppc_find_by_name()将导致qemu搜索注册的ppc_def数组(target-ppc/translate_init.c,后面会继续分析这个文件)。这个数据定义了所有类型(qemu支持的)的PPC的属性(如指令兼容类型,MMU类型,异常类型等等),目前对我们最重要的是里面的init_proc()函数指针。
cpu_ppc_register_internal(env, def)会调用注册的这个init_proc()函数指针,对heathrow来说,这个函数是init_proc_740() (target-ppc/translate_init.c)。其中调用了后面我们会提到的函数:init_excp_7x0()。
cpu_exec_init()并为做太多(我们关心的)工作,ppc_translate_init()主要用于初始化qemu的后端(我们将host machine的环境定义为前端,qemu的中间环境定义为中端,target machine成为后端)的相关机器代码定义及寄存器。因此目前对我们来说更重要的是函数: cpu_ppc_reset()。
void cpu_ppc_reset (void *opaque)
{
    CPUPPCState *env = opaque;
    target_ulong msr;
    if (qemu_loglevel_mask(CPU_LOG_RESET)) {
        qemu_log("CPU Reset (CPU %d)\n", env->cpu_index);
        log_cpu_state(env, 0);
    }
    msr = (target_ulong)0;
    if (0) {
        /* XXX: find a suitable condition to enable the hypervisor mode */
        msr |= (target_ulong)MSR_HVB;
    }
    msr |= (target_ulong)0 nip = env->hreset_vector | env->excp_prefix;
    if (env->mmu_model != POWERPC_MMU_REAL)
        ppc_tlb_invalidate_all(env);
#endif
    env->msr = msr & env->msr_mask;
    hreg_compute_hflags(env);
    env->reserve = (target_ulong)-1ULL;
    /* Be sure no exception or interrupt is pending */
    env->pending_interrupts = 0;
    env->exception_index = POWERPC_EXCP_NONE;
    env->error_code = 0;
    /* Flush all TLBs */
    tlb_flush(env, 1);
}
注意这几行:
    env->nip = env->hreset_vector | env->excp_prefix;
    if (env->mmu_model != POWERPC_MMU_REAL)
        ppc_tlb_invalidate_all(env);
其中env->hreset_vector与excp_prefix分别定义为:
static void init_excp_7x0 (CPUPPCState *env)
{
#if !defined(CONFIG_USER_ONLY)
    env->excp_vectors[POWERPC_EXCP_RESET]    = 0x00000100;
    env->excp_vectors[POWERPC_EXCP_MCHECK]   = 0x00000200;
    env->excp_vectors[POWERPC_EXCP_DSI]      = 0x00000300;
    env->excp_vectors[POWERPC_EXCP_ISI]      = 0x00000400;
    env->excp_vectors[POWERPC_EXCP_EXTERNAL] = 0x00000500;
    env->excp_vectors[POWERPC_EXCP_ALIGN]    = 0x00000600;
    env->excp_vectors[POWERPC_EXCP_PROGRAM]  = 0x00000700;
    env->excp_vectors[POWERPC_EXCP_FPU]      = 0x00000800;
    env->excp_vectors[POWERPC_EXCP_DECR]     = 0x00000900;
    env->excp_vectors[POWERPC_EXCP_SYSCALL]  = 0x00000C00;
    env->excp_vectors[POWERPC_EXCP_TRACE]    = 0x00000D00;
    env->excp_vectors[POWERPC_EXCP_PERFM]    = 0x00000F00;
    env->excp_vectors[POWERPC_EXCP_IABR]     = 0x00001300;
    env->excp_vectors[POWERPC_EXCP_SMI]      = 0x00001400;
    env->excp_vectors[POWERPC_EXCP_THERM]    = 0x00001700;
    env->excp_prefix = 0x00000000UL;
    /* Hardware reset vector */
    env->hreset_vector = 0xFFFFFFFCUL;
#endif
}
细心的读者可能会发现这里的excp_vector都是(虚)低地址,而CPU初始化的时候能访问的仅仅最后一页而已。注意函数cpu_ppc_reset()中用这么一行(对MPC85xx是MSR.MSR_IS):
    msr |= (target_ulong)1
void isa_mmio_init(target_phys_addr_t base, target_phys_addr_t size)
{
    if (!isa_mmio_iomemtype) {
        isa_mmio_iomemtype = cpu_register_io_memory(0, isa_mmio_read,
                                                    isa_mmio_write, NULL);
    }
    cpu_register_physical_memory(base, size, isa_mmio_iomemtype);
}
首先isa_mmio_init()调用了cpu_register_io_memory()分配了一个I/O区域的index,然后通过调用cpu_register_physical_memory(),注册内存区域@0xFE00_0000,size=2M到这个I/O MEM index。最重要的,将MMIO内存通过qemu softmmu注册到指定的区域。
    ...
    for (i = 0; i irq_inputs)[PPC6xx_INPUT_INT];
            break;
        default:
            cpu_abort(env, "Bus model not supported on OldWorld Mac machine\n");
            exit(1);
        }
    }
    /* init basic PC hardware */
    if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) {
        cpu_abort(env, "Only 6xx bus is supported on heathrow machine\n");
        exit(1);
    }
    pic = heathrow_pic_init(&pic_mem_index, 1, heathrow_irqs);
注意heathrow_irqs[0] = ((qemu_irq*)env->irq_inputs)[PPC6xx_INPUT_INT];这一行。PPC_INPUT_INT就是PPC处理器的外部中断(级联/cascade到了第一个heathrow PIC的最低中断位)。因此通过这种方式,我们将heathrow PIC挂到了PPC的外部中断之上。通过heathrow_pic_init(),初始化目标的中断处理例程,注意heathrow_pic_init()返回的是一个qemu_irq。
注意这段代码:
/* update the CPU irq state */
static void heathrow_pic_update(HeathrowPICS *s)
{
    if (check_irq(&s->pics[0]) || check_irq(&s->pics[1])) {
        qemu_irq_raise(s->irqs[0]);
    } else {
        qemu_irq_lower(s->irqs[0]);
    }
}
其中s->irqs为heathrow的所有PIC中断向量,包含两个PIC控制器,共支持64个中断,s->irqs[0]即为最低位的中断,亦即级联到CPU中断控制器的INT中断的那一位;heathrow_pic_update()会分发中断至上一层的中断控制器。heathrow_pic_init()之后,中断处理大概可以表示为下图:
+------+------+------+------+-----+| HRST | SRST | ...  | INT  | ... |+------+------+------+------+-----+                        |                     +------+------+-----+------+                     |PIC 0 |PIC 1 | ... |PIC 63|                     +------+------+-----+------+
当qemu_irq_raise()/qemu_irq_lower()的时候,相应的中断事件会被cascade的内部中断控制器处理,见函数hw/ppc.c:ppc6xx_set_irq():
    ...
        case PPC6xx_INPUT_INT:
            /* Level sensitive - active high */
            LOG_IRQ("%s: set the external IRQ state to %d\n",
                        __func__, level);
            ppc_set_irq(env, PPC_INTERRUPT_EXT, level);
    ...
qemu_irq *heathrow_pic_init(int *pmem_index,
                            int nb_cpus, qemu_irq **irqs)
{
    HeathrowPICS *s;
    s = qemu_mallocz(sizeof(HeathrowPICS));
    /* only 1 CPU */
    s->irqs = irqs[0];
    *pmem_index = cpu_register_io_memory(0, pic_read, pic_write, s);
    register_savevm("heathrow_pic", -1, 1, heathrow_pic_save,
                    heathrow_pic_load, s);
    qemu_register_reset(heathrow_pic_reset, s);
    heathrow_pic_reset(s);
    return qemu_allocate_irqs(heathrow_pic_set_irq, s, 64);
}
(这里再次调用了cpu_register_io_memory()分配了一个IO MEM index。)
qemu_register_reset()注册了中断控制器的reset()函数。并通过显示调用heathrow_pic_reset(),初始化了(两个)PIC中断控制器。注意传递进来的@irqs为前面的heathrow_irqs,这个从env->irq_inputs中拷贝而来,而env->irq_inputs在调用cpu_ppc_init()=>init_proc_750()=>ppc6xx_irq_init()时被初始化。(target-ppc/translate_init.c:init_proc_750())。
static void heathrow_pic_reset(void *opaque)
{
    HeathrowPICS *s = opaque;
    heathrow_pic_reset_one(&s->pics[0]);
    heathrow_pic_reset_one(&s->pics[1]);
    s->pics[0].level_triggered = 0;
    s->pics[1].level_triggered = 0x1ff00000;
}
由此我们也可以注意到PIC 0均为level triggered,PIC1的有9(可能是最后9个)个不是level triggered(如edge triggered,详情请参见hw/heathrow_pic.c)。
最后heathrow_pic_init()调用了qemu_allocate_irqs()分配了qemu的(虚拟)IRQ。
qemu虚拟的设备如ne2k_pci(hw/ne2000.c)在产生中断时调用qemu_set_irq(),由于我们调用了qemu_allocate_irqs,因此最终调用到了相应中断控制器(heathrow PIC)的代码,从而完成了对外部中断的虚拟。
后面涉及的vga与相关PCI,IDE驱动目前我们不予讨论。
(原创文章,未经作者同意,请勿转载,谢谢合作)
(未完待续)
               
               
               
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/98727/showart_1965448.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP