精华内容
下载资源
问答
  • ddr原理及初始化

    2011-12-12 22:31:41
    ddr初始化 原理 sdram bootloader初始化
  • Zircon - Fuchsia 内核分析 - 启动(平台初始化)

    万次阅读 多人点赞 2018-12-23 16:30:30
    简介 Zircon 是 Google 新操作系统 Fuchsia 的内核,基于 LK - Little Kernel 演变而来。而 Little Kernel 前面一直作为 Android 系统的 ...Zircon 目前支持 X86/X64 和 ARM 两种 CPU 平台,下面我将以 ARM6...

    简介

    Zircon 是 Google 新操作系统 Fuchsia 的内核,基于 LK - Little Kernel 演变而来。而 Little Kernel 前面一直作为 Android 系统的 Bootloader 的核心而存在。Zircon 在此基础上增加了 MMU,System Call 等功能。
    Zircon 目前支持 X86/X64 和 ARM 两种 CPU 平台,下面我将以 ARM64 为例,一行行分析 Zircon 内核的早期启动过程,看一下 Zircon 和 ARM64 是如何完成平台初始化的,这部分由汇编实现。
    需要事先声明的是,本人平时从事的是 Android 开发,对于 ARM 了解有限,此次源码阅读也会参考一些其他资料,其中难免会有一些错误,望广大读者谅解。

    带注释的 Zircon 内核源码(未完成):https://github.com/ganyao114/zircon/tree/doc

    ARM64

    首先需要简单过一下涉及到的 ARM64 背景,以前虽有简单接触过嵌入式的 ARM,但相比之下 ARM64 确实要复杂很多很多。。。

    特权模式/异常等级

    在 ARM32 中,我们使用 SVC 等 7 种特权模式来区分 CPU 的工作模式,操作系统等底层程序会运行在高特权模式,而普通用户程序则运行在低特权的用户模式。
    而在 ARM64 中,其实也类似,不过在 ARM64 中统一成了 4 个异常等级 EL-0/1/2/3

    EL 架构:
    在这里插入图片描述

    • 特权等级 EL3 > EL2 > EL1 > EL0 , EL0 为非特权执行等级
    • EL2 无 Secure State,有 None-Secure State。EL3 只有 Secure-State,并且控制 EL0和EL1 在两种模式间切换
    • EL0 和 EL1 必须实现,EL2 和 EL3 是可选的

    关于 4 个特权等级在系统软件中的实际使用:

    EL用途
    EL0运行用户程序
    EL1操作系统内核
    EL2Hypervisor (可以理解为上面跑多个虚拟内核)
    EL3Secure Monitor(ARM Trusted Firmware)

    Secure State 的影响:

    State影响
    Non-SecureEL0/1/2 只能访问 Non-Secure Memory
    SecureEL0/1/3, 可以访问Non-secure memory & Secure memory,可起到物理屏障安全隔离作用

    多核心

    在多核心处理器下,ID = 0 的 CPU 内核为 prime 核心,或者被称为 BSP 引导处理器 - bootstrap processor,其他处理器则为 non - prime 核心,或者 AP 核心 - Application Processor,开机和内核初始化由 prime 核心完成,AP 核心只要完成自身的配置就可以了。

    ARM MP 架构图:

    在这里插入图片描述

    • CPU 内核间通过核间中断 - IPI 彼此通讯
    • 每个 CPU 内核都能看到同样的内存总线和数据,一般 L1 缓存每个内核独享,L2/L3 为所有内核共享
    • 所有 CPU 内核共享同一个 I/O 周边与中断控制器,中断控制器会根据配置将中断分发到合适的 CPU 内核

    寄存器

    仅说明本文所涉及的

    寄存器----位宽用途
    X0 - X3064通用寄存器
    X0 - X764用于传递子程序参数和结果,使用时不需要保存,多余参数采用堆栈传递,64位返回结果采用X0表示,128位返回结果采用X1:X0表示
    X864保存子程序返回地址
    X9 - X1564临时寄存器,使用时不需要保存
    X16 - X1764子程序内部调用寄存器,使用时不需要保存,尽量不要使用
    X1864平台寄存器,用于 ABI 调用
    X19 - X2864临时寄存器,使用时必须保存
    X2964帧指针寄存器,用于连接栈帧,使用时需要保存
    X30(PLR)64链接寄存器LR
    WZR / XZR64零寄存器,没什么意义,它不是一个实际存在的寄存器,其实相当于一个 0 常量
    MPIDR_EL164多核标志处理器,我们只要关心其中 AFF0 和 AFF1 位。AFF0 [0 - 7 bit] 位,表示当前 CPU 内核的当前的线程编号,由于直到最新的 A76 都不支持超线程技术,所以这个默认为 0,无需关心;AFF1 [8-10 bit] 当前 CPU 簇中当前 CPU 内核的编号,0 号就是 prime CPU 内核
    PSTATE程序状态寄存器,PSTATE不是一个寄存器,是保存当前PE状态的一组寄存器统称,其中可访问寄存器有:PSTATE.{NZCV,DAIF,CurrentEL,SPSel},属于ARMv8新增内容,64bit下代替CPSR
    CurrentEL当前所处的异常等级, 只读
    SCR_EL332Secure 配置寄存器,用于配置上文的 Secure State,这里用到了里面的 3 个 Flag 位,1. SCR_EL3_NS:EL0/EL1 的 Secure State,EL0/EL1 是否能访问 Secure Memory; 2.SCR_EL3_HCE:开关 HVC 指令;3.SCR_EL3_RW:设置 EL1/EL2 使用 AARCH32 还是 AARCH64。
    SP_ELX(X = 0-3)64对应异常等级下的栈指针寄存器
    SPSR_ELX(X = 1-3)32程序状态保存寄存器,保存进入 ELX 异常等级时的 PSTATE 状态,用于等级降下来的时候恢复状态,由于不会有异常把系统状态迁移到EL0,因此也就不存在 SPSR_EL0了。
    ELR_ELX(X = 1-3)64异常链接寄存器,用于保存异常进入ELX的异常地址,在返回异常现场的时候,可以使用 ELR_ELX(x = 1/2/3) 来恢复PC值, 异常迁移到哪一个 exception level 就使用哪一个 ELR 同样的,由于不会有异常把系统状态迁移到EL0,因此也就不存在ELR_EL0了。
    VBAR_ELX(X = 0-3)64保存任意异常进入 ELX 的跳转向量基地址
    HCR_EL264HCR_EL2.{TEG,AMO,IMO,FMO,RW}控制 EL0/EL1 的异常路由 逻辑1允许
    VTTBR_EL264保存了转换表的基地址,负责在 EL2 下进行 EL0 -> EL1 的非安全存储器访问的转换

    内核代码中的通用范例

    有了上文对 ARM64 的简单介绍,我们就可以看懂代码中的一些代码了
    以下是比较通用的代码

    判断是否是 prime CPU 内核

    mrs     cpuid, mpidr_el1
    ubfx    cpuid, cpuid, #0, #15 /* mask Aff0 and Aff1 fields */ //aff0 记录 cpu ID,aff1 记录是否支持超线程
    cbnz    cpuid, .Lno_save_bootinfo //如果不是 prim 核心(0 号核心),则不需要启动内核,也就不需要准备内核启动参数,直接执行核心初始化工作
    

    前两行取出 mpidr_el1 的 AFF01 放入 cpuid
    第三行如果 cpuid = 0 则代表是 prime cpu 内核,并且也是第一个线程,虽然现在超线程没有实现就是了

    取标签/数据地址

    这里需要解释一下,因为 kernel 在链接的时候是根据虚拟地址来的。而在内核引导的早期阶段,也就是本文所介绍的这个过程中,MMU 是处于关闭状态的,这段时间内核实际是跑在物理地址上的。
    那么,这段代码就必须是 PIC 位置无关代码,除了尽量使用寄存器,在不得不访问内存时,这段代码还不能依赖链接器所给的地址,那么如果在这段代码中需要取到内存中的地址只能使用指令计算数据/Label的实际地址。

    Zircon 将这一操作简化成了一个宏:

    .macro adr_global reg, symbol
    //得到包含 symbol 4K 内存页的基地址
    adrp \reg, \symbol
    //第一个全局变量的地址
    add \reg, \reg, #:lo12:\symbol
    .endm
    

    第一行得到得到包含 symbol 4K 内存页的基地址
    第二行在基地址上加上偏移就是 symbol 的实际地址

    判断当前所在的 EL

    mrs x9, CurrentEL 
    cmp x9, #(0b01 << 2)
    //不等于 0 时,说明不是在异常级别 1,跳转到 notEL1 代码
    bne .notEL1 
    

    循环遍历

    str     xzr, [page_table1, tmp, lsl #3]
    add     tmp, tmp, #1
    cmp     tmp, #MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP
    bne     .Lclear_top_page_table_loop
    

    等价于

    for(int tmp = 0;tmp < MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP;tmp++) {
         page_table1[tmp] = 0;
    }
    

    启动过程概述

    启动早期,即内核在进入 C++ 世界之前,主要分为以下几步

    • 初始化各个 EL1 - EL3 下的异常配置
    • 创建启动阶段页表
    • 为打开 MMU 做准备
    • 打开 MMU
    • 配置栈准备进入 C 世界

    启动时序与代码

    在多核处理器架构中,很多初始化代码仅需要由 prime 处理器完成,其他处理器完成各自的配置即可。

    prime 核心其他核心
    保存内核启动参数跳过
    初始化 EL1 - EL3 的异常配置
    初始化缓存
    修复 kernel base 地址 跳过
    检查并等待 .bss 段数据清除跳过
    创建启动阶段的页表自旋等待页表创建完成
    打开 MMU 之前的准备工作
    打开 MMU (以上代码运行在物理地址,以下代码运行在虚拟地址)
    重新配置内核栈指针配置其他 CPU 的栈指针
    跳转到 C 世界继续初始化休眠等待唤醒

    保存内核启动参数

    start.S - _start

     	mrs     cpuid, mpidr_el1 //aff0 记录 cpu ID,aff1 记录是否支持超线程
        cbnz    cpuid, .Lno_save_bootinfo //如果不是 prim 核心(0 号核心),则不需要启动内核,也就不需要准备内核启动参数,直接执行核心初始化工作
        /* save x0 in zbi_paddr */
        //prim 核心走这里,准备并保存内核启动参数
        //计算 zbi_paddr 段中数据的地址,保存在 x0 中,下同
        adrp    tmp, zbi_paddr
        str     x0, [tmp, #:lo12:zbi_paddr]
        /* save entry point physical address in kernel_entry_paddr */
        adrp    tmp, kernel_entry_paddr
        adr     tmp2, _start
        str     tmp2, [tmp, #:lo12:kernel_entry_paddr]
        adrp    tmp2, arch_boot_el
        mrs     x2, CurrentEL
        str     x2, [tmp2, #:lo12:arch_boot_el]
        //总之,x0 - x4 现在保存了核心初始化需要的参数,为跳转到 C 世界作准备。
    

    初始化 EL1 - EL3

    asm.S - arm64_elX_to_el1
    对各个 EL 的配置,需要 CPU 在对应的 EL 状态下才能配置

    EL1

    EL1 不需要配置直接返回

    	//读取现在的异常级别
        mrs x9, CurrentEL 
        cmp x9, #(0b01 << 2)
        //不等于 0 时,说明不是在异常级别 1,跳转到 notEL1 代码
        bne .notEL1 
        /* Already in EL1 */
        //EL1 直接返回
        ret
    

    EL2

    EL2 状态下主要配置了:

    • 配置 EL2 的异常向量表
    • 配置时钟
    • 清除 EL2 的转换表寄存器
    • 配置 SPSR 和 ELR 寄存器,这两个看上面的寄存器介绍

    实际上 EL2 在 Zircon 中还没有具体用处,所以此处初始化基本上就是设一些空值

    /* Setup the init vector table for EL2. */
        //计算EL2的异常向量表的基地址
        adr_global x9, arm64_el2_init_table
        //设定EL2的异常向量表的基地址
        msr vbar_el2, x9
    
        /* Ensure EL1 timers are properly configured, disable EL2 trapping of
            EL1 access to timer control registers.  Also clear virtual offset.
        */
        //检查并配置时钟
        mrs x9, cnthctl_el2
        orr x9, x9, #3
        msr cnthctl_el2, x9
        msr cntvoff_el2, xzr
    
        /* clear out stage 2 translations */
        //清除 vttbr_el2 寄存器,vttbr_el2 保存了转换表的基地址,负责在 EL2 下进行 EL0 -> EL1 的非安全存储器访问的转换
        msr vttbr_el2, xzr //http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100403_0200_00_en/lau1457340777806.html
    
        //当系统发生了异常并进入EL2,SPSR_EL2,Saved Program Status Register (EL2)会保存处理器状态,ELR_EL2,Exception Link Register (EL2)会保存返回发生exception的现场的返回地址。
        //这里是设定SPSR_EL2和ELR_EL2的初始值。
        adr x9, .Ltarget
        msr elr_el2, x9
        //ELR 定义看上面
        mov x9, #((0b1111 << 6) | (0b0101)) /* EL1h runlevel */
        msr spsr_el2, x9
    

    EL3

    EL3 状态的主要任务就是配置 EL0/EL1 的 Secure State/HVC/运行指令集,其他的也是上面 EL2 一样付空值。

    • 设置 EL0/EL1 为 non-Secure State
    • 开启 HVC 指令
    • 使用 AARCH64 指令
       cmp x9, #(0b10 << 2)
    
        //当前为异常级别 2,跳转到 inEL2
        beq .inEL2
    
        //当不在 EL2 状态时,则为 EL3
        /* set EL2 to 64bit and enable HVC instruction */
        //scr_el3 控制EL0/EL1/EL2的异常路由  逻辑1允许
        //http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100403_0200_00_en/lau1457340777806.html
        //若SCR_EL3.RW == 1,则决定 EL2/EL1 是使用AArch64,否则AArch32
        mrs x9, scr_el3
        //打开 EL0/EL1 的非安全状态,EL0/EL1 无法访问安全内存 
        orr x9, x9, #SCR_EL3_NS
        //开启 HVC 指令
        //关于 HVC,看 http://www.wowotech.net/armv8a_arch/238.html
        orr x9, x9, #SCR_EL3_HCE
        //设置 SCR_EL3.RW == 1,EL2/EL1 是使用AArch64
        orr x9, x9, #SCR_EL3_RW 
        msr scr_el3, x9
    
        //ELR 寄存器 Exception Link Register,用于保存异常进入ELX的异常地址,在返回异常现场的时候,可以使用 ELR_ELX(x = 1/2/3) 来恢复PC值, 异常迁移到哪一个exception level就使用哪一个ELR
        //同样的,由于不会有异常把系统状态迁移到EL0,因此也就不存在ELR_EL0了。
        adr x9, .Ltarget
        //这里异常进入地址为 Ltarget
        msr elr_el3, x9
    
    
        //设定 spsr_el3
        mov x9, #((0b1111 << 6) | (0b0101)) /* EL1h runlevel */
        msr spsr_el3, x9
    
        //配置 EL1 并准备进入 EL1 *
        b   .confEL1
    

    从 EL3 返回 EL1

        /* disable EL2 coprocessor traps */
        mov x9, #0x33ff
        msr cptr_el2, x9
    
        /* set EL1 to 64bit */
        //设置 EL1 的异常处理为 AARCH64 指令,同上
        mov x9, #HCR_EL2_RW
        msr hcr_el2, x9
    
        /* disable EL1 FPU traps */
        mov x9, #(0b11<<20)
        msr cpacr_el1, x9
    
        /* set up the EL1 bounce interrupt */
    
        //配置 EL1 栈指针
        mov x9, sp   
        msr sp_el1, x9
    
        isb
        //模拟异常返回,执行该指令会使得CPU返回EL1状态
        eret
    

    初始化缓存

        //使缓存失效 *
        bl      arch_invalidate_cache_all
    
        /* enable caches so atomics and spinlocks work */
        //启用缓存,使原子操作和自旋锁生效
        mrs     tmp, sctlr_el1
        //打开指令缓存
        orr     tmp, tmp, #(1<<12) /* Enable icache */
        //打开数据缓存
        orr     tmp, tmp, #(1<<2)  /* Enable dcache/ucache */
        msr     sctlr_el1, tmp
    

    修复重定向的 Kernel Base 地址

    此工作由 prime cpu 完成,其他 cpu 开始进入自旋等待

        //加载 kernel_relocated_base 段地址
        //内核重定向的基地址,即内核开始的虚拟地址
        adr_global  tmp, kernel_relocated_base
        //负值给 kernel_vaddr
        ldr     kernel_vaddr, [tmp]
    
        // Load the base of the translation tables.
        //貌似 Zircon 中 1GB 物理内存由一个 translation_table 维护,所以这里 tt_trampoline 相当于一级页表?
        adr_global page_table0, tt_trampoline
        //虚拟地址内存页地址转换表
        adr_global page_table1, arm64_kernel_translation_table
    
        // Send secondary cpus over to a waiting spot for the primary to finish.
        //如果不是 prim CPU 内核,则跳转到 Lmmu_enable_secondary 后等待 prim 内核运行完下面代码
        cbnz    cpuid, .Lmmu_enable_secondary
        //下面的代码只有 prim CPU 内核执行
    
        // The fixup code appears right after the kernel image (at __data_end in
        // our view).  Note this code overlaps with the kernel's bss!  It
        // expects x0 to contain the actual runtime address of __code_start.
        //将内核代码开始的虚拟地址保存到 x0 中
        mov     x0, kernel_vaddr
        //跳转到 __data_end *
        //__data_end 指向 image.S - apply_fixups 方法
        bl      __data_end
    
    FUNCTION(apply_fixups)
        // This is the constant address the kernel was linked for.
        movlit x9, KERNEL_BASE
        sub x0, x0, x9
    
    // The generated kernel-fixups.inc invokes this macro for each run of fixups.
    .macro fixup addr, n, stride
        adr x9, FIXUP_LOCATION(\addr)
    .if \n >= 4 && \stride == 8
        // Do a loop handling adjacent pairs.
        mov x16, #(\n / 2)
    0:  fixup_pair
        subs x16, x16, #1
        b.ne 0b
     .if \n % 2
        // Handle the odd remainder after those pairs.
        fixup_single 8
     .endif
    .elseif \n >= 2 && \stride == 8
        // Do a single adjacent pair.
        fixup_pair
     .if \n == 3
        // Do the third adjacent one.
        fixup_single 8
     .endif
    .elseif \n > 1
        // Do a strided loop.
        mov x16, #\n
    0:  fixup_single \stride
        subs x16, x16, #1
        b.ne 0b
    .else
        // Do a singleton.
        fixup_single 8
    .endif
    .endm
    
    .macro fixup_pair
        ldp x10, x11, [x9]
        add x10, x10, x0
        add x11, x11, x0
        stp x10, x11, [x9], #16
    .endm
    
    .macro fixup_single stride
        ldr x10, [x9]
        add x10, x10, x0
        str x10, [x9], #\stride
    .endm
    
    #include "kernel-fixups.inc"
    
        ret
    
    DATA(apply_fixups_end)
    END_FUNCTION(apply_fixups)
    

    检查等待清除 .bss 段

    //检查内核 bss 段是否被清除,猜测是因为前面 bss 段所在内存已经被操作过   
    .Ldo_bss:
        //见 kernel.ld
        //计算保存内核 .bss 段开始地址
        adr_global tmp, __bss_start
        //计算保存内核 .bss 段结束地址
        adr_global tmp2, _end
        //计算 .bss 段大小
        sub     tmp2, tmp2, tmp
        //.bss 段大小为 0 则跳转 Lbss_loop_done
        cbz     tmp2, .Lbss_loop_done
    
    //不为 0 则循环等待
    .Lbss_loop:
        sub     tmp2, tmp2, #16
        stp     xzr, xzr, [tmp], #16
        cbnz    tmp2, .Lbss_loop
    .Lbss_loop_done:
    

    创建启动阶段的页表

    首先要把也表中的内存清除:

    //清除内核虚地址转换表  
    .Lclear_top_page_table_loop:
    
        //遍历转换表中的所有条目并设置 0
        /**
            等价于
            for(int tmp = 0;tmp < MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP;tmp++) {
                page_table1[tmp] = 0;
            }
    
        关于 xzr 寄存器 https://community.arm.com/processors/f/discussions/3185/wzr-xzr-register-s-purpose
        **/
        str     xzr, [page_table1, tmp, lsl #3]
        add     tmp, tmp, #1
        cmp     tmp, #MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP
        bne     .Lclear_top_page_table_loop
    

    在初始化阶段,需要映射三段地址:

    • 第一段是identity mapping,其实就是把物理地址mapping到物理地址上去,在打开MMU的时候需要这样的mapping(ARM ARCH强烈推荐这么做的)
    • 第二段是kernel image mapping,内核代码欢快的执行当然需要将kernel running需要的地址(kernel txt、dernel rodata、data、bss等等)进行映射了
    • 第三段是blob memory对应的mapping

    因为页表映射调用到了 C 方法,所以需要提前为 CPU 配置 SP 指针:

    .Lbss_loop_done:
    
        /* set up a functional stack pointer */
        //设定内核栈地址,准备调用 C 代码
        adr_global tmp, boot_cpu_kstack_end
        mov     sp, tmp
    
        /* make sure the boot allocator is given a chance to figure out where
         * we are loaded in physical memory. */
        bl      boot_alloc_init
    
        /* save the physical address the kernel is loaded at */
        //保存内核开始地址到 kernel_base_phys 全局变量
        adr_global x0, __code_start
        adr_global x1, kernel_base_phys
        str     x0, [x1]
    
        /* set up the mmu according to mmu_initial_mappings */
    
        /* clear out the kernel translation table */
    
        mov     tmp, #0
    

    映射物理内存

    //准备调用 C 函数 arm64_boot_map
        //1.该函数任务是帮内核映射物理内存
        //先准备 5 个参数 x0-x4 寄存器保存函数参数
        /* void arm64_boot_map(pte_t* kernel_table0, vaddr_t vaddr, paddr_t paddr, size_t len, pte_t flags); */
        /* map a large run of physical memory at the base of the kernel's address space */
        mov     x0, page_table1
        mov     x1, KERNEL_ASPACE_BASE
        mov     x2, 0
        mov     x3, ARCH_PHYSMAP_SIZE
        movlit  x4, MMU_PTE_KERNEL_DATA_FLAGS
        //调用 arm64_boot_map *
        bl      arm64_boot_map
    

    映射内核运行内存

        //2.映射内核的地址
        /* map the kernel to a fixed address */
        /* note: mapping the kernel here with full rwx, this will get locked down later in vm initialization; */
        mov     x0, page_table1
        mov     x1, kernel_vaddr
        adr_global x2, __code_start
        adr_global x3, _end
        sub     x3, x3, x2
        mov     x4, MMU_PTE_KERNEL_RWX_FLAGS
        bl      arm64_boot_map
    

    通知页表配置完毕

        //标记页表已经设置完毕,通知其他 CPU 内核可以继续往下跑了
        adr_global tmp, page_tables_not_ready
        str     wzr, [tmp]
        //prime CPU 内核跳入 Lpage_tables_ready
        b       .Lpage_tables_ready
    

    准备打开 MMU

    打开 MMU 之前需要做一些配置

    清理垃圾数据

    需要重置一下 MMU 和 Cache 的状态以清除里面的残余数据,在进入 Kernel 代码之前,Bootloader 可能使用过 MMU 和 Cache,所以 ICache 和 TLB 中可能还有前面留下来的残余垃圾数据。

    //使 TLB 失效以清除数据
        /* Invalidate TLB */
        tlbi    vmalle1is
    

    初始化 Memory attributes 配置

    Memory attributes 简单来说就是将 Memory 加上了几种属性,每种属性都会影响 Memory 的读写策略。

    因为 Memory 读写策略是非常复杂的,比如一段内存区域指向的是一个 FIFO 设备,对内存的读写有严格的时序要求,则需要配置 Memory attributes 来禁止 CPU 读写重排,Cache 等等优化,因为这些对于这段 Memory 没有意义,还会影响数据的读写的正确性。

        movlit  tmp, MMU_MAIR_VAL
        msr     mair_el1, tmp
    
        /* Initialize TCR_EL1 */
        /* set cacheable attributes on translation walk */
        /* (SMP extensions) non-shareable, inner write-back write-allocate */
        movlit  tmp, MMU_TCR_FLAGS_IDENT
        msr     tcr_el1, tmp
    

    看一下 Zircon 的默认 Memory Attribute 配置:

    /* Default configuration for main kernel page table:
     *    - do cached translation walks
     */
    
    /* Device-nGnRnE memory */
    #define MMU_MAIR_ATTR0                  MMU_MAIR_ATTR(0, 0x00)
    #define MMU_PTE_ATTR_STRONGLY_ORDERED   MMU_PTE_ATTR_ATTR_INDEX(0)
    
    /* Device-nGnRE memory */
    #define MMU_MAIR_ATTR1                  MMU_MAIR_ATTR(1, 0x04)
    #define MMU_PTE_ATTR_DEVICE             MMU_PTE_ATTR_ATTR_INDEX(1)
    
    /* Normal Memory, Outer Write-back non-transient Read/Write allocate,
     * Inner Write-back non-transient Read/Write allocate
     */
    #define MMU_MAIR_ATTR2                  MMU_MAIR_ATTR(2, 0xff)
    #define MMU_PTE_ATTR_NORMAL_MEMORY      MMU_PTE_ATTR_ATTR_INDEX(2)
    
    /* Normal Memory, Inner/Outer uncached, Write Combined */
    #define MMU_MAIR_ATTR3                  MMU_MAIR_ATTR(3, 0x44)
    #define MMU_PTE_ATTR_NORMAL_UNCACHED    MMU_PTE_ATTR_ATTR_INDEX(3)
    
    #define MMU_MAIR_ATTR4                  (0)
    #define MMU_MAIR_ATTR5                  (0)
    #define MMU_MAIR_ATTR6                  (0)
    #define MMU_MAIR_ATTR7                  (0)
    
    #define MMU_MAIR_VAL                    (MMU_MAIR_ATTR0 | MMU_MAIR_ATTR1 | \
                                             MMU_MAIR_ATTR2 | MMU_MAIR_ATTR3 | \
                                             MMU_MAIR_ATTR4 | MMU_MAIR_ATTR5 | \
                                             MMU_MAIR_ATTR6 | MMU_MAIR_ATTR7 )
    
    #define MMU_TCR_IPS_DEFAULT MMU_TCR_IPS(2) /* TODO: read at runtime, or configure per platform */
    
    /* Enable cached page table walks:
     * inner/outer (IRGN/ORGN): write-back + write-allocate
     */
    #define MMU_TCR_FLAGS1 (MMU_TCR_TG1(MMU_TG1(MMU_KERNEL_PAGE_SIZE_SHIFT)) | \
                            MMU_TCR_SH1(MMU_SH_INNER_SHAREABLE) | \
                            MMU_TCR_ORGN1(MMU_RGN_WRITE_BACK_ALLOCATE) | \
                            MMU_TCR_IRGN1(MMU_RGN_WRITE_BACK_ALLOCATE) | \
                            MMU_TCR_T1SZ(64 - MMU_KERNEL_SIZE_SHIFT))
    #define MMU_TCR_FLAGS0 (MMU_TCR_TG0(MMU_TG0(MMU_USER_PAGE_SIZE_SHIFT)) | \
                            MMU_TCR_SH0(MMU_SH_INNER_SHAREABLE) | \
                            MMU_TCR_ORGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \
                            MMU_TCR_IRGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \
                            MMU_TCR_T0SZ(64 - MMU_USER_SIZE_SHIFT))
    #define MMU_TCR_FLAGS0_IDENT \
                           (MMU_TCR_TG0(MMU_TG0(MMU_IDENT_PAGE_SIZE_SHIFT)) | \
                            MMU_TCR_SH0(MMU_SH_INNER_SHAREABLE) | \
                            MMU_TCR_ORGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \
                            MMU_TCR_IRGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \
                            MMU_TCR_T0SZ(64 - MMU_IDENT_SIZE_SHIFT))
    #define MMU_TCR_FLAGS_IDENT (MMU_TCR_IPS_DEFAULT | MMU_TCR_FLAGS1 | MMU_TCR_FLAGS0_IDENT)
    

    打开 MMU

    这里打开 MMU 非常简单,打开之前,以上代码都在物理地址下运行,打开之后则在虚拟地址下运行了。

       //内存栅栏 
        isb
    
        //保存 EL1 状态的异常向量表
        /* Read SCTLR */
        mrs     tmp, sctlr_el1
    
        //打开 MMU
        /* Turn on the MMU */
        orr     tmp, tmp, #0x1
    
        //恢复 EL1 状态的异常向量表
        /* Write back SCTLR */
        msr     sctlr_el1, tmp
    

    这里注意备份还原异常向量表
    内存栅栏防止打开 MMU 前后代码乱序执行,造成逻辑错误

    准备跳入 C 世界

    重新设置栈指针

    在此之前,重新设置栈指针,因为此时已经变成虚拟地址

        //重新设置 prime CPU 的内核栈指针,因为现在 MMU 已经打开,需要使用虚拟地址
        // set up the boot stack for real
        adr_global tmp, boot_cpu_kstack_end
        mov     sp, tmp
    

    设置栈溢出异常

    配置 Stack Guard,其实就是在栈末尾设置一个页中断,如果程序读写到这里,代表栈溢出,触发异常。
    防止编译期间没有启用栈保护

    	//配置 Stack Guard,其实就是在栈末尾设置一个页中断,如果程序读写到这里,代表栈溢出,触发异常
        adr_global tmp, boot_cpu_fake_thread_pointer_location
        msr     tpidr_el1, tmp
        
       // set the per cpu pointer for cpu 0
        adr_global x18, arm64_percpu_array
    
        // Choose a good (ideally random) stack-guard value as early as possible.
        bl      choose_stack_guard
        mrs     tmp, tpidr_el1
        str     x0, [tmp, #ZX_TLS_STACK_GUARD_OFFSET]
        // Don't leak the value to other code.
        mov     x0, xzr
    

    其他 CPU 设置栈,初始化, 并且进入休眠

    .Lsecondary_boot:
    
        //配置其他 CPU 内核的栈指针
        bl      arm64_get_secondary_sp
        cbz     x0, .Lunsupported_cpu_trap
        mov     sp, x0
        msr     tpidr_el1, x1
    
        bl      arm64_secondary_entry
    
    .Lunsupported_cpu_trap:
        //其他 CPU 内核初始化完毕
        wfe
        b       .Lunsupported_cpu_trap
    

    prime 内核进入 C 世界

        //跳转到内核 C 代码入口
        bl  lk_main
        b   
    

    关于内核初始化后期

    这部分大部分在 C/C++ 中完成,下一篇分析。

    下一篇:https://blog.csdn.net/ganyao939543405/article/details/86220466

    展开全文
  • 云计算:利用叶子云桌面虚拟化管理平台搭建企业的桌面云,叶子云初始化(三)

    云计算:利用叶子云桌面虚拟化管理平台搭建企业的桌面云,叶子云初始化(三)

                                                              --企业轻松搭建kvm桌面云系列教程 

           当我们安装好叶子云虚拟化以后,我们就不需要在服务器上操作了。此时我们应该找一台pc机,在浏览器上进行后面的操作。需要注意的是,由于叶子云采用的是web管理的方式,其中也用到了html5的一些技术,而市场上的浏览器对html5的支持也不统一,所以叶子云在后台管理方面,强制要求使用chrome浏览器。如果你用别的浏览器访问,叶子云提供了chrome的本地下载地址。

           叶子云虚拟化服务器默认的IP地址为:192.168.0.100,  所以你可以在浏览器的地址栏中输入该 ip地址,进行远程访问。若你不能访问,请将本机ip的修改到192.168.0.x的网段,例如,将你的ip设置为:192.168.0.2


           叶子云虚拟化的初始化有2种方式,分别为:生产模式 、 测试模式。


    一、初始化测试模式:

           1.点击“测试模式”打开“初始化ip地址”页面,点击“编辑”链接进行ip地址的修改,建议大家不要用192.168.0.100的ip地址,因为如果你再安装一台服务器的话,也是这个默认ip地址,就会发生IP地址冲突。
             

        2. ip填写范例。
         
        3.点击 “下一步”,在新页面中设置后台超级管理员的账号与密码,注意,这是超级账号,如果这个密码丢了,后果很严重,找回来也十分的困难。伪nfs挂载名称,则是在服务器的第一块硬盘上创建一个目录,用来保存虚拟机所有的数据。之所以称之为伪nfs,是因为叶子云是基于NFS存储的虚拟化,为了提供方便,快捷的测试,所以才有了伪NFS。生产模式中不可以使用伪NFS模式。"允许前端会员将虚拟机状态保存到该存储" 意思是,当你需要保存虚拟机状态的时候,可以使用该存储。


    4. 点击“继续”按钮,系统开始初始化,这个过程大约需要2-10分钟左右,跟你的硬件与网络有关,如果初始化完毕,跳转到新的页面不能正常打开,请多刷新几次页面。页面初始化成功后,会转到登录页面,如下图。




    二、生产模式初始化。
            该模式为正式模式,必须拥有NFS 存储服务器才可以使用,建议购买专业的NFS 存储服务器,这样让你的数据更安全。


    1.点击 “生产模式”打开“初始化IP地址页面”。点击“编辑”链接进行ip地址的修改,建议大家不要用192.168.0.100的ip地址,因为如果你再安装一台服务器的话,也是这个默认ip地址,就会发生IP地址冲突。

      2. ip填写范例。


     3.点击 “下一步” 打开系统初始化页面。数据库初始化分三种情况,这个跟网络架构有关,叶子云的数据库是放在NFS共享存储上面的,多台服务器共用一个数据库,这样你就可以从任意一台服务器管理其他的服务器,避免了管理环节的单点故障。如果这是首台叶子云服务器,请选择“创建数据库”,如果网络中已有叶子云服务器,请选择“使用网络中已有的数据库”,如果你的服务器网络中已有叶子云服务器,但是都不能访问外网,请选择第三个选项。这里,大家可能觉得奇怪,系统是如何判断其他服务器的呢,别急,关键就是后面的选项“NFS 服务器Ip”,网络中的叶子云服务器是以为NFS共享存储为分组的。所以,当我们初始化的时候,无乱网络中是否有其他的叶子云服务器,都应该使用同一个NFS服务器上的同一个共享目录来进行初始化。后台超级账号用来管理整个叶子云虚拟化网络,非常重要,注意,这是超级账号,如果这个密码丢了,后果很严重,找回来也十分的困难。"允许前端会员将虚拟机状态保存到该存储" 意思是,当你需要保存虚拟机状态的时候,可以使用该存储。



    4.点击“继续”按钮,系统开始初始化,这个过程大约需要2-10分钟左右,跟你的硬件与网络有关,如果初始化完毕,跳转到新的页面不能正常打开,请多刷新几次页面。页面初始化成功后,会转到登录页面,如下图。

    如需试用,请到淘宝搜索:“叶子云 虚拟化” ,申请试用!

    展开全文
  • OGRE 初始化

    千次阅读 2012-12-28 17:10:54
    第四章 Ogre的初始化 读到这本书的这个章节的时候,相信你已经很好的了解了Ogre 3D的设计理念,以及其中各部分协同工作的方式。现在已经可以开始用Ogre API真正的写一个应用程序了。这个章节给你提供一个简单...
    第四章 Ogre的初始化

    读到这本书的这个章节的时候,相信你已经很好的了解了Ogre 3D的设计理念,以及其中各部分协同工作的方式。现在已经可以开始用Ogre API真正的写一个应用程序了。这个章节给你提供一个简单的开端,实现了一个Ogre API的外壳,这些内容也是任何Ogre应用程序所需要的基础。


    Ogre最有名的“卖点”之一,就是它所拥有的良好的适应性以及弹性。你可以根据自己希望控制的程度,选择对Ogre不同的控制强度。Ogre能很好的满足你所有不同的需要,是用Ogre就如同你有拥有了一台可以轻易切换手动档和自动档的豪华跑车一样。



    在这一章节中,我们会把所有Ogre的使用方法告诉你,从“自动档”一直到“手动档”。 不过在书中我只会把关键的代码段列出来。如果你需要这些代码的完整版本,可以到这本书的网站去下载,那些下载代码都是可以直接编译运行的。


    Ogre初始化:自动档

    使用Ogre的程序所需要作的第一件事情就是实例化一个Root对象。如果没有这个对象,你就无法调用(除了日志管理以外)的任何一个功能。Root类的构造函数接受一些符串对象的参数,这些字符代表着不同作用的文件名称。



    Root * root = new Root();

    Root * root = new Root(“plugins.cfg”); 

    Root * root = new Root(“plugins.cfg”, “ogre.cfg”);

    Root * root = new Root(“plugins.cfg”, “ogre.cfg”, “ogre.log”);

    Root * root = new Root(“”, “”);




    上面列出了一些不同的方法来创建Root实例,这里面任何的方法都能单独的正确执行。其中倒数第二行的方法所使用的参数也是系统所默认的值(“plugins.cfg”, “ogre.cfg”, “ogre.log”——当你没有填写参数的时候,系统就认为采用了默认的这些值)。



    plugins.cfg文件

    Ogre中所谓的插件就是符合Ogre插件接口的代码模块(在Windows下面是DLL文件,在Linux下是.so文件),比如场景管理(SceneManager)插件和渲染系统(RenderSystem)插件等。在启动的Ogre时候,他会载入plugins.cfg配置文件来查看有哪些插件可以被使用。代码4-1展示了配置文件的内容。



    代码4-1:上面是在Ogre源代码和发行版中提供的“plugins.cfg”文件的内容

    # Defines plugins to load



    # Define plugin folder

    PluginFolder=.



    # Define plugins

    Plugin=RenderSystem_Direct3D9

    Plugin=RenderSystem_GL

    Plugin=Plugin_ParticleFX

    Plugin=Plugin_BSPSceneManager

    Plugin=Plugin_OctreeSceneManager

    Plugin=Plugin_CgProgramManager



    代码4-1列出了Ogre演示程序中所使用的plugins.cfg文件内容。你可以通过改变这个文件的内容来达到配置自己程序中使用的插件的目的。当然,没必要非得是用 “plugins.cfg”作为文件名;可以根据自己的喜欢随意取名,然后在构建Root实例的时候作为参数传递给系统就好了。当没有配置这个参数的时候程序就会采用默认的“plugins.cfg”文件来配置插件。如果你用了一个空的字符串来作为文件名(在之前的构建Root的例子中有这种使用方法),Root实例就不会载入任何文件来索引插件,这时候你就需要自己手动的方式来配置插件了(在后面的章节“Ogre初始化:手动档”中会介绍具体的方法)。



    在上面的提供的配置文件中:PluginFolder标签的值告诉了Ogre到哪个目录下找下面所使用得的插件。如何配置这个值完全取决于你的需要:你可以配置绝对路径或者相对路径(也就是不用“\”或“/”作为开始的路径)。在使用相对路径的时候,系统会以当前目录作为参照目录(一般情况下是程序的执行目录,不过在Windows调试的时候可能是在.vcproj文件所在的目录)。在演示程序中配置了在当前目录(“.”)中寻找插件。值得注意的是,不论你是否设置了这个值,Ogre程序都会在搜索路径后面自动加上“\”或“/”字符(取决于不同的平台)。这就是说如果你把空白字符(””)作为设置的值的时候,系统就会在“\”或“/”目录下来搜索插件(代表着:在Windows下面搜索当前驱动器的根目录,在Linux下搜索root目录)。如果你没有填写这项声明(或者使用#字符注释掉这一行),Ogre就当作你输入了空白字符。一般情况下Ogre所有的插件载入行为都会以这个路径作为标准,但有一种例外情况,就是在Mac的OS X系统上,这一项设置因为系统的关系而被忽略,取而代之的是在OS X自己提供的Resources/目录中搜索插件。



    在配置文件的剩余部分,列出了提供给Ogre载入的具体插件。值得注意的是,你会发现文件名称中并不包含扩展名;不要以为这是疏忽,Ogre开发者通过这种手段来兼容各种平台上扩展名的差异(比如.dll和.so等等)。



    警告:可能你也注意到了在“=”的两边并没有写入空位符号(比如空格或者Tab),因为在这里如果填入空位的话就会被系统认为是语法错误,导致无法载入插件。



    在所列出的插件中,最上面两行列出了两种不同的渲染系统以供选择;在后面的分别是特效系统和场景管理插件等等。在你自己的程序中,虽然没必要载入所有插件,但至少要包含一个渲染系统插件系统才能正常工作。但是如果真的光使用渲染系统插件也不是一个很好的决定,因为这样只能实现一个简单的场景(极其简陋!)。所以至少还要加上一个场景管理(例如OctreeSceneManager)的才能算真正的开始。



    Ogre.cfg文件

    在启动Ogre的时候,引擎提供了一个简单的图形界面,可以通过它来配置基本的渲染属性。如图4-1.




    图4-1:Ogre在Windows32系统下面的启动配置画面



    看到了上面的图片,可能你会兴奋得说:“真棒!Ogre免费的送了我一个配置窗口!”不过不要高兴得太早了,提醒你上面的是Ogre的配置窗口,换句话说它也只是Ogre的配置窗口而已,没有提供输入系统或者音频系统等等的配置选项(似乎除了改变上面那个兽头的Logo之外你什么也改变不了)。除非你决定程序中只采用Ogre来实现。否则你就一定需要定制自己的配置菜单和相应的模块。在后面的段落中会提供一些相关实现的细节。



    不过,在这里我们还是要学会如何启动显示这个简单的窗口,用来配置Ogre。



    Root * root = new Root();

    bool rtn = root->showConfigDialog();




    上面列出了所有需要的代码。其中showConfigDialog()返回一个布尔值,可以用来确定用户是点击了“OK(确定)”还是“Cancel(取消)”:true代表“OK”,false代表“Cancel”。如果你的用户选择了“Cancel”按键,就意味着应该结束你的程序运行了。使用了Ogre的配置对话框,用户就可以根据自己的需要来设置具体的渲染系统参数了。



    你可能会奇怪我们上面所说的对话框和Ogre.cfg文件有什么关系呢?事实上,在大多数情况下Ogre.cfg文件是当用户设置完渲染系统之后自动产生,虽然你手动填写这个文件也没什么问题,不过似乎没有这个必要。在后面你会看到当我们通过手动来配置Ogre初始化的时候,并不需要这个文件。不过现在还是让我们先来看看这个文件里面的内容吧(代码4-2)。

    代码4-2:Ogre.cfg文件的内容



    Render System=Direct3D9 Rendering Subsystem



    [Direct3D9 Rendering Subsystem]

    Allow NVPerfHUD=No

    Anti aliasing=None

    Floating-point mode=Fastest

    Full Screen=No

    Rendering Device=NVIDIA GeForce Go 7300

    VSync=No

    Video Mode=800 x 600 @ 32-bit colour



    [OpenGL Rendering Subsystem]

    Colour Depth=32

    Display Frequency=60

    FSAA=0

    Full Screen=Yes

    RTT Preferred Mode=FBO

    VSync=No

    Video Mode=1024 x 768






    你会发现上面所列出来的条目和对话框中设置选项中的条目是对应的。如果你真的决定使用Ogre.cfg来配置你的程序,当需要改变这个文件内容的时候,你最好调出Ogre配置窗口来重新生成。这个文件虽然看起来很容易被手动修改,但是也很容易导致程序无法识别。因为它虽然看起来似乎直观且可读,但事实上它仍是用于机器识别的脚本语言。



    Ogre也提供了直接载入Ogre.cfg文件来配置程序的方法:



    if(!root->restoreConfig())

    root->showConfigDialog();




    上面代码中是提供了载入Ogre.cfg文件常用做法。如果restoreConfig()调用失败,也就是说没有一个合法的Ogre.cfg文件存在的时候,程序会提供一个配置窗口用于初始化(当你点击OK之后会帮你把新生成的Ogre.cfg储存)。通过这种方法,你可以保证在任何情况下都能正确运行你的程序。



    通过使用Root对象的通过使用saveConfig()方法,你也可以在任何时候保存当前状态到Ogre.cfg文件(或者是其他在你构建Root实例时候提供给系统的文件)。



    root->saveConfig();






    Ogre.log文件

    Ogre提供了日志管理对象作为记录系统诊断和异常信息的日志工具。当用户程序出现问题的时候,开发者可以通过查询日志信息了解具体情况,而没有必要去询问用户技术细节和程序的具体设置。在日志的输出中包括以Ogre为基础的程序的所有事件、初始值、状态以及性能信息等。默认的情况下这些信息被输出到磁盘上;你可以改变输出文件的名字,但是不能使用一个空值来作为Root构造函数的第三个参数(除非你在构建Root实例之前,已经通过直接调用LogMananger::createLog()来构建日志系统,下面代码4-3会提供方法),你可以提供任意文件作为日志输出。



    代码4-3: 使用手动方法来建立一个日志管理器实例

    //首先通过LogManager::getSingleton()建立一个日志管理器实例。

    LogManager * logMgr = new LogManager;

    Log * log = LogManager::getSingletonn().createLog(“mylog.log”, true, true, false);



    //在我们已经建立了日志管理器之后,我们就不需要设置第三个参数了。

    Root * root = new Root(“”,””);




    上面代码中所展示的方法,可以帮助你在构造日志的时候定制一些除了文件名之外具体信息。这里通过createLog产生了一个系统默认情况下使用的日志对象(Log)。在createLog方法中,第一个参数指定了Ogre的日志输出到“mylog.log”文件;第二个参数告诉了Ogre是否把当前日志作为系统默认的日志来处理;第三个参数表明是否把记录到日志中的信息是否同样的输出到std::cerr(标准错误流)中去;第四个参数用来告诉系统是否真的要把信息输出到文件中(也就是说当为true的时候,并没有真的向日志文件输出信息)。在设置新的日志文件之前,这个日志对象一直作为系统默认的日志输出文件。(也就是说可以通过Root的构造函数或日志管理器设置新的日志文件)。



    虽然Ogre的日志文件管理并不能直接支持流(Stream)的可变的操作方式,不过Ogre提供了日志监听(Log Listener)方法来监听日志管理器(LogManager),这可以帮助你用自己喜欢的方法来处理日志信息。如果你不希望日志输出到文件中,你也可以控制日志禁止输出。不过你仍然要提供一个默认的输出文件名给Ogre系统(虽然这听起来似乎有些莫名其妙)。



    注意:我可以在里告诉你一种方法完全禁止日志文件的存在,在上面的代码中,只要简单的在构建LogManager实例之后,并不调用createLog()方法来设置日志信息,而直接构建Root实例,就可以达到禁止日志文件的目的。因为没有一个默认给日志信息输出的文件,这些信息就会被丢失。看起来这是一个很不错的办法,不过事实上这并不明智,毕竟这样当程序出错的时候,做就难以找到程序运行中的线索了。当然,你可以让用户通过命令行的方式来设置是否使用日志文件。除非你有绝对充分地理由,否则不要禁止使用日志文件!






    代码4-4展示了截获日志信息的方法,并允许你在回调方法(write)里面填入适合你程序的日志处理代码。



    代码4-4: 截获Ogre日志

    class MyLogListener : public LogListener

    {

    public:

    void write(const String& name, const String& message,

    LogMessageLevel level, bool maskDebug)

    {

    //日志信息,被截获在这里处理。

    }

    };



    MyLogListener * myListener = new MyLogListener;



    //在构建LogManager之后

    //调用LogManager::getSingletonPtr()就能返回这个实例的指针

    LogManager * logMgr = new LogManager;



    logMgr->addLisener(myListener);

    logMgr->createLog(“mylog.log”, true, false, true);

    logMgr->setLogDetail(LL_NORMAL);



    Root * root = new Root(“”, “”);


    你可能发现,代码里改变了createLog()方法的几个参数。其中最后一个参数被设置为true;这样就能禁止日志信息写入文件,转而让你可以通过喜欢的方法来手动处理这些日志数据(比如显示在调试窗口,或者通过网络提供给远程主机等等)。同时我们也把三个参数设置为false,关掉了到std::cerr的输出,这样用户就得不到任何日志信息,取而代之的是你必须在write()方法中处理这些得到的数据。我们在这里把日志的关注细节的等级设置为“正常(LL_NORMAL)”;你也可以设置为LL_LOW(非常低的细节,记录重要的信息)或者LL_BOREME(记录所有信息,只有当需要细微的调试的时候可能才会需要)。


    渲染窗口(Render Window)

    在系统被决定使用那种渲染系统之后(在我们的例子中,就是当用户在配置对话框中选择并配置渲染系统,然后点击OK之后),你可以调用Root对象的initialise()方法来进行系统初始化。



    root->initialise(true, ”My Render Window”);

    RenderWindow * window =root->getAutoCreatedWindow ();




    这里提供了两行程序代码,其中第一行用来完成了Root的初始化,并建立了相应的渲染窗口(Render Window)。初始化的参数来源于用户在配置对话框中的选择。initialise方法的第一个参数告知Ogre系统是否自动建立一个渲染窗口来给用户使用。在我们这里选择了简单的方法,让Ogre给我们提供渲染窗口。第二个参数并且把“My Render Window”作为程序窗口的标题。在这里如果没有提供你自己的窗口标题,那程序就会使用默认的“OGRE Render Window”。之后的第二行代码可以用来得到自动创建的渲染窗口实例的指针。



    Ogre渲染窗口的概念是唯一可以被系统用来渲染场景的地方。就如同现实世界中的画布一样,Ogre把你程序的内容画到它的表面。为了实现这个目的,Ogre至少需要一个摄影机(Camera)来“拍摄”你的场景,同时也需要一个或几个视口(Viewport),用于存放摄影机的拍摄下来的“胶片”(和渲染窗口的功能有些类似)。



    在之后的“场景管理(Scene Manager)”章节中你会了解更多的关于场景管理器的细节,不过对于现在而言,你只要简单的知道一些简单的场景管理API就足够了。可以把场景管理器想象为一个生产在你场景中不同对象实体的“工厂”。当然,这里所说的对象实体也包括摄像机。通过简单调用SceneManager的createCamera()方法,就能得到一个新的摄像机实例(比在现实生活中达到相同的目的简单的多)。之后你就可以使用这台摄像机来拍摄渲染你的场景了。



    Camera * cam = sceneMgr ->createCamera(“MainCamera”);

    cam->setNearClipDistance(5);

    cam->setFarClipDistance(1000);

    cam->setAspectRatio(Real(1.333333));




    在上面的代码中,假设sceneMgr是一个指向已存在SceneManager(场景管理器)实例的指针。后面是Camera(摄像机)实例使用自身的方法来摄像机的细节。



    在这段代码中,展示了应用程序对摄像机(Camera)最少的设置(也就是必要的设置)。通过使用setAspectRatio方法把屏幕的纵宽比设置成为4:3(这是绝大多数CTR显示器和非宽平液晶显示器的比例,在这里我们设置了参数1.333333,因为这个值是4/3的近似值),这个值可以在任何时候改变和重置。当我们在创建视口(Viewport)的时候经常也会采用相同的纵宽比。



    通过setNearClipDistance方法和setFarClipDistance方法,我们也同时设置了摄像机的近截面和远截面的距离。在这里给我的室外场景设置了标准的从5到1000单位的距离,你也可以设置任何你喜欢的距离,只要保证近截面和远截面之间距离等于或者小于1000就可以。



    提示:这里有一个非常普遍的误解——可以通过减少可视区域的距离就能很容易的减少渲染物体数量,进而加快渲染速度。可视区域距离的确是可以影响渲染速度(虽然现在有很多显示卡支持无限远的距离)。设置显示区域最直接的办法就是改变深度缓存的精度(depth buffer resolution),因为深度缓存精度直接决定了摄像机近截面和远截面之间距离,但是如果也能因为这个精度太过于粗糙,而导致“深度冲突(depth fighting)”现象。当GPU在对物体进行深度排序算法的时候,如果无法辨认是否一个物体在另外一个物体前面,就会产生所谓的“深度冲突”,这种情况多是因为你有很多物体互相穿过对方而引起的。当深度缓存的精度太小,无法确切区分一些物体深度的差别,就会认为它们在同一深度。解决这种问题的唯一方法就是加大深度缓存的精度,最有效的办法也就是把近截面拉近摄像机方向(当然你也可以把远截面放到更远的地方,不过效果不如改变近截面明显)。去英文网上查询一下“depth fighting”关键词,可能有助于你更深入的理解这个问题。 




    在这章节后面,我们将要介绍如何使用一些高级的方法来控制摄像机(Camera),但现在我们首先要知道如何在渲染窗口中创建一个视口“Viewport”。



    Viewport * vp = window->addViewport(camera);

    vp->setBackgroundColour(ColourValue(0, 0, 0));



    这里的代码使用了在我们通过前面我们初始化Root时候创建的渲染窗口,通过指向它的window指针创建了一个视口对象的实例,同时我们也设置了视口(viewport)的背景颜色。


    渲染循环(Render Loop)

    如果已经决定开始渲染你的场景,那么调用Root对象的srartRendering()方法是最简单的手段。



    root->startRendering();




    在调用之后,Ogre就会不断地渲染在你场景中所有能被渲染的东西。你可以通过关闭渲染窗口来结束这个过程(比如单击窗口右上角的x图标,或者在Windows任务栏中右键菜单中选择关闭)。当你在注册了帧监听(Frame Listener,之后会介绍)对象,然后在回调结束后返回一个false值给系统,同样也能结束程序渲染。作为备用,Ogre还提供了一个可以在程序的任何地方调用的方法Root::getSingleton().queueEndRendering()来立刻结束程序。不过在使用startRendering()进行渲染的时候,最常用的办法还是在帧监听中返回false。



    帧监听(Frame listener)

    如果决定使用startRender()来开始你的渲染过程,你就只能通过使用帧监听对象来在渲染循环中插入你自己的代码了。所谓帧监听对象就是一个符合FrameListener接口的类的实例。当Ogre渲染每一帧的开始和结束的时候会回调FrameListener接口的方法(参考代码4-5)。



    代码4-5:建立并挂载一个帧监听对象到Ogre的Root中

    class MyFrameListener : public FrameListener{

    public:

    bool frameStarted (const FrameEvent &evt);

    bool frameEnded (const FrameEvent &evt );

    };



    bool MyFrameListener::frameStarted (const FrameEvent &evt){

    //在每一帧画面渲染前,调用这里你写好的代码

    return true;

    }



    bool myFrameListener::frameEnded (const FrameEvent &evt ){

    //在每一帧画面渲染后,调用这里你写好的代码

    return true;

    }





    Root * root = new Root();

    MyFrameListener myListener;



    //在这里你需要在调用startRendering()方法前,注册你的帧监听对象!!!

    root->addFrameListener(myListener);

    root->startRendering();






    当程序执行的时候,会在每一次进入Ogre渲染管线之前调用你定义的frameStarted()方法。相对而言,frameEnded方法并不常用,因为当Ogre完成一次渲染工作之后才会调用它,所以一般而言只有在你需要在每一帧结束时后程序做清理工作时候有用。



    在通常的情况来说,在每次渲染的空隙你都要去处理HID(用户输入接口,比如键盘、鼠标或者操纵杆)。通过分析接收到的事件,你就可以让模型移动或者旋转,也可以让摄像机移动,更或者让NPC的魔法师吟唱一段能让玩家角色睡着的咒语,或者任何需要的事情。但不论是什么么事情,都发生在系统调用帧监听的方法的时候——可能只是frameStarted(),因为我觉得大多数人更喜欢在产生画面前改变游戏状态,而不是马后炮。



    在这本书的支持网站上,你可以下载到这本书相关的代码;其中你可以在CH04解决方案中找到QuakeStart工程;我把上面这些代码放到这个可以工作的应用程序中,你可以从中学到更多的细节。不过其中只有最基本的功能,我简单的让帧监听在启动后15秒钟就结束程序。的确是无聊的程序,不过总算还能帮助你了解Ogre环境最基本的配置。

    Ogre初始化:手动档

    现在到了另一个阶段,我将要告诉你所有设置Ogre应用程序的步骤,而不是简单得调用自动处理的方法,你可以在这里了解“幕后”所发生的一切。同时你也将要学会如何使用自己的主循环来处调用Ogre的帧渲染。



    Root

    在前面的章节中,你了解了构造Root对象实例的所有方法,按照你的喜欢填入配置文件、插件管理文件和日志文件。你也了解了如何通过简单的方法构建Ogre的渲染窗口。所有的这些功能都是靠调用Root中的高级方法来实现的,在接下来我们手动配置的过程中,你会了解到所有具体的细节。



    “手动”似乎意味着你将要控制Ogre初始化过程中所有的细节。但事实上并没有必须这么做的理由;你可以手动的设置渲染系统和插件(你可以在游戏中提供一组GUI来配置),但仍然使用前面提到的自动创建的窗口和自动循环来做渲染。不过在这个章节中,你仍会看到如何全部手工配置,以及了解其中不同设置的功效。



    Plugins.cfg

    载入插件应该是Ogre初始化中最简单的步骤,所以我们将要从这里开始。Root提供了两种方法来处理手动载入插件。



    void loadPlugin(const String& pluginName);

    void unloadPlugin(const String& pluginName);




    上面第一个方法是用来通过名称载入相应插件的。第二个方法是被用来卸载插件。这两种方法所用到的共同参数,就是插件文件的名称,我们可以使用类似“Plugin_ParticleFX”这种字符串来载入相应的插件。你可能注意到了我们有意忽略了插件文件的扩展名,这是Ogre为了“忽略”各种平台之间文件的差异(Windows下的.dll、Mac OS X和Linux下的.so)。当然这并不意味着你会使用一个没有扩展名的插件。在不同平台上,插件仍然有不同的扩展名(Ogre并不是去掉插件的扩展名,而是在你所提供的文件名字上加上执行平台上所需的扩展名)。简单一句话:让自己轻松,让Ogre去做琐碎的“家务”。



    然后开始通过相应平台的规则在目录中寻找插件,Ogre是通过分别调用不同平台的API来实现动态库的载入的(在Windows下面使用的是LoadLibrary(),在Mac OS X和Linux下面使用dlopen())。因此,按照系统平台的差别,程序会首先在包含执行程序的目录下面寻找插件,然后如果是Windows平台就会寻找PATH系统环境变量所指的目录下面,在Linux下面就相应的搜索LD_LIBRARY_PATH环境变量。



    一般来说,Ogre同一个插件的Debug版本和Release版本使用了相同的名字;这可能很容易让你混淆它们。如果你真的搞错了的话,臭名昭著的“ms_singleton”断言就会跳出来让你的程序死掉。



    提示:“ms_singleton”断言经常会出现在你的应用程序试图载入一个相同的动态连接库的Debug和Release版本的时候。这是Ogre采用单件模式所必然导致的结果。Ogre在Root对象在其实例中构建所有遵守单件模式的“唯一”类型的实例,并且允许通过相应的静态方法来操作这些“单件”。一般情况下这些都能工作的很好,但是如果你的程序运行时载入了一个“不匹配”的动态连接库时就会出错。大多是因为插件不匹配的原因(Debug插件插入了Release的程序,或者反之)。当插件进入了不匹配的程序中的时候,它需要Ogre库的一些支持,但事实上它并不知道自己进入了“不匹配”的程序,仍然会索取对于程序“不匹配”的对象,当程序企图提供不匹配的对象的时候,因为之前已经构建了一个实例,这时候单件模式就会发现违反了“唯一性”。最后“当”!……弹出了断言窗口。






    解决Debug与Release插件之间的冲突问题,最简单的办法就是使用Ogre的命名规范,把插件重新命名,就如同OgreMain_d.dll一样,把调试的Debug插件名称后面增加“_d”后缀。然后在你的程序中使用#if defined标记来区分它们,正如下面这样:



    #if defined(_DEGUG)

    root->loadPlugin(“Plugin_ParticleFX_d”);

    #else 

    root->loadPlugin(“Plugin_ParticleFX”);

    #endif




    很值得用这一点点精力去避免那些让人烦到脱发的插件“ms_singleton”断言问题。当然了,如果你决定永远不使用Debug模式来构建你的程序,你同样可以避免这个问题。不过,这也意味着你永远不会调试你的程序,节省这小小的精力的代价是——为了解决程序问题,要花费大量时间去阅读你的代码(这时候你可能迫切需要使用《星机迷航》中瓦肯人的心灵融合[1]能力来解决问题了)。所以奉劝你,还是回来使用Debug模式吧。



    基本上你是用不到卸载插件的方法的,因为Root在清理自己的时候,总会把插件合理的释放掉。当然,有时候你会希望在程序结束前尽早释放你不需要的插件,不过相信我,删除Root时候程序自动做的插件卸载工作,已经足够好用了。



    Ogre程序已经给你带来了以下的插件:



    ·Plugin_OctreeSceneManager:以八叉树空间管理为基础的OctreeSceneManager(八叉树场景管理器——OSM)。同时包含了从其中派生出来的TerrainSceneManager(地形场景管理器),用来处理从高度图(heightMapped)派生出来的地形场景。



    ·Plugin_BSPSceneManager:提供一个对BSP场景的管理系统,用来读取和处理来自雷神之锤III中的地图文件。不过在今天看来这已经是一个古老的地图格式,并且已经没有人再维护和支持它了(提供这个插件的唯一原因是为了某个演示程序的执行)。



    ·Plugin_CgProgramManager:这个插件负责载入、分析、编译并且管理Cg语言所写的GPU渲染程序。在今天看来,似乎Cg逐渐被当今技术所抛离(它只能支持3.0版本以前的profiles),因此其价值也越来越小;幸好Ogre在其内部同时支持HLSL和GLSL程序的GPU开发。



    ·Plugin_ParticleFX:粒子系统管理插件;提供了很多粒子的效果器(Affector)和发射器(Emitter),用来实现一些基本的粒子特效。



    ·RenderSystem_Direct3D9:Windows上面对Direct3D 9的抽象层实现。



    ·RenderSystem_GL:针对所有平台上OpenGL的抽象层实现。



    我们将要在后面的场景管理章节中学习到关于OctreeSceneManager得更多内容,也会在更后面的章节中介绍如何控制ParticleFX的高级特性。就像上面我们提到的,这两个插件相对而言更有实际意义(虽然其他的插件也可以良好的工作),因此我们不会在这本书中花精力研究其他插件。

    在插件列表中,你也可以发现一些渲染系统(Render Systems)的插件。当然,他们也是被“插入”到系统中的。在Ogre的设计中,这些图形API被抽象成“可以插入的”,Ogre通过和处理其他插件同样的方法来处理这些系统。



    渲染系统(Render Systems)

    Ogre系统需要一个渲染系统来进行我们希望的工作。你可以使用插件管理的loadPlugin()方法来载入所需的API:



    //建立一个没有配置文件的Root实例

    Root *root = new Root(“”, “”);



    root->loadPlugin(“RenderSystem_Direct3D9”);

    root->loadPlugin(“RenderSystem_GL”);




    上面的代码让两种渲染系统都可以被Ogre使用(你要确定之前已经安装和配置了硬件的驱动)。Root类也提供了用来确认那些API可以被使用的getAvailableRenderers()方法。



    RenderSystemList* getAvailableRenderers();




    Root同时也提供了一些用于设置和得到已经载入渲染系统的方法。



    void addRenderSystem(RenderSystem* naeRend);

    RenderSystem* getRenderSystemByName(const String& name);

    void setRenderSystem(RenderSystem* system);

    RenderSystem* getRenderSystem(void);




    基本上你可能只会用到getAvailableRenderers()和setRenderSystem ()两个方法来检查设置Ogre所使用的渲染系统,更高级的做法是通过用户的选择来决定使用哪个(参考代码4-6)。



    代码4-6:设置Ogre应用程序使用的渲染系统

    //RenderSystemList是std::vector类型

    RenderSystemList *rList = root->getAvailableRenderers();

    RenderSystemList::iterator it = rList->begin();



    while(it != rList->end()){



    //Ogre的字符串类型String是std::string的扩展

    RenderSystem *rSys = *(it++);

    if(rSys->getName().find(“OpenGL”)){



    //把OpenGL渲染系统设置为我们使用的渲染系统

    root->setRenderSystem(rSys);

    break;

    }

    }

    //注意,如果系统没有支持OpenGL的话,我们就没有设置任何渲染系统!这将会

    //引起一个Ogre设置期间的异常产生。






    上面的代码展示了如何从可用渲染系统列表中找出OpenGL渲染系统,并将其设置导入系统中去。就如同代码中最后两行的注释所说的,如果我们找不到OpenGL渲染系统,就无法设置渲染系统到程序中,这导致调用initialise()方法的时候程序会抛出异常。因此我们必须找到解决这个问题的方法:比如,你可以把可用的系统列表在配置程序的GUI界面提供下拉菜单中给用户选择,然后再根据选择的结果来具体配置使用的渲染系统。这就既能保证渲染系统是从getAvailableRenderers()方法是从中得到的,也可以确定在setRenderSystem()设置了用户选择的渲染系统。



    addRenderSystem()是提供给插件初始化时调用的方法,除非你要通过非插件的方式来设置渲染系统(比如在程序中定制自己的载入方法),否则基本不会用到这方法。getRenderSystemByName()经常被用来通过名称来直接索引得到渲染系统,如果没有所需要的渲染系统就会返回空指针(在这里要注意插件名称的拼写和大小写)。在我们现在所展示的程序中,你可以通过“Direct3D9 Rendering Subsystem”和“OpenGL Rendering Subsystem”来分别得到我们所使用的两个渲染系统。最后,getRenderSystem()方法可以返回当前所使用的渲染系统,如果当前没有使用任何系统就会返回一个空指针。



    渲染窗口(Render Windows)

    如果你希望自己管理渲染窗口的构造参数和指针的时候,你就应该手动的创建应用程序中使用的渲染窗口。这样做的目的可能是:当你希望把渲染窗口融入到一个其他的工具或者系统中(比如Qt或者wxWidges等跨平台库,甚至MFC中),你就需要自己来设置相应的参数(如果你要制作一个关卡或者地图编辑器,所做的这些工作就是必要的)。或者你也可能想要在用户已经设置窗口的属性之后,改变或者增加使用的参数。



    当使用手动方式创建渲染窗口的时候,需要注意到是通过RenderSystem类而不是Root类的方法来创建。因此,当你得到RenderSystem有效的实例之后,你就可以创建一个或者更多的渲染窗口来使用。就好像在前面“Ogre初始化:自动档”中所提及的一样,构建视口(viewport)需要通过RenderWindow的方法一样,所以当你已经自己建立了一个渲染窗口,就可以在Root初始化前构建视口。Root在初始化过程中会进行插件的初始化工作,所以如果你希望初始化插件和初始化渲染窗口有一定次序,你就可以在这里调整(不过并不建议你这么做,毕竟可能有一些对象初始化顺序是互相依赖的)。



    代码4-7:手动启动Ogre应用程序

    #include “Ogre.h”



    //建立一个没有配置文件的Root实例

    Root *root = new Root(“”, ””);



    //载入渲染系统插件

    root->loadPlugin(“RenderSystem_Direct3D9”);

    root->loadPlugin(“RenderSystem_GL”);



    //在这里我们伪装成用户已经选择了OpenGL渲染器

    String rName(“OpenGL Render Subsystem”);

    RenderSystemList * rList = root->getAvailableRenderers();

    RenderSystemList::iterator it = rList->begin();

    RenderSystem *rSys = 0;



    while(ii != rList->end()){

    rSys = * (it++);

    if(rSys->getName() == rName){ 

    //设置渲染器,并结束循环

    root->setRenderSystem(rSys);

    break;

    }

    }



    //如果没有找到一个可用的OpenGL渲染器,就在这里结束程序。

    if(root->getRenderSystem() == NULL){



    delete root;

    return -1;

    }



    //root初始化的时候,我们可以传入一个false值来告知Root不用给我们创建渲染窗口。

    root->initialise(false);



    //在这里我们仍然使用默认的参数来创建渲染窗口

    RenderWindow *window = rSys->createRenderWindow(

    “Manual Ogre Window”, //窗口的名字

    800, //窗口的宽度(像素)

    600, //窗口的高度(像素)

    false, //是否全屏显示

    0); //其他参数,使用默认值



    //在这之后你就可以向之前所说的一样创摄像机和视口了。




    在上面的代码中没有做任何值得让人觉得惊奇的事情;我们假设已经从其他的代码片断中(比如游戏的显示控制)得到了参数来构建渲染窗口。如果我们不在参数列表(构造函数最后一个参数)中做特殊设置,那么窗口名称和窗口标题(等同于窗口的标题栏和系统的组件栏)为相同字符串。



    注意:RenderWindow(渲染窗口)对象是对RenderTarget(渲染目标)接口的一个实现,它被接口抽象成一个渲染表面(Rendering Surface)。在我们在我们要把场景渲染到没有帧缓冲(non-frame-buffer)的贴图之类的渲染目标的时,这种抽象就显得非常有用。所有的RenderTarget实例都可以通过名称到相应的工厂方法(Ogre使用的一种设计模式)中来构造,在我们上面调用createRenderWindow()方法的第一个参数就是我们需要的名称。




    在上面的例子createRenderWindow()方法中,所有参数我们都是用了最常用的设置,你可以通过线上API手册或者直接去看OgreRenderSystem.h文件去了解参数每一项的具体含义(在这里我建议你先去看看,因为参数中的很多都经常被改变。我在这里介绍了的一些,可能在不久也会过时。)



    如果你希望让渲染窗口显示在屏幕的左上角,或者想要让渲染窗口的名称和渲染窗口的标题是用不一样的字符串。那么你就需要用到NameValuePairList类(参数列表)的支持,其实这不过是一个标准模板库(STL)的map对象,被createRenderWindow方法作为最后一个参数传入,你只要把你希望改变的属性写在这个map中,系统就把会这些设置过滤出来,其他你没有填入的选项仍采用默认设置(参考代码4-7)。



    NameValuePairList params;

    params[“left”] = “0”;

    params[“top”] = “0”;

    params[“title”] = “Alternate Window Title”;



    RenderWindow *window = rSys->createRenderWindow(

    “MainWindow”, //渲染目标的名字

    800, //窗口的宽度(像素)

    600, //窗口的高度(像素)

    false, //是否全屏显示

    ?ms); //其他参数,这次我们在上面已经设置了




    上面的代码创建了一个在屏幕的左上角现实的窗口,名称为“MainWindow”而标题是“Alternate Window Title”。



    到现在为止,还没有很完美的方法来重新设置渲染窗口或者渲染系统。举例来说,如果你希望在程序运行的时候从Direct3D渲染系统转换到OpenGL系统,就只能关闭当前渲染系统,然后初始化一个新的OpenGL的渲染系统。虽然你也可以对渲染窗口做一些简单的改变,比如改变大小(宽度和高度),移动它在屏幕中的位置。但是如果要改变一些复杂的属性,比如全屏反锯齿效果,就需要释放这个窗口再去创建新的。



    正如同我们之前提到的,有些时候你可能需要多个渲染窗口同时运行。在类似关卡编辑器中经常会出现这种情况,提供不同的显示区域来展示你的场景。这和简单的使用多个视口来渲染不一样,可以让每个视口充满整个上层Windows。



    你也可以把Ogre的渲染窗口插入到一些窗口系统或者组件系统中来(比如Qt或者wxWidgets)。通过RenderWindow的getCustomAttribute()方法来得到当前渲染窗口在系统中的句柄。相应的,你也可以让Ogre使用你提供给它的窗口来作为渲染的父窗口。如下边的代码:



    //hWnd是一个Win32系统中存在的窗口的句柄。

    //渲染系统的指针所指的是一个初始化过的D3D9RenderSystem的实例。



    NameValuePairList opts;

    opts[“parentWindowHandle”] = StringConverter::toString(hWnd);



    //everything but “opts” is somewhat irrelevant in the context of an

    //explicitly parented window

    RenderWindow *window = RenderSystem->createRenderWindow(

    “WindowName”,

    800, 600,

    false, &opts);




    使用上面的代码,允许让你把Ogre渲染窗口插入到一个已经存在的窗口系统中。但同时要注意一些事情,Ogre的窗口消息处理函数在这里被忽略了,因此你要手动处理Ogre相关的消息,比如当用户点击关闭按键时候,清理Ogre渲染窗口。



    摄像机和场景管理(Camera and SceneManager)

    在这里为了避免过多地介绍这两个对象,我会尽量只介绍那些能足够展示它们功能的方法。尽管如此,还是希望你能了解,是这两个类渲染了你的场景。



    场景管理器(SceneManager)

    我们会在下一个章节中详细介绍这个类,所以我尽量避免在这里过多的深入。尽管如此,为了目前章节的完整性,我还是需要讲一下最基础的知识,来帮助你建立一个应用程序所使用的场景管理系。



    在你是用场景管理器之前,你首先需要构建一个相应的实例。



    SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC, “MySceneManager”);




    在我们之前提到的插件里面,其中有一类是场景管理器的构造器,当Ogre载入它们时,这些构造器就把自己注册到系统中,并同时绑定到某一种场景管理器类型:



    ·ST_GENERIC:最简单的场景管理器的构造器类型,其场景管理器没有对场景内容和结构做任何优化。在极其简单的场景中(例如菜单界面)中才有其价值。



    ·ST_INTERIOR:这种场景管理器的构造器所产生的管理器,优化了室内近距离的渲染,比较适合高密度的场景。

    ·ST_EXTERIOR_CLOSE:优化了室外场景里面的中近距离可视体,比较适合用一个简单模型或者高度场产生的场景地图。



    ·ST_EXTERIOR_FAR:Ogre历史遗留的错误,已经不需要在考虑使用它。在需要的时候用ST_EXTERIOR_CLOSE和ST_EXTERIOR_REAL_FAR来代替这个选项。



    ·ST_EXTERIOR_REAL_FAR:这种类型的场景管理器特别适合那种需要动态加载的地形或者场景。动态加载的地形通常都非常巨大,甚至可能描绘了一个星球的地貌。



    在前面的例子中,我们创建了一个ST_GENERIC类型的场景管理器。如果我们要载入一个雷神之锤III(Quake3)的场景,使用ST_INTERIOR来载入场景管理器就没错,因为BSPSceneManager插件已经把置身注册到ST_INTERIOR类型中。如果你希望建立一个以高度场为基础的地形,你就需要载入TerrainSceneManager(TSM)插件所带来的场景管理器,我们可以通过ST_EXTERIOR_CLOSE类型来创建。ST_GENERIC类型中并没有一个特定的场景管理插件,但是当你载入OctreeSceneManager插件之后,它就会接管ST_GENERIC的职责。



    摄像机(Camera)

    摄像机的概念和真实世界中的一样,会放在一个合适的位置(这意味着它拥有位置方向的属性),为你的场景进行“拍摄” 每一帧的工作。同时它是一个不能被渲染的物体,所以就算你的一台摄像机的拍摄范围你有另外一台摄像机出现,另外一台也不会被“拍摄”渲染出来(参考下面的摄像机视截体3D模型,你会更好理解我说的意思的)。摄像机(以及我们之后要提及的灯光)既可以挂在场景节点上面(这就意味着它可以被动画控制器操作),也可以直接放到空间中(你可以通过手动的方法改变其位置和方向)。就像前面说的,摄像机有一个“可视区域”的概念,它的形状是一个顶点在摄像机位置上的棱锥,并被远截面和近截面所切割成的视截体。如下图4-2所示。



    图4-2:摄像机视截体



    在图示中,(x, y, z)代表摄像机所在点的坐标。X和Y分别代表近截面的宽度和高度,Z代表从摄像机到近截面的距离。X’和Y’代表远截面的宽度和高度,其中(Z+Z’)的和代表着从摄像机到远截面的距离。其他的一些信息有,近截面和远截面的距离,摄像机的纵宽比率(X/Y),代表视方向和视截体下截面(或者上截面)的夹角W(代表了在Y轴的可视范围),以及可以由摄像机类计算出来的与地平线的夹角等。



    现在假设我们需要建立一个这样的摄像机:它拥有标准3:4的纵宽比;近截面距离摄像机5单位,远截面距离1000单位;视线方向和视截体的下平面(以及上平面)拥有30度夹角(换句话说,就是上图的W等于30°)。下面的代码实现了以上工作:



    //sceneManager是一个已经存在的场景管理器实例的指针。

    //我们在这里构建名称为“MainCam”的摄像机。

    Camera *camera = sceneMgr->createCamera(“MainCam”);



    //并不需要计算什么,可以直接从视口中得到这个尺寸

    camera->setAspectRatio(1.333333f);



    //30度角可以让我们看到一个长而远的视野

    camera->setFOVy(30.0f);

    camera->setNearClipDistance(5.0f);

    camera->setFarClipDistance(1000.0f);




    系统根据摄像机的设置而产生的视截体,然后再剔除在视截体六个面外面的几何体(意味着把这些集合体从当前帧的渲染列表中清除)。



    渲染模式(Rendering Modes)

    我们的摄像机支持3种不同的渲染模式:边框,实体,“点”(只渲染顶点)。



    camera->setPolygonMode(PM_WIREFRAME);

    camera->setPolygonMode(PM_POINTS);

    camera->setPolygonMode(PM_SOLOD);

    PolygonMode mode = camera->getPolygonMode();




    这些设置可以一直持续到下一次重新设置的时候(也就是说,不用在每一帧都调用这些函数)。系统默认的参数是PM_SOLOD。



    位置和变换(Position and Translation)

    摄像机(视截体)是一个MovableObject(活动对象)接口的实现,因此也具有这对象的所有方法和特性。MovableObject的大部分属性都是为了可以让它挂接到一个场景节点上面,并“背着”摄像机到处“拍摄”可以被渲染的物体。当你想要有一些不同的摄像机“追尾”技术的时候,你就会觉得这是一个不错的方法,在后面的章节中你会看到一些第三人称“追尾”技术的实现;不过在这里,我们首先来了解一下它内在用来改变位置和方向的方法。



    //确认我们已经有一个指向“Camera”类型实例的指针camera。

    camera->setPosition(200, 10, 200);



    //也可以用一个三维向量来设置摄像机坐标,在我们得到场景坐标时候这么做会方便一些

    //camera->setPosition(Vector3(200, 10, 200));




    上面的代码把摄像机设置到世界坐标系的绝对点(200,10,200)上。这和我们使用move()方以及moveRelative()方法有很大的区别,后面两种方法会把摄像机移动到当前位置的相对位置上。



    //假设摄像机还在我们之前设置的200, 10, 200空间位置上。
    camera->move(10, 0, 0); //摄像机移动到210, 10, 200

    camera->moveRelative(0, 0, 10); //摄像机移动到210, 10, 210




    在这里要注意一下moveRelative()方法。它关注的是本地坐标系,也就是说变换是根据当前摄像机所朝向的方向。在前面的例子中,摄像机仍然垂直地面,面向Z轴正方向。假如我们现在把摄像机向右转90度角。这时候摄像机本地坐标和世界的绝对坐标就不同了,如果我们这时候再调用moveRelative(0, 0, 10),在本地坐标移动10个z方向,但在世界坐标却是移动x方向,最后摄像机在世界坐标系的位置是(220, 10, 200)。



    指向,方向,和“着眼点”(Direction,Orientation,and“Look-At”)

    Ogre提供了相当多的方法,来在场景中任意操作你的摄像机。



    void setDirection(Real x, Real y, Real z);

    void setDirection(const Vector3& vec);

    Vector3 getDirection(void) const;

    Vector3 getUp(void) const;

    Vector3 getRight(void) const;

    void lookAt( const Vector3& angle);

    void lookAt(Real x, Real y, Real z);

    void roll(const Radian& angle);

    void roll (Real degrees){roll (Angle ( degrees ) );}

    void yaw(const Radian& angle);

    void yaw(Real degrees){yaw (Angle ( degrees ) );}

    void pitch(const Radian& angle);

    void pitch(Real degrees){yaw (Angle ( degrees ) );}

    void rotate(const Vector3& axis, const Radian& angle);

    void rotate(const Vector3& axis, Real degrees){

    rotate(axis, Angle(degrees));}

    void setFixedYawAxis (bool useFixed, const Vector3 &fixedAxis=Vector3::UNIT_Y) 

    const Quaternion & getOrientation (void) const 

    void setOrientation(const Quaternion& q);

    void setAutoTracking(bool enabled,SceneNode *target=0,const Vector3 &offset=Vector3::ZERO);






    可以通过调用roll(),yaw(),以及pitch()来控制摄像机相对于自身的方向进行环绕Z轴(Roll:滚动)、Y轴(Yaw:偏移)或者X轴(Pitch:倾斜)的旋转;setDirection()方法通过一个三维向量来在本地空间(Local space)设置方向; rotate()方法被用来操作摄像机绕着给定的轴向旋转,你既可以通过角度-轴的方法来调用,也可以使用四元数作为参数。lookAt()是一个比较常用的方法,它的作用是直接让摄像机方向对准世界空间内的目标点或者对象,避免你自己对四元数进行几何运算;最后介绍一下setFixedYawAxis()这个方法,它可以锁定某一个轴向的自由度。比如在第一人称的设计游戏中,摄像机一般都只会在X-Z平面上移动,这时候你就需要锁定Y轴的自由度,让它不能绕Y轴旋转。然而,如果在飞行模拟游戏中,可能就用不着做任何锁定了,你需要完全自由的摄像机操作。



    setAutoTracking()是一个有趣的方法,你可以让你的摄像机总是盯着你场景中的某一个节点。值得注意的是,这和第三人称射击游戏中所描述的跟踪有本质上的区别,游戏中摄像机需要对准控制角色所观察的方向,而不是我们这里得到的一直盯着这个节点。方法中第一个参数确定是否打开自动跟踪,在任何一帧渲染之前都可以重新设置它。并且需要注意在关掉自动跟踪之前,要确保所被跟踪的节点没有被删除(否则系统会抛出异常)。方法的第二个参数是被跟踪节点得指针,除非你第一个参数是false(这时候可以用NULL),否则你必须确定调用的时候指针指向的节点必须有效。有时候你可能发现你所要跟踪的物体太大了,以至于你都不知道“看”哪里才好,这时候你可以设置第三个参数来定着眼点,它是一个本地空间中相对于场景节点的定位点。



    下面的几个方法提供了一些得到摄像机当前的方向的信息,这些信息和被绑定场景节点旋转和变换有关(在其中有“Derived”关键字的方法中,同时被自身的矩阵影响)。



    const Quaternion& getDerivedOrientation(void) const;

    const Vector3& getDerivedPosition(void) const;

    Vector3 getDerivedDirection(void) const;

    Vector3 getDerivedUp(void) const;

    Vector3 getDerivedRight(void) const;

    const Quaternion& getRealOrientation(void) const;

    const Vector3& getRealPosition(void) const;

    Vector3 getRealDirection(void) const;

    Vector3 getRealUp(void) const;

    Vector3 getRealRight(void) const;




    其中有“Real”关键字的方法返回的是世界空间的坐标,而拥有“Derived”关键字的方法的返回值是在“轴绑定”的本地坐标系中(也就是说这个坐标系原点是摄像机所在的点,而它的轴向和世界坐标系相同)。



    高级摄像机属性

    Ogre支持立体渲染,通过调整摄像机(视截体)的setFrustumOffset()和setFocalLength()方法来实现。例如,你可以在同一水平线上布置两个摄像机,用来模拟人两眼所看到的实景。你需要把两个摄像机渲染的场景调整一个不同的角度,然后分别输出到使用者的两眼中(至少需要使用一个红蓝眼镜[2]),进而产生“3D”电影,在美国50年代很流行这些玩应儿。不过,在今天看来似乎这个用处不大,并且显得有些“学究”气。但是系统还是提供了这个功能以备不时之需(如果你在实验室里面工作,可能会需要)。



    Ogre同时允许你自己来操作视图和投影(View&Projection)矩阵。这确实是一个极其“高级”的话题,所以只有你拥有“知道怎么做和为什么这么做”的前提条件下,才能很好的使用这些特性。这样你才可以很好的通过自己来处理矩阵来控制摄像机的位置和方向。下面的方法提供了可被用于操作的视图和投影(view and projection)矩阵。



    const Matrix4& getProjectionMatrixRS(void) const;

    const Matrix4& getProjectionMatrixWithRSDepth(void) const;

    const Matrix4& getProjectionMatrix(void) const;

    const Matrix4& getViewMatrix(void) const;

    void setCustomViewMatrix(bool enable,

    const Matrix4& viewMatrix = Matrix4::IDENTITY);

    bool isCustomViewMatrixEnabled(void) const;

    void setCustomProjectionMatrix(bool enable,

    const Matrix4& projectionMatrix = Matrix4::IDENTITY);

    bool isCustomPeojectionMatrixEnabled(void) const;




    最上面两个函数返回渲染系统相关(system-specific)投影矩阵。getProjectionMatrixRS()返回一个符合系统本地渲染系统所提供坐标系的矩阵(可能是左手或者右手坐标系),而getProjectionMatrixWithRSDepth()返回的矩阵是符合Ogre内建的坐标系规格(右手坐标系)。在不同的系统中坐标系深度范围既可能是[0,1]或者是[-1,1]。你可以用getProjectionMatrix()方法代替getProjectionMatrixWithRSDepth()方法来取得统一的深度[-1,1]。



    当你决定自己手动定制视图和投影(view and projection)矩阵,你必须关闭Ogre自动对视截体(摄像机)的变换和定位的矩阵处理,取而代之的是当你要进行移动变换的时候要手动更新矩阵。你可以通过调用setCustomViewMatrix()和setCustomProjectionMatrix()的enable参数来开关手动对矩阵的更新。当关掉的时候,系统重新接管对矩阵的自动更新。



    在大多时候,系统都能很好的完成对LoD(细节等级level-of-detail)的控制。但如果你希望手动控制这些操作,Ogre提供了Camera中的方法来进行操作:



    void setLodBias(Real factor = 1.0);

    Real getLodBias(void) const;




    其中setLodBias()方法并不总是好用;因为对于实体(elements)来说,LoD只不过是场景或者程序提供的一种有提示性质的信息,实体可能会忽略这种指示。方法中的factor参数控制摄像机增加(或者减少)渲染的细节等级。当参数大于1的时候增加渲染精度,否则当小于的时候就相应减少。这种方法对于比如实现监视器功能时候会很有用,可以忽略整个视口的具体等级信息。

    从世界空间转换到屏幕空间(World Space to Screen Space)

    这个方法最常用于你希望用鼠标在应用程序中点选物品,这时候就执行了世界空间到屏幕空间的坐标转换。摄像机的中心线(精确点说,就是世界中摄像机中心的“视线”)和屏幕的焦点被定为点[0.5, 0.5](单位坐标系上面的坐标)。当你在屏幕上面移动你鼠标的光标的时候,你会希望有一条线连接你屏幕的坐标到世界中要选取的点。这条线被称为ray(射线),可以通过getCameraToViewportRay()方法来得到:



    //x和y都是单位坐标系(范围从0.0到1.0)屏幕上的坐标

    Ray getCameraToViewportRay(Real x, Real y) const;




    通过这条射线,就可以继续向场景管理器查询有哪些物体和此线相交。在这本书后面的章节里面会具体讨论场景管理器的具体细节。



    视口(Viewport)

    回顾一下4-2图所展示的画面,X和Y围成了一个矩形。这个矩形就是当前摄像机的视口,或者更精确的说,应该是这个摄像机的“一个”视口。如果你能回忆起前面章节的代码,Viewport对象实例是从RenderWindow的方法中创建,Camera(摄像机)对象只不过是用来构建Viewport的一个参数。这就意味着,一个单独的Camera实例可以绑定任意数量的Viewport实例。



    视口的抽象有助于解决一些不好的影响。默认的,Ogre在渲染视口之后再进行深度缓存和颜色缓存的清理,所有的工作都在其他视口的“上面”,这样就能避免深度混合的问题。你可以根据程序的需要(在内存允许范围内)构建很多的摄像机和视口。这么做很重要的用途就是在游戏中实现 “画中画”的放大功能。



    提示:对摄像机的画面进行放大的并没有想象中的简单,你要关注一些视口和摄像机的问题。这是因为虽然在进行画面放大,Ogre却仍然通过摄像机原来的位置来进行细节等级(LoD)的计算,结果仍然采用了原来的细节程度,在放大后的窗口中就会感觉物体很粗糙。解决的办法:当你放大的时候,简单把摄像机拉近放大目标;或者使用另外一台摄像机来处理(就是我们所说的画中画放大),当然你也可以通过手动设置Camera::setLodBias()方法来增加渲染的细节。




    视口也拥有自己的z-order(z次序)用来决定哪个是视口在哪个视口前面。更高的次序决定在堆栈中“更高”的位置(意味着当z-order值为0的时候,这个视口就在最下面)。一个z-order对应一个视口。就是说,如果你把两个视口的z-order都设为0的话就会抛出一个运行期异常。



    视口拥有自己的背景颜色属性。你可以做下面的事情来验证:首先建立一个覆盖整个窗口的视口,把它的背景颜色设置为黑色,然后把它的z-order设置为0,这样就把它放在了最底下;然后构建一个小一点的绿色窗口,覆盖在上面(设置一个大一点的z-order值,比如说1)。下面的代码能帮你实现我们所讲的动作:







    //假定window是一个已经存在渲染窗口实例的指针,camera是一个摄像机实例的指针

    Viewport *vpTop, *vpBottom;



    //第二个参数是z-order值,后面忽略了位置和尺寸的参数,从而使用了默认的值。

    vpBottom = window->addViewport(camera, 0);



    //在屏幕中心建立一个屏幕25%大小的视口,

    vpTop = window->addViewport(camera, 1

    0.375f, 0.375f, 

    0.25, 0.25 );



    //把上层视口的背景色设置成为蓝色(因为默认颜色为黑色,所以我们不用设置底层的)。

    vpTop->setBackgroundColour(ColourValue(0.0f, 0.0f, 1.0f));



    //可以用一种简单的办法来设置蓝色,象下面这样

    //vpTop->setBackgroundColour(ColourValue::Blue);








    默认情况下,Ogre系统在每一帧更新深度缓存和颜色缓存;你可以通过视口的方法对其进行管理。



    //在默认的情况下,两种缓存的更新都被设置成为true,

    //这等同于调用setClearEveryFrame(true, FBT_COLOUR|FBT_DEPTH)。

    vpTop->setClearEveryFrame(true, FBT_COLOUR);

    vpTop->setClearEveryFrame(false);




    另外一个很重要的问题,当你使用“画中画”的渲染方式处理你的视口的时候,程序界面仍然默认的显示在所有视口中。但可能这并不是你需要的结果(你不会希望HUD操作台显示在缩影窗口中),同样的,你也可以关闭前端视口的天空(天空盒等)和阴影,下面是实现的方法。



    vpTop->setOverlaysEnable(false);

    vpTop->setSkiesEnable(false);

    vpTop->setShadowsEnabled(true);




    还有很多关于视口的高级话题,比如改变渲染队列的顺序,或者选择另外的前端视口所拥有的材质主题。但是我们把这些话题留给后面的章节去具体介绍。



    主渲染循环(Main Rendering Loop)

    使用Ogre基础的应用程序典型的运行过程是:不间断的循环渲染每帧画面,直到你停止程序。在我们之前的章节中通过调用Root::startRendering()方法来执行这个循环。其实,在这个方法里面也只是简单的循环调用renderOneFrame()(渲染一帧)来进行程序运行工作。



    renderOneFrame()方法的存在有很重要的作用。比如当你希望把Ogre插入到一个已经存在的程序框架中,这时候你可能以希望自己来处理循环,而不是依赖Ogre自身的startRendering()/FrameListener结构框架。这时候你可能只需要一个类似3D渲染API的工作。



    采用renderOneFrame()还有一个很重要的作用。当你使用startRendering()的时候,因为渲染循环交给了系统,所以没有办法把Ogre结合到窗口系统的消息循环中去。我们考虑处理WM_PAINT消息的时候,当Windows程序处理这个消息时,将会重新绘制窗口用户区域的内容(就算被别的窗口覆盖了,也至少会执行让窗口无效的操作)。如果你希望把Ogre的渲染窗口插入到处理WM_PAINT消息的过程中来,一定会希望在这个消息处理完之后再进行Ogre的窗口渲染(通过renderOneFrame()),而是用startRendering()就没有办法掌握这个过程。



    然而,选用renderOneFrame()进行手动处理渲染循环的最主要原因是(对于某一些3D游戏引擎或者程序来说)使用startRendering()和帧监听结构来处理主循环并不是一个明智的选择。举例来说,当构建一个局域网络游戏的引擎时,可能服务器端和客户端采用相同的游戏循环,但是并不需要进行渲染的支持。这时候仍然把程序的主循环交给Ogre来驱动的话,就会变得很别扭了。



    幸运的是,Ogre并没有强制在你的程序必须使用某一种方法:你可以自由选择最适合你情况的方法。如果你最后决定手动来进行渲染循环,就可以使用renderOneFrame()方法。



    代码4-8展示了一个简短的程序段落;它只是用来告诉你如何在你的程序中创建一个手动循环,在你之后的代码中也可以直接使用它作为基础。



    代码4-8:手动渲染循环的框架例程



    bool keepRendering = true;



    //在这里填入所有这个章节之前提到的设置过程:

    //载入插件,创建渲染窗口和场景管理器以及摄像机和视口,

    //然后再场景中填入你希望的内容。



    while(keepRendering)

    {

    //在这里处理引擎使用的网络消息。



    //在这里处理引擎使用的输入消息。



    //根据消息更新场景管理状态。



    //根据新的场景管理状态,渲染下一帧。

    root->renderOneFrame();



    //检查是否需要退出渲染循环

    //注意:NextMessageInQueue()这个函数只是为了便于解释程序含义

    //而虚构出来的函数——Ogre引擎中并没有实现它。

    If(NextMessageInQueue() == QUIT)

    {

    keepRendering = false;

    }

    }



    //在这里进行你需要的清理工作

    //然后,关闭Ogre



    delete root;






    在执行renderOneFrame()的时候,你仍然可以良好的使用FrameListener类来处理程序。renderOneFrame()方法可以正确的处理注册在Root类实例中的帧监听,代码4-9清楚的描述了过程。



    代码4-9:Root中renderOneFrame()的全部代码(在Ogre源码的root.cpp中)

    bool Root::renderOneFrame(void)

    {

    if(!_fireFrameStarted())

    return false;



    _updateAllRenderTargets();



    return _fireFrameEnded();

    }








    代码4-9中包含了整个renderOneFrame()的代码。就像你看到的,分别发送了“帧开始(frame-started)”和“帧结束(frame-ended)”事件到你的帧监听中执行,所以当你手动处理渲染循环的时候,renderOneFrame()和FrameListener也能很好的协作。



    结语

    在这个章节中,你学到了如何初始化并启动一个Ogre程序,以及如何控制多样的支持显示的对象(RenderWindow,Camera,Viewport)协同工作来展示场景。在下一个章节中,我们将要开始具体的控制场景运作,那时候你就会了解现在所学的这些知识有多么有趣了。





    --------------------------------------------------------------------------------

    [1]心灵融合(Mind Meld):出自于《星际迷航记》系列(Star Trek),系列里面的一种瓦肯人(Vulcan)技术。通过心灵融合进入另一人的大脑里,从而分享记忆和信息。

    [2] 红蓝眼镜:一个几块钱就能买到的左红右蓝的滤色眼镜,观看称为红蓝补色立体电影。观看影片时,影院会给观众发,带上后,左眼就能看到屏幕上的红影图象,右眼看到蓝影图像,从而产生立体影像。

    结语

    在这个章节中,你学到了如何初始化并启动一个Ogre程序,以及如何控制多样的支持显示的对象(RenderWindow,Camera,Viewport)协同工作来展示场景。在下一个章节中,我们将要开始具体的控制场景运作,那时候你就会了解现在所学的这些知识有多么有趣了。
    展开全文
  • pycharm此应用程序无法启动,因为无法初始化qt平台插件 This application failed to start because no Qt platform plugin could be initialized. 这是我的第一的博客,就是下午安装PyQt5的时候老是出错,我查了一...

    pycharm此应用程序无法启动,因为无法初始化qt平台插件 This application failed to start because no Qt platform plugin could be initialized.

     这是我的第一的博客,就是下午安装PyQt5的时候老是出错,我查了一下午百度,终于解决了我的问题,希望有遇到跟我相同问题的朋友我可以帮到你。讲真成功debug能运行出结果所得来的开心是真的开心!
    

    我的问题:我把.ui 文件转换为.py文件运行的时候,总报错如下图

    意思在这里插入图片那些男生就
    大概意思就是:pycharm此应用程序无法启动,因为无法初始化qt平台插件,建议我重新装我的程序
    我的解决方法:setting–>project interpreter
    改一下project interpreter这个,我是换了另一个python 来编译的,就不再报错,希望这个方法能帮到你呦!
    在这里插入图片描述

    展开全文
  • 【高通SDM660平台】Camera Init 初始化流程一、Camera 系统架构二、Camera Init 初始化流程2.1 CameraService 启动2.2 CameraService::instantiate()2.3 CameraService.cpp2.3.1 CameraService::CameraService()...
  • GLUT初始化函数

    千次阅读 2011-12-16 11:27:36
    void glutInit(int* argc,char** argv) 初始化GLUT库。对应main函数的形式应是:int main(int argc,char* argv[]); 这个函数从main函数获取其两个参数。void glutInitWindowSize(int width,int height);//设置主...
  • 阿里百川SDK初始化失败 错误码是203

    千次阅读 2019-03-22 14:58:14
    集成阿里百川SDK的时候遇到下面报错,查询了一下阿里百川文档的错误码发现是SDK初始化失败的原因。 阿里百川开发者平台:http://console.baichuan.taobao.com/authimg.htm ...
  • emmc初始化流程

    千次阅读 2018-05-06 08:22:12
    转载至:https://blog.csdn.net/kickxxx/article/details/51130888最近在解EMMC的一个bug,发现Linux EMMC有点小复杂,先整理个文档出来吧用的是TI 平台,仅分析MMC,不分析SD和SDIOmmc_init[html] view plain copy ...
  • ARM初始化过程详解

    千次阅读 2009-09-04 00:38:00
    ARM初始化过程详解ARM嵌入式系统初始化过程 基于ARM内核微处理器的系统一般为复杂的片上系统,这种复杂系统的多数硬件模块都是可配置的...初始化代码完成的操作与具有的硬件平台相关,但一般包括一下的内容: • 初始化
  • SD卡初始化步骤详解

    千次阅读 2016-12-15 14:55:29
    硬件平台:飞思卡尔s12xep100 开发环境:codewarrior 5.2 结果:批量存储稳定 最近在做文件系统的升级,先前用的是21ic论坛上面down的znfat,相信做单片机软件的兄弟们应该都知道。 后来用的过程发现此...
  • Python是当今业界最流行的编码平台之一。从业余爱好者到专业人士,每个人都使用Python编写代码并制作用于移动和Web的应用程序。作为这样一个通用平台,有些方面在用户还不太为人所知...
  • GPIO的初始化

    千次阅读 2018-12-22 18:10:44
    定义了一系列的初始化函数,在linux启动的过程会按等级   初始化函数调用了“exynos4_gpiolib_init” static __init int exynos4_gpiolib_init(void) { struct s3c_gpio_chip *chip; int i; int ...
  • 上一篇文章讲述了关于函数“OSTaskCreateExt”创建任务函数体里面重要一些的内容,本文接着上一张讲述关于“OSTaskCreateExt”函数体里面调用的一个函数“OS_TCBInit”初始化任务控制块,“OS_TCBInit”也是与系统...
  • ARM嵌入式系统初始化过程

    千次阅读 2010-06-10 15:15:00
    初始化代码完成的操作与具有的硬件平台相关,但一般包括一下的内容:--初始化异常向量表(中断向量表)--初始化存储器系统--初始化堆栈--初始化有特殊要求的端口和设备--初始化应用程序的运行环境--调用...
  • 关于Quartus和ISEROM的初始化和仿真的一些小结 最近在玩Altera的FPGA,当我用Quartus II自带的IP核生成ROM时,出现了各种问题,于是在网上各种查资料,终于解决了我的问题。这里做一下小结,方便自己日后查阅。 ...
  • C99结构体指定初始化

    千次阅读 2008-07-11 22:12:00
    在阅读GNU/Linux内核代码时,我 们会遇到一种特殊的结构初始化方式。...下面我们看一个例子,Linux-2.6.x/drivers/usb/storage/usb.c有这样一个结构体初始化 项目: static struct usb_driver usb_sto
  • Linux内核初始化步骤(一)

    千次阅读 2018-11-25 23:33:45
    内核的初始化过程由start_kernel函数(\init\main.c)开始,至第一个用户进程init结束,调用了一系列的初始化函数对所有的内核组件进行初始化。其中,start_kernel、rest_init、kernel_init、init_post4个函数构成了...
  • Spark源码解读之SparkContext初始化

    千次阅读 2016-12-13 17:24:27
    SparkContext初始化是Driver应用程序提交执行的前提,这里以local模式来了解SparkContext的初始化过程。 本文以 val conf = new SparkConf().setAppName("mytest").setMaster("local[2]") val sc = new SparkContext...
  • 其实指的是把一个一个任务排列成一个表,要将这个表初始化意思。要执行响应任务就需要知道任务的优先级、任务控制块、目前指向的任务等一系列信息。   3 .   OS_InitTCBList (必须类) 这个函数...
  • 测试平台为vc6.0,即p.j STL的版本. string s;时做了一件事: string s = "";时做了两件事: 这两个函数的定义如下: 重点是_Ptr = 0,它是char* 类型,指向字符串,也即声明它为空指针.同时令字符串...
  • 最近刚刚接触深度学习,由于项目涉及到一些移动端开发的问题,也听了一些朋友的建议,最后决定选择tensorflow作为研究深度学习的平台。这两天照着tflearn官网的VGGNet的demo,用tensorflow实现了VGGNet,然而在用17...
  • 本文代码以MTK平台Android 4.4为...本文主要介绍MTK Android开机时,SIM卡的Framework部分初始化过程。 先看一段注释: /* Once created UiccController registers with RIL for "on" and "unsol_sim_status_change
  • EFI Driver初始化过程

    千次阅读 2012-11-16 13:20:52
    EFI Driver再被LoadImage()加载进内存之前,是存在于存储器的,例如:系统ROMs,Flash芯片,PCI option ROM,或者网络服务器。 (1)LoadImage() LoadImage()为Boot Service,通过调用LoadImage()将driver加载...
  • Android 8.1 Audio框架(一)初始化分析

    千次阅读 多人点赞 2019-07-19 17:14:52
    AudioPolicyService和它的总管AudioPolicyManager的初始化 类简介: 1.AudioPolicyService:APS是音频框架的服务,在Main_audioserver.cpp生成,它在第一次强引用的时候会创建AudioCommandThread和...
  • 1、下载git的安装包: ...3、初始化账号: 找到git的安装路径(默认为:C:\Program Files\Git) 双击:git-bash.exe来到如下命令框: 输入命令: git config --global user.name "你的用户名...
  • Linux EMMC子系统分析-初始化流程

    万次阅读 2016-04-12 13:04:53
    最近在解EMMC的一个bug,发现Linux EMMC有点小复杂,先整理个文档出来吧 ...1初始化 1.1mmc_init 2769 static int __init mmc_init(void) 2770 { 2774 2775 workqueue = alloc_ordered_workqueue("kmmcd",
  • 萤石云初始化爬坑1

    千次阅读 2017-04-18 14:10:26
    萤石云开放平台Android SDK使用说明 根据萤石云提供的官方文档 一开始看到armeabi-v7a有点眼熟,下面图的情况。 于是博主就把so文件都站进去了 之后接着根据官方文档配置,直到配完运行(见下图)。...
  • SPI初始化LCD以及参数设置问题

    千次阅读 2014-01-17 22:21:07
    SPI初始化LCD以及参数设置问题 简介: 做一个2440上LCD的驱动 原来平台上用的LCD型号为:WXCAT35 现在由于出货问题换了一款替代的LCD,型号为:NMA35QV65问题描述: 看这两款屏的资料发现,硬件引脚定义完全...
  • Wireshark(1)——初始化

    千次阅读 2018-02-06 14:18:05
    灰色部分暂且不叙述,因为每个平台初始化方式都不一样,对应wireshark的协议分析功能来讲也不是必须的,因为在接下来的文章当中会将重点放在Wireshark是如何实现协议分析的上面,因此对于初始化的描述也将围绕...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 84,159
精华内容 33,663
关键字:

平台初始化中是什么意思