嵌入式linux基础教程第二版 第二章续

时间:2022-06-27 18:51:54

7.第一个用户空间进程:init

       INIT:version 2.86 booting

       在上述代码中,直到这个地方内核都在执行自己的代码,他在一个称为内核上下文的环境中完成大量的初始化工作。在这个运行状态下内核拥有所有的系统内存并且全权控制所有的系统资源。内核能够访问所有的物理内存和所有的I/O子系统。他在内核虚拟地址空间中执行代码,使用一个由内核自己创建和支配的栈。

       当内核完成其内部初始化并挂载了根文件系统后,默认会执行一个名为init的应用程序,内核一启动init,它随即进入用户空间或用户空间上下文。在这个运行状态下,用户空间进程对系统的访问是受限的,必须使用内核系统调用来请求内核服务,比如设备和文件io。这些用户空间进程或线程,运行在一个由内核随即选择和管理的虚拟内存空间中,在处理器中专门的内存管理硬件的协助下,内核为用户空间进程完成虚拟地址到物理地址的转换。这种架构最大的好处是某个进程中的错误不会破坏其他进程的内存空间。

8.存储

       硬盘因其笨重,包含旋转部件,并且要求提供多种供电电压,因此并不适用于嵌入式系统中。闪存中没有活动的部件,相对坚固,只需一种电压。闪存可以在软件的控制下写入和擦出数据。和传统的旋转硬盘驱动器技术相比,它的写入和擦出速度较慢。

       在NOR型闪存中数据可以在软件控制下,使用直接向某个存储单元写入的简单方法将其从二进制的1改为二进制0.然而要将0改回1则要擦除整个擦除块。在擦除时需要向闪存写入一串特别的控制指令序列。为了修改存储在闪存阵列中的数据,必须完全擦除待修改数据所在的块。更新闪存中的数据所耗费的写入时间会是硬盘驱动的好多倍。部分原因就是每次更新数据时都有大量的数据需要被写回和擦除。另一个方面就是NOR型闪存存储单元的写入次数是有限的。

       NAND型闪存它与NOR型闪存的区别在于闪存内部的存储架构。NAND型闪存设备通过提供更小的块尺寸改进了传统闪存的一些限制。NOR型闪存为微处理器提供的接口方式是并行的数据总线和地址总线,直接连接到微处理器的数据/地址总线上。闪存阵列的每个字节或字可以随机寻址。NAND型闪存设备是通过复杂的接口串行访问的,而且这些接口因厂商而异。NAND型闪存数据是以串行突发方式访问的,每次突发访问的数据量远远小与NAND型闪存的块大小。与NOR型闪存相比,NAND型闪存的写入时间要少很多,但其写寿命却比NOR型闪存高出一个数量极。

       总的来说,NOR型闪存可以被处理器直接访问,甚至于代码可以直接在NOR型闪存中执行。实际上,很多处理器都不能像对待DRAM一样缓存访问闪存的指令。这进一步降低了代码的执行速度。相反,NAND型闪存更适合文件系统的格式大容量存储数据,而不是撇开文件系统,直接存储二进制可执行代码和数据。

       闪存的用途

      有多种闪存布局和使用方法可供嵌入式系统的设计者选择。在最简单的系统中,资源没有过度受限,可以将原始的二进制数据存储在闪存设备中。系统引导时,存储在闪存中的文件系统镜像被读入linux内存磁盘(ramdisk)块设备中。这个块设备由linux挂载为一个文件系统,并且只能从内存中访问。当闪存中的数据几乎不需要更新时,这种方式通常是很好的选择。相比于内存磁盘的容量,需要更新的数据量是很少的。但是,当系统重起或断电时对内存磁盘中文件的修改会丢失,牢记这一点。

      典型闪存组织结构

       引导加载程序及配置               ----------闪存顶部

       linux kernel

       内存磁盘的文件系统镜像(该镜像中包含了根文件系统)

       更新空间

      一般来说linux内核和ramdisk文件系统镜像都被压缩过,并由引导加载程序在系统引导时解压。

       可以在闪存中专门开辟一小块存储区域,或者使用其他类型的非易失性存储设备来存放那些重起或掉电后仍需保留的动态数据。对于需要保存配置数据的嵌入式系统,这种方式很常见。

      闪存文件系统

      上述描述的简单存储布局策略有局限性,但可以通过使用闪存文件系统来克服。闪存文件系统以类似于硬盘驱动器组织数据的方式来管理闪存设备中的数据。目前,比较受欢迎的闪存文件系统是JFFS2A第二代日志闪存文件系统。这个文件系统有很多的特性,旨在提高整体性能、延长闪存寿命(耗损均衡算法,将写操作均匀分布到闪存的各个物理擦除块上)并降低系统掉电时数据丢失的风险(写一个小文件必须擦除和重写整个闪存块,最坏的情况下这个写入会花费几秒钟才能完成,这极大增加了系统掉电后丢失数据的风险)。

      内存空间

      高性能的微处理器中包含一个复杂的硬件殷勤(MMU)熟悉linux内存管理人亲们应该很熟悉。

      执行上下文

      系统引导时,linux最先要完成一项琐碎的工作,即配置处理器中的硬件mmu以及相应的数据结构,并使之能够进行地址转换。这一步完成以后内核运行在自己的虚拟内存空间中,这个内存空间成为内核空间。接下来讲述的是内核空间和用户空间,熟悉linux操作系统的亲们应该很清楚了,假设我们是了,哈哈哈哈。例如,在用户空间写一个read函数,C库向内核发送一个读请求,这个读请求造成一次进程上下文的切换,从用户程序切换到内核,以服务这个请求并读取文件中的数据。在内核中,这个读请求最终会被转换为对硬盘驱动器的访问,从包含文件内容的扇区中读取相应数据。通常,这个对硬盘驱动器的读请求是以异步的形式发往硬件自身的。处理器将这个读请求发送给硬件并不会等待其完成请求。硬件收到请求后读取数据,当数据准备好的时候,通过中断的方式来告诉处理器读请求已经完成了。等待数据的应用程序会阻塞在一个等待队列中,直到有数据可用。当硬盘准备好数据时它将向处理器发送一个硬件中断,当内核接受到这个硬件中断时它会挂起正在执行中的任何进程,并从硬盘驱动器中读取应用程序所等待的数据。真的都没必要再敲这一遍,练字儿了

      进程虚拟地址空间

      分页和交换。我会操作系统我懂,哈哈。嵌入式系统开发者常常会因为性能的原因或资源限制而禁用嵌入式系统中的交换功能,多数情况下,使用慢速且写寿命有限的闪存设备作为交换设备是很不明智的。如果没有交换设备可用,就必须仔细检查应用程序,使其能够运行在有限的物理内存中。

