1.44k likes | 1.63k Views
系统结构. 第一部分. 系统结构. 本讲分析 DLLs 内存管理 线程和进程. 系统结构. Symbian OS 高层简单概述 它是为高级移动电话设计的基于开放标准的一个多任务操作系统 这些电话 具有负责的图形用户界面( GUI ),以及许多内置的使用 GUI 的应用程序 例如,消息通讯和日历 它所谓被称为是 “ 开放 ” 的平台是因为,除了生产商内置的应用程序 用户可以安装其他程序,例如游戏、企业应用程序(如发送 email )或者工具软件. 系统结构. Symbian OS 授权给世界领先的手持设备制造商
E N D
系统结构 • 第一部分
系统结构 • 本讲分析 • DLLs • 内存管理 • 线程和进程
系统结构 • Symbian OS高层简单概述 • 它是为高级移动电话设计的基于开放标准的一个多任务操作系统 • 这些电话 • 具有负责的图形用户界面(GUI),以及许多内置的使用GUI的应用程序 • 例如,消息通讯和日历 • 它所谓被称为是“开放”的平台是因为,除了生产商内置的应用程序 • 用户可以安装其他程序,例如游戏、企业应用程序(如发送email)或者工具软件
系统结构 • Symbian OS授权给世界领先的手持设备制造商 • Arima, BenQ, Fujitsu, Lenovo, LG Electronics, Motorola, Mitsubishi, Nokia, Panasonic, Samsung, Sharp and Sony Ericsson • Symbian OS 具有一个灵活的体系结构 • 允许在核心操作系统上面运行不同的用户界面 • Symbian OS 用户界面(UIs)包括 • Nokia的S60 和Series 80 平台, NTT DoCoMo的FOMA以及索尼爱立信的UIQ
系统结构 • EKA1 和 EKA2 • 指Symbian OS内核的不同版本 • EKA 表示“EPOC内核体系结构(EPOC Kernel Architecture)“ • (Symbian OS 以前叫“EPOC”,较早前叫“EPOC32”) • EKA1是32为内核,最早在1997的Psion Serial 5中发布 • EKA2 在以后的幻灯片中讨论
系统结构 • EKA2 • 首先在Symbian OS version 8.0b中引入 • 但是直到version 8.1b才发布到手机产品中 • 在日本MOAP 2.0的FOMA 902i系列手机中可以找到 • 是Symbian32位内核的第二代 • 与EKA1在本质上的很大不同 • 为内核及用户态线程提供硬实时保证
Symbian OS中的DLLs • 知道和理解多态接口(polymorphic interface)和共享库(静态)DLL的特点 • 知道UID2值被用于区别静态和多态DLL以及不同的插件(plug-in)类型 • 对于共享库,理解必须导出那些函数,如果其他二进制组件要想访问它们的话 • 知道Symbian OS不允许通过名字进行库查找,而只能通过序号
共享库和多态接口DLLs • 动态链接库 • DLLs 是编译过C++代码库 • 可以被加载进运行的进程 • 在一个现有线程的上下文中 • 有主要有两种类型DLL • 共享库 (静态接口) DLLs • 多态接口(插件) DLLs
共享库DLLs • 一个共享库DLL • 实现可有其他库或者EXE使用的库代码 • 共享库的文件扩展名是dll • 这种DLL的例子是 • 用户库EUser.dll • 文件系统库EFsrv.dll • 一个共享库 • 根据模块定义(.def)文件导出API函数 • 可以有多个导出函数 • 每个函数都是DLL的入口
共享库DLLs • 一个共享库发布 • 一个其他组件编译时使用的头文件 (.h) • 一个链接时用于解析导出函数的导入库 (.lib) • When executable code that uses the library runs • 当使用库的可执行代码运行时 • Symbian OS加载器加载它所链接的所有共享库,以及这些共享库所要求的其他DLLs • 这是个递归操作直到可执行程序所有的需要的共享代码都被加载
多态接口DLLs • 一个多态接口DLL • 实现一个独立定义的抽象接口 • 例如由框架定义的接口 • 它可能具有.dll文件扩展名 • 但是它通常使用不同的扩展名以便进一步标识DLL的不同性质 • 例如 • .fsy表示文件系统插件 • .prt表示协议模块插件 • 文件系统和套接字在稍后讨论
多态接口DLLs • 多态接口DLLs 被用作插件 • 它们具有单个入口的”门(gate)“函数或”工厂(factory)“函数 • 它将实例化实现了接口的具体类 • 它们被用于 • 提供一个固定接口的多个不同实现(插件) • 它们被动态加载 • 典型得由框架加载
ECOM插件 • 从Symbian OS v7.0 以后 • 绝大多数公共类型的插件是ECOM插件 • ECOM 是实现接口的通用框架 • 以及用于查找和加载实现接口的那些插件 • 许多Symbian OS 框架要求其插件被写成ECOM插件 • 而不是加载多态接口DLL插件的私有类型的框架 • 例如,识别器框架
ECOM 插件 • 使用ECOM 使得每个框架代为寻找和加载合适的插件 • 而不是由自己去执行这个任务 • 所以使得设计和实现新服务或者特性变得更加简单 • ECOM 提供了一个一致的设计模式
DLLs使用的UIDs • UIDs被用来标识文件类型 • 运行的执行代码(包括DLLs) • 相应的应用程序关联的数据文件 • 一个UID 是全球唯一的32位标识值 • Symbian OS 使用三个UID的组合来唯一标识一个二进制执行程序 • DLLs使用的三个UID 值如下...
UID1 • UID1 是一个系统级的标识符 • 区别EXEs 和DLLs • 该值从来不需要显式声明 • 由Symbian构建工具通过MMP文件指定的targettype来决定 • 对于共享库 • 指定的targettype应该是DLL • UID1 = KDynamicLibraryUid = 0x10000079
UID1 (续) • 对于多态ECOM插件DLLs • targettype是PLUGIN • 或者ECOMIIC(对Symbian OS v9.0以前的版本) • 其他多态非ECOM插件DLL 目标类型 • FSY (文件系统插件) • PRT (协议模块插件). • targettype关键字和构建工作稍后讨论
UID2 • UID2 区分共享库DLLs 和多态接口DLLs • 共享库总是KSharedLibraryUid (0x1000008d) • 多态接口DLLs 用UID2 值致命其类型 • 例如套接字服务器协议模块的UID2 值是0x1000004A
UID3 • UID3 被用于唯一性的标识一个组件 • Symbian 通过一个中心数据库管理UID的分配 • 保证UID 确实是唯一值 • 开发者必须进行Symbian签名以请求UIDs • Symbian Signed 的更多内容后面介绍
EXEs • UID1 值由targettype EXE语句设置成 (KExecutableImageUid=0x1000007a) • UID2 对EXE 无用 • 可以不用指定 • 或者显式的设置为KNullUid (=0) • UID3 • 在Symbian OS v9 及后续版本UID3 应当被设置成一个唯一值以作为二进制代码的安全标识符 • Symbian OS v9 以前的版本不需指定
从 DLL中导出函数 • 一个共享库DLL 通过导出其函数提供对其APIs的访问 • 被另一个DLL或者XE使用,被编译到一个单独的库组件 • 通过创建一个.lib文件,导出使得函数对其他模块是”公开的“(“public”) • 库包含了要被调用代码链接使用的导出表
从 DLL中导出函数 • 将被导出的函数 • 应当在类定义的头文件中用宏IMPORT_C 标记 • 客户端代码将包含该都文件 • 有效的将每个函数”导入“到其模块中 • 当要使用它时 • 相应的函数实现 • 应该在实现它的.cpp文件中使用宏EXPORT_C作为前缀
从 DLL中导出函数 • IMPORT_C和EXPORT_C 的使用: class CMyExample : public CSomeBase { public: IMPORT_C static CMyExample* NewL(); public: IMPORT_C void Foo(); ... }; EXPORT_C CMyExample* CMyExample::NewL() {...} EXPORT_C void CMyExample::Foo() {...}
从 DLL中导出函数 • 关于那些函数应当被导出的规则如下: • 内联(Inline)函数绝对不要导,因为没有必要这样做 • 这就是为什么: • IMPORT_C和EXPORT_C宏添加函数到导出表,以使其对于链接了库的组件是可以访问的 • 但是一个内联函数的代码已经对于调用者是可以访问的,因为它们是在头文件内声明的 • 所以编译器在解释inline指令时,在任何调用它的客户端代码中直接加入内联代码 • 没有需要导出它
从 DLL中导出函数 • 只有那些在DLL外使用的函数应当用IMPORT_C和EXPORT_C进行导出 • 如果函数对于类是私有的 • 它永远不可能被客户端代码访问 • 导出它未必会在模块定义文件(.def)中的导出列表中添加它
从 DLL中导出函数 • 所有的虚函数都应被导出 • 不管是公开的,保护的还是私有的 • 因为它们能够被派生类在其他的代码模块中重新实现 • 所有具有虚函数的类 • 也必须导出构造函数 • 哪怕它是空的 • 这样虚函数表 • 通过访问基类构造函数被正确生成
通过序号和通过名字查找 • DLL 代码的大小是经过优化的 • 以便节省ROM 和RAM 空间 • 在绝大多数操作系统中,为了加载动态库,一个DL的入口可能: • 或者是通过字符串匹配它们的名字来鉴别——通过名字查找 • 或者是通过它们在模块定义文件中导出的顺序——通过序号查找 • Symbian OS 不提供通过名字查找 • 因为这会增加DLL的大小 • 保存所有导出文件的名称是对有限的ROM和RAM空间的浪费
通过序号和通过名字查找 • Symbian OS 只使用通过名字链接 • 这对二进制兼容有重大影响 • 在DLL的多个发布版本间,序号必须没有修改 • 例如 • Code which links against a library and uses an exported function with a specific ordinal number in an early version of the library • 链接了库代码,它并使用了具有早期版本库中特定序号的导出函数 • 将不能调用新版本库的那个函数,如果函数的序号改变了的话 • 二进制兼容将在后续讲义中讨论
注意 • 有种类型的虚函数不应从DLL导出,即纯虚函数 • 因为纯虚函数通常没有实现代码 • 所有没有代码供导出
可写静态数据 • 知道可写静态数据在EKA1的DLL中是不允许的,在EKA2中也是不鼓励 • 知道从DLL中移除可写静态数据的基本移植策略
对可写静态数据的支持 • Symbian OS支持在EXE中使用全局可写静态数据 • 在所有的版本和手持设备上 • 在包含EKA1的Symbian OS中( Symbian OS versions 8.1a, 8.0a 或者更早的版本) • 在DLL中不能使用可写静态数据 • 这是因为DLL具有分离程序代码和只读数据区域 • 但是没有可写数据的区域
DLL中支持可写静态数据的Symbian OS 版本 • 包含EKA2的Symbian OS版本(Symbian OS 8.0b,8.1a,9.0以及更高版本) • 现在支持在DLL中使用可写静态数据 • 但是这仍然是不推荐的 • 因为从内存使用看它的代价很大 • 并且Symbian OS模拟器的支持也有限 • Symbian 推荐只在万不得已的使用使用它 • 例如当移植为其他平台上编写的使用了大量可写静态数据的代码
GUI应用程序中的可写静态代码 • 在EKA1 中 • 所有的GUI应用程序都构建成DLL • 应用程序代码不能使用可写静态或者全局数据 • 在EKA2 中 • 应用程序现在被构建成EXE,因此这不再是一个问题 • 可修改全局或静态数据在EXE中已经是被允许的
在DLL中如何使用可写静态数据 • 为了能够在EKA2中使用全局可写静态数据 • 必须在DLL的MMP文件中添加关键字EPOCALLOWDLLDATA • 在使用EKA1的Symbian OS中不使用该关键字 • 当为手机硬件构建DLL代码时,工具链会返回一个错误
避免DLL中使用可写静态数据的方法 • 1. 线程本地存储 • 一种替代可写静态数据的方法叫线程本地存储(TLS) • 它可以通过如下方法访问 • Symbian OS 8.1b以前的版本使用类Dll • 8.1b,9.0及后续版本使用类UserSvr • 线程本地存储是一个32位指针 • 每个线程可以不同,它可用来指向一个对象,该对象模拟了全局可写静态数据 • 所有的全局数据必须组合到单个的对象中 • And allocated on the heap when the thread is created • 当创建线程时在堆中分配该对象
线程本地存储 • 函数Dll::SetTls()或 UserSvr::DllSetTls() • 被用于保存对象指针 • 线程本地存储指针 • 函数Dll::Tls()和UserSvr::DllTls() • 被用于访问全局对象 • 在线程销毁时 • 该数据也被销毁
避免DLL中使用可写静态数据的方法 • 2. 客户端-服务器框架 • Symbian OS 支持在EXE中使用可写全局数据 • 一个普遍的移植策略是将代码包装在一个Symbian服务器中 • 它是一个EXE • 以客户端接口方式导出API
避免DLL中使用可写静态数据的方法 • 3. 将全局变量嵌入到类中 • 如果是少量代码,将绝大多数全局数据转移到类中是可能的 • 数据就可以作为函数参数在对象和函数间传递
定义可写静态数据 • 全局可写静态数据是任何进程都可以修改的变量 • 它在进程的声明周期中都存在 • 实际上这意味着任何全局范围的数据都被定义在以下列表之外 • 函数 • 结构或类 • 函数范围的静态变量
定义可写静态数据 • 唯一可以在DLL中使用全局数据是 • 内建类型的全局常量 • 或者无构造函数的类的全局常量 • 所以以下定义是可以接受的: static const TUid KUidFooDll = { 0xF000C001 }; static const TInt KMinimumPasswordLength = 6;
定义可写静态数据 • 以下定义是不可用的,因为它们含有非平凡的类构造函数 • 即, 这些对象必须在运行时构造 static const TPoint KGlobalStartingPoint(50, 50); static const TChar KExclamation(’!’); // 下面的文字类是要被废弃的 static const TPtrC KDefaultInput =_L("");
定义可写静态数据 • 对象的内存是代码中预分配的,但是它不会被真正的初始化并保持不变 • 而是到运行构造函数之后 • 这样,在构建时每个都创建一个非常量的全局对象 • 将导致为手机硬件的构建失败 • 除非在DLL的MMP文件中加入了关键字EPOCALLOWDLLDATA
定义可写静态数据 • 下面对象也是非常量的 • 虽然由ptr指向的数据是常量 • 指针本身不是常量: • 可以通过将指针改成常量对上面语句进行更正 // 可写静态数据! static constTText*ptr = (const TText*)"data"; static const TText*constptr = (const TText*)"data";
注意 • 在EKA1中 • 模拟器可以使用底层的Windows DLL机制提供每个限于单个进程的DLL数据 • 如果非常量的全局数据被不小心使用了,在模拟器构建时不会被发现 • 当PETRAN工具在为硬件平台构建时遇到它,就会导致失败
在ROM和RAM中执行 • 认识关于Symbian OS在ROM和RAM中执行DLL和EXE的基本陈述的正确性
ROM和RAM中的EXEs • 在目标硬件上 • 可执行代码可以在生产时被构建到手机的只读存储(ROM)上, • 也能以后安装到手机上,可以使安装到手机的内部存储器或者可移动存储器,如记忆棒或MMC • 基于ROM的EXEs • 可以认为能直接在ROM上执行 • 这意味着程序代码和只读数据(如文字描述符)直接从ROM中读取 • 在RAM中只分配一个独立的数据区域用于数据读写
ROM和RAM中的EXEs • 如果安装EXE(而不是直接构建到ROM中) • 完全在RAM上执行 • 它有为程序代码和只读静态数据分配的一个区域 • 另一个单独的区域用于静态数据的读写 • 如果EXE的第二个副本被启动 • 只读区域是共享 • 为读写数据分配一个新的区域
ROM和RAM中的DLL • ROM 中的DLLs • 不会被加载到内存中 • 在ROM上的固定地址就地执行 • 从RAM 运行 DLLs • 加载到特定地址 • 该地址只有在加载时才能决定 • 使用引用计数 • 只有当没有任何组件使用DLL时,它们才允许被卸载
ROM和RAM中的DLL • 从RAM加载DLL • 不同与简单的将DLL存储到内部驱动(RAM)上 • Symbian OS • 将其复制到RAM中为程序代码保留的区域中 • 通过修正重定位信息,为执行做准备