赞
踩
目录
3.2.1 jffs2:Journalling Flash File System v2
3.2.2 yaffs:Yet Another Flash File System
3.2.3 Cramfs:Compressed ROM File System
3.2.4 网络文件系统NFS(Network File System)
我们知道系统上电之后,需要一段程序来进行初始化:关闭WATCHDOG、改变系统时钟、初始化存储控制器、将更多的代码复制到内存中等。如果它能将操作系统内核复制到内存中运行,无论从本地(比如Flash)还是从远端(比如通过网络),就称这段程序为Bootloader。
可以认为Bootloader在系统上电时开始执行,初始化硬件设备、准备好软件环境,最后调用操作系统内核。
Bootloader的实现非常依赖于具体硬件,在嵌入式系统中硬件配置千差万别,即使是相同的CPU,它的外设(比如Flash)也可能不同,所以不可能有一个Bootloader支持所有的CPU、所有的电路板。即使是支持CPU架构比较多的U-Boot,也不是一拿来就可以使用的(除非里面的配置刚好与你的板子相同),需要进行一些移植。
CPU上电后,会从某个地址开始执行。比如MIPS结构的CPU会从0xBFC00000取第一条指令,而ARM结构的CPU则从地址0x0000000开始。嵌入式开发板中,需要把存储器件ROM或Flash等映射到这个地址,Bootloader就存放在这个地址开始处,这样一上电就可以执行。
Bootloader可以分为以下两种操作模式(Operation Mode):
(1)启动加载(Boot loading)模式。
上电后,Bootloader从板子上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。
(2)下载(Downloading)模式。
在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信方式从主机(Host)下载文件(比如内核映象、文件系统映象),将它们直接放在内存运行或是烧入Flash类固态存储设备中。
像U-Boot这样功能强大的Bootloader通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,U-Boot在启动时处于正常的启动加载模式,但是它会延时若干秒(这可以设置),等待终端用户按下任意键,而将U-Boot切换到下载模式。如果在指定时间内没有用户按键,则U-Boot继续启动Linux内核。
嵌入式Linux系统从软件的角度通常可以分为以下4个层次。
(1)引导加载程序,包括固化在固件(firmware)中的boot代码(可选)和Bootloader两大部分。
有些CPU在运行Bootloader之前先运行一段固化的程序(固件,firmware),比如x86结构的CPU就是先运行BIOS中的固件,然后才运行硬盘第一个分区(MBR)中的Bootloader。在大多嵌入式系统中并没有固件,Bootloader是上电后执行的第一个程序。
(2)Linux内核。
特定于嵌入式板子的定制内核以及内核的启动参数。内核的启动参数可以是内核默认的,或是由Bootloader传递给它的。
(3)文件系统。
包括根文件系统和建立于Flash内存设备之上的文件系统。里面包含了Linux系统能够运行所必需的应用程序、库等,比如可以给用户提供操作Linux的控制界面的shell程序、动态连接的程序运行时需要的glibc或uClibc库等。
(4)用户应用程序。
特定于用户的应用程序,它们也存储在文件系统中。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式GUI有:Qtopia和MiniGUI等。
显然,在嵌入系统的固态存储设备上有相应的分区来存储它们,如图1.1所示为一个典型的分区结构。
图1.1 嵌入式Linux系统中的典型分区结构
“Boot parameters”分区中存放一些可设置的参数,比如IP地址、串口波特率、要传递给内核的命令行参数等。正常启动过程中,Bootloader首先运行,然后它将内核复制到内存中,并且在内存某个固定的地址设置好要传递给内核的参数,最后运行内核。内核启动之后,它会挂接(mount)根文件系统(Root filesystem),启动文件系统中的应用程序。
Bootloader的启动过程可以分为单阶段(Single Stage)、多阶段(Multi-Stage)两种。通常多阶段的Bootloader能提供更为复杂的功能以及更好的可移植性。从固态存储设备上启动的Bootloader大多都是两阶段的启动过程。第一阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
一般而言,这两个阶段完成的功能可以如下分类。
(1)Bootloader第一阶段的功能。
在第一阶段进行的硬件初始化一般包括:关闭WATCHDOG、关中断、设置CPU的速度和时钟频率、RAM初始化等。
(2)Bootloader第二阶段的功能。
为了方便开发,至少要初始化一个串口以便程序员与Bootloader进行交互。
检测内存映射,就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入式开发中Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置。
Flash上的内核映象有可能是经过压缩的,在读到RAM之后,还需要进行解压。当然,对于有自解压功能的内核,不需要Bootloader来解压。
将根文件系统映象复制到RAM中,这不是必需的。这取决于是什么类型的根文件系统,以及内核访问它的方法。
将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列条件要满足。
(1)CPU寄存器的设置。
(2)CPU工作模式。
(3)Cache和MMU的设置。
[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断_ooonebook的博客-CSDN博客
Boot(Universal Boot Loader),即通用Bootloader,是用来引导启动内核的,它的最终目的就是从flash中读出内核,放到内存(SDRAM)中,启动内核。
U-Boot有如下特性:
U-Boot根目录下的各子目录大致可以分为4类:
(1)平台相关的或开发板相关的。
(2)通用的函数。
(3)通用的设备驱动程序。
(4)U-Boot工具、示例程序、文档。
图1.2 U-Boot顶层目录的层次结构
配置:执行“make <board_name>_config“(以S3C2410开发板为例:make smdk2410_config)
通过分析实际上执行“./mkcconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。而mkconfig文件的作用如下:
U-Boot还没有类似Linux一样的可视化配置界面(比如使用make menuconfig来配置),要手动修改配置文件include/config/<board_name>.h来裁减、设置U_Boot。
配置文件中有以下两类宏。
(1)一类是选项(Options),前缀为“CONFIG_”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:
(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如:
可以认为“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。
配置完成之后,直接执行“make all”即可编译。
U-Boot的编译流程总结:
(1)首先编译cpu/$(CPU)/start.s,对于不同的CPU,还可能编译cpu/$(CPU)下的其它文件。
(2)然后,对于平台/开发板相关的每个目录、每个通用目录都使用它们各自的Makefile生成相应的库。
(3)将1、2步骤生成的.o、.a文件按照board/$(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/U-Boot.lds链接脚本进行链接。
(4)第3步得到的是ELF格式的U-Boot,后面Makefile还会将它转换为二进制格式、S-Record格式。
U-Boot属于两阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.s和board/smdk2410/lowlevel_init.s,前者是平台相关的,后者是开发板相关的。(以开发板smdk2410的U-Boot为例。)
(1)硬件设备初始化。
依次完成如下设置:将CPU的工作模式设为管理模式(SVC),关闭WATCHDOG,设置AFCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存器),关闭MMU、CACHE。
代码都在cpu/arm920tstart.S中。
(2)为加载Bootloader的第二阶段代码准备RAM空间。
所谓准备RAM空间,就是初始化内存芯片,使它可用。对于S3C2410/S3C2440,通过在start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。代码在board/smdk2410/lowlevel_init.S中。(lowlevel_init.S文件是开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关宏。)
要注意这时的代码、数据都只保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址。
(3)复制Bootloader的第二阶段代码到RAM空间中。
这里将整个U-Boot的代码都复制到SDRAM中,这在cpu/arm920tstart.S中实现。
(4)设置栈。
栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。
(5)跳转到第二阶段代码的C入口点。
在跳转之前,还要清除BSS段(初始值为0、无初始值的全局变量、静态变量放在BSS段)。
图1.3 U-Boot第二阶段流程图
内核是任何基于Linux操作系统的核心组件。它代表了台式机与服务器的Linux发行版的核心方面。整体内核不仅包含中央处理器、IPC(进程间通信)和内存,而且具有系统服务器调用、设备驱动程序和文件系统管理功能。Linux内核充当设备软件和硬件之间的一层。
内核充当服务提供者,因此程序可以请求内核完成多项任务,例如请求使用磁盘,网卡或其他硬件,并且内核为CPU设置中断以启用多任务处理。它不让错误的程序进入其他程序的操作功能,从而保护了计算环境。它通过不允许存储空间来阻止未经授权的程序进入入口,并限制它们消耗的CPU时间。
内核是任何操作系统的心脏,因为它控制系统中的所有其它程序。当设备启动时,内核会经历一个初始化的过程,例如检查内存。它负责内存分配部分,并创建了一个运行应用程序的环境,而没有任何干扰。
Linux的启动过程可以分为两部分:架构/开发板相关的引导过程、后续的通用启动过程。如图2.1所示是ARM架构处理器上Linux内核vmlinux的启动过程。之所以强调是vmlinux,是因为其它格式的内核在进行与vmlinux相同的流程之前会有一些独特的操作。比如对于压缩格式的内核zlmage,它首先进行自解压得到vmlinux,然后执行vmlinux开始“正常的”启动流程。
引导阶段通常使用汇编语言编写,它首先检查内核是否支持当前架构的处理器,然后检查是否支持当前开发板。通过检查后,就为调用下一阶段的start_kernel函数作准备了。这主要分如下两个步骤:
①连接内核时使用的虚拟地址,要设置页表、使能MMU。
②调用C函数start_kermel之前的常规工作,包括复制数据段、清除BSS段、调用start_kernel函数。
第二阶段的关键代码主要使用C语言编写。它进行内核初始化的全部工作,最后调用rest_init函数启动init过程,创建系统第一个进程:init进程。在第二阶段,仍有部分架构/开发板相关的代码,比如图2.1中的setup_arch函数用于进行架构/开发板相关的设置(比如重新设置页表、设置系统时钟、初始化串口等)。
图2.1 ARM处理器的Linux内核启动过程
文件系统是OS用来明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。
文件系统由三部分组成:文件系统的接口、对对象操作和管理的软件集合、对象及属性。从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。
尽管内核是linux的核心,但文件却是用户与操作系统交互所采用的主要工具。这对linux来说尤其如此,这因为在UNIX传统中,它使用文件I/O机制管理硬件设备和数据文件。
根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,还是内核启动时所挂载(mount)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本(如rcS,inittab)和服务等加载到内存中去运行。
根文件系统之所以在前面加一个“根”,说明它是加载其它文件系统的“根”,那么如果没有这个根,其它的文件系统也就没有办法进行加载的。
根文件系统包含系统启动时所必须的目录和关键性的文件,以及使其它文件系统得以挂载(mount)所必要的文件。例如:
总之:一套Linux体系,只有内核本身是不能工作的,必须要rootfs(/etc目录下的配置文件,/bin/sbin等目录下的shell命令,还有/lib目录下的库文件等...)相配合才能工作。
Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。在Linux中将一个文件系统与一个存储设备关联起来的过程称为挂载(mount)。使用mount命令将一个文件系统附着到当前文件系统层次结构中(根)。在执行挂载时,要提供文件系统类型、文件系统和一个挂载点。根文件系统被挂载到根目录下“/”后,在根目录下就有根文件系统的各个目录,文件:/bin/sbin/mnt等,再将其他分区挂接到/mnt目录上,/mnt目录下就有这个分区的各个目录和文件。
为了在安装软件时能够预知文件、目录的存放位置,为了让用户方便地找到不同类型的文件,在构造文件系统时,一般建议遵循FHS标准(Filesystem Hierarchy Standard,文件系统层次标准)。它定义了文件系统中目录、文件分类存放的原则,定义了系统运行所需的最小文件、目录的集合。
Linux根文件系统中一般有如图3.1所示的几个目录。
图3.1 Linux根文件系统结构
该目录下存放所有用户(包括系统管理员和一般用户)都可以使用的、基本的命令,这些命令在挂接其他文件系统之前就可以使用,所以/bin目录必须和根文件系统在同一个分区中。
/bin目录下常用的命令有:cat、chgrp、chmod、cp、Is、sh、kill、mount、umount、mkdir、mknod、test等。
该目录下存放系统命令,即只有管理员能够使用的命令,系统命令还可以存放在/usr/sbin、/usr/local/sbin目录下。/sbin目录中存放的是基本的系统命令,它们用于启动系统、修复系统等。与/bin目录相似,在挂接其他文件系统之前就可以使用/sbin,所以/sbin目录必须和根文件系统在同一个分区中。
/sbin目录下常用的命令有:shutdown、reboot、fdisk、fsck等。
该目录下存放的是设备文件。设备文件是Linux中特有的文件类型,在Linux系统下,以文件的方式访问各种外设,即通过读写某个设备文件操作某个具体硬件。比如通过“/dev/ttySAC0”文件可以操作串口0,通过“/dev/mtdblock1”可以访问MTD设备(NAND Flash、NOR Flash等)的第2个分区。
设备文件有两种:字符设备和块设备。
该目录下存放各种配置文件。对于PC上的Linux系统,/etc目录下目录、文件非常多。这些目录、文件都是可选的,它们依赖于系统中所拥有的应用程序,依赖于这些话层序是否需要配置文件。在嵌入式系统中,这些内容可以大为精减。
该目录下存放共享库和可加载模块(即驱动程序),共享库用于启动系统、运行根文件系统中的可执行程序,比如/bin、/sbin目录下的程序。其它不是根文件系统所必需的库文件可以放在其它目录,比如/usr/lib、/usr/X11R6/lib、/var/lib等。
用户目录,它是可选的。对于每个普通用户,在/home目录下都有一个以用户名命名的子目录,里面存放用户相关的配置文件。
根用户(用户名为root)的目录,与此对应,普通用户的目录是/home下的某个子目录。
/usr目录的内容可以存在另一个分区中,在系统启动后再挂接到根文件系统中的/usr目录下。里面存放的是共享、只读的程序和数据,这表明/usr目录下的内容可以在多个主机间共享,这些主机也是符合FHS标准的,/usr中的文件应该是只读的,其他主机相关、可变的文件应该保存在其它目录下,比如/var。
与/usr目录相反,/var目录中存放可变的数据,比如spool目录(mail、news、打印机等用的),log文件、临时文件。
这是一个空目录,常作为proc文件系统的挂接点。proc文件系统是个虚拟的文件系统,它没有实际的存储设备,里面的目录、文件都是由内核临时生成的,用来表示系统的运行状态,也可以操作其中的文件控制系统。
用于临时挂接某个文件系统的挂接点,通常是空目录,也可以在里面创建一些空的子目录,比如/mnt/cdram、/mnt/hda1等,用来临时挂接光盘、硬盘。
用于存放临时文件,通常是空目录。一些需要生成临时文件的程序要用到/tmp目录,所以/tmp目录必须存在并可以访问。
(注:通常/etc、/bin、/sbin、/lib、/dev五个目录必须存储在根文件系统上,缺一不可)
JFFS嵌入式系统文件系统最早是由瑞典Axis Communications公司基于Linux2.0的内核为嵌入式系统开发的文件系统。JFFS2是RedHat公司基于JFFS开发的闪存文件系统,最初是针对RedHat公司的嵌入式产品eCos开发的嵌入式文件系统,所以JFFS2也可以用在Linux,uCLinux中。
Jffs2:日志闪存嵌入式系统文件系统版本2(Journalling Flash File System v2)主要用于NOR型闪存,基于MTD驱动层,特点是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃/掉电安全保护,提供“写平衡”支持等。缺点主要是当文件系统已满或接近满时,因为垃圾收集的关系而使jffs2的运行速度大大放慢。
jffs不适合用于NAND闪存主要是因为NAND闪存的容量一般较大,这样导致jffs为维护日志节点所占用的内存空间迅速增大,另外,jffs文件系统在挂载时需要扫描整个FLASH的内容,以找出所有的日志节点,建立文件结构,对于大容量的NAND闪存会耗费大量时间。
yaffs/yaffs2是专为嵌入式系统使用NAND型闪存而设计的一种日志型文件系统。与jffs2相比,它减少了一些功能(例如不支持数据压缩),所以速度更快,挂载时间很短,对内存的占用较小。另外,它还是跨平台的文件系统,除了Linux和eCos,还支持WinCE,pSOS和ThreadX等。
yaffs/yaffs2自带NAND芯片的驱动,并且为嵌入式系统提供了直接访问文件系统的API,用户可以不使用Linux中的MTD与VFS,直接对文件系统操作。当然,yaffs也可与MTD驱动程序配合使用。
yaffs与yaffs2的主要区别在于,前者仅支持小页(512 Bytes)NAND闪存,后者则可支持大页(2KB)NAND闪存。同时,yaffs2在内存空间占用、垃圾回收速度、读/写速度等方面均有大幅提升。
Cramfs是Linux的创始人Linus Torvalds参与开发的一种只读的压缩文件系统。它也基于MTD驱动程序。在Cramfs文件系统中,每一页(4KB)被单独压缩,可以随机页访问,压缩比高达2:1,为嵌入式系统节省大量的Flash存储空间,使系统可通过更低容量的FLASH存储相同的文件,从而降低系统成本。
Cramfs文件系统以压缩方式存储,在运行时解压缩,所以不支持应用程序以XIP方式运行,所有的应用程序要求被拷到RAM里去运行,但这并不代表比Ramfs需求的RAM空间要大一点,因为Cramfs是采用分页压缩的方式存放档案,在读取档案时,不会一下子就耗用过多的内存空间,只针对目前实际读取的部分分配内存,尚没有读取的部分不分配内存空间,当我们读取的档案不在内存时,Cramfs文件系统自动计算压缩后的资料所存的位置,再即时解压缩到RAM中。另外,它的速度快,效率高,只读的特点有利于保护文件系统免受破坏,提高了系统的可靠性。
由于以上特性,Cramfs在嵌入式系统中应用广泛。但是它的只读属性同时又是它的一大缺陷,使得用户无法对其内容对进扩充。Cramfs映像通常是放在Flash中,但是也能放在别的文件系统里,使用loopback设备可以把它安装别的文件系统里。
NFS是一项在不同机器、不同操作系统之间通过网络共享文件的技术。在嵌入式Linux系统的开发调试阶段,可以利用该技术在主机上建立基于NFS的根文件系统,挂载到嵌入式设备,可以很方便地修改根文件系统的内容。
以上介绍的都是基于存储设备的文件系统(memory-based file system),它们都可用作Linux的根文件系统。实际上,Linux还支持逻辑的或伪文件系统(logical or pseudo file system),例如procfs(proc文件系统),用于获取系统信息,以及devfs(设备文件系统)和sysfs,用于维护设备文件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。