9.交叉开发环境

      开发嵌入式系统应用和设备驱动之前,需要一套工具(编译器、实用工具)来生成合适目标系统的二进制可执行文件。本地编译:使用本机系统中的编译器生成可以在本机上运行的程序。本地编译并不意味着我们就能知道用于编译和运行程序的系统架构。如果你有一个可以在目标板上运行的工具连,就可以在目标板上本地编译生成适合此目标板架构的应用程序。要对一个新的嵌入式内核和定制单板进行压力测试,一个好办法就是在上面反复编译linux内核。

       交叉开发环境:编译器运行在主机之上,但生成的二进制可执行文件的格式与开发主机不兼容,不能在上面运行。他们存在的原因就是在资源受限的嵌入式系统上本地开发和编译代码常常是不现实和不可能的。后续章节会细讲的,

       当编译一个程序时编译器一般都要知道怎样找到所需要的头文件和和正确编译代码所需要的库。编译器一般使用一些默认的搜索路径来定位头文件。在代码中引用某个头文件时编译器在默认的几个路径中查找这个文件。类似的,连接器以这种方式来解析外部符号printf的引用。连接器知道默认在C库中搜索未解析的引用,并且知道在系统中的那些位置可以找到这些程序库;这种默认工作是内置在工具连中的

        假设你为Power架构的嵌入式系统编写应用程序。显然,你需要一个交叉编译器。用于生成Power架构处理器的二进制可执行文件。如果你使用交叉编译并且采用类似的编译命令来解析前面的hello.c程序,在解析外部符号printf()引用时连接器很可能意外的将二进制可执行文件连接到一个X86版本的C库。当然,由于生成了可执行程序混合了power架构和x86二进制指令,系统崩溃。解决这个困境的方法是在非标准路径中进行查找,以使用针对目标架构的头文件和程序库。这个例子只是简单的说明了两种开发环境的区别。