文章目录
- 1 嵌入式系统概述
- 1.1 嵌入式系统基本概念
- 1.1.1 嵌入式系统定义
- 1.1.2 嵌入式系统的发展
- 1.1.3 嵌入式系统的特点
- 1.2 嵌入式系统分类
- 1.2.1 单个微处理器
- 1.2.2 嵌入式处理器可扩展的系统
- 1.2.3 复杂的嵌入式系统
- 1.2.4 在制造或过程控制中使用的计算机系统
- 1.3 嵌入式处理器
- 1.4 嵌入式系统的组成
- 1.4.1 嵌入式系统的硬件
- 1.4.2 嵌入式系统的软件
- 1.5 嵌入式操作系统
- 1.5.1 嵌入式操作系统的发展
- 1.5.2 嵌入式操作系统的分类
- 1.5.4 主流嵌入式操作系统简介
- 2 ARM11体系结构
- 2.1 ARM微处理器概述
- 2.1.1 arm公司简介
- 2.1.2 ARM特点
- 2.1.3 ARM体系结构的版本
- 2.2 ARM11 系列微处理器
- 2.3 ARM11系列微处理器架构
- 2.4 ARM11 流水线
- 2.4.1 流水线结构的性能
- 2.4.2 流水线级数的影响
- 2.4.3 ARM11处理器中流水线的管理
- 2.5 ARM工作模式及寄存器组
- 2.5.1 ARM核工作模式
- 2.5.2 ARM寄存器分组
- 2.5.3 工作模式分析
- 2.6 各种模式的工作机制
- 2.6.1 CPSR、PC、SPSR_XXX和LR_XX寄存器
- 2.6.4 Supervisor特权模式
- 2.6.5 Abort特权模式
- 2.6.6 Undefined特权模式
- 2.6.7Secure monitor模式
- 2.6.8 System模式
- 2.6.9 ARM中各个异常处理响应优先级
- 2.7 进入和退出异常中断的过程
- 2.7.1 异常中断响应过程
- 2.7.2 从异常中断处理程序返回
- 3 ARM为处理器的指令系统
- 3.1 Arm微处理器的指令集概述
- 3.1.1 Arm微处理器的指令的分类与格式
- 数据处理指令
- 加载/存储指令
- MOV 指令
- 分支指令
- 程序状态寄存器(PSR)处理指令
- 协处理器指令
- 异常产生指令
- 3.1.2 指令的条件域
- ARM指令的条件码
- ARM指令的条件码在寄存器中位置
- 3.2 ARM指令寻址方式
- 3.2.1· 立即寻址
- 3.2.2 寄存器寻址
- 3.2.3 寄存器移位寻址
- 3.2.4 寄存器间接寻址
- 3.2.5 基址寻址
- 3.2.6 多寄存器寻址
- 3.2.7 堆栈寻址
- 3.2.8 块拷贝寻址
- 3.2.9 相对寻址
- 3.3 ARM指令集补充
- 3.3.1 直接写跳转指令
- 3.3.2 数据处理指令
- 3.3.3 乘法与乘加指令
- 3.3.7 数据交换指令
- 3.3.8 移位指令
- 3.4 Thumb指令及应用
- 4 S3C6401处理器
- 4.5 存储器映射
- 5 GPIO接口
- 8 ADC和触摸屏接口
- 9 Andrioid系统开发概述
- 9.1 Android的发展
- 9.2 Android系统架构
- 9.2.1 应用程序层
- 9.2.2 应用程序框架层
- 9.2.3 系统运行层库
- 9.2.4 Linux内核层
- 9.3 Android系统内核
- 9.3.1 Linux内核结构
- 9.3.2 Android内核和驱动
- 9.4 系统移植的概念和驱动开发的方法
- 10 Android系统开发环境
- 10.1 交叉开发环境
- 10.1.1 交叉开发环境概述
- 10.1.2 宿主机与目标机的连接
- 10.1.3 宿主机环境
- 串口终端
- BOOTP
- TFTP协议
- TFTP协议与FTP协议的区别
- 交叉编译工具链
- 10.1.4 目标机环境
- 10.2 Linux操作系统及其开发工具。
- 10.2.1 Linux操作系统及其概述
- 1. 开放性和自由性
- 2. 高度的稳定性和可靠性
- 3. 多用户和多任务处理能力
- 4. 强大的网络功能
- 5. 安全性能高
- 6. 丰富的软件生态系统
- 10.2.2 Linux操作系统核心与驱动程序
- 1. 字符设备驱动(Character Device Driver)
- 2. 块设备驱动(Block Device Driver)
- 3. 网络设备驱动(Network Device Driver)
- 10.2.3 Linux交叉编译工具链
- 10.3 Android系统开发工具
- 10.3.1 Android代码目录结构
- 10.3.2 Ubuntu与虚拟机
- 10.3.3 安卓系统及开发工具链
- 1. 变量定义部分
- 2. 目标文件生成规则(编译规则)
- 3. 可执行文件构建规则(链接规则)
- 4. 伪目标部分
- 11 Boot Loader
- 11.1 Boot Loader概述
- 11.1.1 BootLoader主要功能
- 11.1.2 Boot Loader操作模式
- 11.1.3 BootLoader通信
- 11.2 BootLoader的工作过程
- 11.2.1 BootLoader工作过程概述
- 11.2.2 BootLoader 阶段1
- 11.2.3 BootLoader阶段2
- 11.3 U-Boot启动流程分析
- 11.3.1 U-Boot概述
- 13.3.2 U-boot代码结构
- 13.3.3 U-Boot启动流程分析
1 嵌入式系统概述
1.1 嵌入式系统基本概念
1.1.1 嵌入式系统定义
IEEE(国际电气和电子工程师协会)的定义:嵌入式系统是用于控制监视或者辅助操作机器和设备的装置。
1.1.2 嵌入式系统的发展
直到19世纪70年代末,随着微电子技术的发展,嵌入式计算机才逐步兴起。
1. 嵌入式应用始于微型机时代
电子数字计算机诞生于1946年,在初期的计算机都是在机房中的。直到19世纪70年代微处理器的出现,才使计算机发生了历史性的变化。
随着微处理器的发展,具有高速数据计算能力的微型机表现出的计算能力引起了控制专业人士的关注。他们将微型计算机通过电气或者是机械加固,然后配置各种外围的电路之后安装到大型船舶的自动驾驶或者是轮船监测系统中。
并且为了区分于原有的通用计算机系统,我们把嵌入到对象体系中的控制计算机称之为嵌入式计算机系统。其本质就是将一个计算机嵌入到一个对象体系中。
2. 现代计算机技术的两大分支
现代计算机技术有两个分支,就是通用计算机和嵌入式计算机。
通用计算机系统的技术要求高速海量的数据计算,技术发展方向是总线速度的无限提升、存储容量的无限扩大。
嵌入式计算机系统的控制技术要求是对对象的智能化控制能力,技术发展方向是与对象体系密切相关的嵌入式性能控制能力与控制的可靠性。
3. 嵌入式发展的里程碑
纵观嵌入式技术的发展历程,大致经历了4个阶段。
- 以单芯片为核心的可编程控制器形式的系统具有监测、伺服、指示设备相配合的功能。这类系统大部分应用一些专业性较强的工控领域。一般没有操作系统的支持,通过汇编语言对系统直接进行控制,运行结束后再清除内存。
- 以嵌入式cpu为基础,以简单操作系统为核心的嵌入式系统。在1980年左右,随着微电子工艺水平的提高,集成电路制造商开始把嵌入式应用中所需要的微处理器io接口串行接口以及ram和rom等部件通通集成到一片超大规模集成电路中制造出面向io设计的微控制器。在这一时期出现了大量高可靠低功耗的嵌入式cpu,但通用性比较弱。
- 以嵌入式操作系统为标志的嵌入式系统,20世纪90年代嵌入式系统飞速发展面向实时信号处理算法的dsp产品,则向着高速、高精度、低功耗的方向发展。随着硬件实时性的需求提高,嵌入式系统的软件规模不断扩大,逐渐形成了实时多任务操作系统rtos,并开始成为嵌入式系统的主流。
- 以internet为标志的嵌入式系统。目前大多数的嵌入式系统还孤立于internet之外信息。时代和数字时代的到来为嵌入。式系统的发展带来巨大机遇。
1.1.3 嵌入式系统的特点
- 面向特定应用
- 功耗低、体积小、集成度高、成本低
- 具有较长的生命周期
- 具有固化的代码
- 需要专用开发工具和环境
- 需要RTOS开发平台
- 以应用专家为主
1.2 嵌入式系统分类
1.2.1 单个微处理器
由单片的嵌入式处理器组成,集成了io设备以及ad转换设备,再加上简单的电源和时钟就可以工作,常用于小型设备中。
1.2.2 嵌入式处理器可扩展的系统
根据需要,可以扩展存储器,同时也可以使用片上的存储器,处理器一般容量在64k左右,字长为8位或者是16位在处理器上扩展少许的存储器和外接接口。
1.2.3 复杂的嵌入式系统
一般是16、32位,用于大规模应用。由于软件量大,所以需要扩展存储器。破产存储器一般都在一1M以上,外部接口一般仍然集成在处理器上。常用的嵌入式处理器有ARM系列,摩托罗拉的powerPC系列、Coldfire系列。
在开关装置、控制器、电梯等方面可以看到这类嵌入式系统的应用。
1.2.4 在制造或过程控制中使用的计算机系统
计算机与仪器、机械及设备与系统相连来控制这些装置。这类系统包括自动仓储系统和发货系统,在这些系统中计算机用于总体控制和监视,而不是对单个设备直接控制。
1.3 嵌入式处理器
全世界处理器的品种已经超过了1000多种,流行的体系有30多个系列。
嵌入式处理器的寻址空间一般是64k~16m。当然了,现在的STM32F407已经到了4G,这本书有点老了,处理速度0.1-2000MIPS,常用封装8-144个引脚。
根据现状,嵌入式处理器可以分成嵌入式微控制器EMCU、嵌入式微处理器EMPU、嵌入式dsp处理器EDSP、嵌入式片上系统ESoC。
三者的区别
1.4 嵌入式系统的组成
嵌入式系统一般都是由硬件、软件以及开发工具和开发系统三部分组成。
1.4.1 嵌入式系统的硬件
1.4.2 嵌入式系统的软件
- 驱动层程序
- 实时操作系统
- 操作系统的应用程序接口,应用程序接口是一系列复杂的函数、消息和机构的集合体。嵌入式操作系统下的api和一般操作系统下的api在功能含义和知识体系上完全一致。
- 应用程序。实际的嵌入式系统应用软件建立在系统的主任务基础。之上,用户应用程序主要通过调用系统的api函数对系统进行操作,完成用户功能的开发。
1.5 嵌入式操作系统
嵌入式操作系统是一种专用的可定制的操作系统。除了能完成一般操作系统的功能,如进程管理、存储管理、文件管理、设备管理等,还可以包括和硬件相关的底层驱动软件,系统内核设备驱动接口,通信协议图形界面标准化浏览器等。
1.5.1 嵌入式操作系统的发展
在最开始的控制领域,中设计者往往根据汇编语言或高级语言编程对系统进行直接控制,没有操作系统的概念。
随着嵌入式cpu的投入使用嵌入式操作系统也随之发展起来。
1.5.2 嵌入式操作系统的分类
收费不收费
- 免费的操作系统有:Linux、Embedded Linux、FreeRTOS等。
- 收费的嵌入式操作系统:VxWorks、Windows CE等。
按照系统对时间的敏感程度:
- 硬实时系统:系统对响应时间有严格的要求。 响应时间不满足的是不能接受的,会导致系统的崩溃和致命错误。
- 软实时系统:系统对响应时间没有严格要求。响应时间如果不能满足要求,可能会导致结果错误,但不影响系统的正常运行。
- 非实时系统:系统的响应时间没有要求,如果响应时间不满足要求,也不会影响系统运行。
1.5.4 主流嵌入式操作系统简介
1. VxWorks
VxWorks操作系统是WindRiver公司于1983年设计的一种嵌入式实时操作系统RTOS,是嵌入式开发环境的关键组成部分。
支持多种处理器x86、i960、Sun Sparc、Motorola MC68xxx、MIPS RX0000、POWER PC等。
这个系统比较贵,通常要花费数10万才能建起一个可靠的开发环境。
2. Windows CE
Windows CE是微软开发的32位嵌入式操作系统,得益于windows优秀的图形用户界面,与桌面版的windows基本一致。
3. μC/OS-II
μC/OS-II是一种开源但不免费的RTOS,具有可剥夺实时内核。μC/OS-II是μC/OS的升级版,发布1992年。目前,μC/OS-II已经被移值到40多种不同架构的CPU上,可以运行在8位到64位的各种操作系统之上。
4. 嵌入式Linux
Linux最早由芬兰人Linus Torvalds于1991年创立,经过短短十几年发展,已经成为一个功能强大稳定可靠的操作系统。典型的Linux系统有Red Hat、Ubuntu、Red Flag等。
我们要讲的嵌入式Linux是标准Linux在嵌入式系统上的移植。
2 ARM11体系结构
2.1 ARM微处理器概述
2.1.1 arm公司简介
ARM于1990年11月在英国伦敦成立前身为 Acorn计算机公司后改名为advance RISC Machines Limited公司(ARM)。
采用arm技术知识产品核的微处理器就是我们通常所说的ARM微处理器。基于arm技术的微处理器应用约占了30位RISC微处理器75%以上的市场份额。
2.1.2 ARM特点
- 体积小、功耗低、成本低、性能高
- 支持Thumb(16位)/ARM(32位)双指令集,很好的兼容8位\16位器件。
- 大量使用寄存器,指令执行速度高。
2.1.3 ARM体系结构的版本
到目前ARM体系一共定义了7个版本的指令集,以版本V1-V7表示。
版本V6
2001年发布。
使用此版本的核: ARM11、ARM1156T2-S、ARM1176JZF-S、ARM11JZF-S。
版本V7
ARM体系V7是在2005年发布。该版本扩展了130条指令的Thumb2指令集。具有NEON媒体引擎,该引擎具有SIMD执行流水线和寄存器堆可共享访问的l1、l2高速缓存。
使用次版本的处理器核:ARM Cortex。
2.2 ARM11 系列微处理器
ARM11系列微处理器是新一代的RISC处理器,但它是基于 arm v6的第1代设计实现的,系列主要有ARM1136J、ARM1156T2。
ARM11系列处理器在其性能上有巨大提升,首先推出的350M - 500M 的时钟频率的内核,在未来将上升到1GHz。
通过动态调整时钟频率和供应电压,开发者完全可以控制性能和功耗间作出平衡。在0.13微米的工艺下,1.2V条件下,ARM11处理器的功耗可以低至0.4mW/MHz。
ARMv6架构决定了可以达到高性能处理器的基础。
ARM11处理器规格如上所示。
2.3 ARM11系列微处理器架构
Arm11系列包括了arm11MPCore处理器ARM1176处理器、ARM1156处理器和ARM1136处理器。
2.4 ARM11 流水线
流水线是RISC执行指令时采用的一种重要机制。流水线既可以达到更高的性能,还可以让用户更加方便地实现流程。
2.4.1 流水线结构的性能
系统在处理数据时,一个指令周期含有4~6个时钟脉冲。每个脉冲周期由不同的部件完成不同的操作。
流水线结构是指每个时钟脉冲都接收下一条处理数据。的指令,只是不同部件。做不同的事情,流水线处理器。一般把一条指令的执行分成几个级,每一级在一个时钟周期内完成。
如果说处理器的流水线有k级,那么同时可执行的指令条数就是k。每一条指令处于不同的执行阶段。
2.4.2 流水线级数的影响
ARM7采用的是三级流水线,arm9采用的是5级流水线,ARM10采用的是6级流水线,ARM11处理器采用的是8级流水线,比以前的ARM内核减少了40%的吞吐量,8级流水线可以同时执行8条指令。
当出现多周期指令时跳转分支指令和中断发生时,流水线都会发生阻塞,而且相邻指令之间也可能因为寄存器冲突导致流水阻塞,从而降低了流水线效率。
同时随着系统的时钟频率的增加,指令执行周期也相应减少对其对硬件要求也更高,而且以内核执行一条指令前需要更多的周期来填充流水线。
Arm流水线的一条指令只有在完全通过执行阶段才会被处理。如果随后的指令需要用到前面指令的执行结果作为输入,它就要等前面的指令执行完。
2.4.3 ARM11处理器中流水线的管理
为了解决延时避免流水线的数据,冲突让前面的指令执行的结果能够快速进入到后面指令的流水线中。Rm11处理器采用了预测技术、存储管理、并行机制等技术来保证最佳的流水线效率。
1 预测技术
对跳转的预测分为两种,静态和动态的预测。
动态预测:在arm处理器中包含了64个4状态跳转地址缓存器,来保存最近使用过的转换地址。通过对这些转换记录地址的查询,处理器就可以预测当前的跳转指令是否会被执行。
静态预测:当采用动态转换预测机制无法在寻址缓冲内找到正确的地址时。ARM11处理器就会从跳转的方式来判断是否执行。静态预测检查分支是向前跳转还是向后跳转。
2. 存储管理
在arm11处理器中,指令和数据可以更长时间地保存在Cache中。
由于物理地质Cache本身的存在,使数据交换避免了反复重载Cache。
3. 流水线的并行机制
在流水线的后端使用了三个并行部件机构。ALU、MAC(乘加)、LS(存取)。
LS流水线是专门用于处理存储操作指令,把数据的存取操作与数据的算术操作的耦合性分隔开来可以更加有效地执行指令。
一旦指令被解码,将根据不同的操作类型发射到不同的执行单元中,考虑到不同的指令需要不同的执行时间。当三类指令先后被发射到流水线中,ALU或者是MAC指令,不会由于LS指令的等待而停下来,他们可以同时被执行,即取址、译码和执行等操作可以重叠执行。
通过技术和机制上的改进,arm11处理器改善了因为级数的增加带来的影响,使系统能在优化到更高的流水线吞吐量的同时,还能保持与以前版本处理器中的流水线同样的有效性。
2.5 ARM工作模式及寄存器组
2.5.1 ARM核工作模式
常规cpu工作核心模块都是由寄存器组和ALU两大模快组成。 Alu完成数据的加工处理,寄存器用来保存数据,这些数据直接参与alu运算。
ARM处理器的寄存器分成许多组,不同的组完成不同环境下的工作,不过他们都共用一套alu数据处理模块。ARM一般有37个寄存器,Cortex A8的ARM核的寄存器则多达40个。
程序状态寄存器是一个32位的寄存器,它指示和记录了当前程序的运行状态。PSR(Program State Register).
2.5.2 ARM寄存器分组
ARM芯片将寄存器分成很多组来运行不同模式下的程序,让cpu运行的更加稳定。
ARM寄存器分为以上8个组,其中User和System两个组的寄存器完全共用,Secure monitor是Cortex A8的ARM核开始追加的模式。
8组寄存器各自拥有可访问的寄存器。
17个寄存器是公用的。另外,多大23个专用的寄存器分散在各个模式组中。
- User模式是程序正常运行的模式,它不需要任何异常的特殊的异常处理。
- Secure monitor模式是安全模式,需要协处理器来启动该模块方能进行工作。
2.5.3 工作模式分析
1. 特权(异常)模式
8组寄存器中,除了User外,其余7种都为特权模式或异常模式。
特权模式有以下规定:
- 对PSR寄存器中的模式位组有修改能力。
- 除了能访问公用寄存器外,还有一些专用寄存器。
除了User模式外,其它的都属于特权模式。
- 异常模式都属于特权模式的类型。
- 异常模式都有自己的专用处理程序入口地址,又称为异常向量表。
2. 中断模式
属于异常模式。ARM核用来接入核外部的异常事件,称为中断。分为FIQ和IRQ两种。
FIQ 快速中断
- 当处理器的快速中断请求引脚有效,而且CPSR寄存器的F控制位被清除时,处理器产生快速中断请求异常中断。
IRQ外部请求中断
- 当处理器的外部中断请求引脚有效。而且CPSR寄存器的I控制位被清除时,处理器产生外部中断请求异常中断。
- 系统中各外设通过该异常中断请求处理中断服务。
2.6 各种模式的工作机制
ARM工作模式的切换分为手动切换与异常切换。
- 在某种特权模式下可以切换到另一个特权模式,但是user不可以。
- 当异常发生时,可以从user模式下切到异常模式下,或切到其它模式下。
2.6.1 CPSR、PC、SPSR_XXX和LR_XX寄存器
当异常发生时LR_XX保存当前PC值,SPSR_XXX保存当前CPSR值。因为此时pc会继续存放异常模式下的指令地址,而CPSR将继续反映异常模式下程序运行状态,所以需要保存。
- 这些动作由硬件自动完成。
异常结束时将CPSR和PC的值恢复,正常程序运行。
- 用户在异常程序的末尾写入一条完成指令PC<=lr,就自动完成CPSR<=SPSR_xxx动作。
2.6.4 Supervisor特权模式
进入Supervisor模式,一般有两种方式,硬件方式芯片复位和软件方式执行swi指令。
2.6.5 Abort特权模式
Abort异常模式由arm11内部硬件自动引发,对内存数据的存储和指令预取失败会引发该异常。
2.6.6 Undefined特权模式
Undefined异常,由arm11内部硬件自动引发,当指令执行一条无法识别的指令时将发生该异常。
2.6.7Secure monitor模式
当有私密的数据程序需要执行时(如货币交易),用户软件调用SMI指令即可进入该模式。
2.6.8 System模式
System特权模式是arm11中的7个特权模式中唯一一个没有自己的程序入口地址的模式,所以它不属于异常模式。
- System模式下的工作寄存器与user用户模式下的寄存器完全一样。
- 但是system模式能操作CPSR_mod权限,也就是说可以切换各种特权模式,访问他们的资源。
2.6.9 ARM中各个异常处理响应优先级
各种异常中断的中断向量地址以及中断的处理优先级如下图。
2.7 进入和退出异常中断的过程
2.7.1 异常中断响应过程
- 保存处理器的当前状态,中断屏蔽位以及各个条件标志位。通过将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中实现。
- 设置当前程序CPSR中相应的位。使处理器进入相应的执行模式。当进入IRQ模式,禁止IRQ中断。当进入FIQ模式,禁止FIQ中断。
- 将寄存器LR-mode(R14)设置成返回地址。R14从R15中得到PC的备份。
- 将程序计数器值pc设置成异常中断的中断向量地址,从而跳转到相应的异常中断处执行。
响应IRQ异常中断处理过程的伪代码:
其它还有响应未定义指令异常中断、响应swi异常中断、响应指令预取中止异常中断、响应数据访问中止异常中断等。
2.7.2 从异常中断处理程序返回
- 恢复被中断的程序的处理器状态。也就是将SPSR-mode的寄存器内容复制到当前程序状态寄存器CPSR中。
- 返回到发生异常中断的指令的下一条指令处执行。也就是将 LR_mode寄存器的内容复制到程序计数器pc中。
3 ARM为处理器的指令系统
3.1 Arm微处理器的指令集概述
3.1.1 Arm微处理器的指令的分类与格式
Arm微处理器的指令集是加载型的,即指令集仅能处理寄存器中的数据,而且处理结果必须要放回寄存器中。
对系统存储器的访问则需要通过专门的加载指令来完成。
ARM微处理器的指令集可以分为跳转指令、数据处理指令、程序状态寄存器处理指令、加载/存储指令,协处理器指令和异常产生指令六大类。
ARM指令集主要包括以下几类:
数据处理指令
- 算术运算指令:
- ADD:将两个操作数相加,并将结果存储在目标寄存器中。例如,
ADD R0, R1, R2
表示将寄存器R1和R2中的值相加,结果存入R0 。 - SUB:执行减法操作。例如,
SUB R3, R4, R5
是用R4中的值减去R5中的值,差存入R3。 - MUL:实现乘法运算。比如
MUL R6, R7, R8
将R7和R8中的值相乘,结果存于R6 。 - ADC:带进位的加法。例如在进行128位加法时,可通过
ADDS R0,R4,R8
;ADCS R1,R5,R9
;ADCS R2,R6,R10
;ADCS R3,R7,R11
实现,其中ADCS
中的S
后缀用于更改进位标志。 - SBC:带进位的减法,可用于有符号数或无符号数的减法运算。
- RSC:带借位的反向减法,用于把操作数2减去操作数1,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。
- ADD:将两个操作数相加,并将结果存储在目标寄存器中。例如,
- 逻辑运算指令:
- AND:对两个操作数按位进行与操作。例如,
AND R9, R10, R11
将R10和R11中的每一位进行与运算,结果放入R9,常用于掩码操作。 - ANDS 是 ARM 指令集中的逻辑与操作指令,并且会更新条件标志位。
- 例如:ANDS R0, R1, #0xF。这条指令将 R1 寄存器的值和立即数 0xF(二进制为 00001111)进行按位逻辑与运算,结果存放在 R0 寄存器中,同时更新条件标志位(N、Z、C、V)。
- ORR:按位进行或运算。如
ORR R12, R13, R14
将R13和R14的每一位进行或操作,结果存入R12,可用于设置某些位的值。 - EOR:执行按位异或操作。例如
EOR R15, R16, R17
把R16和R17按位异或后的结果存到R15,在数据加密等操作中有应用。 - BIC:位清除指令,是在一个字中清除位的一种方法,操作数2是一个32位位掩码。如果在掩码中设置了某一位,则清除这一位,未设置的掩码位指示此位保持不变。
- AND:对两个操作数按位进行与操作。例如,
- 比较指令:
- CMP:用于比较两个操作数的值,它会更新条件标志位,但不存储结果。例如
CMP R1, R2
,通过比较R1和R2的值来设置条件标志,后续可根据这些标志位进行条件跳转等操作。 - CMN:比较两个数的相反数。
- CMP:用于比较两个操作数的值,它会更新条件标志位,但不存储结果。例如
加载/存储指令
- LDR:从内存中将一个32位的数据加载到寄存器中。例如
LDR R0, [R1]
表示从R1所指向的内存地址处读取一个字的数据到R0中。 - STR:将寄存器中的32位数据存储到内存中。例如
STR R2, [R3]
把R2中的数据存储到R3所指向的内存地址。 - LDM:多寄存器加载指令,可以一次性加载多个寄存器的数据。例如
LDMIA R4!, {R5 - R7}
以递增的地址顺序从R4所指向的内存位置加载多个数据到R5 - R7寄存器中,并且R4会根据加载的字节数自动更新。 - STM:多寄存器存储指令,一次性存储多个寄存器的数据。例如
STMDB R8!, {R9 - R11}
以递减的地址顺序将R9 - R11寄存器中的数据存储到R8所指向的内存位置,R8也会自动更新。
MOV 指令
-
立即数移动:可以将一个 8 位的立即数移动到寄存器中。例如MOV R0, #42,将立即数 42 移动到寄存器R0中
-
寄存器间数据移动:把一个寄存器的值移动到另一个寄存器。如MOV R1, R0,将寄存器R0的值赋给R1
-
寄存器与立即数的逻辑操作后移动:先将一个寄存器里保存的值与一个立即数做逻辑运算,然后将结果移动到目标寄存器里。例如MOV R1, R0 or #0x7,将R0与立即数0x7进行逻辑或操作后的结果存入R1
-
寄存器移位后移动:对一个寄存器的值进行移位操作,并将结果存储到另一个寄存器。比如MOV R1, R0, LSL #2,将R0的值左移 2 位后存入R1
分支指令
- B:无条件分支指令,直接跳转到指定的地址。
- 它可以使程序流程无条件地跳转到指定的目标地址。目标地址是通过将指令中的相对偏移量与当前程序计数器(PC)的值相加得到的。
- 示例:B label,其中label是一个标号,表示要跳转的目标位置。假设在一个简单的程序中,有一段初始化代码,完成后想要直接跳转到主程序部分,就可以这样写:
MOV R0, #0 ;初始化操作
MOV R1, #1
B main_loop ;跳转到main_loop标号处的主程序
main_loop:
;主程序代码
-
BEQ:相等则跳转,即当比较结果相等时,跳转到指定地址。
-
BNE:不相等则跳转,当比较结果不相等时,跳转到指定地址。
-
BGE:大于或等于跳转。
-
BGT:大于跳转。
-
BLE:小于或等于跳转。
-
BLT:小于跳转。
-
BL:带链接的相对跳转指令,在跳转前会将当前的PC值保存到链接寄存器R14中,以便后续返回。
- 示例:BL subroutine,当执行这条指令时,程序会跳转到subroutine标号所代表的子程序处,同时把返回地址(也就是BL指令后的下一条指令地址)保存到 LR 中。在子程序结束时,可以使用MOV PC, LR指令返回。
-
BLX:带链接的切换跳转,除了保存PC值到R14外,还可以切换指令集。
- 如果当前处理器处于 ARM 状态,它会先判断目标地址的最低位:若为 1,则切换到 Thumb 状态,并将目标地址与 0xFFFFFFFE 进行与操作得到实际的跳转地址,然后跳转到该地址执行 Thumb 指令;
- 若为 0,则保持 ARM 状态,直接跳转到目标地址执行 ARM 指令 。在跳转之前,会将 PC 的当前值(即 BLX 指令的下一条指令地址)保存到 LR 寄存器中。
- 当子程序执行完毕后,可以通过将 LR 寄存器的值复制到 PC 中来实现返回。
ARM_CODE:
MOV R0, #1
BLX thumb_func ; 跳转到thumb_func,并切换到Thumb状态,保存返回地址到LR
MOV R1, #2
...
thumb_func:
ADD R0, R0, #1
BX LR ; 返回调用处,切换回ARM状态
- BX:切换跳转,用于在ARM和Thumb指令集之间切换。
程序状态寄存器(PSR)处理指令
- MRS:把状态寄存器的值送到通用寄存器。
- 它在特权模式下使用,对于操作系统内核、设备驱动程序等需要访问和修改处理器状态的程序非常重要。
- 例如:MRS R0, CPSR,这条指令将 CPSR 的值传送到 R0 寄存器中。
- 应用:保存处理器状态,异常发生时,处理器状态可能会被改变,保存原始状态有助于在异常处理完成后恢复现场。
IRQ_Handler:
MRS R1, CPSR
; 进行中断处理的其他操作
- MSR:把通用寄存器的值传送到状态寄存器PSR。
- PSR 是一个关键的寄存器,包含了如处理器模式、中断使能位、条件标志位(N、Z、C、V)等重要信息。
- 基本格式有两种:
MSR{cond} psr_fields, #immediate
MSR{cond} psr_fields, Rm
- 其中,
{cond}
是条件码。和其他ARM指令一样,它可以指定指令执行的条件,例如EQ
(相等)、NE
(不相等)等条件。如果不满足条件,指令将不被执行。 psr_fields
表示目标程序状态寄存器的字段。它可以是以下几种:- CPSR(Current Program Status Register):这是当前程序状态寄存器,包含了所有状态信息。
- SPSR(Saved Program Status Register):用于在异常处理过程中保存CPSR的内容,在异常返回时可以恢复处理器状态。
- CPSR_f或SPSR_f:代表访问标志位字段,主要包括N(Negative)、Z(Zero)、C(Carry)和V(oVerflow)这些标志位,用于反映算术和逻辑运算的结果状态。
- CPSR_c或SPSR_c:用于访问控制位字段,像中断使能位、处理器模式位等控制信息都在这里。例如,在ARM处理器中有多种运行模式,如用户模式、系统模式、管理模式等,这些模式的切换就是通过修改控制位来实现的。
- CPSR_s或SPSR_s:访问状态位字段。
- CPSR_x或SPSR_x:访问扩展位字段。
协处理器指令
Arm微处理器可支持多达16个协处理器。用于各种协处理操作。在程序执行过程中,每个鞋处理器只执行对自身的斜处理指令,忽略按摩处理器以及其他协处理器的指令。
Arm的协处理器指令主要用于arm处理器初始化ARM协处理器的数据处理操作,以及 arm处理器的寄存器和谐处理器的寄存器之间进行数据传递。
- CDP、CDP2:协处理器数据处理操作。
- LDC、LDC2:从协处理器取一个或多个32位值。
- STC、STC2:从协处理器中把一个或多个32位值存到内存。
- MCR、MCR2、MCRR:从寄存器送数据到协处理器。
- MRC、MRC2、MRRC:从协处理器传送数据到寄存器 。
异常产生指令
- BKPT:断点指令,用于设置断点,使程序在执行到该指令时暂停,以便进行调试。
- SWI:软件中断指令,用于产生软件中断,将执行转移到内存地址0x00000008处,并切换到管理模式,可用于实现操作系统调用等功能。
3.1.2 指令的条件域
当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足时指令被执行,否则指令被忽略。
ARM指令的条件码
- 定义与作用
- ARM指令的条件码是一种机制,用于根据处理器的状态标志来决定指令是否执行。这些标志存储在程序状态寄存器(PSR)中,主要包括N(Negative)、Z(Zero)、C(Carry)和V(oVerflow)等标志位。通过条件码,程序可以根据之前操作的结果,如比较运算、算术运算等,有条件地执行后续指令,从而实现高效的分支和控制流程。
- 条件码的种类及其含义
条件码共有16种,每种条件码可用两个字符表示。这两个字符可以添加在指令助记符的后面和指令同时使用。- EQ(Equal):当Z标志位被设置为1时满足条件,即表示上一次操作的结果为零。例如,在执行比较指令
CMP R0, R1
后,如果R0
和R1
的值相等,那么Z标志位会被置1,后续带有EQ
条件码的指令就会执行。常用于判断两个数是否相等,如在循环中判断计数器是否达到某个特定值来决定是否退出循环。 - NE(Not Equal):当Z标志位为0时满足条件,意味着上一次操作的结果非零。例如,在
CMP
指令比较两个不相等的寄存器值后,Z = 0,带有NE
条件码的指令可以执行。它可以用于在一组数据中筛选出与特定值不相等的数据。 - CS/HS(Carry Set/High or Same):当C标志位为1时满足条件。在无符号数的算术运算中,比如加法
ADD
指令,如果产生进位,C标志位会被置1。这在处理无符号数的大小比较和加法运算的溢出判断等场景中很有用,例如判断两个无符号数相加是否超过了寄存器所能表示的范围。 - CC/LO(Carry Clear/Low):当C标志位为0时满足条件。例如在减法运算中,如果没有产生借位,C标志位为0,带有
CC/LO
条件码的指令可以执行。 - MI(Minus):当N标志位为1时满足条件,N标志位表示运算结果的符号位。如果运算结果为负数,N标志位会被置1,带有
MI
条件码的指令就可以执行,用于判断运算结果是否为负。 - PL(Plus):当N标志位为0时满足条件,即运算结果为正数或者零,用于区分非负的运算结果。
- VS(oVerflow Set):当V标志位为1时满足条件。在有符号数的算术运算中,如果发生溢出,V标志位会被置1。例如,在有符号数的加法运算中,当两个正数相加结果为负数,或者两个负数相加结果为正数时,V标志位会被置1,带有
VS
条件码的指令就可以执行,用于检测有符号数运算的溢出情况。 - VC(oVerflow Clear):当V标志位为0时满足条件,用于判断有符号数运算没有溢出的情况。
- HI(Higher):满足
C = 1
且Z = 0
的条件。在无符号数比较中,用于判断一个无符号数是否大于另一个无符号数。例如,在CMP
指令比较两个无符号数R2
和R3
后,如果R2
大于R3
,则满足HI
条件,带有HI
条件码的指令可以执行。 - LS(Lower or Same):满足
C = 0
或者Z = 1
的条件。在无符号数比较中,用于判断一个无符号数是否小于或等于另一个无符号数。 - GE(Greater than or Equal):在有符号数比较中,对于
SUB
或CMP
等指令,当N
和V
标志位相同(N == V
)时满足条件。用于判断一个有符号数是否大于或等于另一个有符号数。 - LT(Less than):在有符号数比较中,当
N
和V
标志位不同(N!= V
)时满足条件。用于判断一个有符号数是否小于另一个有符号数。 - GT(Greater than):满足
Z = 0
且N == V
的条件。在有符号数比较中,用于判断一个有符号数是否严格大于另一个有符号数。 - LE(Less than or Equal):满足
Z = 1
或者N!= V
的条件。在有符号数比较中,用于判断一个有符号数是否小于或等于另一个有符号数。
- EQ(Equal):当Z标志位被设置为1时满足条件,即表示上一次操作的结果为零。例如,在执行比较指令
ARM指令的条件码在寄存器中位置
在ARM的程序状态寄存器(PSR)中,条件码标志的存储位置如下 :
- N标志位:位于PSR的第31位,它表示指令结果的符号位。若运算结果为负数,则N标志位被置1;若结果为非负数,则N标志位被置0 。例如,在执行减法操作
R1 - R2
后,如果结果是负数,那么N标志位会被设置为1。 - Z标志位:处于PSR的第30位,当指令的运算结果为零时,Z标志位被置1;反之,若结果不为零,则Z标志位被置0。比如,在执行比较指令
CMP R3, R4
后,若R3
和R4
的值相等,此时Z标志位就会被置为1。 - C标志位:存于PSR的第29位,其设置情况较为复杂,具体有以下几种情形 :
- 在加法(包括比较指令
CMN
)运算中,如果产生无符号溢出,即加法运算的结果超出了无符号数所能表示的范围,C标志位会被置1;否则置0。例如,两个无符号8位寄存器R5
和R6
相加,结果大于255时,C标志位为1。 - 在减法(包括比较指令
CMP
)运算中,若产生借位,C标志位被置1;否则置0。如R7 - R8
时,若R7
的值小于R8
的值,就会产生借位,C标志位为1。 - 对于包含移位操作的非加减运算,C标志位被设置为移位器移出的最后一位。
- 对于其他的非加减运算,C标志位通常保持不变。
- 在加法(包括比较指令
- V标志位:位于PSR的第28位,在有符号数的算术运算中,如果发生符号溢出,即两个有符号数相加或相减的结果超出了有符号数所能表示的范围,且符号位发生错误时,V标志位被置1;否则置0 。比如,两个有符号8位寄存器
R9
和R10
,分别存储的值为127和1,执行加法操作后结果为-128,此时就发生了符号溢出,V标志位会被置为1 。
不同版本的ARM架构以及不同的处理器模式下,PSR寄存器可能会有一些细微的差异,但上述条件码标志位的基本位置和含义是相对固定的.
3.2 ARM指令寻址方式
寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式。
3.2.1· 立即寻址
- 定义
- 立即寻址是ARM指令集中一种基本的寻址方式。在这种寻址方式中,操作数本身作为指令的一部分直接包含在指令代码中,这个操作数被称为立即数。立即数是一个固定的值,在指令执行过程中,处理器直接使用这个包含在指令中的值进行操作,而不需要从寄存器或者内存中获取。
- 格式与示例
- 在ARM指令格式中,立即数通常跟在操作码之后。例如,在指令
MOV R0, #0x12
中,#0x12
就是立即数。这条指令的功能是将十六进制数0x12(十进制为18)传送到寄存器R0中。这里的MOV
是操作码,表示数据传送操作,R0
是目标寄存器,#0x12
是立即数,也就是要传送到R0中的数据。
- 在ARM指令格式中,立即数通常跟在操作码之后。例如,在指令
- 立即数的编码规则
- ARM指令中的立即数并不是任意的32位数值。它是通过一种特定的编码方式来表示的,这种编码方式允许在指令中高效地表示一个8位的常数,并可以通过循环右移偶数位(0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30)来得到一个32位的立即数。例如,一个合法的立即数可以是
0x0000000F
(它本身就是8位的0xF循环右移0位得到的),但像0x12345678
这样的数就不能直接作为立即数,因为它不符合ARM的立即数编码规则。
- ARM指令中的立即数并不是任意的32位数值。它是通过一种特定的编码方式来表示的,这种编码方式允许在指令中高效地表示一个8位的常数,并可以通过循环右移偶数位(0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30)来得到一个32位的立即数。例如,一个合法的立即数可以是
- 应用场景
- 常量赋值:常用于初始化寄存器。例如,在设置中断向量表的起始地址时,可能会使用立即寻址来将一个特定的内存地址值赋给寄存器。比如,将中断向量表的起始地址
0x00000000
赋给某个寄存器(假设为R1),可以使用指令MOV R1, #0x00000000
。 - 计数操作:在循环中作为计数器的初始值。比如,实现一个简单的循环,循环次数为10,可使用指令
MOV R2, #10
来初始化计数器R2,然后在循环体中通过递减R2并判断是否为0来控制循环的结束。 - 设置模式位或控制位:在系统初始化或者模式切换时,用于设置处理器的某些控制寄存器的特定位。例如,设置处理器进入某种特权模式,可能需要将一个特定的立即数写入模式控制寄存器的相应位。
- 常量赋值:常用于初始化寄存器。例如,在设置中断向量表的起始地址时,可能会使用立即寻址来将一个特定的内存地址值赋给寄存器。比如,将中断向量表的起始地址
3.2.2 寄存器寻址
- 原理:操作数的值存放在寄存器中,指令中的地址码字段指出的是寄存器编号,指令执行时直接取出寄存器中的值来进行操作。
- 示例:
MOV R1, R2
,这条指令将寄存器R2
的值存入R1
中;SUB R0, R1, R2
则是将R1
的值减去R2
的值,结果保存到R0
。
3.2.3 寄存器移位寻址
- 原理:这是ARM指令集特有的寻址方式。当第2个操作数是寄存器移位方式时,第2个寄存器操作数在与第1个操作数结合之前,先进行移位操作。
- 示例:
MOV R0, R2, LSL #3
,表示将R2
的值左移3位,结果放入R0
中,即R0 = R2 × 8
;`
3.2.4 寄存器间接寻址
- 原理:指令中的地址码给出的是一个通用寄存器的编号,所需的操作数保存在该寄存器指定地址的存储单元中,即寄存器作为操作数的地址指针。
- 示例:
LDR R1, (R2)
,其功能是将R2
指向的存储单元的数据读出并保存在R1
中;
3.2.5 基址寻址
- 原理:将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址。常用于查表、数组操作、功能部件寄存器访问等。
- 示例:
LDR R2, (R3, #0x0C)
,该指令会读取R3+0x0C
地址上的存储单元的内容,并放入R2
中;
3.2.6 多寄存器寻址
- 原理:一次可传送多个寄存器的值,允许一条指令传送16个寄存器的任何子集或所有寄存器。
- 示例:
LDMIA R1!, {R2-R7, R12}
,这条指令可以将寄存器R2
至R7
、R12
的值保存到R1
指向的存储单元中,并且R1
的值会自动增加,以指向下一个存储单元。
3.2.7 堆栈寻址
- 原理:使用一个专门的寄存器(堆栈指针)指向一块存储区域(堆栈),指针所指向的存储单元即是堆栈的栈顶。根据堆栈的生长方向和指针指向的不同,可以分为满递增、空递增、满递减、空递减四种类型 。
- 示例:
PUSH {R4-R8}
,该指令会将寄存器R4
至R8
的值依次存储到堆栈中,堆栈指针会自动调整;POP {R4-R8}
则是从堆栈中依次弹出数据,并将其存入寄存器R4
至R8
中。
3.2.8 块拷贝寻址
- 原理:多寄存器传送指令用于将一块数据从存储器的某一位置拷贝到另一位置。
- 示例:通过使用特定的指令格式和寄存器组合,可以实现将一段连续的内存数据批量地从一个地址范围拷贝到另一个地址范围,但具体指令因不同的ARM架构和应用场景可能会有所不同 。
3.2.9 相对寻址
- 原理:相对寻址是基址寻址的一种变通方式。由程序计数器
PC
提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。 - 示例:
LDR R1, (PC, #8)
,它会从PC+8
所指向的地址中加载数据到R1
中。这种寻址方式常用于实现程序中的相对跳转和位置无关代码。
3.3 ARM指令集补充
3.3.1 直接写跳转指令
概念
在 ARM 架构中,程序计数器(PC)用于保存下一条要执行指令的地址。通过直接向 PC 写入一个新的地址,可以实现程序流程的跳转。这个新地址可以是绝对地址,也可以是相对地址。当向 PC 写入跳转地址时,处理器会在下一个指令周期从新的地址开始取指令并执行,从而改变程序的执行路径。
方式
使用指令直接写入:可以使用一些能够对寄存器进行写操作的指令来修改 PC 的值。
例如,在某些特殊情况下,通过MOV指令直接将目标跳转地址写入 PC。不过这种方式比较少见,因为直接操作 PC 可能会导致一些不可预测的问题,而且在很多 ARM 处理器的指令集中,对 PC 的直接写入有一些限制。
例如,MOV PC, #addr(其中#addr是目标跳转地址),但在实际应用中,这种简单的方式可能因为对齐问题或者指令流水线等因素而不能正常工作。
3.3.2 数据处理指令
ARM指令中的数据处理指令大致可分为以下几类:
- 数据传送指令:
- MOV:将立即数或寄存器中的数据传送到目标寄存器。例如
MOV R1, #0x10
,将立即数0x10传送到寄存器R1中;MOV R0, R1
,则是将寄存器R1中的值传送到寄存器R0 。 - MVN:将立即数或寄存器中的数据按位取反后传送到目标寄存器。如
MVN R1, #0xff
,会将0xff取反后的值0xffffff00传送到寄存器R1中;MVN R1, R2
,把寄存器R2中的值取反后存到R1。
- MOV:将立即数或寄存器中的数据传送到目标寄存器。例如
- 算术逻辑运算指令:
- 加法运算指令 ADD:将operand2数据与Rn的值相加,结果保存到Rd寄存器。例如
ADD R1, R1, #1
,实现R1 = R1 + 1;ADD R1, R1, R2
,则是R1 = R1 + R2 。 - 减法运算指令 SUB:用寄存器Rn减去operand2,结果保存到Rd中。如
SUB R0, R0, #1
,即R0 = R0 - 1;SUB R2, R1, R2
,得到R2 = R1 - R2 。 - 逆向减法指令 RSB:用寄存器operand2减去Rn,结果保存到Rd中。像
RSB R3, R1, #0xff00
,结果为R3 = 0xff00 - R1;RSB R1, R2, R2, LSL #2
,即R1 = R2 << 2 - R2 = R2 × 3 。 - 带进位加法指令 ADC:将operand2的数据与Rn的值相加,再加上CPSR中的C条件标志位,结果保存到Rd寄存器。常与ADD指令结合使用实现64位加法,如
ADDS R0, R0, R2
配合ADC R1, R1, R3
,实现(R1、R0)=(R1、R0)+(R3、R2) 。 - 带进位减法指令 SBC:用寄存器Rn减去operand2,再减去CPSR中的C条件标志位的非,结果保存到Rd中。可与SUBS指令联合实现64位减法,如
SUBS R0, R0, R2
配合SBC R1, R1, R3
,实现(R1, R0)-(R3, R2) 。 - 带进位逆向减法指令 RSC:用寄存器operand2减去Rn,再减去CPSR中的C条件标志位,结果保存到Rd中。可用于求64位数值的负数,如
RSBS R2, R0, #0
配合RSC R3, R1, #0
。 - 逻辑与操作指令 AND:将operand2值与寄存器Rn的值按位作逻辑与操作,结果保存到Rd中。例如
ANDS R0, R0, #x01
,取出R0的最低位数据;AND R2, R1, R3
,则是R2 = R1 & R3 。 - 逻辑或操作指令 ORR:将operand2的值与寄存器Rn的值按位作逻辑或操作,结果保存到Rd中。如
ORR R0, R0, #x0f
,将R0的低4位置1;MOV R1, R2, LSR #4
配合ORR R3, R1, R3, LSL #8
,将近R2的高8位数据移入到R3低8位中 。 - 逻辑异或操作指令 EOR:将operand2的值与寄存器Rn的值按位作逻辑异或操作,结果保存到Rd中。比如
EOR R1, R1, #0x0f
,将R1的低4位取反;EOR R2, R1, R0
,即R2 = R1 ^ R0 。 - 位清除指令 BIC:将寄存器Rn的值与operand2的值的反码按位作逻辑与操作,结果保存到Rd中。例如
BIC R2, R1, #0x0f
,将R1的低4位清零,其它位不变 。
- 加法运算指令 ADD:将operand2数据与Rn的值相加,结果保存到Rd寄存器。例如
- 比较指令:
- CMP:使用寄存器Rn的值减去operand2的值,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。本质上等同于SUBS指令,只是不存储运算结果
- 如
CMP R1, R2
,根据比较结果设置CPSR中的标志位,若标志位Z = 1,则两者相等;若Z = 0,则两者不相等 。
- 如
- CMN:将寄存器中的值加上表示的数值,根据操作的结果更新CPSR中的条件标志位,也是默认更新,无需添加“S”。常用于比较两个数的大小关系及后续的条件判断.
- TST:将表示的数值与寄存器的值按位做逻辑与操作,根据操作的结果更新CPSR中相应的条件标志位。通常用于测试寄存器中某些位是1还是0,一般会与BEQ或者BNE指令配合使用完成条件判断跳转功能.
- TEQ:将表示的数值与寄存器的值按位做逻辑异或操作,根据操作的结果更新CPSR中相应的条件判断位。常用来判断两个数是否相等,但这种比较操作不会影响到CPSR寄存器中的V位跟C位;同时,TEQ指令也可用于比较操作数符号是否相同,该指令执行后,CPSR寄存器中的N位为两个操作数符号位做异或的结果.
- CMP:使用寄存器Rn的值减去operand2的值,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。本质上等同于SUBS指令,只是不存储运算结果
3.3.3 乘法与乘加指令
- 乘法指令(MUL)
- 功能描述:MUL指令用于实现两个寄存器中的无符号整数相乘,并将结果存储在目标寄存器中。
- 指令格式:
MUL{cond}{S} Rd, Rm, Rs
。其中,{cond}
是条件码,用于指定指令执行的条件;{S}
表示是否更新标志位;Rd
是目标寄存器,用于存放乘法运算的结果;Rm
和Rs
是源寄存器,分别提供两个相乘的操作数。 - 示例及应用场景:
- 例如,
MUL R0, R1, R2
,这条指令将寄存器R1
和R2
中的无符号整数相乘,结果存放在R0
中。在计算数组元素的偏移量、进行简单的图形缩放(如在二维图形中,将坐标乘以缩放因子)等场景中经常使用。假设要计算一个二维数组array[10][10]
中某个元素的地址,已知每行的元素个数为10,元素大小为4字节,数组的起始地址存放在R3
中,要计算array[i][j]
的地址(i
存于R1
,j
存于R2
),可以使用乘法指令:MUL R4, R1, #40
(计算行偏移量,每行40字节);MUL R5, R2, #4
(计算列偏移量,每个元素4字节);ADD R6, R3, R4
(加上行偏移量);ADD R6, R6, R5
(加上列偏移量得到元素地址)。
- 例如,
- 乘加指令(MLA)
- 功能描述:MLA指令在乘法的基础上增加了加法操作。它先将两个源寄存器中的无符号整数相乘,然后将乘积与第三个源寄存器中的值相加,最后将结果存储在目标寄存器中。
- 指令格式:
MLA{cond}{S} Rd, Rm, Rs, Rn
。其中,Rn
是提供相加操作数的寄存器,其他参数含义与MUL指令相同。 - 示例及应用场景:
- 例如,
MLA R0, R1, R2, R3
,先将R1
和R2
中的值相乘,然后将乘积与R3
中的值相加,结果存于R0
。在数字信号处理中的滤波算法中应用广泛。例如,一个简单的有限脉冲响应(FIR)滤波器的计算,假设滤波器系数存放在coeff
数组中,输入信号样本存放在input
数组中,中间计算结果和最终结果存放在result
数组中。对于某个时刻k
,计算滤波器输出可以使用乘加指令:LDR R1, =coeff[0]
(加载第一个滤波器系数);LDR R2, =input[k]
(加载当前输入样本);MLA R3, R1, R2, #0
(计算第一个乘积项)。然后通过循环,依次加载其他系数和样本,使用MLA指令进行乘加运算,最终得到滤波器输出。
- 例如,
- 有符号乘法指令(SMUL)和有符号乘加指令(SMLA)
- 功能描述:这两个指令与MUL和MLA类似,但是用于有符号整数的运算。它们在处理有符号数时,会考虑操作数的符号位,按照有符号数的乘法和乘加规则进行运算。
- 指令格式和应用场景:
- 指令格式与无符号数对应的指令格式相似,如
SMUL{cond}{S} Rd, Rm, Rs
和SMLA{cond}{S} Rd, Rm, Rs, Rn
。在涉及有符号数的数学计算、数值分析等领域应用较多。例如,在计算带有正负权重的加权求和时,需要使用有符号乘法和乘加指令。假设weight
数组存储有符号的权重值,value
数组存储相应的数据值,计算加权和sum
:MOV R0, #0
(初始化结果寄存器);LDR R1, =weight[0]
;LDR R2, =value[0]
;SMLA R0, R1, R2, R0
(开始计算加权和),然后通过循环处理其他数组元素。
- 指令格式与无符号数对应的指令格式相似,如
- 长乘法指令(UMULL、UMLAL、SMULL、SMLAL)
- 功能描述:
- UMULL(无符号长乘法):用于实现两个32位无符号整数相乘,得到64位的乘积结果。乘积的低32位存储在一个指定的寄存器中,高32位存储在另一个指定的寄存器中。
- UMLAL(无符号长乘加):先执行两个32位无符号整数的乘法,得到64位乘积,然后将这个乘积与另外两个32位寄存器中的64位无符号数相加,结果的低32位和高32位分别存储在指定的寄存器中。
- SMULL(有符号长乘法):和UMULL类似,但是用于有符号整数的乘法,会考虑操作数的符号位,得到64位的有符号乘积结果并存储。
- SMLAL(有符号长乘加):和UMLAL类似,用于有符号整数的乘加运算,将两个32位有符号整数相乘得到的64位乘积与另外两个32位有符号数相加,结果的低32位和高32位分别存储在指定的寄存器中。
- 指令格式和应用场景:
- 以UMULL为例,指令格式为
UMULL{cond}{S} RdLo, RdHi, Rm, Rs
,其中RdLo
存放乘积的低32位,RdHi
存放乘积的高32位。长乘法指令在高精度计算、密码学中的大整数运算(如RSA算法中的大整数乘法)等场景中非常有用。例如,在实现一个简单的64位无符号整数乘法时:UMULL R0, R1, R2, R3
,将R2
和R3
中的32位无符号整数相乘,结果的低32位存于R0
,高32位存于R1
。
- 以UMULL为例,指令格式为
- 功能描述:
3.3.7 数据交换指令
-
SWP(交换)指令
- 指令格式:
SWP{cond}{B} Rd, Rm, [Rn]
。{cond}
:条件码,用于指定指令执行的条件,如EQ
(相等)、NE
(不相等)等。{B}
:字节操作选项。如果有{B}
,则进行字节交换;无{B}
则默认进行32位字交换。Rd
:目标寄存器,用于存放从内存读取的数据,同时其原有数据将写入内存。Rm
:备份寄存器,用于提供要写入内存的数据。[Rn]
:以Rn
寄存器的值为地址的内存单元,该内存单元的数据将与Rd
的数据交换。
- 工作原理:首先,将
Rn
指向的内存单元中的数据读取并存储到Rd
中。然后,把Rm
中的数据写入Rn
指向的内存单元,以此完成数据交换。例如SWP R0, R1, [R2]
,先把R2
指向内存的数据存入R0
,再把R1
的数据写入R2
指向的内存。 - 应用场景:
- 互斥锁实现:用于实现多任务环境下的互斥锁。例如,有一个内存地址存储互斥锁变量
lock
(初始值为0,表示未锁定)。当任务要获取锁时,执行SWP R0, R1, [R3]
(R3
存储lock
地址,R1
初始化为1),若R0
读取为0,则获取锁成功并将lock
设为1;若R0
读取为1,则表示锁已被占用,任务等待。 - 数据同步更新:在多处理器共享数据场景下,确保数据更新的一致性。例如双处理器系统更新共享内存数据结构,通过SWP指令可避免一个处理器更新时被另一个干扰。
- 互斥锁实现:用于实现多任务环境下的互斥锁。例如,有一个内存地址存储互斥锁变量
- 注意事项:
- 原子性:SWP指令的原子性很重要,保证了数据交换过程不被中断,避免多任务或多处理器环境下的数据不一致。
- 数据大小选择:根据数据类型(字节或32位字)正确选择是否使用
{B}
选项。
- 指令格式:
-
SWPB(交换字节)指令
- 指令格式:
SWPB{cond} Rd, Rm, [Rn]
。与SWP指令类似,但专门用于字节交换。 - 应用场景:适用于处理字节类型数据交换。例如在简单的通信协议中,接收和发送字节数据并在寄存器和内存间交换字节时非常有用。
- 指令格式:
-
字节序交换指令(如REV、REV16、REVSH)
- REV指令:
- 功能:用于反转32位寄存器中的字节顺序。例如,将寄存器中的数据从大端序转换为小端序,或者反之。
- 指令格式:
REV Rd, Rm
,其中Rd
是目标寄存器,Rm
是源寄存器。 - 应用场景:在处理不同字节序的数据时非常有用。例如,当从一个大端序存储的设备读取数据到ARM处理器(通常采用小端序),可以使用REV指令进行字节序转换。
- REV16指令:
- 功能:对寄存器中的两个半字(16位)分别进行字节序反转。
- 指令格式:
REV16 Rd, Rm
。 - 应用场景:适用于处理半字数据,如在某些音频或视频处理应用中,对16位音频样本或像素数据进行字节序调整。
- REVSH指令:
- 功能:对寄存器中的低半字(16位)进行字节序反转,并将结果符号扩展到32位,用于有符号半字数据的字节序转换。
- 指令格式:
REVSH Rd, Rm
。 - 应用场景:在处理有符号半字数据的字节序转换,如在一些通信协议中处理有符号短整型数据时很有用。
- REV指令:
3.3.8 移位指令
- 逻辑左移(LSL)指令
- 功能描述:
- LSL(Logical Shift Left)指令用于将寄存器中的数据向左移动指定的位数。左移时,低位补0,高位的数据会因为移动而丢失。移动的位数可以是一个立即数或者由另一个寄存器指定。
- 指令格式:
LSL{cond}{S} Rd, Rm, #shift_imm
或LSL{cond}{S} Rd, Rm, Rs
。其中,{cond}
是条件码,用于指定指令执行的条件;{S}
表示是否更新条件码标志位;Rd
是目标寄存器,用于存储移位后的结果;Rm
是源寄存器,包含要移位的数据;#shift_imm
是立即数,表示移位的位数(范围通常是0 - 31);Rs
是一个寄存器,其值用于指定移位的位数。
- 示例及应用场景:
- 例如,
LSL R0, R1, #2
,将寄存器R1
中的数据向左移动2位,结果存放在R0
中。这相当于将R1
中的值乘以4,在乘法运算中,如果乘数是2的幂次方,就可以用LSL指令来高效地实现。例如,在计算内存地址偏移量时,如果每个元素的大小是4字节(32位),要访问第n
个元素的地址,可以先将元素索引n
存放在一个寄存器中,然后使用LSL指令将其左移2位,得到正确的偏移量。
- 例如,
- 功能描述:
- 逻辑右移(LSR)指令
- 功能描述:
- LSR(Logical Shift Right)指令是将寄存器中的数据向右移动指定的位数。右移时,高位补0,低位的数据会因为移动而丢失。
- 指令格式和应用场景:
- 格式与LSL类似,如
LSR{cond}{S} Rd, Rm, #shift_imm
或LSR{cond}{S} Rd, Rm, Rs
。它常用于将一个无符号数进行除法运算,当除数是2的幂次方时,通过右移相应的位数来实现。例如,LSR R2, R3, #3
相当于将R3
中的无符号数除以8,结果存放在R2
中。在处理位域(bit - field)提取时,也可以使用LSR指令将需要的位移动到最低位,方便后续操作。
- 格式与LSL类似,如
- 功能描述:
- 算术左移(ASL)指令
- 功能描述:
- ASL(Arithmetic Shift Left)指令在功能上和LSL指令基本相同,也是将数据向左移动,低位补0,高位的数据会丢失。在一些ARM处理器的指令集中,ASL是LSL的别名,它们执行相同的操作。
- 功能描述:
- 算术右移(ASR)指令
- 功能描述:
- ASR(Arithmetic Shift Right)指令用于将寄存器中的有符号数据向右移动指定的位数。右移时,符号位(最高位)保持不变,用于保持数据的符号性质,低位的数据会因为移动而丢失。
- 指令格式和应用场景:
- 格式如
ASR{cond}{S} Rd, Rm, #shift_imm
或ASR{cond}{S} Rd, Rm, Rs
。在有符号数的除法运算中,如果除数是2的幂次方,可以使用ASR指令来实现。例如,ASR R4, R5, #2
可以将有符号数R5
除以4,结果存放在R4
中,同时保持符号位不变。在对有符号数进行位域操作,如提取符号位扩展后的数值部分时,ASR指令也很有用。
- 格式如
- 功能描述:
- 循环左移(ROL)指令
- 功能描述:
- ROL(Rotate Left)指令将寄存器中的数据向左循环移动指定的位数。即移出的高位数据会循环移到低位,形成一个循环移位的效果。
- 指令格式和应用场景:
- 格式类似其他移位指令,如
ROL{cond}{S} Rd, Rm, #shift_imm
或ROL{cond}{S} Rd, Rm, Rs
。在加密算法(如简单的位循环加密)、数据编码等领域有应用。例如,在一个简单的位循环编码算法中,通过ROL指令将数据循环左移一定位数来实现编码操作。
- 格式类似其他移位指令,如
- 功能描述:
- 循环右移(ROR)指令
- 功能描述:
- ROR(Rotate Right)指令是将寄存器中的数据向右循环移动指定的位数。移出的低位数据会循环移到高位。
- 指令格式和应用场景:
- 格式为
ROR{cond}{S} Rd, Rm, #shift_imm
或ROR{cond}{S} Rd, Rm, Rs
。在数据校验、错误检测等场景中有应用。例如,在一些简单的循环冗余校验(CRC)算法的实现中,可能会用到ROR指令来对数据进行循环右移操作。
- 格式为
- 功能描述:
- 带扩展的循环右移(RRX)指令
- 功能描述:
- RRX(Rotate Right with eXtension)指令将寄存器中的数据向右循环移动1位,并且将CPSR(程序状态寄存器)中的C(Carry)位的值移到最高位,同时将最低位的值移到C位。
- 指令格式和应用场景:
- 格式为
RRX{cond}{S} Rd, Rm
。在多字节加法、减法运算中,用于处理进位或借位的循环传递,确保运算结果的准确性。例如,在实现高精度的有符号数加法或减法运算时,RRX指令可以帮助处理进位和借位的传递。
- 格式为
- 功能描述:
3.4 Thumb指令及应用
-
定义与背景
- Thumb指令集是ARM架构下的一种指令集。ARM处理器传统上使用32位的ARM指令集,但为了提高代码密度(在给定的存储容量下能存储更多的指令),ARM公司开发了Thumb指令集。Thumb指令集的指令长度主要是16位,相比32位的ARM指令,它可以使程序占用更少的存储空间。
-
指令特点
- 代码密度高:由于指令长度较短,通常为16位,在存储容量有限的情况下,可以存储更多的指令。这对于一些对存储资源较为敏感的应用场景,如嵌入式系统中的小型设备,具有很大的优势。例如,在一个资源受限的物联网传感器节点中,使用Thumb指令集能够在有限的闪存空间中存储更复杂的程序逻辑。
- 执行效率权衡:Thumb指令的执行效率相对ARM指令在某些情况下可能会稍低。因为Thumb指令集是ARM指令集的一个子集,并且为了缩短指令长度,有些操作可能需要更多的指令来完成相同的功能。不过,在现代处理器设计中,通过指令流水线和缓存技术等优化手段,这种效率差异在很多情况下可以被减小。
- 所有的Thumb指令都有对应的arm指令,当处理器在执行Arm程序段时称arm处理器处于arm工作状态,当处理器在执行Thumb程序段时,称arm处理器处于Thumb工作状态。
- 与ARM指令集的兼容性:Thumb指令集能够很好地与ARM指令集配合使用。ARM处理器可以在ARM指令集和Thumb指令集之间进行切换,这使得程序员可以根据实际应用场景灵活选择使用哪种指令集。例如,在对性能要求较高的关键代码部分使用ARM指令集,而在对代码密度要求更高的部分使用Thumb指令集。
- 与arm指令集相比,Thumb指令集的数据处理指令的操作位数仍是32位,指令地址也是32位,但是指令集实现了16位的指令长度。舍弃了一些arm指令集的特性,如大多数的Thumb指令集是无条无条件执行的。
- 由于Thumb指令的长度是16位,也就是只用到了arm指令一半的位数来实现相同的功能,所以要实现特定的程序的功能。所需要的Thumb指令的条数就要比Arm的指令条数多。
- Thumb指令与ARM指令的时间效率和空间效率
4. 若使用16位的存储器,samba代码比rm代码快约40%~50%。
5 与Arm代码比较,使用上部代码存储器的功耗会降低一个30%。
- 如果对系统的性能要求比较高,应该使用32位的存储系统和arm指令集。如果对系统的成本和功耗有较高要求,则应该使用16位的存储系统和Thumb指令集。
-
指令格式与类型
- 数据处理指令:
- 包括算术运算指令(如加法、减法等)和逻辑运算指令(如与、或、异或等)。例如,Thumb指令集中有
ADD
指令用于两个操作数相加,但其指令格式和操作数的指定方式与ARM指令集有所不同。它可以实现如寄存器与寄存器相加、寄存器与立即数相加等操作,用于简单的数值计算。 - 还有数据传送指令,用于在寄存器之间或者寄存器与内存之间传输数据。像
MOV
指令可以将一个寄存器的值传送到另一个寄存器,或者将立即数传送到寄存器,这在数据初始化和变量赋值等操作中经常使用。
- 包括算术运算指令(如加法、减法等)和逻辑运算指令(如与、或、异或等)。例如,Thumb指令集中有
- 分支指令:
- 提供了无条件分支和条件分支指令。例如
B
(无条件跳转)指令可以使程序跳转到指定的地址。BEQ
(相等则跳转)、BNE
(不相等则跳转)等条件分支指令则根据之前比较操作的结果(通过设置的条件码标志位来判断)决定是否跳转,这些指令用于实现程序的分支结构,如循环和条件判断。
- 提供了无条件分支和条件分支指令。例如
- 加载/存储指令:
- 用于从内存加载数据到寄存器或者将寄存器中的数据存储到内存。例如
LDR
(加载字)指令可以从内存地址中读取32位的数据到寄存器,STR
(存储字)指令则相反,将寄存器中的32位数据存储到指定的内存地址,这对于操作内存中的数据(如数组、结构体等)是必不可少的。
- 用于从内存加载数据到寄存器或者将寄存器中的数据存储到内存。例如
- 数据处理指令:
-
应用场景
- 嵌入式系统:在资源受限的嵌入式设备中广泛应用,如智能手表、智能家居传感器等。这些设备通常具有较小的内存空间和较低的功耗要求,Thumb指令集的高代码密度特性有助于在有限的存储资源内实现更多的功能。
- 移动设备:早期的移动电话等移动设备也大量使用Thumb指令集。因为在这些设备中,存储容量和电池电量都是宝贵的资源,Thumb指令集能够在保证一定性能的前提下,减小程序占用的空间和功耗。
4 S3C6401处理器
- 概述
- S3C6410是三星(Samsung)公司开发的一款基于ARM1176JZF - S内核的高性能、低功耗32位RISC(精简指令集计算机)微处理器。它主要应用于移动设备、嵌入式系统等领域,如智能手机、PDA(个人数字助理)和便携式多媒体播放器等。
- 处理器核心特性
- 高性能内核:ARM1176JZF - S内核具有较高的处理能力。它采用了哈佛架构(Harvard architecture),这种架构将指令和数据存储在不同的存储空间中,使得数据的读取和指令的执行可以同时进行,从而提高了处理速度。例如,在执行复杂的多媒体任务时,如视频解码,能够快速地获取指令和数据,提升系统性能。
- 工作频率:该处理器可以在不同的电压下运行不同的频率。在ARM Core电压为1.1V时,能够运行到553MHz;在1.2V的情况下,可以运行到667MHz,能够根据系统的功耗需求和性能要求灵活调整。
- 内部总线结构:通过AXI(Advanced eXtensible Interface)、AHB(Advanced High - performance Bus)和APB(Advanced Peripheral Bus)组成的64/32bit内部总线与外部模块相连。AXI总线提供了高性能、高带宽的通信通道,适用于高速数据传输的模块,如内存控制器等;AHB总线用于连接一些对带宽要求较高的外设;APB总线则主要用于连接一些低速的外围设备,这种分层的总线结构优化了系统的整体性能。
- 存储系统
- 内存控制器:
- 拥有两个片选,能够支持多种类型的内存,包括SDRAM(同步动态随机存取存储器)、DDR SDRAM(双倍数据速率同步动态随机存取存储器)、mobile SDRAM和mobile DDR SDRAM。每个片选最大支持256MB的内存容量,这为系统提供了足够的内存扩展空间。例如,在智能手机应用中,可以使用DDR SDRAM来满足系统运行多个应用程序和处理多媒体数据的内存需求。
- NandFlash控制器:
- 支持SLC(Single - Level Cell)/MLC(Multi - Level Cell)NandFlash。SLC NandFlash具有写入速度快、寿命长的特点,而MLC NandFlash存储容量大、成本低。它还支持512/2048Bytes Page的Nandflash,支持8 - Bit Nandflash,并且能够进行1/4/8 - Bit ECC(Error - Correcting Code)校验,用于检测和纠正数据存储过程中的错误,保证数据的完整性。同时,支持NandFlash Boot功能,使得系统可以直接从NandFlash启动。
- OneNAND控制器:
- 支持2个OneNAND控制器,可外接16 - Bit OneNand Flash。它可以支持同步异步读取数据,并且具备OneNAND Boot功能,在一些对数据存储速度和可靠性有要求的应用场景中发挥作用。
- SROM控制器:
- 具有六个片选,支持SRAM(静态随机存取存储器)、ROM(只读存储器)和NOR Flash。支持8/16 - Bit数据位宽,每个片选支持128MB,为系统提供了多样化的存储选择,方便存储程序代码和数据。
- 内存控制器:
- 系统外设
- RTC(实时时钟):
- 系统掉电时由备份电池支持,确保时间信息不会丢失。需要外接32.768KHz时钟,年/月/日/时/分/秒等时间信息都是以BCD(Binary - Coded Decimal)码格式存储,这种格式便于时间信息的处理和显示。
- PLL(锁相环):
- 支持三个PLL,分别是APLL、MPLL和EPLL。APLL为ARM提供时钟,产生ARMCLK;MPLL为所有和AXI/AHB/APB相连的模块提供时钟,产生HCLK和PCLK;EPLL为特殊的外设提供时钟,产生SCLK。通过PLL可以灵活地调整系统各个部分的时钟频率,以满足不同模块的性能需求,同时降低功耗。
- TIMER/PWM(定时器/脉冲宽度调制):
- 支持5个32 - Bit Timer。其中Timer0和Timer1具有PWM功能,可用于控制电机转速、调节灯光亮度等应用。而Timer2、3、4没有输出管脚,为内部Timer,主要用于系统内部的定时操作,如定时中断等。
- WATCHDOG(看门狗定时器):
- 可当作16 - Bit的内部定时器。它的主要作用是防止系统出现故障或死锁,当系统在规定时间内没有对看门狗定时器进行复位操作时,会触发系统复位,从而提高系统的可靠性。
- DMA(直接内存访问):
- 支持4个DMA控制器,每个控制器包含8个通道,支持8/16/32 - Bit传输,并支持优先级,其中通道0优先级最高。DMA可以在不需要CPU干预的情况下,直接在内存和外设之间传输数据,大大提高了数据传输效率,减轻了CPU的负担,尤其适用于高速数据传输的场景,如音频、视频数据的传输。
- KEYPAD(键盘):
- 支持8x8键盘,与GPIO(通用输入输出端口)复用。按下和抬起按键都可产生中断,方便系统对键盘输入进行及时响应,可应用于设备的按键输入功能。
- RTC(实时时钟):
- 通信接口
- I2S(Inter - IC Sound):
- 用于和外接的Audio Codec传输音频数据,支持普通的I2S双通道以及5.1通道I2S传输。音频数据可以是8/16/32 - Bit,采样率从8KHz到192KHz,能够满足高质量音频传输的需求,适用于多媒体设备中的音频处理部分。
- I2C(Inter - Integrated Circuit):
- 支持2个I2C控制器。I2C是一种简单的双向二线制同步串行总线,主要用于连接微控制器及其外围设备,在连接一些低速设备,如传感器、EEPROM(电可擦可编程只读存储器)等方面应用广泛。
- UART(通用异步收发传输器):
- 支持4个UART口,支持DMA和Interrupt模式,UART0/1/2还支持IrDA1.0功能,UART最高速度达3Mbps。UART是一种通用的串行通信接口,常用于设备之间的异步通信,如与PC进行调试信息的传输等。
- GPIO(通用输入输出端口):
- 提供了通用的输入输出功能,其端口功能可以复用,通过软件配置可以实现多种功能,如作为输入读取外部信号,或者作为输出控制外部设备等,是连接外部设备的重要接口。
- IrDA(红外数据协会):
- 独立的IrDA控制器,兼容IrDA1.1,支持MIR(中等速率红外)和FIR(高速红外)模式,用于实现设备之间的红外通信,在一些近距离无线数据传输的应用场景中使用。
- SPI(串行外设接口):
- 支持全功能的SPI。SPI是一种高速的全双工串行通信接口,主要用于连接微控制器和外围设备,如闪存、传感器等,能够实现高速的数据传输。
- MODEM(调制解调器):
- 内置8KB SRAM用于S3C6410和外接Modem交换数据,该SRAM还可为Modem提供Boot功能,方便系统通过调制解调器进行通信,如在移动设备中实现数据的远程传输。
- USB OTG(USB On - The - Go):
- 支持USB OTG 2.0,同时支持Slave和Host功能,最高速度480Mbps。USB OTG功能使得设备可以在主机和从机模式之间灵活切换,方便设备之间的数据共享和通信,例如连接外部USB存储设备或与其他USB设备进行数据交换。
- USB HOST:
- 独立的USB Host控制器,支持USB Host 1.1,用于连接USB设备,如USB鼠标、键盘等,扩展了设备的输入输出功能。
- MMC/SD(多媒体卡/安全数字卡):
- SD/MMC控制器,兼容SD Host 2.0,SD Memory Card 2.0,SDIO Card 1.0和High - Speed MMC。用于连接SD卡或MMC卡,实现大容量数据存储,如存储照片、视频等多媒体数据。
- PCM AUDIO(脉冲编码调制音频):
- 支持两个PCM Audio接口,传输单声道16 - Bit音频数据,主要用于音频数据的传输和处理,是音频系统的重要组成部分。
- AC97(Audio Codec '97):
- AC97控制器,支持独立的PCM立体声音频输入,单声道MIC输入和PCM立体声音频输出,通过AC - Link接口与Audio Codec相连,用于音频信号的输入输出处理,提供了高质量的音频功能。
- I2S(Inter - IC Sound):
- 多媒体处理
- TFT LCD(薄膜晶体管液晶显示器)控制器:
- 支持TFT 24 - Bit LCD屏,分辨率能支持到1024x1024,用于驱动液晶显示屏,实现高质量的图像显示,在便携式设备的显示屏驱动方面发挥关键作用,如智能手机、平板电脑等设备的屏幕显示。
- TFT LCD(薄膜晶体管液晶显示器)控制器:
4.5 存储器映射
- 概述
- S3C6401的存储器映射是指将不同类型的存储器和外围设备映射到处理器的地址空间中,使得处理器能够通过统一的地址访问这些资源。这对于系统的集成和软件编程是非常重要的。
S3C6401的主存地址范围是0x0000_0000至0x6FFF_FFFF,此范围又被划分为以下几个区域 :
- S3C6401的存储器映射是指将不同类型的存储器和外围设备映射到处理器的地址空间中,使得处理器能够通过统一的地址访问这些资源。这对于系统的集成和软件编程是非常重要的。
- 引导镜像区:地址范围从0x0000_0000至0x07FF_FFFF,大小为128MB,但实际上这个区域并没有真实对应的存储器设备,它存有引导芯片的信息,复位后处理器的程序计数器PC会跳到0x0000_0000处开始运行指令.
- 内部内存区:地址范围是0x0800_0000至0x0FFF_FFFF,共128MB,内部的ROM和SRAM都分布在这个区间。其中,0x0800_0000至0x0BFF_FFFF对应着内部ROM,不过实际的内部ROM只有32KB是有实际存储介质的只读区,存放IROM方式下的启动代码;0x0C00_0000至0x0FFF_FFFF对应内部SRAM,实际可用的SRAM一般为4KB.
- 静态存储区:地址范围从0x1000_0000至0x3FFF_FFFF,共3*128MB。这个区域用于访问挂在外部总线上的设备,如SRAM、NOR flash、oneNand等,该区域被分割为6个bank,每个bank为128MB,数据宽度最大支持16bit.
- 动态存储区:物理地址为0x4000_0000至0x6FFF_FFFF,共3*256MB,其中第一个256MB为保留区,实际使用的动态内存区为0x5000_0000至0x6FFF_FFFF,又分为2个区间,分别占256MB,可以通过DMC的Xm1CS(1:0)来进行这2个区间的选择,此内存区主要是用于扩展DRAM,最大可扩展512MB的DRAM.
-
内存区域映射
- SDRAM区域:S3C6401的内存控制器可以支持SDRAM(同步动态随机存取存储器)。SDRAM区域在存储器映射中有特定的地址范围,用于存储程序代码和运行时的数据。例如,它可能被映射到从某个起始地址(如0x30000000)开始的一段连续地址空间,其大小取决于系统配置和SDRAM芯片的容量。这样,当处理器需要访问存储在SDRAM中的数据或指令时,就可以通过这个地址范围进行读取和写入操作。
- NAND Flash区域:NAND Flash用于存储大量的数据,如系统的启动代码、操作系统内核等。在存储器映射中,NAND Flash也有自己的地址空间。通常,处理器通过特殊的NAND Flash控制器来访问NAND Flash。它可能会被映射到与SDRAM不同的地址范围,比如从0x00000000开始(这是一种常见的映射方式,实际情况可能因系统设计而不同)。由于NAND Flash的读写特性与SDRAM不同,所以处理器在访问时需要使用专门的指令或驱动程序。
- 其他存储区域:还包括SRAM(静态随机存取存储器)等存储设备的映射区域。SRAM速度快,常被用于存储一些对读写速度要求极高的临时数据。它在存储器映射中的地址范围会根据系统设计来确定,可能与SDRAM和NAND Flash的地址区域相互独立,方便处理器进行区分访问。
-
外围设备映射
- GPIO(通用输入输出端口)区域:GPIO用于连接外部设备,如按键、LED等。在存储器映射中,GPIO寄存器会被分配一个特定的地址范围。例如,这些寄存器可能被映射到0x7F008000 - 0x7F008FFF之间的地址。通过对这些地址进行读写操作,软件可以控制GPIO引脚的输入输出状态。比如,向某个GPIO寄存器写入数据可以设置引脚为高电平或低电平,从而控制外部设备的开启或关闭。
- UART(通用异步收发传输器)区域:UART用于串行通信,在存储器映射中有对应的寄存器地址。这些寄存器存储了UART的配置信息,如波特率设置、数据格式(数据位、停止位、奇偶校验位等)、发送和接收缓冲区等。当处理器需要发送数据时,它会将数据写入UART的发送寄存器,而接收数据时则从接收寄存器读取。其地址可能在0x7F005000 - 0x7F005FFF左右的范围,通过对这些地址的操作可以实现处理器与外部设备(如PC通过串口进行通信)的串行通信。
- I2C、SPI等接口区域:I2C和SPI也是常用的外围接口。I2C用于连接低速的外围设备,如温度传感器、EEPROM等;SPI主要用于连接高速的外围设备,如闪存芯片。它们在存储器映射中都有各自的寄存器地址范围。对于I2C,其寄存器可能位于0x7F00B000 - 0x7F00BFFF附近,通过对这些寄存器的操作可以配置I2C总线的工作模式、发送和接收数据等。SPI的寄存器地址范围根据系统设计而定,通过操作这些寄存器可以控制SPI设备的通信,如设置时钟频率、数据传输模式等。
-
特殊功能寄存器区域
- S3C6401还有一些特殊功能寄存器,用于控制处理器的各种核心功能,如时钟管理、中断控制等。这些寄存器在存储器映射中有特定的位置。例如,时钟管理寄存器可能用于设置处理器各个模块的时钟频率,它们的地址可能在0x7E00F000 - 0x7E00FFFF之间。通过对这些寄存器的操作,可以调整系统的时钟配置,以满足不同的性能和功耗需求。中断控制寄存器则用于管理和响应外部和内部的中断请求,其地址范围也在特定的区域,通过对这些寄存器的读写可以设置中断优先级、使能或禁止中断等操作。
5 GPIO接口
-
GPIO概述
- GPIO(General - Purpose Input/Output)即通用输入/输出端口,是S3C6401芯片中非常重要的外设接口。它提供了一种灵活的方式来连接外部设备,使得芯片能够与各种外部组件(如按键、LED、传感器等)进行通信。
-
引脚数量和分组
- S3C6401有多个GPIO端口,这些端口被分组为Port A - Port H等。每个端口包含数量不等的引脚。例如,Port A可能有23个引脚,Port B可能有11个引脚等,具体的引脚数量和功能定义是由芯片的硬件设计决定的。
- 总体而言,S3C6401大约有128个左右的GPIO引脚。这些引脚可以灵活地配置为输入、输出或者复用功能模式,以满足各种不同的应用场景需求,如连接外部设备(如按键、LED、传感器等)或者实现通信接口(如UART、I2C、SPI等)的功能。
-
功能模式
- 输入模式:当GPIO引脚被配置为输入模式时,它可以读取外部设备的信号状态。例如,连接一个按键到GPIO引脚,当按键按下或松开时,会产生不同的电平信号,通过将该引脚设置为输入模式,芯片就能够检测到这些电平变化,从而实现对按键事件的监测。
- 输出模式:在输出模式下,芯片可以通过GPIO引脚向外部设备输出电平信号。例如,连接一个LED到GPIO引脚,通过将引脚设置为输出模式,并控制引脚的电平为高或低,就可以实现LED的点亮或熄灭。
- 复用功能模式:除了基本的输入输出功能外,GPIO引脚还可以被配置为复用功能模式。在这种模式下,引脚可以用于实现一些特殊的功能,如UART(通用异步收发传输器)、I2C(内部集成电路)、SPI(串行外设接口)等通信接口的功能。这些通信接口在芯片内部有对应的硬件模块,当需要使用这些接口时,相关的GPIO引脚就会被配置为复用功能模式,以满足通信的需求。
-
寄存器配置
- 控制寄存器:S3C6401通过一系列的寄存器来配置GPIO引脚的功能和状态。其中,控制寄存器用于设置引脚的工作模式(如输入、输出、复用等)。每个端口组通常都有自己对应的控制寄存器,通过向这些寄存器写入特定的值,可以对该组内的引脚进行统一的模式配置。例如,写入0x0表示将引脚配置为输入模式,写入0x1表示配置为输出模式等。
- 数据寄存器:数据寄存器用于在输出模式下设置引脚的输出电平,或者在输入模式下读取引脚的输入电平。对于输出模式,向数据寄存器的相应位写入1或0,可以使对应的GPIO引脚输出高电平或低电平。对于输入模式,读取数据寄存器的相应位可以获取外部设备输入到引脚的电平状态。
- 上拉/下拉电阻寄存器:为了确保输入信号的稳定性,GPIO引脚还可以配置上拉或下拉电阻。上拉电阻寄存器和下拉电阻寄存器用于设置引脚是否连接上拉电阻或下拉电阻,以及电阻的大小(如果有多种可选电阻值的话)。通过合理配置上拉/下拉电阻,可以防止引脚处于浮空状态,避免信号干扰。
-
电气特性
- GPIO引脚具有特定的电气特性,包括输出驱动能力、输入电压范围等。输出驱动能力决定了引脚能够提供的最大电流,这对于连接一些需要较大电流驱动的外部设备(如大功率LED)非常重要。如果输出驱动能力不足,可能无法正常驱动外部设备。输入电压范围则规定了引脚能够正确识别的输入电平范围,超出这个范围的电压可能会导致引脚损坏或读取错误的信号。
-
中断功能
- S3C6401的GPIO引脚还可以支持中断功能。当引脚配置为输入模式并且使能中断后,外部设备输入信号的变化(如电平从高到低或从低到高的跳变)可以触发中断请求。在芯片内部,有相应的中断控制器来处理这些GPIO中断。一旦发生中断,芯片可以暂停当前的任务,转而执行中断服务程序,以快速响应外部设备的变化,这种中断机制在实时监测外部事件(如按键按下、传感器触发等)方面非常有用。
8 ADC和触摸屏接口
采样转换的4个过程采样、保持、量化以及编码。
S3C6410的ADC具有8位通道模拟输入10位转换精度的cmos型模数转换器,它将模拟的输入信号转换成数字编码。最大转换速率是500ksps和2.5MHz的ADC时钟。
-
概述
- S3C6410集成了模数转换器(ADC),它用于将外部的模拟信号转换为数字信号,从而使得处理器能够对这些模拟信号进行数字处理。这在很多应用场景中非常重要,如温度传感器、光照强度传感器等模拟信号的采集。
-
主要特性
- 分辨率:S3C6410的ADC通常具有一定的分辨率,例如10 - bit分辨率。这意味着它可以将模拟输入信号量化为2^10(即1024)个不同的数字级别。较高的分辨率能够更精确地表示模拟信号的变化,但同时也可能需要更多的处理时间和存储空间来处理这些更精细的数据。
- 转换通道数量:它拥有多个ADC转换通道,这些通道可以同时或分别连接不同的外部模拟信号源。例如,可能有8个或更多的通道,方便同时采集多个传感器的模拟信号,如在一个环境监测系统中,可以通过不同通道分别采集温度、湿度、光照等模拟信号。
- 转换速度:具有一定的转换速度范围,以每秒转换次数(SPS - Samples Per Second)来衡量。其转换速度可以满足多种应用的需求,如对于一些变化缓慢的模拟信号(如温度信号),较低的转换速度就可以满足要求;而对于一些快速变化的信号(如音频信号),则可能需要较高的转换速度。
-
工作原理
- 采样阶段:ADC首先对外部模拟信号进行采样。它通过内部的采样保持电路,在一个特定的时间点获取模拟信号的瞬时值。这个采样时间是可以配置的,合适的采样时间能够确保采集到准确的模拟信号值。
- 量化和编码阶段:采样后的模拟信号值被转换为数字值。在量化过程中,根据ADC的分辨率,将模拟信号的幅度范围划分为多个离散的区间。例如,对于10 - bit分辨率的ADC,模拟信号的幅度范围被划分为1024个区间。然后,每个区间被赋予一个唯一的数字编码,这个编码就是最终转换得到的数字信号。
-
寄存器配置
- 控制寄存器:通过对控制寄存器的操作来配置ADC的工作模式。例如,可以设置转换通道、转换速度、采样时间等参数。用户可以根据具体的应用需求,向控制寄存器写入合适的值来启动和控制ADC转换过程。
- 数据寄存器:转换完成后,数字结果被存储在数据寄存器中。处理器可以读取这个寄存器来获取转换后的数字信号,然后进行后续的数字处理,如计算、存储或传输等操作。
-
应用场景
- 环境监测:在环境监测设备中,ADC可用于采集温度传感器(如热敏电阻)、湿度传感器、光照传感器等的模拟信号。将这些模拟信号转换为数字信号后,通过软件算法进行处理,从而得到环境参数的数值,并可以进一步实现环境参数的监测、记录和报警等功能。
- 电池电量监测:用于监测电池的电压,将电池的模拟电压信号转换为数字信号,以便系统能够准确地判断电池的剩余电量,从而实现电池管理功能,如低电量报警、充电控制等。
9 Andrioid系统开发概述
9.1 Android的发展
最初的安卓系统由美国的一家小型公司设计开发,公司名称也叫做安卓。
安卓的名称来源于法国作家利尔亚于1886年发表的科幻小说未来夏娃。小说中那个类人机器的名字就是安卓。
2005年,谷歌收购了安卓公司,继续进行安卓系统的研发。
2007年,谷歌正式向外界展示了安卓的移动设备操作系统平台。同年,谷歌宣布建立一个全球性的联盟组织,即开放手机联盟OHA。
- 该组织由34家手机制造商软件开发商电信运营商以及芯片制造商共同组成。
- 该组织将支持谷歌发布的手机操作系统以及应用软件,将共同开发安卓系统的开放源代码。
- 中国三大电信运营商都是OHA成员,移动还是创始成员。
在2008年的谷歌大会上,谷歌提出了安卓HAL架构图。同年安卓获得了美国联邦通信委员会的批准,2008年9月,谷歌正式发布了安卓系统1.0。
2008年,以智能手机为代表的移动设备仍然是以诺基亚的塞班系统为主。在短短几年内,安卓系统迅速成长为一个完整的系统平台,并成为市场占有率最高的系统。
在最初的谷安卓系统1.0发布之后,谷歌又发布了多个安卓版本。每个版本还具有与之对应的api级别,这里的api级别通常是指安卓系统中Java层的api接口。
作为一个具有开放开源属性的系统,安卓具有较多优势。它允许任何移动设备厂商使用安卓系统,从而获得了更多的开发者获得更多的移动应用支持。
此外,安卓移动应用不受审查限制,可以从不同的安卓应用商店下载。由于安卓系统发展过程中得到了众多软硬件厂商运营商的支持,因此安卓系统的发展极为迅速。
安卓系统并不是完全免费的。对于操作系统底层的驱动通常是根据移动设备进行定制开发,这部分是不开源的。
安卓应用主要使用Java语言开发,其运行效率和硬件消耗一直对系统性能具有较大的影响。
9.2 Android系统架构
安卓将系统架构分为4层,从上到下,依次为应用程序层、应用程序框架层、系统运行库层以及Linux内核层。
- 安卓系统的Linux内核层由c语言实现,运行于操作系统的内核空间。
- 系统运行库层由c和c+加实现。
- 应用程序框架层和应用程序层主要有Java语言实现。
从Linux操作系统的角度来看,是内核空间与用户空间的分界线。系统运行库存和应用程序框架层之间是本地代码层和Java代码层的接口。应用程序框架层和应用程序之间是安卓的系统api接口。
9.2.1 应用程序层
应用程序层包含一个核心应用集合,联系人管理、浏览器等,同时开发人员利用Java语言设计和编写的程序也都运行在这一层上,如游戏和音乐播放器。
也就是说安卓系统自带的应用和第三方开发的应用都位于这个层次上。
- 安卓系统的应用是由谷歌官方提供的,会使用一些隐藏的类。
- 而第三方开发的应用是以安卓系统的sdk为基础进行开发的,以Java作为编程语言来编写的应用程序。
安卓系统通过本机开发程序包NDK,提供了从应用层穿越Java框架层与底层包含了JNI接口的c/c++库直接通信的方法。
安卓的应用程序是指主要用Java语言编写的用户程序。其中还包括各种的资源文件等相关资源经过编译后最终生成的一个apk包。
一个完整的安卓应用程序要包括一个或几个组件:
-
Activity(活动)
- 功能概述:Activity是安卓应用中最基本的组件,用于实现用户界面。它就像是一个单独的屏幕,用户可以在上面进行各种交互操作,如点击按钮、输入文字等。例如,在一个购物应用中,商品展示页面、购物车页面、支付页面等都是不同的Activity。
- 生命周期管理:Activity有完整的生命周期,包括onCreate(创建)、onStart(启动)、onResume(恢复)、onPause(暂停)、onStop(停止)、onDestroy(销毁)等方法。这些方法在Activity的不同状态转换时被调用,用于管理资源的分配和释放。例如,在onCreate方法中可以进行视图的初始化和数据的加载,在onDestroy方法中可以释放一些占用的资源,如关闭数据库连接等。
- Intent启动方式:Activity可以通过Intent(意图)进行启动。显式Intent明确指定要启动的Activity类,用于在应用内部进行页面跳转。隐式Intent则通过指定动作(action)和数据(data)来启动能够处理该意图的Activity,这种方式可以用于跨应用启动Activity。例如,一个地图应用可以通过隐式Intent接收来自其他应用的位置信息,并显示对应的地图位置。
-
Service(服务)
- 功能概述:Service是一种在后台运行的组件,没有用户界面。它主要用于执行长时间运行的操作,如音乐播放、文件下载、数据同步等。例如,在音乐播放应用中,播放音乐的功能通常是由Service来实现的,即使应用的界面被切换或者手机屏幕关闭,音乐依然可以继续播放。
- 启动方式和生命周期:Service可以通过startService(启动服务)或bindService(绑定服务)方法启动。startService方法启动的Service会一直运行直到被停止,而bindService方法启动的Service与调用者绑定,当所有绑定的组件都解绑后,Service会被销毁。Service的生命周期包括onCreate(创建)、onStartCommand(启动命令)、onBind(绑定)、onUnbind(解绑)、onDestroy(销毁)等阶段。
- 与其他组件的交互:Service可以通过Broadcast Receiver接收广播消息来执行相应的操作,也可以与Activity等组件进行通信。例如,一个文件下载Service可以在下载完成后通过广播通知Activity更新界面显示下载完成的信息。
-
Broadcast Receiver(广播接收器)
- 功能概述:Broadcast Receiver用于接收系统或应用发出的广播消息。广播消息可以是系统事件,如开机完成、网络连接变化、电池电量变化等,也可以是应用自定义的广播。例如,当手机的网络从Wi - Fi切换到移动数据时,系统会发送一个网络连接变化的广播,Broadcast Receiver可以接收到这个广播并采取相应的措施,如提醒用户网络已切换。
- 注册方式:Broadcast Receiver有两种注册方式,静态注册和动态注册。静态注册是在AndroidManifest.xml文件中声明,这种方式的Broadcast Receiver可以在应用未启动时接收到广播。动态注册是在代码中通过registerReceiver方法进行注册,通常在Activity或Service的生命周期内有效,当组件被销毁时,需要通过unregisterReceiver方法注销,否则可能会导致内存泄漏。
- 应用场景:广播接收器在很多场景下都有应用,如推送通知接收、系统事件响应、应用间通信等。例如,在推送服务中,当服务器推送一条消息到手机时,应用内的Broadcast Receiver可以接收这个消息并在通知栏显示推送内容。
-
Content Provider(内容提供者)
- 功能概述:Content Provider用于在不同的应用之间共享数据。它将数据封装在一个标准的接口后面,其他应用可以通过这个接口来访问和操作数据。例如,安卓系统中的联系人应用通过Content Provider来允许其他应用访问和修改联系人信息。
- 数据共享方式:Content Provider使用统一资源标识符(URI)来标识数据资源。其他应用通过ContentResolver对象,使用Content Provider提供的查询(query)、插入(insert)、更新(update)、删除(delete)等方法来操作数据。例如,一个第三方的短信备份应用可以通过Content Provider访问短信应用中的短信内容,并将短信备份到云端。
- 数据安全与权限管理:Content Provider可以通过权限设置来控制对数据的访问。只有具有相应权限的应用才能访问特定的Content Provider提供的数据。这可以保护数据的安全性和用户的隐私。例如,系统的短信Content Provider通常会设置只有具有读取短信权限的应用才能访问短信内容。
应用程序在安卓系统中运行的主要特征为每一个应用程序都默认运行在一个Linux进程中。安卓系统会自动为需要运行的应用程序启动一个进程,这个进程会一直存在直到说到包含要求退出的信息的代码。
- 安卓系统的每个进程互不干涉都运行在一个相对独立的Dalvik a virtual machine中.
- 在默认情况下,一般每个应用程序都被给予唯一的一个Linux user id。
- 在极端特殊的情况下,两个应用程序也可以用一个id,但是在共用的时候,他们之间是可以彼此访问的。
9.2.2 应用程序框架层
应用程序框架层是安卓应用开发的基础,开发者可以使用谷歌发布的api框架进行程序开发。Api框架简化了程序开发的架构设计,但开发者必须遵守开发的规则。
安卓系统的应用程序框架层包含的组件包含:
-
Activity Manager(活动管理器)
- 功能概述:Activity Manager主要负责管理Activity的生命周期。它决定了一个Activity何时创建、启动、暂停、停止和销毁。例如,当用户从一个应用的主界面跳转到另一个功能界面时,Activity Manager会协调旧Activity的暂停和新Activity的启动过程。
- 任务栈管理:它还负责维护任务栈(Task Stack)。任务栈是一种后进先出(LIFO)的数据结构,用于存储用户打开的Activity序列。例如,当用户依次打开了应用A中的三个Activity(A1、A2、A3),它们会按照A1、A2、A3的顺序堆叠在任务栈中。当用户按下返回键时,Activity Manager会按照栈的顺序,先销毁A3,然后是A2,以此类推。
-
Window Manager(窗口管理器)
- 功能描述:Window Manager负责管理窗口的显示和布局。它控制着屏幕上所有窗口的位置、大小和层级关系。例如,在多窗口模式下,Window Manager会确定各个应用窗口的排列方式,是平铺还是层叠,以及每个窗口的大小和位置等。
- 与视图系统的协作:它和视图系统(View System)紧密协作。视图系统负责构建和绘制用户界面的各个组件,而Window Manager则负责将这些视图组件正确地显示在屏幕上。例如,当一个Activity的视图结构发生变化,如添加或移除了一个视图,Window Manager会根据新的视图布局要求重新调整窗口的显示。
-
Content Providers(内容提供者)
- 数据共享功能:Content Providers是用于在不同应用之间共享数据的组件。它将应用内部的数据封装起来,通过统一的接口供其他应用访问。例如,安卓系统中的联系人应用通过Content Providers允许其他应用(如拨号应用、短信应用等)访问联系人数据。
- 数据访问机制:其他应用通过ContentResolver来与Content Providers交互,使用查询(query)、插入(insert)、更新(update)、删除(delete)等操作来获取或修改共享的数据。例如,一个第三方的联系人管理应用可以使用ContentResolver从系统的联系人Content Providers中读取联系人信息,并进行显示或修改。
-
View System(视图系统)
- 用户界面构建:View System是构建安卓应用用户界面的基础。它包含了各种视图组件(Views)和视图组(View Groups)。视图组件如TextView(用于显示文本)、Button(用于触发操作)等,视图组如LinearLayout(线性布局)、RelativeLayout(相对布局)等用于组织和管理多个视图组件。例如,在一个简单的登录界面中,用户名和密码输入框可以是EditText视图组件,登录按钮是Button组件,它们通过LinearLayout或RelativeLayout等视图组进行布局。
- 事件处理机制:View System还具备事件处理机制,能够处理用户在视图上的各种操作,如点击、滑动、长按等。当用户在一个视图上执行操作时,相应的事件会在视图层次结构中传播,直到被合适的事件处理器(如OnClickListener、OnTouchListener等)处理。例如,当用户点击一个按钮时,按钮的OnClickListener会被触发,执行相应的点击事件处理代码。
-
Package Manager(包管理器)
- 应用管理功能:Package Manager负责安卓系统中所有应用程序包(APK)的管理。它能够获取应用的各种信息,如应用的名称、版本号、权限要求、安装位置等。例如,在应用商店中,当用户查看一个应用的详细信息时,这些信息就是由Package Manager提供的。
- 安装、卸载和更新:它还负责应用的安装、卸载和更新操作。当用户从应用商店下载并安装一个应用时,Package Manager会解析APK文件,将应用安装到合适的位置,并注册应用相关的组件(如Activity、Service等)。在应用更新时,它会比较新旧版本的差异,进行相应的更新操作。
-
Telephony Manager(电话管理器)
- 电话功能支持:Telephony Manager提供了对手机通信功能的管理和访问接口。它可以获取手机的信号强度、网络运营商信息、通话状态(如正在通话、来电等待等)等。例如,在一个手机信号检测应用中,可以通过Telephony Manager获取信号强度数据并显示给用户。
- 与通信相关的服务:它还用于支持一些与电话通信相关的服务,如短信发送和接收的底层管理。虽然具体的短信发送和接收功能通常由其他组件(如短信应用的Activity和Content Providers)实现,但Telephony Manager在其中起到了协调和提供基础数据的作用。
-
Location Manager(位置管理器)
- 位置信息获取:Location Manager用于获取设备的地理位置信息。它可以通过多种方式获取位置,如GPS(全球定位系统)、Wi - Fi定位、基站定位等。例如,在地图应用中,Location Manager提供的位置信息可以帮助应用显示用户当前的位置,并提供导航等服务。
- 位置更新和精度控制:它能够根据应用的需求设置位置更新的频率和精度。例如,一个运动健身应用可能需要高精度的位置更新,以准确记录用户的运动轨迹,而一个基于位置的广告推送应用可能只需要较低精度的位置信息,并且更新频率也可以较低,以节省电量和网络流量。
9.2.3 系统运行层库
-
概述
- Android系统运行层库是Android系统架构中的重要组成部分,它为应用程序的开发提供了丰富的功能支持。这些库以C/C++编写,通过Android的应用程序框架(Application Framework)为Java等高级语言编写的应用程序提供服务,并且借助Android运行时环境(Android Runtime)来执行。
-
主要的系统运行层库
-
SQLite
- 功能:SQLite是一个轻量级的关系型数据库管理系统。它允许应用程序存储和管理结构化的数据。例如,在许多Android应用中,如联系人管理应用、待办事项应用等,SQLite用于存储用户数据。它支持标准的SQL语法,能够进行数据的插入、查询、更新和删除操作。
- 优势:SQLite的优点在于其占用资源少、易于嵌入应用程序。它不需要单独的服务器进程,所有的数据存储和管理操作都在本地文件系统上完成,这使得它非常适合移动设备这种资源有限的环境。
-
OpenGL ES
- 功能:OpenGL ES(OpenGL for Embedded Systems)是用于嵌入式系统的图形处理库。它提供了2D和3D图形渲染的功能,使开发者能够创建具有高质量视觉效果的应用程序。例如,在3D游戏开发中,OpenGL ES用于渲染游戏场景中的各种3D模型、纹理和光照效果。
- 版本与特性:Android支持多个版本的OpenGL ES,如OpenGL ES 1.0、1.1、2.0等。随着版本的升级,功能不断增强,如OpenGL ES 2.0引入了可编程渲染管线,让开发者能够更灵活地控制图形渲染过程,实现更加复杂的图形效果。
-
FreeType
- 功能:FreeType是一个用于字体渲染的库。它允许Android应用程序正确地显示各种字体。在文字处理应用、阅读应用等众多应用中,FreeType发挥着关键作用。它可以将字体文件(如TrueType字体)转换为可以在屏幕上显示的位图,并且支持字体的缩放、旋转等操作。
-
Libc(C标准库)
- 功能:Libc为应用程序提供了基本的C语言函数支持,如字符串处理函数(strcpy、strcat等)、内存管理函数(malloc、free等)、数学函数(sqrt、sin等)。这些函数是很多应用程序正常运行的基础。例如,在进行数据处理、算法实现等过程中,都需要用到这些基本的C函数。
- 与系统结合:Android中的Libc库是经过优化的,以适应移动设备的特点。它与Android的系统调用和底层硬件相结合,确保应用程序在不同的Android设备上都能够高效地运行。
-
Media Framework Libraries
- 功能:这是一组用于处理音频和视频的库。它支持多种音频和视频格式的播放、录制和编码/解码。例如,在视频播放应用中,Media Framework Libraries负责对视频文件(如MP4、AVI等格式)进行解码,并将解码后的视频帧和音频数据进行同步播放。在视频录制应用中,它则用于对摄像头和麦克风采集的音频和视频信号进行编码,生成相应的文件。
- 格式支持:它支持的音频格式包括MP3、AAC等,视频格式包括H.264、VP8等主流格式,并且随着Android系统的更新,不断增加对新格式的支持,以满足用户对多媒体内容的多样化需求。
-
桌面管理器
- 在 Android 系统运行库层中,桌面管理器(Launcher)是一个关键组件。它主要负责管理和显示设备的主屏幕(Home Screen)和应用程序抽屉(App Drawer),是用户与设备上各种应用程序进行交互的重要入口。
-
Web浏览器引擎web kit
- 一个开源的浏览器引擎
-
SGL库
- 基本都2D图形引擎
-
9.2.4 Linux内核层
安卓以Linux内核为基础,借助Linux内核提供核心系统服务,例如安全性内存管理以及进程管理等方面。 Linux内核作为一个抽象层,存在于硬件和软件栈之间。
- 在安卓4.0之前,基于Linux2.6系列内核。
- 在安卓4.0之后使用更新的Linux3.x内核。
- Linux3.3内核中包含了一些安卓代码,可以让开发人员利用Linux内核运行于安卓系统。
- Linux3.4增加了电源管理等更多功能,以增加与安卓的硬件兼容性。
安卓在Linux内核的基础上进行了增强,增加了一些面向移动计算的特有功能。
- 这些内核的增强是安卓在继承Linux内核安全机制的同时,进一步提升了内存管理进程间通信。等方面的安全性。
- 需要注意的是,由于安卓系统使用的Linux内核做了定制化的修改和优化,从内核代码上来说很难融合到Linux的主线内核中。
- 因此,在2010年安卓系统的Linux内核曾被Linux kernel2.6.33版代码库移除,直到2012年才重新回归到Linux3.3代码库。
安卓中主要包含了以下的驱动程序。
-
显示驱动(Display Driver)
- 功能:负责控制和管理屏幕显示相关的功能。它与图形处理单元(GPU)紧密合作,将图形数据转换为屏幕可以显示的信号。例如,在手机屏幕上显示应用程序的用户界面、视频播放内容、游戏画面等。它支持多种显示技术,如液晶显示器(LCD)、有机发光二极管(OLED)等不同类型屏幕的显示控制,包括对屏幕分辨率、刷新率、色彩模式等参数的设置。
- 应用场景:在任何需要在屏幕上显示内容的场景中都发挥关键作用。从简单的系统界面显示,如主屏幕、通知栏等,到复杂的多媒体内容展示,如高清视频播放、3D游戏运行等,都离不开显示驱动的支持。
-
触摸驱动(Touch Driver)
- 功能:主要用于处理触摸屏幕输入的信号。它能够检测触摸屏幕上的触摸位置、触摸压力(如果屏幕支持压力感应)、触摸手势(如滑动、点击、长按、缩放等)等信息,并将这些信息传递给上层的系统软件和应用程序。触摸驱动通过与硬件传感器紧密配合,实现高精度的触摸输入检测,以提供良好的用户触摸交互体验。
- 应用场景:是所有触摸式安卓设备(如智能手机、平板电脑)正常使用的关键驱动。用户在操作设备时,无论是通过触摸屏幕打开应用程序、浏览网页、进行游戏操作还是输入文字等,触摸驱动都在背后负责准确地获取和传递触摸信息。
-
摄像头驱动(Camera Driver)
- 功能:控制摄像头硬件,实现拍照和录像功能。它管理摄像头的各种参数,如焦距、光圈、曝光时间、白平衡等,以确保拍摄出高质量的照片和视频。摄像头驱动还负责将摄像头捕获的光信号转换为数字图像或视频数据,并将这些数据传递给上层的媒体处理库或应用程序进行进一步的处理,如图像编辑、视频录制和直播等。
- 应用场景:在相机应用、视频通话应用、监控应用等众多需要使用摄像头的应用程序中发挥核心作用。例如,当用户使用手机相机拍摄风景照片或者进行视频会议时,摄像头驱动负责启动摄像头、设置拍摄参数,并将拍摄的数据传输给应用程序。
-
音频驱动(Audio Driver)
- 功能:管理音频设备,包括扬声器、麦克风等。它负责音频信号的输入和输出处理。对于音频输出,它将音频数据转换为模拟信号,驱动扬声器播放声音,支持多种音频格式和音频效果,如音量调节、均衡器设置等。对于音频输入,它从麦克风获取声音信号,将其转换为数字音频数据,用于录音或语音识别等应用。
- 应用场景:在音频播放应用(如音乐播放器、视频播放器)、录音应用、语音助手等应用中是必不可少的。例如,当用户使用手机播放音乐时,音频驱动将音乐文件中的音频数据进行解码并输出到扬声器;当用户使用语音助手进行语音输入时,音频驱动从麦克风获取语音信号并传递给语音识别软件。
-
传感器驱动(Sensor Drivers)
- 功能:安卓设备中包含多种传感器,如加速度计、陀螺仪、地磁传感器、环境光传感器、距离传感器等,每种传感器都有对应的驱动程序。这些驱动程序负责与传感器硬件通信,获取传感器测量的数据,并将其传递给上层的系统服务或应用程序。例如,加速度计驱动可以获取设备的加速度信息,用于屏幕自动旋转、运动检测等功能;环境光传感器驱动能够获取环境光照强度,用于自动调节屏幕亮度。
- 应用场景:广泛应用于各种需要感知环境或设备状态的应用场景。如在运动类游戏中,通过加速度计和陀螺仪驱动获取设备的运动状态来控制游戏角色;在自动亮度调节功能中,环境光传感器驱动提供的数据用于根据环境光照强度动态调整屏幕亮度。
-
蓝牙驱动(Bluetooth Driver)
- 功能:实现安卓设备与其他蓝牙设备之间的通信。它负责管理蓝牙设备的配对、连接、数据传输等操作。蓝牙驱动支持多种蓝牙协议和配置文件,如蓝牙音频传输协议(A2DP)用于连接蓝牙耳机,蓝牙人机接口设备协议(HID)用于连接蓝牙键盘、鼠标等。
- 应用场景:在无线音频设备连接(如蓝牙耳机、蓝牙音箱)、无线输入设备连接(如蓝牙键盘、蓝牙鼠标)、文件传输(如与其他蓝牙设备之间传输照片、文档等)等场景中发挥作用。
-
Wi - Fi驱动(WiFi Driver)
- 功能:使安卓设备能够连接到Wi - Fi网络。它负责Wi - Fi设备的初始化、扫描可用网络、连接网络、传输数据等操作。Wi - Fi驱动与底层的Wi - Fi硬件芯片紧密配合,支持不同的Wi - Fi标准,如802.11a/b/g/n/ac/ax等,以实现高速、稳定的无线网络连接。
- 应用场景:在几乎所有需要通过Wi - Fi连接网络的场景中都必不可少,如浏览网页、下载应用程序、在线视频播放、云存储等,只要设备需要通过Wi - Fi与互联网或本地网络中的其他设备进行通信,就需要Wi - Fi驱动的支持。
-
存储驱动(Storage Driver)
- 功能:管理存储设备,如内部闪存、外部SD卡等。存储驱动负责存储设备的初始化、读写操作、分区管理等功能。它将文件系统的操作请求转换为对存储硬件的实际读写操作,确保数据能够正确地存储和读取。例如,它支持常见的文件系统,如FAT32、exFAT、ext4等,以满足不同存储设备和应用场景的需求。
- 应用场景:在设备的所有存储相关操作中都发挥作用,包括安装应用程序、存储用户数据(如照片、视频、文档等)、系统更新等。无论是内部存储用于保存系统文件和应用数据,还是外部存储用于扩展设备的存储容量,都需要存储驱动来进行管理。
9.3 Android系统内核
安卓采用了Linux的内核,所以具有Linux内核的一些特性:
- 强大的内存管理和进程管理方案。
- 基于权限的安全模式。
- 支持共享库。
- 经过认证的驱动模型。
- Linux本身就是开源的项目。
9.3.1 Linux内核结构
Linux的内核主要由5个子系统组成,一是进程调度,二是内存管理,三是虚拟文件系统,四是网络接口,五是进程间通讯。
-
进程管理子系统
- 进程创建与终止:
- 进程是程序的一次执行过程,Linux内核通过
fork()
系统调用创建新进程。例如,当用户在终端输入一个命令来启动一个程序时,内核就会使用fork()
为这个程序创建一个进程。fork()
创建的子进程几乎是父进程的副本,它会复制父进程的地址空间(包括代码段、数据段、堆栈等)。 - 当进程完成任务或者遇到错误等情况时,内核会通过
exit()
系统调用终止进程。在这个过程中,内核会回收进程占用的资源,如内存空间、打开的文件描述符、占用的CPU时间等,以避免资源浪费。
- 进程是程序的一次执行过程,Linux内核通过
- 进程调度:
- 内核采用多种调度算法来分配CPU时间片。如时间片轮转(RR)调度算法,每个进程会被分配一个固定的时间片来使用CPU。假设时间片为10ms,当一个进程的时间片用完后,内核会暂停这个进程,将CPU资源分配给下一个进程,从而实现多个进程的并发执行。
- 优先级调度也是常用的算法之一。进程可以有不同的优先级,内核会优先将CPU资源分配给优先级高的进程。例如,对于实时性要求高的进程(如音频播放进程),内核会赋予它较高的优先级,确保它能及时获得CPU资源,保证音频的流畅播放。
- 进程间通信(IPC):
- 管道(Pipe)是一种简单的IPC机制,用于在具有亲缘关系(父子进程)的进程之间传递数据。例如,在一个命令管道中,一个进程的输出可以作为另一个进程的输入,就像
ls | grep ".txt"
这个命令,ls
进程的输出通过管道传递给grep
进程进行筛选。 - 消息队列(Message Queue)允许不同进程之间发送和接收消息。进程可以将消息发送到消息队列中,其他进程可以从队列中读取消息。这种方式支持消息的异步传递,适用于需要在多个进程之间进行通信和协调的场景。
- 共享内存(Shared Memory)机制允许多个进程共享同一块物理内存区域。这可以大大提高数据共享的效率,因为进程之间不需要进行数据的复制。但是,为了避免多个进程同时访问共享内存时出现冲突,需要配合信号量(Semaphore)等同步机制来进行访问控制。
- 管道(Pipe)是一种简单的IPC机制,用于在具有亲缘关系(父子进程)的进程之间传递数据。例如,在一个命令管道中,一个进程的输出可以作为另一个进程的输入,就像
- 进程创建与终止:
-
内存管理子系统
- 虚拟内存管理:
- 虚拟内存是Linux内存管理的核心概念。每个进程都有自己独立的虚拟地址空间,通过内存分页(Paging)技术与实际的物理内存进行映射。例如,在32位系统中,进程的虚拟地址空间通常为4GB,其中一部分用于用户空间,另一部分用于内核空间。当进程访问虚拟内存中的某个页面时,内核会根据页表(Page Table)来查找对应的物理内存页面。如果该页面不在物理内存中,内核会从磁盘等存储设备中将页面调入物理内存(这个过程称为页面调入,Page - in)。
- 内存分配与回收:
- 内核通过系统调用(如
kmalloc()
、vmalloc()
等函数)为内核空间分配内存,通过malloc()
等函数为用户空间分配内存。当进程申请内存时,内核会从空闲内存列表中查找合适的内存块进行分配。例如,malloc()
函数会在堆(Heap)上为应用程序分配内存,应用程序可以使用这些内存来存储数据结构、变量等。 - 当内存不再需要时,内核会回收这些内存。对于用户空间的内存,应用程序可以通过
free()
函数释放内存。内核会将释放的内存标记为空闲状态,以便后续重新分配。同时,为了避免内存碎片问题,内核采用了一些策略,如伙伴系统(Buddy System)来管理内存块的分配和合并。
- 内核通过系统调用(如
- 内存保护:
- 借助内存管理单元(MMU)等硬件设施,内核实现了内存保护机制。每个进程只能访问自己的虚拟内存空间,当一个进程试图访问不属于它的内存区域时,MMU会触发一个异常,如缺页中断(Page Fault)。内核会处理这些异常,可能是将需要的页面调入物理内存,或者判断为非法访问而终止进程,从而确保不同进程之间的内存互不干扰,提高系统的安全性和稳定性。
- 虚拟内存管理:
-
文件系统子系统
- 文件系统支持:
- Linux内核支持多种文件系统,如ext4、XFS、Btrfs等。ext4是一种广泛使用的文件系统,它具有良好的性能和稳定性,支持文件的创建、删除、读写等基本操作,还支持目录结构、文件权限管理等功能。例如,用户在ext4文件系统的磁盘分区上创建一个新文件,内核会在文件系统的元数据区域记录文件的相关信息(如文件名、文件大小、创建时间等),并在数据区域为文件分配存储空间。
- XFS文件系统适用于高性能的存储环境,它在处理大型文件和高并发读写操作方面表现出色。Btrfs文件系统具有一些先进的特性,如支持快照(Snapshot)功能,用户可以对文件系统的某个状态进行快照,方便数据备份和恢复。
- 虚拟文件系统(VFS)层:
- VFS是Linux文件系统的一个抽象层,它为上层应用程序提供了一个统一的文件操作接口。无论底层使用的是哪种实际的文件系统,应用程序都可以通过相同的系统调用(如
open
、read
、write
、close
等)来操作文件。例如,当应用程序调用open
函数打开一个文件时,VFS层会根据文件所在的文件系统类型(如ext4或XFS),调用相应文件系统的打开文件操作函数来完成文件的打开过程。
- VFS是Linux文件系统的一个抽象层,它为上层应用程序提供了一个统一的文件操作接口。无论底层使用的是哪种实际的文件系统,应用程序都可以通过相同的系统调用(如
- 文件缓存管理:
- 为了提高文件系统的性能,内核会对文件数据进行缓存。当应用程序读取文件时,内核首先会检查缓存中是否已经存在所需文件的数据。如果缓存命中,就直接从缓存中获取数据,而不需要从磁盘等存储设备中读取,大大加快了文件访问速度。例如,对于经常访问的系统配置文件,内核可能会将其数据缓存在内存中,下次读取时就可以快速获取。同时,内核会根据一定的策略(如最近最少使用,LRU)来管理缓存的更新和清理,以确保缓存的有效性和内存的合理利用。
- 文件系统支持:
-
设备驱动子系统
- 设备驱动框架:
- Linux内核提供了一个设备驱动框架,用于编写和管理各种硬件设备的驱动程序。这个框架定义了设备驱动的基本结构和接口,包括设备的初始化、操作函数(如读写操作)、中断处理等。例如,对于一个网卡设备,网卡驱动程序需要按照内核的设备驱动框架编写,实现初始化网卡、发送和接收网络数据包、处理网卡中断等功能。
- 设备驱动加载与卸载:
- 内核能够动态地加载和卸载设备驱动程序。当一个新的设备插入系统(如通过USB接口插入一个移动硬盘)时,内核会检测到设备的接入,并根据设备的类型和识别信息,尝试从已有的驱动程序库中加载合适的驱动程序。在加载过程中,内核会为设备分配资源,如内存空间、中断号等。当设备拔出时,内核会卸载相应的驱动程序,释放设备占用的资源。
- 设备抽象与统一接口:
- 通过设备驱动子系统,内核将不同类型的硬件设备抽象为统一的设备接口。对于上层的软件(如应用程序、系统服务)来说,它们可以以相同的方式访问不同的设备。例如,无论是对磁盘设备(属于块设备)还是对终端设备(属于字符设备)进行读写操作,应用程序都可以使用
read
和write
系统调用,内核会根据设备的类型,通过设备驱动将这些操作转换为对具体设备的实际操作。
- 通过设备驱动子系统,内核将不同类型的硬件设备抽象为统一的设备接口。对于上层的软件(如应用程序、系统服务)来说,它们可以以相同的方式访问不同的设备。例如,无论是对磁盘设备(属于块设备)还是对终端设备(属于字符设备)进行读写操作,应用程序都可以使用
- 设备驱动框架:
-
网络子系统
- 网络协议栈:
- Linux内核包含完整的网络协议栈,支持多种网络协议。从底层的物理层协议(如以太网协议)到链路层、网络层(如IP协议)、传输层(如TCP和UDP协议),再到应用层协议(如HTTP、FTP等)。例如,当一个应用程序通过HTTP协议发送一个网页请求时,请求数据会首先在应用层进行封装,然后依次经过传输层(TCP会对数据进行分段和编号,确保可靠传输)、网络层(IP会为数据添加源地址和目的地址等信息)、链路层(以太网协议会将数据封装为帧,并添加MAC地址等信息),最后通过物理层(网卡)发送到网络中。
- 网络设备管理:
- 内核有效地管理各种网络设备,包括网卡、无线网络设备等。它可以配置网络设备的参数,如IP地址、子网掩码、网关等。例如,通过
ifconfig
或ip
命令,用户可以设置网络设备的IP地址和子网掩码等信息。内核还可以对网络流量进行监控和管理,通过netstat
等工具可以查看网络连接状态和网络流量统计信息。同时,内核支持网络设备的热插拔,当插入一个新的网卡时,内核能够自动检测并配置这个网卡。
- 内核有效地管理各种网络设备,包括网卡、无线网络设备等。它可以配置网络设备的参数,如IP地址、子网掩码、网关等。例如,通过
- 网络服务与应用支持:
- 为网络服务(如Web服务、邮件服务等)和网络应用(如浏览器、邮件客户端等)提供支持。内核通过网络协议栈接收和发送网络数据包,使得网络服务能够提供服务,网络应用能够与服务器进行通信。例如,一个基于Linux的Web服务器(如Apache、Nginx)可以通过内核的网络管理系统接收来自客户端浏览器的HTTP请求,然后将网页内容发送回客户端。
- 网络协议栈:
9.3.2 Android内核和驱动
安卓采用了以Linux为基础的操作系统内核,同时也对Linux内核作了修改,以适应移动设备上的应用。
- 内核就相当于一个介于硬件层和系统中其他软件组之间的一个抽象层次。但是不算做Linux操作系统。
- 因为安卓系统的Linux内核是由标准的Linux内核修改而来的。
安卓系统在Linux内核的基础上增加了称为Dalvik的Java虚拟机和安卓runtime运行环境,构成了安卓的运行库。
-
每个安卓的应用都运行在自己的进程上使用由环由运行环境分配的专有实例。
-
安卓系统中的Dalvik的Java虚拟机被修改为支持。多个虚拟机高效运行在同一设备上。
-
Dalvik的Java虚拟机执行的是Dalvik格式的可执行文件.dex。该格式经过优化,将内存耗用降到了最低。
-
Java编译器将Java的源文件转化为class文件。class文件又被内置的dx工具转化为dex格式文件。这种文件在Dalvik虚拟机上注册并运行。
-
安卓系统的应用软件都是运行在Dalvik上的Java软件,而dalvik可是运行在Linux中的。
- 在一些底层功能,例如线程和低内存管理方面到位,可虚拟机是依赖于Linux内核的。
-
可以认为,安卓是运行在Linux内核之上的操作系统。
安卓系统是为移动设备设计和实现的系统,尽管安卓系统采用了Linux内核作为操作系统的最基本内核,但仍然与Linux内核有较多的差别。
-
内核功能裁剪
- 安卓系统:安卓中的Linux内核是经过裁剪的。由于安卓设备主要是移动设备,如智能手机和平板电脑,其硬件资源有限,并且功能需求相对集中。因此,在安卓的Linux内核中,一些在传统桌面Linux系统中可能用到的功能被移除。例如,对于服务器相关的高级网络服务功能(如复杂的网络文件系统支持)可能会被裁剪掉,因为安卓设备一般不作为文件服务器使用。同时,一些对移动设备不太重要的设备驱动也会被精简,以减小内核体积,节省存储空间和内存资源。
- 传统Linux系统:传统Linux内核功能更全面,旨在支持各种各样的应用场景。它不仅要支持桌面计算机的功能,如多用户登录、复杂的图形界面系统、大量的外部设备连接(包括各种专业设备如打印机、扫描仪等),还要满足服务器的需求,如提供各种网络服务(Web服务、邮件服务、文件共享服务等)。所以,传统Linux内核包含了完整的设备驱动集、网络协议栈和文件系统支持,以适应不同用户在不同环境下的使用。
-
电源管理方面
- 安卓系统:安卓系统的Linux内核针对移动设备的电池供电特点进行了电源管理方面的优化。它具有专门的电源管理模块,用于动态调整硬件设备的电源状态。例如,当屏幕关闭一段时间后,内核可以自动降低CPU的频率,关闭一些不必要的硬件模块(如Wi - Fi、蓝牙等,在未使用时),以延长电池续航时间。同时,安卓系统还可以根据应用程序的使用情况来管理电源,比如当某个应用在后台运行但没有进行重要任务时,内核会限制其对硬件资源的使用,从而降低功耗。
- 传统Linux系统:传统Linux内核虽然也有电源管理功能,但重点更多地放在桌面计算机或服务器的电源管理上。在桌面环境中,电源管理主要关注的是系统在不同电源模式(如睡眠、待机、关机)之间的切换,以及对硬件设备的简单节能措施(如硬盘在空闲一定时间后自动停止旋转)。在服务器环境中,电源管理相对简单,因为服务器通常是一直插电运行的,主要考虑的是硬件设备的散热和整体能耗效率,而不是像安卓设备那样对电池续航的精细管理。
-
硬件抽象层(HAL)与设备驱动集成
- 安卓系统:安卓系统在Linux内核之上引入了硬件抽象层(HAL)。HAL的作用是在Linux内核的设备驱动和安卓的上层应用框架之间提供一个中间层。这样做的好处是,当硬件发生变化或者需要更新设备驱动时,只需要修改HAL和驱动的接口部分,而不会影响到上层的应用程序。例如,对于摄像头硬件的升级,只要新的摄像头驱动和HAL之间的接口保持一致,应用程序就可以继续正常使用摄像头功能,无需重新编写。
- 传统Linux系统:传统Linux系统通常直接通过设备驱动与上层软件进行交互。当硬件更新或者驱动需要修改时,可能会影响到上层的应用程序,特别是那些直接与硬件设备打交道的软件。例如,在Linux系统中,如果更新了声卡驱动,一些音频播放软件可能会因为驱动接口的变化而出现兼容性问题,需要重新进行适配。
-
安全机制侧重点
- 安卓系统:安卓系统中的Linux内核安全机制更侧重于保护用户的隐私和应用程序的隔离。它采用了多种安全措施,如应用沙盒(App Sandbox)机制,每个安卓应用在安装后都运行在自己独立的沙盒环境中,不能随意访问其他应用的资源和数据。同时,安卓系统的Linux内核在权限管理方面也更加精细,用户可以在安装应用时或者在系统设置中对应用的各种权限(如访问摄像头、麦克风、通讯录等)进行管理。
- 传统Linux系统:传统Linux系统的安全机制主要基于用户和用户组的权限管理。不同的用户具有不同的权限,以访问不同的文件和执行不同的命令。例如,普通用户可能无法修改系统的关键文件和执行一些需要管理员权限的命令,而管理员(root)用户则拥有最高权限。虽然传统Linux系统也注重安全,但在应用程序隔离和隐私保护方面的机制不像安卓系统那样专门针对移动应用进行精细设计。
-
启动流程和系统初始化
- 安卓系统:安卓系统的启动流程相对复杂,除了Linux内核的启动阶段外,还包括安卓特有的初始化过程。在启动过程中,首先是引导加载程序(Bootloader)加载Linux内核,然后内核启动后会挂载根文件系统,并启动安卓的init进程。init进程会负责启动一系列安卓系统服务,如Zygote进程(用于孵化应用程序进程)、SurfaceFlinger(用于管理图形显示)等,这些服务协同工作来建立起安卓的运行环境。
- 传统Linux系统:传统Linux系统的启动主要是围绕初始化硬件设备、挂载文件系统、启动系统服务(如网络服务、登录服务等)和启动用户登录界面等。它的启动过程相对简单直接,重点是为用户提供一个可以登录并使用的桌面或者服务器环境,没有安卓系统中那么多针对移动应用的复杂初始化环节。
-
安卓系统中采用了安卓Binder
- 安卓Binder是基于openBinder的框架的驱动。主要用于安卓应用程序之间以及应用程序与系统服务之间的通信。
- 在安卓的架构中,为了实现组件化开发,不同的应用程序(如一个相机应用和一个图像编辑应用)或者应用程序与系统服务(如应用与电量管理服务)需要进行高效、安全的通信,Android Binder 就是为此而设计的。
-
安卓系统中的存储管理进行了较多的优化。
- 安卓系统将应用数据存储划分为多个分区,如内部存储(Internal Storage)和外部存储(External Storage)。
- 内部存储是应用的私有存储区域,其他应用通常无法访问,这为应用数据提供了安全性。例如,应用的数据库文件、配置文件等重要数据一般存储在内部存储。
- 对于内部存储,安卓还进一步细分了缓存(Cache)和数据(Data)区域。
- 缓存区域用于存储临时数据,如网络请求的缓存结果,这些数据可以在需要时被快速访问,并且系统可以根据存储空间的紧张程度自动清理缓存。
- 数据区域则用于存储应用的持久化数据,如用户设置、游戏进度等,这些数据除非用户手动删除或者应用被卸载,否则会一直保存。
- 外部存储则可以用于存储一些共享的、大容量的数据,如照片、视频等。这种分区规划有助于合理利用存储空间,并且方便用户对数据进行管理。
- 安卓系统会对应用的数据存储大小进行限制。
- 当应用的数据存储超过一定限额时,系统会提醒用户清理数据或者自动采取一些清理措施。
- 例如,对于一些长期未使用的应用,系统可能会自动清理其缓存数据,以释放存储空间。
- 同时,应用开发者也可以在应用中实现自己的数据清理功能,如在应用设置中提供 “清除缓存” 选项,方便用户手动清理。
-
安卓系统中采用了 yaffs2作为MTD NAND flash文件系统。
- yaffs2是一个快速稳定的应用于n的flash的跨平台的嵌入式设备文件系统。
- 从其他flash文件系统相比,yaffs2能使用更小的内存来保存其运行状态,因此占用内存小。
- Yaffs2的垃圾回收非常简单而迅速,因此能达到更好的性能,非常适合大容量的flash存储。
-
安卓系统还提供了很多机制来进行系统的优化,或者为系统提供新的功能。
- 安卓Logger 是一个轻量级的日志设备,能用于抓取安卓系统的各种日志。
- 安卓alarm提供了一个定时器,用于把设备从睡眠状态唤醒。
- 安卓usb gadget驱动是一个基于标准Linux usb gadget驱动框架的设备驱动。
- 用于调试功能的安卓的ram console允许将调试日志信息写入一个被称为ram console的设备中。
9.4 系统移植的概念和驱动开发的方法
嵌入式操作系统与桌面操作系统以及服务器操作系统最显著的区别就是它的可移植性。
安卓系统的移植是为了在特定的硬件平台上运行安卓系统。
安卓中具有很多组件,但并不是每一个部件都需要移植。一些纯软的组件就没有移植的必要。
- 例如浏览器引擎虽然需要下层网络的支撑,但是并非直接为其移植网络接口。而是通过无线局域网或者电话系统数据连接来完成标准网络接口的移植。
- 安卓系统的移植并不需要精通安卓的每一个部分。需要考虑的仅仅是安卓系统的硬件抽象层HAL和Linux中的相关设备驱动程序。
安卓系统硬件抽象层运行于用户空间,介于驱动程序和安卓系统之间。
-
驱动程序是硬件和上层软件的接口,Linux的驱动运行在内核空间。在安卓手机系统中需要的机的屏幕、触摸屏、键盘、以及音频摄像头、电话、WiFi、蓝牙等多种设备的驱动程序。
-
具有了特定的硬件系统之后,通常在Linux中需要实现其他的驱动程序。这些驱动程序通常是Linux的标准驱动程序在安卓平台和其他的Linux平台上的功能基本相同。
-
安卓的移植主要可以分成几个类型:基本图形用户界面就是GUI的部分;硬件加速部分包括媒体编解码和openGL;音视频输入输出环节,包括音频、视频输出和摄像头部分;连接部分包括无线局域网、蓝牙和gps。
10 Android系统开发环境
10.1 交叉开发环境
10.1.1 交叉开发环境概述
移动设备本质上来说也是嵌入式系统的一种。移动设备主要是通过电池进行供电,因此在设计时需要考虑传统桌面系统。不同的用户交互设计能耗和重量问题。
不能直接在移动设备上进行软件开发,需要采用基于宿主机-目标机开发环境的交叉开发模式。
交叉开发模式是嵌入式系统领域常用的开发模式,其本质是在一台设备上进行开发和调试开发出来的软件在另一台设备上存储和运行。
- 宿主机就是我们的计算机。
- 目标机就是我们最终的运行设备。
我们要做的就是基于宿主目标机的开发环境,在宿主机上使用既有的工具,面向目标机上有限的硬件资源,为目标机定制软件系统。
- 在交叉环境下开发的时候,需要借助宿主机的编译环境。
- 由于不同架构的处理器有不同的指令集,因此不同架构的处理器需要对应不同的编译器。
- 相对于交叉编译,在本机编译出能在同样架构计算机和操作系统上运行的编译称为本地编译。
- 而交叉编译就是在一种处理器架构的计算机下编译成另外一种处理器架构的目标文件。
10.1.2 宿主机与目标机的连接
宿主主机和目标机的连接方式有4种,分别是串口、以太网接口、usb接口和JTAG接口。
-
大多数的操作系统中,串口可以作为终端使用。宿主机可以利用串口给目标机发送命令,同时也可以接收目标及返回的信息并显示。
-
以太网是当今局域网采用的最通用的通信协议标准。基本上这个传输速率都很快,但就是网络驱动的实现比较复杂。
-
Usb现在已经成为了桌面计算机的通用接口,很多基于usb标准的设备被广泛应用。它是一种快速灵活的总线接口,与其他通信接口相比,比较简单用。
- 另外,usb还支持热插拔,同时无需要无需用户自己配置,系统会自动搜索程序并安装。
-
JTAG 是一种国际标准测试协议 (ieee 1149.1兼容),主要用于芯片内部测试及对系统进行仿真调试。
- 几乎所有的处理器都支持JTAG,调试器的单步调试和断点都需要与基JTAG交互。另外还可以通过JTAG将程序写到目标机上。
10.1.3 宿主机环境
由于目标机的实际操作系统不提供编译器或者开发环境不完整,甚至没有操作系统,通常采用交叉编译的方式产生目标代码。
宿主机所使用的主流操作系统包含种类丰富的开发工具,在这些操作系统中,Linux操作系统是使用最广泛的操作系统之一
- 有大量开源开放的软件可供使用。Shell、glibc、gcc和gdb等。
- 还有许多功能强大的编译工具,如Vim、Emacs和gedit等。
Linux操作系统成为交叉开发模式向宿主端的主要操作系统平台。
- 此外,目标机需要通过通信接口向宿主机提出请求,如ip地址分配和文件传输等。就需要做主机提供相应的服务,如DHCP和TFTP等。
串口终端
- 作为一种宿主机与目标机之间的连接方式,串口并不适用于传输大量数据的场合,更多是作为终端来使用。
- 窗口终端主要用来控制和管理嵌入式系统,如管理bootloader、输入命令等。
- 串口终端的使用非常广泛,因此很多操作系统都已经集成串口终端工具,如windows的超级终端和Linux的minicom。
- Windows的超级终端是有gui的。
- Linux的minicom采用的是命令行用户界面。主要的优点是占用的系统资源少,而且操作方便。
BOOTP
-
- BOOTP(Bootstrap Protocol)概述
- BOOTP是一种基于UDP(用户数据报协议)的网络协议,主要用于无盘工作站或刚启动的网络设备从服务器获取IP地址等网络配置信息。
- 它在网络启动过程中扮演着关键的角色,使设备能够在没有预先配置IP地址的情况下接入网络并获取必要的配置参数,从而正常启动并与其他网络设备通信。
- BOOTP最初是在RFC951中定义。使用的是 UDP67/68两个通信端口。
-
工作原理
- 请求阶段
- 当一个无盘工作站或新启动的网络设备(如某些网络打印机、网络摄像头等)启动时,它会在本地网络中广播一个BOOTP请求消息。这个请求消息包含了设备的硬件地址(如MAC地址),因为此时设备还没有IP地址,只能通过硬件地址来唯一标识自己。广播的目的是让网络中的BOOTP服务器能够接收到这个请求。
- 目标机的BOOTP是由Bootloader启动的,此时他广播的ip地址是0.0.0.0。
- 例如,在一个企业局域网中,一台新连接的无盘工作站启动后,它会在子网内发送BOOTP请求,这个请求会被发送到子网内的所有设备,但只有配置为BOOTP服务器的设备会对这个请求进行处理。
- 当一个无盘工作站或新启动的网络设备(如某些网络打印机、网络摄像头等)启动时,它会在本地网络中广播一个BOOTP请求消息。这个请求消息包含了设备的硬件地址(如MAC地址),因为此时设备还没有IP地址,只能通过硬件地址来唯一标识自己。广播的目的是让网络中的BOOTP服务器能够接收到这个请求。
- 响应阶段
- BOOTP服务器接收到请求后,会根据请求消息中的硬件地址来查找对应的网络配置信息。这些配置信息通常是预先配置在服务器中的,包括IP地址、子网掩码、默认网关、DNS服务器地址等。
- 然后,BOOTP服务器会向发出请求的设备发送一个BOOTP响应消息。这个响应消息包含了设备所需的网络配置信息。设备收到响应后,就可以使用这些配置信息来配置自己的网络接口,从而能够在网络中正常通信。例如,服务器会将分配的IP地址和其他网络参数发送给无盘工作站,工作站就可以利用这些参数来完成网络初始化。
- 请求阶段
-
与DHCP(动态主机配置协议)的关系
- BOOTP可以看作是DHCP的前身。DHCP在BOOTP的基础上进行了扩展和改进。两者都用于网络设备的IP配置,但DHCP具有更多的功能,如动态分配IP地址(可以在一定时间内租用IP地址)、支持更多的配置选项、能够自动更新IP地址租约等。
- 不过,BOOTP仍然在一些特定的场景下使用。例如,在一些对网络配置稳定性要求较高、不希望IP地址频繁变化的网络环境中,或者在一些简单的网络设备启动配置场景中,BOOTP的简单性和确定性(设备每次启动可能获得相同的IP地址)可能更具优势。
-
BOOTP消息格式
- BOOTP消息是通过UDP数据包来传输的,其消息格式相对固定。消息分为多个字段,包括操作码(用于区分请求和响应)、硬件类型(如以太网类型)、硬件地址长度(如MAC地址长度为6字节)、事务标识符(用于匹配请求和响应)、秒数(表示自客户端开始尝试获取配置信息以来经过的秒数)等。
- 消息的数据部分包含了服务器返回的具体配置信息,如IP地址、子网掩码等。这些字段的定义和格式使得BOOTP能够准确地传递网络配置信息,并且不同的网络设备和服务器能够按照统一的标准来进行通信。
-
应用场景
- 无盘工作站环境:在无盘工作站网络中,BOOTP是必不可少的。因为无盘工作站没有本地磁盘来存储操作系统和网络配置信息,它需要通过BOOTP从服务器获取IP地址等配置,然后从服务器下载操作系统和其他应用程序来启动运行。
- 简单网络设备配置:对于一些简单的网络设备,如早期的网络打印机、网络摄像头等,它们可能没有复杂的网络配置界面,通过BOOTP可以方便地从网络中获取配置信息,快速接入网络并开始工作。
TFTP协议
-
TFTP(Trivial File Transfer Protocol)概述
- TFTP是一种简单的文件传输协议,它基于UDP(用户数据报协议),主要用于在网络上进行文件的简单传输。与其他复杂的文件传输协议(如FTP)相比,TFTP具有简单、轻量级的特点,适用于资源有限的设备和简单的文件传输场景。
-
协议特点
- 简单性
- TFTP的设计非常简洁,它只定义了几种基本的操作类型,包括文件读取(RRQ,Read Request)、文件写入(WRQ,Write Request)、数据传输(DATA)、确认(ACK)和错误(ERROR)。这种简单的操作集使得TFTP易于实现和理解。例如,在一个小型的嵌入式系统中,开发人员可以相对容易地在设备上实现TFTP客户端或服务器功能,用于更新系统固件或传输配置文件。
- 它的消息格式也比较简单,没有像FTP那样复杂的用户认证、目录浏览等功能。TFTP消息通常由操作码、文件名、模式(如ASCII或二进制模式)和数据块组成。这种简单的格式减少了协议的开销,使得文件传输能够快速进行。
- 基于UDP
- TFTP运行在UDP之上,这使得它与基于TCP(传输控制协议)的文件传输协议有所不同。UDP是一种无连接的协议,不像TCP那样提供可靠的连接建立、数据确认和重传机制。因此,TFTP自身需要处理一些可靠性问题。例如,TFTP通过在每个数据块传输后等待接收方的确认消息(ACK)来确保数据的正确传输,如果在一定时间内没有收到确认,发送方会重新发送数据块。
- 无用户认证和有限的安全性
- TFTP通常没有用户认证机制,这意味着任何能够访问TFTP服务器端口(默认是69端口)的客户端都可以尝试进行文件传输。这种设计虽然方便了简单的文件传输操作,但也带来了安全风险。例如,如果TFTP服务器暴露在不安全的网络环境中,可能会被恶意用户滥用,用于非法获取或篡改文件。
- 简单性
-
工作模式和操作流程
- 文件读取模式(RRQ)
- 当客户端想要从服务器读取一个文件时,它会向服务器发送一个RRQ消息。这个消息包含文件名和文件传输模式(如ASCII或二进制)。服务器收到RRQ消息后,如果文件存在且有读取权限,就会开始发送文件数据。服务器将文件数据分成一个个固定大小的数据块(通常是512字节),并为每个数据块编号。
- 客户端收到每个数据块后,会向服务器发送一个ACK消息,确认收到的数据块编号。这样,通过ACK和数据块的交互,文件数据从服务器逐步传输到客户端。如果客户端没有收到某个数据块的ACK,服务器会根据超时机制重新发送该数据块。
- 文件写入模式(WRQ)
- 在文件写入模式下,客户端首先向服务器发送一个WRQ消息,包含文件名和传输模式。服务器收到WRQ后,如果允许文件写入,会发送一个ACK消息,表示可以开始接收文件数据。然后客户端将文件数据分成数据块发送给服务器,每个数据块发送后等待服务器的ACK。服务器收到数据块后,会检查数据的正确性,并发送ACK确认。如果数据块有问题,服务器可能会发送ERROR消息,要求客户端重新发送数据块。
- 文件读取模式(RRQ)
-
应用场景
- 嵌入式系统和网络设备固件升级:在许多嵌入式系统和网络设备(如路由器、交换机)中,TFTP被广泛用于固件升级。设备可以通过TFTP从服务器获取新的固件文件,进行系统更新。这种方式简单高效,不需要复杂的用户认证和文件系统操作,适用于资源有限的设备。
- 小型网络中的文件共享和配置备份:在小型的局域网环境中,TFTP可以用于简单的文件共享。例如,将一些常用的配置文件(如网络设备的配置备份)存储在TFTP服务器上,方便网络管理员在需要时进行访问和恢复。不过,由于其安全性较低,这种应用场景通常在相对安全的内部网络中使用。
TFTP协议与FTP协议的区别
-
协议基础与连接方式
- TFTP(Trivial File Transfer Protocol)
- 基于UDP协议:TFTP是基于UDP(用户数据报协议)的简单文件传输协议。UDP是一种无连接的协议,它不提供像TCP(传输控制协议)那样复杂的连接建立、维护和拆除机制。这使得TFTP在传输文件时相对简单快速,但也意味着它需要自己处理数据传输的可靠性问题。例如,在TFTP中,如果一个数据块丢失,发送方需要通过超时重传机制来重新发送数据块,因为UDP本身不会对丢失的数据进行重传。
- 简单的连接模式:TFTP采用简单的请求 - 响应模式进行文件传输,没有用户权限管理的功能。当客户端想要进行文件传输时,它直接向服务器发送请求,不需要像FTP那样先建立复杂的控制连接和数据连接。这种模式使得TFTP在轻量级的文件传输场景中非常高效,如在小型的嵌入式系统或简单的网络设备之间进行文件传输。
- FTP(File Transfer Protocol)
- 基于TCP协议:FTP是基于TCP的文件传输协议。TCP提供了可靠的、面向连接的通信服务。在文件传输前,FTP会先建立控制连接用于传输命令和响应,然后根据需要建立数据连接用于实际的文件传输。这种基于TCP的连接方式保证了文件传输的可靠性和顺序性,因为TCP会自动处理数据的丢失、重复和乱序等问题。例如,在网络状况不稳定的情况下,TCP会通过重传机制确保数据的完整性。
- 复杂的双连接机制:FTP使用两个独立的连接,即控制连接和数据连接。控制连接用于在客户端和服务器之间传递命令(如登录、文件目录操作、文件传输模式选择等)和响应,数据连接用于传输文件数据。这种双连接机制使得FTP能够灵活地处理各种文件传输情况,但也增加了协议的复杂性和资源消耗。
- TFTP(Trivial File Transfer Protocol)
-
功能特性与复杂性
- TFTP
- 功能简单:TFTP的功能相对较少,主要专注于基本的文件传输操作,即文件的读取(RRQ)和写入(WRQ)。它没有像FTP那样丰富的用户认证、文件目录浏览和管理等功能。TFTP的消息格式也比较简单,主要包括操作码、文件名、模式(如ASCII或二进制)和数据块等部分。例如,在TFTP中,客户端不能像在FTP中那样方便地浏览服务器上的文件目录,只能通过事先知道文件名来请求文件。
- 易于实现和使用:由于其简单性,TFTP易于在资源有限的设备上实现。对于小型的嵌入式系统或简单的网络设备,开发人员可以相对容易地编写TFTP客户端或服务器代码,用于设备的固件升级或简单的文件传输。同时,TFTP的使用也很简单,不需要用户了解复杂的命令和参数,只需要发送基本的文件读取或写入请求即可。
- FTP
- 功能丰富:FTP提供了多种功能,包括用户认证(支持用户名和密码登录)、文件目录操作(如列出目录内容、切换目录、创建和删除目录等)、文件传输模式选择(ASCII模式用于文本文件传输,二进制模式用于非文本文件传输)等。这些功能使得FTP能够满足复杂的文件管理和传输需求,如在大型的文件服务器和客户端之间进行文件存储和共享。
- 复杂的命令和参数:FTP使用一套复杂的命令和参数来实现各种功能。例如,用户需要使用“USER”命令输入用户名,“PASS”命令输入密码进行登录,使用“LIST”命令列出服务器上的文件目录内容等。这种复杂性要求用户或开发人员熟悉FTP的命令语法,同时也增加了客户端和服务器软件的开发难度。
- TFTP
-
安全性与应用场景
- TFTP
- 安全性较低:TFTP通常没有用户认证机制,这意味着任何能够访问TFTP服务器端口(默认是69端口)的客户端都可以尝试进行文件传输。这种设计虽然方便了简单的文件传输操作,但也带来了安全风险。例如,如果TFTP服务器暴露在不安全的网络环境中,可能会被恶意用户滥用,用于非法获取或篡改文件。
- 应用场景受限:由于安全性和功能的限制,TFTP主要应用于内部网络中的简单文件传输和设备固件升级。例如,在一个小型的局域网中,TFTP可以用于将配置文件从服务器传输到网络设备,或者在嵌入式系统中用于更新固件。但在需要安全的用户认证和复杂文件管理的场景下,TFTP不太适用。
- FTP
- 安全性相对较高(可配置):FTP支持用户认证,通过用户名和密码的方式可以对用户进行身份验证,限制文件访问权限。此外,还可以通过配置FTP服务器,使用SSL/TLS(安全套接字层/传输层安全)协议对传输的数据进行加密,增强数据传输的安全性。这种安全性措施使得FTP可以在需要保护数据隐私和访问权限的场景中使用。
- 广泛的应用场景:FTP的功能丰富和相对较高的安全性使其在各种网络环境中有广泛的应用。它可以用于大型文件服务器与客户端之间的文件共享、网站的文件上传和下载(如通过FTP客户端管理网站的文件)、企业内部的文件存储和管理等场景。
- TFTP
交叉编译工具链
交叉编译工具链主要包含标准库编译器,连接器,汇编器和调试器。
可以在x86平台上编译出能够在arm平台上运行的程序。比如在ARM平台下选择arm Linux gcc。
通常交叉编译工具链的构件有以下三种方法由易到难分别为下载使用、脚本编译和从头编译。
通常交叉编译工具链的构建有以下三种方法:
1. 下载并使用预构建的交叉编译工具链
- 优点:
- 简单便捷:这是最方便快捷的方式。对于许多常见的目标平台和处理器架构,都可以找到已经构建好的工具链。例如,针对ARM架构的嵌入式开发,有许多官方或者社区提供的预构建工具链。使用者只需要从相关网站下载合适的版本,解压后配置好环境变量,就可以直接使用。
- 节省时间:不需要自己进行繁琐的编译过程,能够快速投入开发工作,尤其适合初学者或者项目时间紧张的情况。
- 缺点:
- 灵活性有限:预构建的工具链可能无法满足一些特殊的需求,例如对工具链版本、编译选项、支持的库有特殊要求时,可能无法通过简单的配置来实现。
- 可能存在兼容性问题:有时候下载的工具链与开发主机或者目标设备的系统版本、硬件细节等可能存在兼容性问题,导致工具链无法正常工作或者出现一些难以调试的错误。
2. 使用交叉编译工具链构建脚本(如crosstool - ng)
- 优点:
- 高度定制化:像crosstool - ng这样的工具允许用户根据自己的具体需求来定制工具链。用户可以选择特定的编译器版本(如GCC的某个特定版本)、目标架构(如ARMv7 - a、MIPS等)、C库(如uClibc、glibc等)以及其他相关的工具和库。
- 自动化构建过程:它通过脚本的方式自动化了工具链构建的大部分流程,减少了人工操作失误的可能性。用户只需要按照自己的需求配置好相应的参数,脚本就会自动下载源代码、进行编译和安装。
- 方便更新和维护:当需要更新工具链的某个组件或者调整配置时,使用构建脚本相对比较容易操作。只需要修改相关的配置参数,然后重新运行构建脚本即可。
- 缺点:
- 学习成本:需要学习和掌握构建脚本(如crosstool - ng)的使用方法和配置参数,对于初学者来说可能有一定的难度。
- 依赖环境较多:构建脚本在运行过程中可能依赖于主机系统的一些软件包、库和环境变量。如果主机环境配置不当,可能会导致构建过程失败。
3. 手动构建交叉编译工具链
- 优点:
- 完全控制构建过程:开发人员对工具链的每一个组件的来源、版本、编译选项等都有绝对的控制权。可以根据项目的特殊需求,如对安全性、性能、体积等方面的要求,精细地调整工具链的构建。
- 深入理解工具链构建原理:通过手动构建,能够深入了解交叉编译工具链各个组件之间的关系、编译过程中涉及的各种参数和环境变量,有助于解决在开发过程中遇到的一些深层次的工具链相关问题。
- 缺点:
- 构建过程复杂且耗时:需要手动下载各个工具链组件的源代码,如编译器(GCC)、二进制工具(binutils)、C库(如glibc)等,并且要按照正确的顺序和配置进行编译和安装。这个过程非常繁琐,容易出现错误,而且可能会花费大量的时间。
- 需要丰富的知识和经验:要求开发人员对编译器、操作系统、硬件架构等多个领域有深入的了解,包括但不限于编译原理、链接过程、库的安装和配置等知识,否则很难成功构建一个可用的工具链。
10.1.4 目标机环境
目击标机环境目前有两个重要的部分,分别是JTAG接口和bootloader。
- JTAG最初用于对芯片进行测试。基本原理就是在器件内部定义一个测试访问接口(TAP test access port),通过专用的JTAG测试工具对内部节点进行测试。
- JTAG测试允许多个器件通过机JTAG接口串联在一起,形成一个JTAG链。能实现对各个器件的分别测试。
- 后来JTAG接口还常用于实现在线编程,也就是isp。
- 标准的JTAG接口有四线: TMS、TCK、TDI、TDO,分别是模式选择线、时钟线、数据输入线和数据输出线。现在常用的JTAG接口有三种标准,即10针、14针和20针。
BootLoader:
- BootLoader是系统加电后操作系统内核运行之前执行的一段小程序。
- 这段程序可以进行硬件设备的初始化建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便最终调用操作系统内核准备好正确的环境。
- 在桌面计算机中起到类似作用的引导代码一般。由bios和位于MBR的操作系统引导程序组成。
- 通常来说,bootloader是严重依赖于硬件环境的,建立一个通用的bootloader几乎是不可能的,每种不同的cpu体系都有不同的bootloader。
- 对于两块不同的嵌入式板,即使是基于同一种cpu建立的bootloader程序在移植过程中也需要修改源代码,才能运行在一块板子上。
10.2 Linux操作系统及其开发工具。
10.2.1 Linux操作系统及其概述
操作系统是计算机必不可少的组成部分。操作系统的功能用一句话来说就是管理与控制计算机资源的软件。
- 目前比较流行的操作系统包括UNIX系统、类UNIX的Linux系统、Windows系统,以及一些嵌入式的操作系统。
Linux是一类UNIX系统的统称。最初由Linus Torvalds带头开发,并由全世界众多爱好者共同维护的操作系统。
- Linux的设计源于UNIX,实现了UNIX操作系统的API。
- 与其它的类UNIX操作系统不同, Linux系统并不是直接修改UNIX系统源代码而来的,而是对Unix系统的重新实现。
- 严格来说,Linux只是一个操作系统内核,所以通常说的不同版本的Linux是指由JNU软件和Linux内核构成的完整的操作系统。
Linux操作系统具有许多优秀的特性,以下是详细介绍:
1. 开放性和自由性
- 开源本质:Linux操作系统的核心代码是开源的,这意味着任何人都可以查看、修改和分发代码。这种开放性促进了全球范围内的开发者共同参与改进系统,使得Linux能够快速吸收各种优秀的技术和想法。例如,Linux内核的开发是一个全球性的协作项目,有众多开发者提交代码补丁、修复漏洞和添加新功能。
- 自由定制:用户可以根据自己的需求自由定制Linux系统。无论是用于服务器、桌面还是嵌入式设备,都可以对系统进行裁剪和配置。比如,在构建一个嵌入式Linux系统时,可以选择只包含特定的设备驱动、应用程序和库,以减少系统体积并满足硬件资源有限的要求。
2. 高度的稳定性和可靠性
- 系统健壮性:Linux系统设计之初就注重稳定性,能够长时间稳定运行而不出现故障。在服务器领域,许多重要的服务器(如Web服务器、数据库服务器等)都运行Linux操作系统,它们可以连续运行数月甚至数年而无需重启。例如,大型互联网公司的数据中心采用Linux服务器来处理海量的用户请求,这些服务器能够承受高负载和复杂的网络环境。
- 错误处理能力:Linux具有强大的错误处理和恢复机制。当系统遇到软件错误或者硬件故障时,能够采取相应的措施来保护数据和系统的正常运行。例如,在文件系统出现错误时,Linux的文件系统检查工具(如e2fsck)可以在系统启动时自动检测并修复文件系统中的问题。
3. 多用户和多任务处理能力
- 多用户支持:Linux系统可以同时支持多个用户登录和使用。每个用户都有自己的账户和权限,可以独立地运行程序和访问资源。这种特性在服务器环境中非常重要,多个系统管理员或者用户可以同时远程登录到服务器进行操作。例如,在一个大学的计算中心服务器上,不同的教师和学生可以使用自己的账户登录,进行教学、科研或者学习相关的操作。
- 多任务处理:Linux能够高效地同时运行多个任务。它通过进程管理和调度机制,合理分配CPU时间和系统资源给各个任务。例如,在一个桌面Linux系统中,用户可以同时打开多个应用程序,如浏览器、文本编辑器、音乐播放器等,这些程序可以同时运行而不会互相干扰,并且系统能够根据任务的优先级和资源需求进行动态调整。
4. 强大的网络功能
- 网络协议支持:Linux支持几乎所有的网络协议,包括TCP/IP、UDP、HTTP、FTP、SMTP等常见协议,以及一些特殊用途的协议。这使得Linux系统可以方便地构建各种网络应用,从简单的文件共享服务器到复杂的云计算平台。例如,使用Linux搭建的邮件服务器可以通过SMTP、POP3和IMAP协议来发送、接收和管理电子邮件。
- 网络服务性能:Linux在网络服务方面表现出色,能够高效地处理大量的网络连接和数据传输。许多高性能的网络服务器软件(如Apache、Nginx等)最初都是为Linux系统开发的,并且在Linux上能够发挥出最佳性能。例如,Nginx在Linux服务器上可以快速地处理大量的HTTP请求,实现高性能的Web服务。
5. 安全性能高
- 用户权限管理:Linux采用了严格的用户权限管理机制。系统中的每个文件和目录都有所有者、所属组和其他用户的权限设置,包括读、写、执行三种权限。这种权限管理可以有效地防止用户对系统文件和其他用户文件的非法访问。例如,普通用户无法修改系统关键文件,除非获得管理员权限。
- 安全更新机制:Linux发行版通常有完善的安全更新机制。一旦发现安全漏洞,发行商和社区会及时发布安全补丁,用户可以通过系统更新工具方便地安装这些补丁。例如,Debian和Ubuntu等发行版会定期推送安全更新,用户可以使用apt - get或者图形化的更新管理器来更新系统,提高系统的安全性。
6. 丰富的软件生态系统
- 软件包管理系统:Linux拥有多种强大的软件包管理系统,如Debian/Ubuntu使用的apt,Red Hat/CentOS使用的yum等。这些软件包管理系统可以方便地安装、更新和卸载软件。例如,在Ubuntu系统中,用户只需要使用“sudo apt - get install [软件名]”命令就可以轻松安装所需软件。
- 软件多样性:涵盖了从服务器软件(如数据库管理系统、邮件服务器)到桌面应用(如办公软件、图形编辑软件)以及开发工具(如编译器、集成开发环境)等各个领域的软件。无论是用于企业级的服务器部署,还是个人的日常办公和开发,都能找到合适的软件。例如,对于开发者来说,Linux下有丰富的编程语言环境,如Python、Java、C/C++等的开发工具和库。
10.2.2 Linux操作系统核心与驱动程序
Linux的内核从逻辑上可以分为进程调度、进程间通讯、内存管理、虚拟文件系统和网络5个部分。
-
进程调度
- 基本概念:在Linux系统中,进程调度是内核的关键部分,它负责决定哪个进程在什么时候使用CPU资源。Linux是一个多任务操作系统,多个进程同时存在,而CPU在某一时刻只能执行一个进程的指令,因此需要合理的调度机制。例如,当你在Linux系统中同时打开浏览器、文本编辑器和音乐播放器,进程调度器会决定这些进程在CPU上的执行顺序和时间分配。
- 调度策略:Linux采用多种调度策略,如完全公平调度(CFS - Completely Fair Scheduler)。CFS的目标是公平地分配CPU时间给每个进程。它通过虚拟运行时间(vruntime)来衡量进程应该获得的CPU时间。每个进程都有一个虚拟运行时间,这个时间是根据进程的实际运行时间和优先级等因素动态计算的。优先级高的进程会有相对较小的vruntime增长速度,这样它们会更频繁地获得CPU时间,但CFS也会保证其他进程不会被“饿死”,即每个进程都能在一定时间内获得CPU资源。
- 调度时机:进程调度主要发生在进程状态转换的时候,如进程从运行态转为等待态(例如等待I/O操作完成),或者新进程被创建、唤醒时。当一个进程由于等待I/O(如等待从硬盘读取数据)而阻塞时,调度器会选择另一个就绪进程来运行,从而提高CPU的利用率。
-
进程间通讯(IPC)
- 管道(Pipe)
- 定义与用途:管道是Linux中最基本的进程间通信机制之一,它提供了一个单向的数据通道,用于具有亲缘关系(如父子进程)的进程之间传输数据。例如,在命令行中使用“ls | grep test”,“|”就是管道的操作符,这里“ls”命令的输出通过管道作为“grep test”命令的输入,实现了两个进程之间的数据传递。
- 实现方式:管道在系统内部是通过内核缓冲区来实现的。当一个进程向管道写入数据时,数据被存储在这个缓冲区中,另一个进程可以从缓冲区读取数据。管道的大小通常是有限制的,当缓冲区满时,写入进程会被阻塞,直到有足够的空间可以写入;当缓冲区为空时,读取进程会被阻塞,直到有数据可读。
- 消息队列(Message Queue)
- 定义与用途:消息队列是一种进程间通信机制,它允许不同进程通过发送和接收消息来进行通信。消息队列的优势在于它不像管道那样是一次性的通信方式,而且可以在多个不相关的进程之间使用。例如,在一个分布式系统中,不同的服务器进程可以通过消息队列来交换任务请求和执行结果。
- 实现方式:消息队列在内核中有一个消息队列标识符,进程通过这个标识符来访问消息队列。消息被发送到队列中,每个消息有自己的类型标识。接收进程可以根据消息类型有选择地接收消息,这样可以实现消息的分类处理。
- 共享内存(Shared Memory)
- 定义与用途:共享内存是一种高效的进程间通信方式,它允许多个进程共享同一块物理内存区域。由于进程可以直接访问共享内存区域,避免了数据在不同进程空间之间的多次复制,大大提高了通信效率。例如,在数据库系统中,多个进程可能需要同时访问数据库的缓存数据,通过共享内存可以快速地实现数据共享和更新。
- 实现方式:在Linux中,通过shmget、shmat等系统调用实现共享内存的操作。首先,通过shmget系统调用创建或获取共享内存段,然后使用shmat系统调用将共享内存段映射到进程的地址空间。多个进程可以通过相同的操作将共享内存映射到自己的地址空间,之后就可以对共享内存区域进行读写操作。为了防止多个进程同时访问共享内存区域导致数据不一致,通常需要配合使用信号量等同步机制。
- 信号量(Semaphore)
- 定义与用途:信号量主要用于进程间的同步和互斥,它是一个计数器,用于控制多个进程对共享资源的访问。例如,在多个进程同时访问一个文件或者一个共享内存区域时,信号量可以确保在同一时刻只有一个进程对资源进行操作,避免数据冲突。
- 实现方式:Linux提供了一组系统调用(如semget、semop等)来操作信号量。通过这些系统调用可以创建信号量集、对信号量进行加1或减1操作等。当一个进程要访问共享资源时,它会先对信号量进行减1操作,如果信号量的值大于等于0,则表示可以访问资源;如果信号量的值小于0,则表示资源正在被其他进程占用,该进程需要等待。当进程访问完资源后,会对信号量进行加1操作,以允许其他等待的进程访问资源。
- 管道(Pipe)
-
内存管理
- 物理内存管理
- 内存分配与回收:Linux内核负责将物理内存分配给各个进程。它把物理内存划分为固定大小的页面(通常是4KB),通过页表来记录页面的使用情况。当一个进程需要内存时,内核会从空闲的页面中分配给它。例如,当你启动一个新的应用程序,内核会根据程序的内存请求,从空闲页面列表中分配足够的页面给这个程序。当一个进程结束运行时,内核会回收该进程占用的页面,将这些页面重新标记为空闲状态,以便分配给其他进程。
- 内存映射(Memory Mapping):物理内存管理还涉及内存映射,这是一种将文件或设备的内容直接映射到内存中的机制。例如,在加载一个可执行文件时,内核会将文件的内容映射到物理内存的某个区域,这样进程就可以直接从内存中读取可执行文件的指令和数据,而不是通过文件系统的读取操作。这种方式可以提高程序的运行效率,特别是对于大型文件或频繁访问的文件。
- 虚拟内存管理
- 虚拟地址空间与物理地址空间的关系:每个进程在Linux系统中有自己独立的虚拟地址空间。虚拟地址空间比实际的物理地址空间大得多,它为进程提供了一个统一的、连续的内存视图。例如,在32位的Linux系统中,每个进程的虚拟地址空间大小为4GB。内核通过页表机制将虚拟地址转换为物理地址。当进程访问一个虚拟地址时,内核会根据页表中的映射关系找到对应的物理地址。如果访问的虚拟地址对应的物理页面不在内存中(例如,页面被交换到磁盘上的交换空间),会触发缺页中断,内核会从磁盘交换空间中将页面调入内存,然后继续执行进程。
- 页面置换算法(Page Replacement Algorithms):由于物理内存是有限的,当内存不足时,需要将一些页面置换到磁盘上的交换空间。Linux使用了多种页面置换算法,如最近最少使用(LRU - Least Recently Used)算法的变种。这种算法会选择最近一段时间内最少被访问的页面进行置换。通过这种方式,可以在内存资源有限的情况下,尽可能地保证系统的性能,使那些经常被访问的页面保留在内存中。
- 物理内存管理
-
虚拟文件系统(VFS)
- 文件系统抽象层:VFS是Linux内核中用于统一管理各种文件系统的抽象层。它隐藏了不同文件系统(如ext4、NTFS、FAT等)的具体细节,为用户和应用程序提供了一个统一的文件操作接口。例如,无论底层是本地的ext4文件系统还是通过网络挂载的CIFS文件系统,应用程序都可以使用相同的系统调用(如open、read、write等)来操作文件。VFS定义了一组通用的文件系统操作函数,如文件的打开、读取、写入、关闭等,所有支持的文件系统都需要实现这些函数,以适应VFS的接口。
- 文件系统挂载(Mount)和卸载(Unmount):在Linux中,文件系统需要挂载到系统的目录树上才能被访问。VFS负责管理文件系统的挂载和卸载过程。当你插入一个USB存储设备并将其挂载到系统中(例如挂载到“/media/usb”目录),VFS会识别设备上的文件系统类型(如FAT32或NTFS),并将其与挂载点相关联。这样,用户就可以通过挂载点目录访问设备上的文件。当你要拔出设备时,需要先卸载文件系统,以确保数据的完整性和设备的安全移除。
- 文件系统缓存(Cache)机制:VFS还包括文件系统缓存机制,用于提高文件访问的性能。当应用程序读取文件时,内核会将文件的内容缓存到内存中。如果后续还有对同一文件的访问,就可以直接从缓存中获取数据,而不需要再次从磁盘读取。这种缓存机制可以大大加快文件访问的速度,特别是对于频繁访问的文件。同时,内核也会根据一定的策略(如文件的访问频率、缓存的大小限制等)来管理缓存,当缓存空间不足时,会淘汰一些不常用的缓存数据。
-
网络部分
- 网络协议栈:Linux内核包含了完整的网络协议栈,从物理层协议(如以太网协议)到网络层协议(如IP协议),再到传输层协议(如TCP和UDP协议)以及应用层协议(如HTTP、FTP等)。例如,当你在浏览器中输入一个网址并发送请求时,请求会首先经过应用层的HTTP协议进行封装,然后通过传输层的TCP协议添加端口号等信息,接着在网络层通过IP协议确定目标主机的IP地址并进行路由选择,最后通过物理层的以太网协议将数据发送到网络上。
- 网络设备驱动与接口:Linux内核支持多种网络设备,如以太网卡、无线网卡等。网络设备驱动负责与这些硬件设备进行交互,实现数据的发送和接收。内核为网络设备提供了统一的接口,使得应用程序可以通过这些接口来访问网络设备。例如,当网卡接收到一个数据包时,网卡驱动会将数据包传递给内核的网络协议栈进行处理;当应用程序要发送一个数据包时,会通过系统调用将数据包交给内核,内核再通过网络设备驱动将数据包发送到网络设备上。
- 网络套接字(Socket)接口:Socket是Linux网络编程的基础接口,它提供了应用程序与网络协议栈之间的通信接口。应用程序可以通过Socket接口创建TCP或UDP套接字,进行网络连接、数据发送和接收等操作。例如,在编写一个简单的网络服务器程序时,会使用Socket接口来监听特定的端口,当有客户端连接时,通过Socket接收客户端的数据并进行处理。Socket接口使得网络编程更加方便和统一,无论底层是何种网络协议和网络设备,应用程序都可以使用相同的方式来进行网络通信。
驱动程序也是Linux系统中的一个重要组成部分。在目前的Linux内核的源代码中,移动程序占了大部分。在Linux操作系统中,系统调用是应用程序和内核之间的接口,而设备驱动程序是操作系统内核和机器硬件之间的接口。
在Linux系统中,驱动程序分成了三种基本的类型:
在Linux操作系统中,驱动程序主要有以下三种基本类型:
1. 字符设备驱动(Character Device Driver)
- 定义与特点
- 字符设备是以字节流的方式进行数据传输的设备,其数据处理没有固定的块大小要求。字符设备驱动主要用于处理像串口、终端设备(如键盘、显示器对应的设备文件)、打印机等设备。这些设备通常按照字符或者字节为单位依次读取或写入数据。
- 例如,键盘作为字符设备,当用户按下一个键时,字符设备驱动就会将这个按键对应的字符代码(如ASCII码)传递给系统,系统根据这个字符代码进行相应的处理,比如在文本编辑器中显示出对应的字符。
- 数据传输方式
- 字符设备驱动提供了顺序访问的接口,应用程序通过系统调用(如read、write)按顺序读取或写入数据。读取或写入操作通常是阻塞式的,即如果没有数据可读或没有空间可写,进程会被阻塞直到条件满足。
- 以串口通信为例,当应用程序通过串口发送数据时,字符设备驱动会将数据一个字节一个字节地发送到串口硬件缓存中,如果缓存满了,发送操作就会阻塞,直到有空间可以继续发送;当从串口接收数据时,驱动会等待数据到达,一旦有数据进入缓存,就会将数据按字节读取并传递给应用程序。
- 设备文件表示
- 字符设备在Linux文件系统中有对应的设备文件,通常位于“/dev”目录下,文件类型标记为“c”。设备文件的主设备号(major number)用于标识设备驱动类型,次设备号(minor number)用于区分同一类型的不同设备实例。
- 例如,系统中的多个串口设备可能具有相同的主设备号(表示它们使用相同的驱动程序类型),但不同的次设备号来区分是COM1、COM2等不同的串口。
2. 块设备驱动(Block Device Driver)
- 定义与特点
- 块设备主要用于存储大量的数据,数据的读写是以固定大小的块(通常是512字节的倍数,如常见的4KB)为单位进行的。像硬盘、固态硬盘(SSD)、USB闪存驱动器等存储设备都属于块设备。块设备驱动的主要任务是高效地管理这些存储设备上的数据读写操作。
- 例如,当操作系统要从硬盘读取一个文件时,它会以块为单位向硬盘块设备驱动发送读取请求。块设备驱动会根据请求的块地址,从硬盘的相应位置读取数据块并返回给操作系统。
- 数据传输方式
- 块设备驱动通过缓存机制来提高性能。它会将经常访问的数据块缓存在内存中,当应用程序再次请求这些数据块时,可以直接从缓存中获取,而不必从物理设备中读取,从而加快数据访问速度。同时,块设备驱动支持随机访问,即可以不按照顺序访问设备中的数据块。
- 例如,在数据库系统中,频繁访问的数据表块可能会被块设备驱动缓存到内存中。当数据库应用程序需要读取某个数据表记录时,如果对应的块在缓存中,就可以快速获取;而且可以通过指定块地址的方式随机读取不同位置的数据块。
- 设备文件表示
- 块设备在Linux文件系统中的设备文件也位于“/dev”目录下,文件类型标记为“b”。和字符设备类似,块设备也通过主设备号和次设备号来区分设备类型和不同的设备实例。
- 例如,系统中的多个硬盘分区可能具有不同的次设备号,但它们的主设备号相同,表示它们属于同一类块设备驱动管理的设备。
3. 网络设备驱动(Network Device Driver)
- 定义与特点
- 网络设备驱动用于控制和管理网络接口设备,如以太网卡、无线网卡等,使Linux系统能够通过这些设备连接到网络并进行数据通信。网络设备驱动与字符设备和块设备驱动不同,它不直接对应到文件系统中的设备文件进行读写操作,而是通过网络协议栈来传输数据。
- 例如,以太网卡的网络设备驱动负责将网络协议栈处理后的数据包发送到物理网络介质(如网线)上,同时也负责接收从网络介质传来的数据包,并将其传递给网络协议栈进行处理。
- 数据传输方式
- 网络设备驱动在数据传输过程中需要处理网络协议相关的操作。当发送数据时,驱动会将从网络协议栈接收到的数据包封装成适合网络介质传输的格式(如以太网帧),并添加源和目的MAC地址等信息;当接收数据时,驱动会从网络介质接收到的数据包中提取有效数据,去除帧头帧尾等信息,然后将数据传递给网络协议栈进行上层协议(如IP、TCP等)的处理。
- 例如,在一个基于TCP/IP协议的网络通信中,当应用程序通过Socket发送数据时,数据经过网络协议栈的层层封装后到达网络设备驱动,驱动将其转换为以太网帧格式发送出去。当收到以太网帧时,驱动会进行相反的操作,将数据传递给网络协议栈进行解包处理。
- 设备表示与管理
- 网络设备在Linux系统中有自己的设备名称(如eth0表示第一个以太网接口、wlan0表示第一个无线接口),通过特定的命令(如ifconfig或ip命令)可以对这些网络设备进行配置和管理。虽然网络设备没有像字符设备和块设备那样在“/dev”目录下对应的传统设备文件,但它们在系统内部通过特殊的设备驱动接口与网络协议栈相连。
- 例如,使用“ifconfig eth0 192.168.1.100 netmask 256.256.256.0”命令可以设置以太网接口eth0的IP地址和子网掩码,这个过程就是通过系统与网络设备驱动的交互来实现的。
10.2.3 Linux交叉编译工具链
交叉编译工具链是一个由编译器连接器和解释器等组成的集成开发环境。以上功能主要由glibc、gcc、binuntils和gdp这4个软件包提供。
-
Glibc(GNU C Library)
- 定义与功能
- Glibc是GNU项目发布的C标准库的实现,几乎所有的Linux程序都依赖它。它提供了一系列的函数,用于字符串处理、数学计算、文件操作、内存管理等诸多方面。例如,像
printf()
函数用于格式化输出,malloc()
和free()
函数用于动态内存分配和释放,这些常用的函数都是Glibc提供的。 - 它还包含了对系统调用的封装,使得应用程序可以以更方便、更抽象的方式使用操作系统提供的底层服务。比如,在进行文件读写时,通过Glibc提供的
fopen()
、fread()
和fwrite()
等函数,开发者可以不用直接处理底层的系统调用细节。
- Glibc是GNU项目发布的C标准库的实现,几乎所有的Linux程序都依赖它。它提供了一系列的函数,用于字符串处理、数学计算、文件操作、内存管理等诸多方面。例如,像
- 重要性
- Glibc是Linux系统中程序运行的基础。没有它,很多应用程序将无法正常编译和运行。它保证了不同的Linux软件在遵循C标准的基础上,能够有统一的函数库来调用,从而实现软件的可移植性。例如,一个在使用Glibc的Ubuntu系统上编译的C程序,在很大程度上可以很容易地移植到另一个同样使用Glibc的CentOS系统上。
- 版本差异
- 不同版本的Glibc可能会有一些函数的行为变化或者新增功能。这可能会导致一些旧的程序在新的Glibc版本下出现兼容性问题,或者一些新的程序需要较新的Glibc版本才能充分利用其新特性。例如,较新的Glibc版本可能会对安全性方面进行增强,如改进内存分配函数的安全性检查。
- 定义与功能
-
GCC(GNU Compiler Collection)
- 定义与功能
- GCC是一个功能强大的编译器集合,它支持多种编程语言,如C、C++、Objective - C、Fortran、Ada等。对于C和C++语言,它可以将源程序(
.c
或.cpp
文件)编译成目标机器能够理解的机器语言(二进制文件)。例如,当你编写一个简单的C程序hello.c
,使用gcc -o hello hello.c
命令,GCC就会将hello.c
编译成一个名为hello
的可执行文件。 - GCC包含了多个编译阶段,包括预处理(处理
#include
、#define
等预处理指令)、编译(将预处理后的代码转换为汇编语言)、汇编(将汇编语言转换为机器语言)和链接(将多个目标文件和库文件链接成一个可执行文件)。在每个阶段,GCC都提供了丰富的选项来控制编译过程,以满足不同的需求。
- GCC是一个功能强大的编译器集合,它支持多种编程语言,如C、C++、Objective - C、Fortran、Ada等。对于C和C++语言,它可以将源程序(
- 优化功能
- GCC具有强大的优化功能,可以生成高效的机器代码。它可以根据不同的优化级别(如
-O0
、-O1
、-O2
、-O3
)对代码进行优化。例如,在-O2
优化级别下,GCC会对循环展开、函数内联等操作进行优化,以提高程序的执行速度和减小程序的体积。这些优化可以使程序在运行时更加高效,减少CPU和内存的消耗。
- GCC具有强大的优化功能,可以生成高效的机器代码。它可以根据不同的优化级别(如
- 在开发中的应用
- 在Linux软件开发中,GCC是最常用的编译器。无论是开发系统软件、应用程序还是内核模块,GCC都发挥着关键作用。开发者可以通过编写符合编程语言标准的源程序,利用GCC的各种功能来生成高质量的可执行文件或者库文件。例如,在开发一个Linux驱动程序时,需要使用GCC将驱动程序的源文件编译成可以加载到内核中的模块文件。
- 定义与功能
-
Binutils(Binary Utilities)
- 定义与功能
- Binutils是一组二进制工具集,主要用于处理目标文件、可执行文件和库文件。它包含了诸如
as
(汇编器)、ld
(链接器)、objdump
(反汇编工具)、readelf
(查看ELF文件信息工具)等工具。例如,as
工具可以将汇编语言文件(.s
文件)转换为目标文件(.o
文件),ld
工具可以将多个目标文件和库文件链接成一个完整的可执行文件。 objdump
工具是一个非常有用的工具,它可以查看目标文件或者可执行文件的详细信息,包括反汇编后的汇编代码、符号表信息等。例如,当你想要查看一个可执行文件内部的函数实现细节,或者检查程序中是否存在未定义的符号时,objdump
就可以派上用场。
- Binutils是一组二进制工具集,主要用于处理目标文件、可执行文件和库文件。它包含了诸如
- 在编译过程中的角色
- 在整个编译过程中,Binutils与GCC紧密配合。GCC在编译阶段生成目标文件后,Binutils中的链接器
ld
会将这些目标文件以及相关的库文件(如Glibc库)链接在一起,形成最终的可执行文件。在调试和分析程序时,Binutils提供的工具可以帮助开发者了解程序的内部结构和运行机制。
- 在整个编译过程中,Binutils与GCC紧密配合。GCC在编译阶段生成目标文件后,Binutils中的链接器
- 重要性
- Binutils提供的工具对于理解和操作二进制文件是不可或缺的。在软件开发过程中,特别是在处理底层代码、进行性能优化或者调试程序时,这些工具能够帮助开发者深入了解程序的行为。例如,在分析程序的内存布局或者查找程序崩溃的原因时,Binutils中的工具可以提供关键的信息。
- 定义与功能
-
GDB(GNU Debugger)
- 定义与功能
- GDB是一个强大的调试器,用于调试用C、C++等语言编写的程序。它允许开发者在程序运行过程中观察程序的状态,包括变量的值、函数的调用栈、程序的执行流程等。例如,当程序出现错误或者行为不符合预期时,开发者可以使用GDB来找出问题所在。
- GDB提供了多种调试功能,如设置断点(在程序的特定位置暂停执行)、单步执行(逐行执行程序代码)、查看变量的值(可以在程序执行的任何时刻查看变量的当前值)和查看调用栈(了解函数之间的调用关系)。通过这些功能,开发者可以深入了解程序的运行过程,发现和修复程序中的错误。
- 调试过程示例
- 假设你有一个简单的C程序,其中有一个函数出现了错误。你可以使用GDB来调试这个程序。首先,使用
gcc -g
命令编译程序,这个选项会在生成的可执行文件中包含调试信息。然后,启动GDB并加载这个可执行文件。在GDB中,你可以在怀疑有问题的函数入口处设置断点,当程序运行到这个断点时,就会暂停执行。此时,你可以查看变量的值,检查是否符合预期,然后通过单步执行来观察程序的后续行为,直到找到错误的根源。
- 假设你有一个简单的C程序,其中有一个函数出现了错误。你可以使用GDB来调试这个程序。首先,使用
- 在开发中的价值
- 在复杂的Linux软件开发中,GDB是必不可少的工具。它可以大大提高调试的效率,尤其是对于大型项目或者涉及多线程、动态库等复杂情况的程序。通过使用GDB,开发者可以更快地定位和解决程序中的错误,从而提高软件的质量。
- 定义与功能
10.3 Android系统开发工具
10.3.1 Android代码目录结构
安卓代码的组织方式是进行安卓系统开发的重要步骤。
安卓代码包含三个部分,分别是核心工程、扩展工程和包。
- 核心工程是建立安卓系统的基础,在跟目录的各个文件夹中。安卓的核心工程包含了对安卓系统基本运行的支持,工程内容包括bootloader、build 、kernel和system等。
- 扩展工程使用的是其他开源项目,扩展功能包含在external文件夹中。是一些经过修改后适应安卓系统的开源工程。
- 包提供了安卓应用程序和服务,其中既包含要在安卓设备上运行的代码,还包括主机编译工具仿真环境等。
第一级别包含的目录和文件如下:
Makefile、bionic、Bootloader、build、dalvik、development、device、external、frameswork、hardware、kernel、packages、prebuilt、recovery、system、vendor。
上面的目录可能在相应的sdk中不存在,或者在sdk包含的目录在该列表中不存在,因此需要查看具体开发的机器和平台。
一般来说,sdk主体就是上述的16个目录,在上述的目录中sdk称为Software Develop Kit,指Android开发完整软件包。
- CSP是CPU Support package是针对某操作系统而适配的与cpu紧密相关的代码与库。
- BSP是Board support package 是针对某操作系统而适配的与具体开发的pcb板紧密相关的代码与库。
编译完成后将在根目录中生成一个out文件夹,生成的所有的安卓代码结构内容均放置在这个文件夹中:
- Out文件夹包含以下目录和文件:CaseCheck.txt、casecheck.txt、host、common、linux-x86、target、product。
- 其中,最主要的两个目录为host和target,前者表示在主机x86生成的工具后者表示目标基于armV5运行的内容。
10.3.2 Ubuntu与虚拟机
为移动设备进行程序设计和开发,同样使用交叉开发模式。
- 目前宿主机上的操作系统一般使用Linux系统,编译采用安卓操作系统,一个真正的Linux环境是必不可少的。
谷歌的官方推荐Ubuntu系统作为安卓的开发机,其名称来自于非洲南部祖鲁语ubuntu。虽然其他的Linux发行版也可以作为开发机,但是需要手动配置大量的库。
- ubuntu是一个以桌面应用为主的开源Linux操作系统,基于Debian GNU/Linux,支持x86、amd64(x64)和pcc架构。
如果设定windows为开发操作系统,工作难度会很大。虽然使用gcc可以在windows下交叉编译,但是安卓自带的很多开发工具如模拟器、rom生成脚本等移植的windows下十分困难。
因此,如果要使用windows作为开发系统,就需要通过虚拟机安装Linux虚拟机是独立运行与主机操作系统的离散环境。
- 加载虚拟机后,计算机就可以运行自己的操作系统和应用程序,并能够在运行于桌面上的多台虚拟器之间进行切换,通过一个网络共享虚拟机挂起、恢复和退出虚拟机。
- Vm ware就是一个常用的虚拟机平台。
- Virtual box是另一个可以采用的x86或者是x64虚拟化产品选择virtual box作为windows下虚拟Linux的虚拟化平台,是因为virtual box是唯一免费开源的虚拟机。同时virtual box还提供高性能的全硬件虚拟化功能。
如果习惯使用windows操作系统可以完成在虚拟机的安装,后在虚拟机支持下安装ubuntu操作系统,然后在ubuntu操作系统中完成安卓系统及开发工作。
- 安装虚拟机和ubuntu之前,要充分注意是否有充足的硬盘空间。
- 安卓对于jdk的版本十分敏感,需要确保安装了正确的gdk版本才能正确的编译安卓的源代码。
10.3.3 安卓系统及开发工具链
安卓系统运行于Linux kernel之上,但并不等同于Linux。
- Cairo、X11、Alsa、FFmpeg、GTK等都被安卓系统摘出来。
- 安卓系统中以bionic取代glibc、以Skia取代Cairo,以OpenCoRE取代FFmpeg。
- 安卓为了达到商业应用的目的,必须移除被GUN GPL授权证约束的部分。
- 将驱动程序移动到userspace。
- 使Linux drive与Linux kernel彻底分开。
安卓系统使用git作为代码管理工具。
安卓所用的交叉编译工具链可以在网上下载获得。如果下载了完整的安卓项目的源代码,则可以在相应的系统目录下找到交叉编译工具。
- 安卓并没有使用glibc作为c库,而是采用了谷歌自己开发的bionic Libc作为C库。
- 安卓系统的官方交叉编译工具链也是基于bionic Libc的。
所以使用或其移植其他工具链来学安卓就比较麻烦。如果要支持安卓的应用程序开发,建议使用官方或具有的安卓系统及开发工具链来进行开发。
- 安卓系统及开发包括内核的移植或者驱动程序的开发,涉及内核的编译。
- 先进行Android系统级的程序开发,然后在编译之前进行配置,再使用build系统进行编译,最后生成启动配置文件。
安卓编译系统。Build system用来编译安卓系统。
- 该系统主要由make文件shell脚本以及Python脚本组成,支持多架构多语言多目标的编译方式。
- Build系统中最主要的处理逻辑都在make文件中,而其他的脚本文件只起到一些辅助作用。
整个Build系统的make文件可以分为三类。
-
顶层Makefile
- 功能概述
- 顶层Makefile是整个Build系统的核心控制文件。它主要负责定义整个项目的构建目标和构建策略,从宏观角度把控项目的构建流程。例如,它会确定是要构建一个完整的产品(如完整的软件系统或硬件设备固件),还是只构建其中的某个模块;还会决定构建的版本类型,是用于开发调试的版本,还是用于发布的稳定版本。
- 项目级别的配置与依赖管理
- 在这个Makefile中,会处理项目级别的配置选项。这些配置选项可能涉及到编译器的选择、编译优化级别、目标平台等众多因素。例如,它可以通过定义变量来指定使用GCC编译器,并根据目标平台(如ARM架构的设备或x86架构的模拟器)来设置不同的编译参数。同时,它会管理项目的全局依赖关系,明确各个模块之间的依赖顺序,确保在构建过程中,先构建被依赖的模块。
- 与子Makefile的协调
- 顶层Makefile会调用和协调各个子Makefile。它就像是一个总指挥,将构建任务分解并分配给不同的子系统。例如,在一个复杂的软件项目中,可能有专门负责用户界面模块、数据库模块、网络通信模块的子Makefile。顶层Makefile会通过一定的规则和命令来触发这些子Makefile的执行,并且收集它们的构建结果,最终组合成完整的产品。
- 功能概述
-
模块级Makefile
- 模块定义与构建规则
- 模块级Makefile主要针对项目中的各个独立模块进行构建规则的定义。每个模块都有自己的功能和结构,模块级Makefile会根据这些特点来确定如何将模块中的源文件编译成目标文件。例如,对于一个图形处理模块,它会规定如何将图形算法的源文件(如
.cpp
文件)编译成对应的目标文件(.o
文件),包括指定合适的头文件路径、编译选项等。
- 模块级Makefile主要针对项目中的各个独立模块进行构建规则的定义。每个模块都有自己的功能和结构,模块级Makefile会根据这些特点来确定如何将模块中的源文件编译成目标文件。例如,对于一个图形处理模块,它会规定如何将图形算法的源文件(如
- 局部依赖处理
- 这个Makefile会处理模块内部的依赖关系。一个模块可能包含多个源文件和头文件,这些文件之间存在相互依赖的情况。例如,一个源文件可能会包含其他头文件,模块级Makefile需要确保在编译这个源文件时,所依赖的头文件是最新的。如果头文件发生变化,相关的源文件会被重新编译。同时,对于模块内的资源文件(如图像、配置文件等),也会明确它们与目标文件的依赖关系。
- 接口与对外提供的目标
- 模块级Makefile还会定义模块的对外接口,即该模块向外提供的目标文件、库文件或者可执行文件。这些接口是其他模块与该模块进行交互的关键。例如,一个模块可能会构建出一个共享库(
.so
文件),其他模块可以通过链接这个共享库来使用该模块提供的功能。模块级Makefile会确保这些对外提供的目标按照正确的方式构建,并遵循一定的命名和版本规则。
- 模块级Makefile还会定义模块的对外接口,即该模块向外提供的目标文件、库文件或者可执行文件。这些接口是其他模块与该模块进行交互的关键。例如,一个模块可能会构建出一个共享库(
- 模块定义与构建规则
-
子目录Makefile(递归Makefile)
- 目录结构与构建顺序
- 子目录Makefile用于处理项目子目录中的构建任务。在一个大型项目中,通常会按照功能或者层次结构划分为多个子目录。子目录Makefile会根据子目录的内容和在项目中的位置来确定构建顺序。例如,在一个包含底层驱动子目录、中间件子目录和应用层子目录的项目中,底层驱动子目录的构建可能需要先完成,因为其他子目录中的模块可能依赖于底层驱动的功能。
- 继承与扩展顶层和模块Makefile规则
- 子目录Makefile会继承和扩展顶层Makefile和模块Makefile的规则。它可以复用一些通用的构建规则,同时根据子目录的特殊情况进行补充和修改。例如,在顶层Makefile中定义了通用的编译器选项,子目录Makefile可以在这个基础上,根据本子目录中的源文件特点(如需要特殊的预处理指令或者包含特定的库)来添加额外的编译选项。
- 相对路径与文件查找
- 子目录Makefile在处理文件时,需要考虑相对路径的问题。由于它是在子目录中运行,源文件、头文件和目标文件的路径与顶层目录或者其他子目录有所不同。它会通过相对路径来查找和引用这些文件,确保构建过程能够正确地找到所需的资源。例如,在一个子目录中,它会使用相对路径来指定本目录下的源文件和头文件,同时也会考虑如何将生成的目标文件放置在合适的位置,以便于其他子目录或者模块能够正确地链接和使用。
- 目录结构与构建顺序
整个系统中包含了大量的模块,每一个模块都有一个专门的make文件,这类文件的名称统一为Android.mk,该文件中定义了如何编译当前模块。
Build系统会在整个源码数中扫描名称为Android.md的文件,并根据其中的内容执行模块的编译。
- 在build的产物中,最重要的是三个镜像文件。三个文件分别是 system.img、ramdisk.img和userdata.img。
- system.img中包含了Android OS的系统文件、库、可执行文件、及预置的应用程序将,被挂载为根分区。
- ramdisk.img在启动时将被Linux内核挂在为只读分区,它包含了初始化的一些文件和配置文件,用来挂载其他系统镜像并启动init进程。
- userdata.img将被挂在位/data,包含了应用程序相关的数据以及和用户相关的数据。
make命令是编译过程中一个非常重要的编译命令。利用make工具可以将大型的开发项目分解成更多易于管理的模块。对于一个包含几百个源文件的应用程序,使用make和Matkefile工具就可以简洁明快的理顺各个源文件之间纷繁复杂的关系。
执行make命令的时候需要一个make file文件以告诉make命令如何去编译和链接程序。
以下是一个简单的示例Makefile文件,用于展示如何编译和链接C语言源文件的基本规则。假设我们有两个C语言源文件 main.c
和 utils.c
,它们共同构建一个可执行程序,以下是对应的Makefile内容:
# 定义编译器,这里使用gcc,你也可以根据需要更换为其他编译器,比如clang等
CC = gcc
# 定义编译选项,例如开启警告信息等,可根据实际情况调整
CFLAGS = -Wall -g
# 定义要生成的最终可执行文件名
TARGET = my_program
# 列出所有的源文件,这里是示例的两个源文件,可以根据实际项目添加更多
SRC_FILES = main.c utils.c
# 根据源文件生成对应的目标文件(.o文件),这里使用了一个通配符规则
# %.o表示以.o结尾的目标文件,其依赖于对应的.c源文件
# 编译命令使用了前面定义的编译器和编译选项
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 定义最终可执行文件的构建规则,它依赖于所有的目标文件(.o文件)
$(TARGET): $(SRC_FILES:.c=.o)
$(CC) $(CFLAGS) $^ -o $@
# 添加一个伪目标clean,用于清除编译生成的中间文件和最终可执行文件
clean:
rm -f $(TARGET) $(SRC_FILES:.c=.o)
# 添加一个伪目标help,用于显示帮助信息
help:
@echo "Available targets:"
@echo " make - Build the $(TARGET) program."
@echo " make clean - Remove generated files."
@echo " make help - Show this help message."
以下是对这个Makefile各部分的详细解释:
1. 变量定义部分
CC
变量:用于指定编译器,这里将其设置为gcc
,如果想使用其他编译器,比如clang
,可以修改为CC = clang
。CFLAGS
变量:用来设置编译选项。-Wall
表示开启大多数警告信息,帮助我们发现代码中潜在的问题;-g
则是添加调试信息,方便后续使用调试工具(如gdb
)调试程序。可以根据实际需求添加或修改其他编译选项,比如-O2
可以开启一定级别的优化。TARGET
变量:定义了最终要生成的可执行文件的名称,在这里我们将可执行文件命名为my_program
。SRC_FILES
变量:列出了项目中所有的C语言源文件。在这个示例中有main.c
和utils.c
,如果项目有更多的源文件,只需按照空格分隔添加在这里即可。
2. 目标文件生成规则(编译规则)
%.o: %.c
这一行定义了一个模式规则,用于从.c
源文件生成对应的.o
目标文件。%
是一个通配符,匹配相同的文件名部分。例如,main.c
会对应生成main.o
,utils.c
会生成utils.o
。$(CC) $(CFLAGS) -c $< -o $@
是具体的编译命令。$(CC)
使用前面定义的编译器,$(CFLAGS)
应用设定的编译选项,-c
表示只进行编译操作,生成目标文件,$<
表示规则中的第一个依赖文件(也就是对应的.c
源文件),-o $@
表示输出的目标文件是当前规则的目标(也就是对应的.o
文件)。
3. 可执行文件构建规则(链接规则)
$(TARGET): $(SRC_FILES:.c=.o)
定义了最终可执行文件my_program
(由$(TARGET)
指定)的构建依赖于所有由源文件生成的目标文件(通过$(SRC_FILES:.c=.o)
将源文件列表中的.c
替换为.o
得到目标文件列表)。$(CC) $(CFLAGS) $^ -o $@
是链接命令,$(CC)
和$(CFLAGS)
含义同前,$^
表示所有的依赖文件(即所有的.o
目标文件),-o $@
表示输出的文件是当前规则的目标(也就是最终的可执行文件my_program
)。
4. 伪目标部分
clean
伪目标:用于清理编译过程中生成的中间文件(.o
文件)和最终的可执行文件。rm -f $(TARGET) $(SRC_FILES:.c=.o)
这条命令使用rm
命令强制删除指定的文件。当执行make clean
命令时,就会执行这个清理操作。help
伪目标:用于显示一些帮助信息,告诉用户可以使用的make
命令目标以及它们的作用。使用@echo
可以在执行make help
时输出相应的提示内容。
使用这个Makefile时,可以在包含该Makefile文件以及源文件的目录下,在终端中输入以下命令:
make
:执行编译和链接操作,生成最终的可执行文件my_program
。make clean
:清除之前编译生成的文件,方便重新编译或者整理项目目录。make help
:显示帮助信息,了解可用的make
命令操作。
你可以根据实际项目的具体情况,比如更多的源文件、不同的编程语言、需要链接的库文件等,对这个Makefile进行相应的修改和扩展。例如,如果要链接外部库,只需要在链接规则的命令中添加对应的库文件名(例如 -l[库名]
的形式)即可。
11 Boot Loader
BootLoader是安卓系统的重要组成部分,负责安卓移动设备的启动|image的烧写、关机、充电等。
11.1 Boot Loader概述
11.1.1 BootLoader主要功能
操作系统可以就地在rom和flash中执行,从而可以不需要任何的引导装入程序。
- 但是大多数嵌入式系统还是采用bootloader的。
- Bootloader的目的就是要准备好初始的运行环境。
嵌入式系统家电或者是复位之后,cpu通常都从cpu制造商预先安排的地址上取地址。
- Arm处理器在复位时通常都从0x00处取第1条指令。
- 基于处理器构建的嵌入式系统通常都有某种类型的固态存储设备被映射到这个预先安排的地址上。
- 因此,如果将boot loader映射到安装地址上,则系统加电之后,cpu将首先执行bootloader程序。
具体来说,bootloader的功能大致分为以下几个部分。
- 对pll时钟进行初始化。处理器在启动的时候,为了获得更好的这个设备兼容性及工作频率一般都很低,在bootloader程序中会提高处理器的时钟频率,以加快运行速度。速度一旦调好就不会发生改变,随着锁相环时钟频率的提高,运行速度加快,系统耗电量也会增加。
- 初始化SDRAM内存控制器。 bootloader自身也需要用到内存,大多数的bootloader都会将自己加载到内存中内存的配置一般包括行地址和列地址的配置以及自动刷新频率的配置。
- 初始化中断控制器和中断服务程序。
- 初始化各地址空间的片选地址寄存器和读写程序。
- 初始化堆栈寄存器。例如x86说实话的esp寄存器。
- Bootloader中需要访问的其他硬件设备进行初始化。
- 将bootloader加载到内存的过程中如果需要解压,还需要完成解压操作。
- 加载需要运行的应用程序,并最终运行到被加载的应用程序。
嵌入式bootloader也支持多种体系的cpu。例如,U-boot就同时支持arm体系结构和MIPS体系结构的cpu。
在嵌入式系统领域中,arm处理器系列的bootloader,大多数以U-boot为基础。U-boot是一个开源的项目,读者可以从网络上下载到最新的源代码。
11.1.2 Boot Loader操作模式
大多数的put load都包含两种不同的操作模式,即启动加载模式和下载模式。这两种仅对于开发人员有意义。
1. 启动加载模式
启动加载模式也称自主模式。即Bootloader从目标机上的某个固态存储器设备上将操作系统加载到ram中运行。
- 操作系统需要运行在rom中,因为rom相对于flash的读写速度更快,所以先把映像从flash复制到ram中,再从ram空间运行是非常高效的,且在调试阶段可以暂时修改代码而不损坏原始映像。
- 操作系统的映像通常处于压缩状态。在解压后,映像会被搬运至rmb中,复制和解压都需要一段程序来配置,这段程序只能在ram中就地执行。
- 之所以压缩是为了降低存储空间。在嵌入式产品启动时,bootloader都工作在这种模式下。
2. 下载模式
在这种模式下,目标机上的bootloader将通过串口连接或者网络连接等通信手段从主机下载文件。
从主机下载的文件通常首先被bootloader保存到目标机的RAM中,然后再被bootloader写到目标机的flash中。bootloader这种模式通常在第1次安装内核与根文件系统时被使用,以后的系统更新时也会使用。
下载模式的bootloader通常会向它的终端用户提供一个简单的命令行接口。
像Blob或U-Boot这样功能强大的bootloader通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。
11.1.3 BootLoader通信
所有主机和目标机之间一般通过串口建立连接。
bootloader在执行时通常会对串口进行io操作,如输出打印信息到窗口,从窗口读取用户控制字符等。
传输协议通常是xmodem、ymodem和zmodem协议中的一种。
串口传输的速度是有限的,因此通过以太网连接并借助TFTP下载文件是更好的选择。
- 如果通过以太网连接和TFTP下载文件时,主机平台中必须有一个软件用来提供 TFTP服务。
11.2 BootLoader的工作过程
11.2.1 BootLoader工作过程概述
从固态存储设备上启动的bootloader大多数都有两个阶段的启动过程,即启动过程可以分为阶段一和阶段二。
- 依赖于cpu体系结构的代码通常都放在阶段一中,而且通过汇编语言来实现。
- 硬件设备初始化。
- 为加载bootloader的阶段二准备ram空间。
- 复制bootloader的阶段二到ram空间。
- 设置堆栈
- 跳转到阶段2的c入口。
- 阶段二通常使用c语言来实现,这样可以实现一些更加复杂的功能。
- 初始化本阶段要使用的硬件设备。
- 检测系统内存映射。Memory Map
- 将内核映像和根文件系统映像从flash读到ram空间。
- 为内核设置启动参数。
- 调用内核。
11.2.2 BootLoader 阶段1
-
硬件复位与基本初始化
- 复位操作
- 当设备上电或复位时,Bootloader阶段一首先被触发。此时,系统硬件会经历一个复位过程,包括CPU和各种外围设备。这个复位操作将硬件的状态设置为一个已知的初始状态,例如,CPU内部的寄存器被清零或者设置为预定义的初始值,各种控制信号被复位,这为后续的有序启动奠定了基础。
- 屏蔽所有中断为中断提供服务通常是操作系统设备驱动程序的责任,因此在bootloader的阶段可以不必响应任何中断。
- 在一些简单的微控制器中,会设置CPU的时钟源,选择内部时钟或者外部晶振作为时钟信号源,并且设置一个基础的时钟频率,以确保CPU能够开始执行指令。
- 同时,会对一些关键的寄存器进行初始化,如设置堆栈指针(SP)寄存器,为程序运行提供基本的栈空间。
- 对于内存(RAM)系统,也会进行简单的初始化。虽然此时可能不会进行完整的内存检测和复杂的内存控制器配置,但会设置一些基本的内存访问参数,确保可以对内存进行最基本的读写操作。例如,在一些简单的嵌入式系统中,会设置内存的初始读写模式和基本的地址映射,使得后续可以将代码和数据加载到内存中。
- 复位操作
-
为加载阶段2准备ram空间
- 为了达到更快的执行速度,通常把阶段二加载到ram空间中执行。因此,必须为加载bootloader的阶段二准备好一段可用的ram空间。
- 由于阶段2通常是c代码执行的,因此在考虑空间大小时除了阶段2可执行映像的大小,还必须把堆栈空间也考虑进来。
- 一般而言,1M 的ram空间已经足够了。
- 必须保证安排的地址范围是可读写的ram空间。这个检测每一个page开始的两个字是否是可读写的,记这个算法为test-mp。
- 为了得到一段干净的ram范围,也可以对安排的ram空间范围进行清零操作。
-
加载初始代码片段
- 代码存储位置识别
- Bootloader阶段一需要确定初始代码片段的存储位置。在不同的设备中,这个初始代码可能存储在不同的地方。例如,在一些基于闪存(Flash)的系统中,初始代码存储在闪存的特定区域,这个区域通常是在设备制造过程中预先编程好的,并且有固定的起始地址。Bootloader阶段一要能够识别这个起始地址,并且知道如何从这个存储位置读取代码。
- 代码加载到内存
- 一旦确定了存储位置,就需要将初始代码片段加载到内存中。由于此时内存可能尚未完全初始化,这个加载过程可能比较简单。例如,通过直接内存访问(DMA)或者简单的指令读取 - 存储操作,将闪存中的初始代码逐字节或逐字地移动到内存的特定位置。这个位置通常是在之前初始化的内存区域中指定的,并且要保证代码能够在这个位置正确地执行。
- 代码存储位置识别
-
初步的异常和中断设置
- 异常向量表初始化
- 在Bootloader阶段一,要建立初步的异常向量表。异常向量表是一个存储了各种异常处理程序入口地址的表格,这些异常包括复位异常、硬件故障异常、软件中断异常等。例如,当系统发生复位异常时,CPU会根据异常向量表中的复位异常入口地址,跳转到相应的复位处理程序。在这个阶段,会将一些最基本的异常处理程序入口地址填充到异常向量表中,如将复位异常处理程序的地址设置为刚刚加载到内存中的初始代码片段的起始地址。
- 中断优先级和屏蔽设置
- 对于中断系统,也会进行初步的设置。这包括设置一些基本的中断优先级。例如,对于一些关键的硬件中断,如电源故障中断,会设置较高的优先级,确保在发生紧急情况时能够及时响应。同时,可能会对一些暂时不需要的中断进行屏蔽,以避免在初始启动阶段受到不必要的干扰。不过,在这个阶段,通常不会进行完整的中断服务程序的设置,只是为后续更详细的中断系统配置打下基础。
- 异常向量表初始化
-
设置堆栈指针
- 设置堆栈指针是为执行c语言代码做好准备,通常可以把sp的值设置为之前的(ram空间结尾-4)。记在前面安排的1M的ram空间的最顶端向下生长。
- 设置堆栈指针是为执行c语言代码做好准备,通常可以把sp的值设置为之前的(ram空间结尾-4)。记在前面安排的1M的ram空间的最顶端向下生长。
-
跳转到阶段2入口
在RAM系统中,可以通过修改pc寄存器为合适的地址来实现这个操作。
11.2.3 BootLoader阶段2
代码通常是c语言实现的,以便实现更加复杂的功能和更好的可疑之性。但与普通的c语言应用程序不同的是,在编译和链接bootloader的程序中不能使用glibc库中的任何支持函数。这就带来一个问题,那就是从哪里跳转进去main函数。
用一段trampoline汇编小程序为m函数的外部包裹。
BootLoader阶段2的主要内容:
-
初始化阶段要用到的硬件设备。
- 初始化至少一个串口
- 初始化计时器等。
- 这个时候可以把led灯点亮,表示已经进入了main函数的执行。
-
检测系统的内存映射
- 内存映射是指在整个4GB的物理地址空间中,哪些地址被分配用作寻址系统的RAM单元。
- 具体的嵌入式系统往往只把cpu预留的全部ram地址空间中的一部分映射到ram单元上。
- Bootloader的第2个阶段必须在执行任何操作之前检测整个系统的内存映射情况,也就是必须知道cpu预留的全部RAM地址空间中哪些被真正映射到ram地址单元。
- 这个检测系统的内存映射情况的算法,也可以将内存映射的详细信息打印到窗口。
-
加载内核映像和根文件系统映像
- 规划内存占用的布局。这里包括两个方面:第1内存内核映像所占用的内存范围。第2根文件系统映像所占用的内存范围。
- 对于内核映象,一般将其复制到从(MEM_START+0x8000)这个基地址开始的大约1M大小的内存范围内。 之所以空出来32k的一个内存,主要是因为Linux内核要在这段内存中存放一些全局数据结构。例如启动参数和内核页表等信息。
- 对于跟文件系统的映射,一般将其复制到MEM_START+0x100000开始的地方。如果用RAM disk作为根文件系统映像,则其解压后的大小一般是1MB。
- 从Flash上复制
- 由于像arm这样的嵌入式cpu通常是在同一的内存地址空间中寻址flash等固态存储设备,因此从flash上读取数据和从RAM地址单元中读取数据并没有什么不同。
- 规划内存占用的布局。这里包括两个方面:第1内存内核映像所占用的内存范围。第2根文件系统映像所占用的内存范围。
-
设置内核的启动参数。
- 在将内核映像和根文件系统映像复制到ram地址空间之后,就可以准备启动Linux内核了。
- 但是在调用内核之前应该做一步准备工作,即设置Linux内核的启动参数。
- 通过设置启动参数来进行启动的配置。Linux内核在启动时可以以命令行参数的形式来接收信息。
-
调用内核。
- BootLoader调用Linux内核的方法就是直接跳转到内核的第1条指令处,即直接跳转到MEM_START+0x8000地址处。
- 跳转时要满足的条件:
11.3 U-Boot启动流程分析
11.3.1 U-Boot概述
U-Boot(Universal Boot Loader)是遵循GPL条款的开放源码项目。
U-Boot不仅支持Linux系统的引导,还支持NetBSD、VxWorks、QNX等嵌入式系统。它支持很多处理器,如x86、ARM、MIPS等。
U-Boot支持ip和MAC等的预制功能,这一点和其他的BootLoader类似。U-Boot还有以下特有功能:
- 在线读写flash、IIC和eerom以及RTC以及IDE等。
- 支持kermit和S-record串行口下载代码。 U-boot可以将elf32位格式的可执行文件转换成S-record格式,直接从串口下载并执行。
- 识别二进制、ELF32、uImage格式的Image,对Linux引导有特别的支持。
- 单任务软件运行环境。 Uboot可以动态加载和运行独立的应用程序。
- 监控命令集:读写io、内存、寄存器、外设测试功能等。
- 脚本语言支持。类似Bash脚本。
- 支持watchdog、 lcd logo和状态指示灯功能。
- 支持MTD(内存技术设备)和文件系统。
- 支持中断。由于传统的bootloader分为阶段一和阶段二,因此在阶段二中添加中断处理服务非常困难。但是U-boot将两部分放到了一块,所以添加中断服务程序比较方便。
10.具有详细的开发文档。
13.3.2 U-boot代码结构
13.3.3 U-Boot启动流程分析
现在的安卓手机都具有两个处理器,应用处理器AP和通信处理器CP。
- Ap负责主操作系统与用户交互的应用程序的执行。
- Cp负责无线电话通信功能,并通过ap向安卓终端用户提供相应的无线通信功能。
- Cp的工作复杂,而机密一般由芯片厂商适配,所以用户主要注意ap。
按代码执行顺序和代码编写语言划分,U-Boot的启动过程可以分为两个阶段
- 第一阶段是汇编代码阶段,在这个阶段,由于要考虑存储空间受限和启动速度要快,所以代码并行采用效率更高的汇编语言。
- 二阶段c代码阶段到此阶段已经可以开始与用户交互,而且机器的硬件大多激活,所以用c语言编写。
第1阶段的代码在start.s中实现。在这段代码里面,U- boot要完成ddr land的flash等基础硬件的初始化。
文章目录
- 1 嵌入式系统概述
- 1.1 嵌入式系统基本概念
- 1.1.1 嵌入式系统定义
- 1.1.2 嵌入式系统的发展
- 1.1.3 嵌入式系统的特点
- 1.2 嵌入式系统分类
- 1.2.1 单个微处理器
- 1.2.2 嵌入式处理器可扩展的系统
- 1.2.3 复杂的嵌入式系统
- 1.2.4 在制造或过程控制中使用的计算机系统
- 1.3 嵌入式处理器
- 1.4 嵌入式系统的组成
- 1.4.1 嵌入式系统的硬件
- 1.4.2 嵌入式系统的软件
- 1.5 嵌入式操作系统
- 1.5.1 嵌入式操作系统的发展
- 1.5.2 嵌入式操作系统的分类
- 1.5.4 主流嵌入式操作系统简介
- 2 ARM11体系结构
- 2.1 ARM微处理器概述
- 2.1.1 arm公司简介
- 2.1.2 ARM特点
- 2.1.3 ARM体系结构的版本
- 2.2 ARM11 系列微处理器
- 2.3 ARM11系列微处理器架构
- 2.4 ARM11 流水线
- 2.4.1 流水线结构的性能
- 2.4.2 流水线级数的影响
- 2.4.3 ARM11处理器中流水线的管理
- 2.5 ARM工作模式及寄存器组
- 2.5.1 ARM核工作模式
- 2.5.2 ARM寄存器分组
- 2.5.3 工作模式分析
- 2.6 各种模式的工作机制
- 2.6.1 CPSR、PC、SPSR_XXX和LR_XX寄存器
- 2.6.4 Supervisor特权模式
- 2.6.5 Abort特权模式
- 2.6.6 Undefined特权模式
- 2.6.7Secure monitor模式
- 2.6.8 System模式
- 2.6.9 ARM中各个异常处理响应优先级
- 2.7 进入和退出异常中断的过程
- 2.7.1 异常中断响应过程
- 2.7.2 从异常中断处理程序返回
- 3 ARM为处理器的指令系统
- 3.1 Arm微处理器的指令集概述
- 3.1.1 Arm微处理器的指令的分类与格式
- 数据处理指令
- 加载/存储指令
- MOV 指令
- 分支指令
- 程序状态寄存器(PSR)处理指令
- 协处理器指令
- 异常产生指令
- 3.1.2 指令的条件域
- ARM指令的条件码
- ARM指令的条件码在寄存器中位置
- 3.2 ARM指令寻址方式
- 3.2.1· 立即寻址
- 3.2.2 寄存器寻址
- 3.2.3 寄存器移位寻址
- 3.2.4 寄存器间接寻址
- 3.2.5 基址寻址
- 3.2.6 多寄存器寻址
- 3.2.7 堆栈寻址
- 3.2.8 块拷贝寻址
- 3.2.9 相对寻址
- 3.3 ARM指令集补充
- 3.3.1 直接写跳转指令
- 3.3.2 数据处理指令
- 3.3.3 乘法与乘加指令
- 3.3.7 数据交换指令
- 3.3.8 移位指令
- 3.4 Thumb指令及应用
- 4 S3C6401处理器
- 4.5 存储器映射
- 5 GPIO接口
- 8 ADC和触摸屏接口
- 9 Andrioid系统开发概述
- 9.1 Android的发展
- 9.2 Android系统架构
- 9.2.1 应用程序层
- 9.2.2 应用程序框架层
- 9.2.3 系统运行层库
- 9.2.4 Linux内核层
- 9.3 Android系统内核
- 9.3.1 Linux内核结构
- 9.3.2 Android内核和驱动
- 9.4 系统移植的概念和驱动开发的方法
- 10 Android系统开发环境
- 10.1 交叉开发环境
- 10.1.1 交叉开发环境概述
- 10.1.2 宿主机与目标机的连接
- 10.1.3 宿主机环境
- 串口终端
- BOOTP
- TFTP协议
- TFTP协议与FTP协议的区别
- 交叉编译工具链
- 10.1.4 目标机环境
- 10.2 Linux操作系统及其开发工具。
- 10.2.1 Linux操作系统及其概述
- 1. 开放性和自由性
- 2. 高度的稳定性和可靠性
- 3. 多用户和多任务处理能力
- 4. 强大的网络功能
- 5. 安全性能高
- 6. 丰富的软件生态系统
- 10.2.2 Linux操作系统核心与驱动程序
- 1. 字符设备驱动(Character Device Driver)
- 2. 块设备驱动(Block Device Driver)
- 3. 网络设备驱动(Network Device Driver)
- 10.2.3 Linux交叉编译工具链
- 10.3 Android系统开发工具
- 10.3.1 Android代码目录结构
- 10.3.2 Ubuntu与虚拟机
- 10.3.3 安卓系统及开发工具链
- 1. 变量定义部分
- 2. 目标文件生成规则(编译规则)
- 3. 可执行文件构建规则(链接规则)
- 4. 伪目标部分
- 11 Boot Loader
- 11.1 Boot Loader概述
- 11.1.1 BootLoader主要功能
- 11.1.2 Boot Loader操作模式
- 11.1.3 BootLoader通信
- 11.2 BootLoader的工作过程
- 11.2.1 BootLoader工作过程概述
- 11.2.2 BootLoader 阶段1
- 11.2.3 BootLoader阶段2
- 11.3 U-Boot启动流程分析
- 11.3.1 U-Boot概述
- 13.3.2 U-boot代码结构
- 13.3.3 U-Boot启动流程分析
1 嵌入式系统概述
1.1 嵌入式系统基本概念
1.1.1 嵌入式系统定义
IEEE(国际电气和电子工程师协会)的定义:嵌入式系统是用于控制监视或者辅助操作机器和设备的装置。
1.1.2 嵌入式系统的发展
直到19世纪70年代末,随着微电子技术的发展,嵌入式计算机才逐步兴起。
1. 嵌入式应用始于微型机时代
电子数字计算机诞生于1946年,在初期的计算机都是在机房中的。直到19世纪70年代微处理器的出现,才使计算机发生了历史性的变化。
随着微处理器的发展,具有高速数据计算能力的微型机表现出的计算能力引起了控制专业人士的关注。他们将微型计算机通过电气或者是机械加固,然后配置各种外围的电路之后安装到大型船舶的自动驾驶或者是轮船监测系统中。
并且为了区分于原有的通用计算机系统,我们把嵌入到对象体系中的控制计算机称之为嵌入式计算机系统。其本质就是将一个计算机嵌入到一个对象体系中。
2. 现代计算机技术的两大分支
现代计算机技术有两个分支,就是通用计算机和嵌入式计算机。
通用计算机系统的技术要求高速海量的数据计算,技术发展方向是总线速度的无限提升、存储容量的无限扩大。
嵌入式计算机系统的控制技术要求是对对象的智能化控制能力,技术发展方向是与对象体系密切相关的嵌入式性能控制能力与控制的可靠性。
3. 嵌入式发展的里程碑
纵观嵌入式技术的发展历程,大致经历了4个阶段。
- 以单芯片为核心的可编程控制器形式的系统具有监测、伺服、指示设备相配合的功能。这类系统大部分应用一些专业性较强的工控领域。一般没有操作系统的支持,通过汇编语言对系统直接进行控制,运行结束后再清除内存。
- 以嵌入式cpu为基础,以简单操作系统为核心的嵌入式系统。在1980年左右,随着微电子工艺水平的提高,集成电路制造商开始把嵌入式应用中所需要的微处理器io接口串行接口以及ram和rom等部件通通集成到一片超大规模集成电路中制造出面向io设计的微控制器。在这一时期出现了大量高可靠低功耗的嵌入式cpu,但通用性比较弱。
- 以嵌入式操作系统为标志的嵌入式系统,20世纪90年代嵌入式系统飞速发展面向实时信号处理算法的dsp产品,则向着高速、高精度、低功耗的方向发展。随着硬件实时性的需求提高,嵌入式系统的软件规模不断扩大,逐渐形成了实时多任务操作系统rtos,并开始成为嵌入式系统的主流。
- 以internet为标志的嵌入式系统。目前大多数的嵌入式系统还孤立于internet之外信息。时代和数字时代的到来为嵌入。式系统的发展带来巨大机遇。
1.1.3 嵌入式系统的特点
- 面向特定应用
- 功耗低、体积小、集成度高、成本低
- 具有较长的生命周期
- 具有固化的代码
- 需要专用开发工具和环境
- 需要RTOS开发平台
- 以应用专家为主
1.2 嵌入式系统分类
1.2.1 单个微处理器
由单片的嵌入式处理器组成,集成了io设备以及ad转换设备,再加上简单的电源和时钟就可以工作,常用于小型设备中。
1.2.2 嵌入式处理器可扩展的系统
根据需要,可以扩展存储器,同时也可以使用片上的存储器,处理器一般容量在64k左右,字长为8位或者是16位在处理器上扩展少许的存储器和外接接口。
1.2.3 复杂的嵌入式系统
一般是16、32位,用于大规模应用。由于软件量大,所以需要扩展存储器。破产存储器一般都在一1M以上,外部接口一般仍然集成在处理器上。常用的嵌入式处理器有ARM系列,摩托罗拉的powerPC系列、Coldfire系列。
在开关装置、控制器、电梯等方面可以看到这类嵌入式系统的应用。
1.2.4 在制造或过程控制中使用的计算机系统
计算机与仪器、机械及设备与系统相连来控制这些装置。这类系统包括自动仓储系统和发货系统,在这些系统中计算机用于总体控制和监视,而不是对单个设备直接控制。
1.3 嵌入式处理器
全世界处理器的品种已经超过了1000多种,流行的体系有30多个系列。
嵌入式处理器的寻址空间一般是64k~16m。当然了,现在的STM32F407已经到了4G,这本书有点老了,处理速度0.1-2000MIPS,常用封装8-144个引脚。
根据现状,嵌入式处理器可以分成嵌入式微控制器EMCU、嵌入式微处理器EMPU、嵌入式dsp处理器EDSP、嵌入式片上系统ESoC。
三者的区别
1.4 嵌入式系统的组成
嵌入式系统一般都是由硬件、软件以及开发工具和开发系统三部分组成。
1.4.1 嵌入式系统的硬件
1.4.2 嵌入式系统的软件
- 驱动层程序
- 实时操作系统
- 操作系统的应用程序接口,应用程序接口是一系列复杂的函数、消息和机构的集合体。嵌入式操作系统下的api和一般操作系统下的api在功能含义和知识体系上完全一致。
- 应用程序。实际的嵌入式系统应用软件建立在系统的主任务基础。之上,用户应用程序主要通过调用系统的api函数对系统进行操作,完成用户功能的开发。
1.5 嵌入式操作系统
嵌入式操作系统是一种专用的可定制的操作系统。除了能完成一般操作系统的功能,如进程管理、存储管理、文件管理、设备管理等,还可以包括和硬件相关的底层驱动软件,系统内核设备驱动接口,通信协议图形界面标准化浏览器等。
1.5.1 嵌入式操作系统的发展
在最开始的控制领域,中设计者往往根据汇编语言或高级语言编程对系统进行直接控制,没有操作系统的概念。
随着嵌入式cpu的投入使用嵌入式操作系统也随之发展起来。
1.5.2 嵌入式操作系统的分类
收费不收费
- 免费的操作系统有:Linux、Embedded Linux、FreeRTOS等。
- 收费的嵌入式操作系统:VxWorks、Windows CE等。
按照系统对时间的敏感程度:
- 硬实时系统:系统对响应时间有严格的要求。 响应时间不满足的是不能接受的,会导致系统的崩溃和致命错误。
- 软实时系统:系统对响应时间没有严格要求。响应时间如果不能满足要求,可能会导致结果错误,但不影响系统的正常运行。
- 非实时系统:系统的响应时间没有要求,如果响应时间不满足要求,也不会影响系统运行。
1.5.4 主流嵌入式操作系统简介
1. VxWorks
VxWorks操作系统是WindRiver公司于1983年设计的一种嵌入式实时操作系统RTOS,是嵌入式开发环境的关键组成部分。
支持多种处理器x86、i960、Sun Sparc、Motorola MC68xxx、MIPS RX0000、POWER PC等。
这个系统比较贵,通常要花费数10万才能建起一个可靠的开发环境。
2. Windows CE
Windows CE是微软开发的32位嵌入式操作系统,得益于windows优秀的图形用户界面,与桌面版的windows基本一致。
3. μC/OS-II
μC/OS-II是一种开源但不免费的RTOS,具有可剥夺实时内核。μC/OS-II是μC/OS的升级版,发布1992年。目前,μC/OS-II已经被移值到40多种不同架构的CPU上,可以运行在8位到64位的各种操作系统之上。
4. 嵌入式Linux
Linux最早由芬兰人Linus Torvalds于1991年创立,经过短短十几年发展,已经成为一个功能强大稳定可靠的操作系统。典型的Linux系统有Red Hat、Ubuntu、Red Flag等。
我们要讲的嵌入式Linux是标准Linux在嵌入式系统上的移植。
2 ARM11体系结构
2.1 ARM微处理器概述
2.1.1 arm公司简介
ARM于1990年11月在英国伦敦成立前身为 Acorn计算机公司后改名为advance RISC Machines Limited公司(ARM)。
采用arm技术知识产品核的微处理器就是我们通常所说的ARM微处理器。基于arm技术的微处理器应用约占了30位RISC微处理器75%以上的市场份额。
2.1.2 ARM特点
- 体积小、功耗低、成本低、性能高
- 支持Thumb(16位)/ARM(32位)双指令集,很好的兼容8位\16位器件。
- 大量使用寄存器,指令执行速度高。
2.1.3 ARM体系结构的版本
到目前ARM体系一共定义了7个版本的指令集,以版本V1-V7表示。
版本V6
2001年发布。
使用此版本的核: ARM11、ARM1156T2-S、ARM1176JZF-S、ARM11JZF-S。
版本V7
ARM体系V7是在2005年发布。该版本扩展了130条指令的Thumb2指令集。具有NEON媒体引擎,该引擎具有SIMD执行流水线和寄存器堆可共享访问的l1、l2高速缓存。
使用次版本的处理器核:ARM Cortex。
2.2 ARM11 系列微处理器
ARM11系列微处理器是新一代的RISC处理器,但它是基于 arm v6的第1代设计实现的,系列主要有ARM1136J、ARM1156T2。
ARM11系列处理器在其性能上有巨大提升,首先推出的350M - 500M 的时钟频率的内核,在未来将上升到1GHz。
通过动态调整时钟频率和供应电压,开发者完全可以控制性能和功耗间作出平衡。在0.13微米的工艺下,1.2V条件下,ARM11处理器的功耗可以低至0.4mW/MHz。
ARMv6架构决定了可以达到高性能处理器的基础。
ARM11处理器规格如上所示。
2.3 ARM11系列微处理器架构
Arm11系列包括了arm11MPCore处理器ARM1176处理器、ARM1156处理器和ARM1136处理器。
2.4 ARM11 流水线
流水线是RISC执行指令时采用的一种重要机制。流水线既可以达到更高的性能,还可以让用户更加方便地实现流程。
2.4.1 流水线结构的性能
系统在处理数据时,一个指令周期含有4~6个时钟脉冲。每个脉冲周期由不同的部件完成不同的操作。
流水线结构是指每个时钟脉冲都接收下一条处理数据。的指令,只是不同部件。做不同的事情,流水线处理器。一般把一条指令的执行分成几个级,每一级在一个时钟周期内完成。
如果说处理器的流水线有k级,那么同时可执行的指令条数就是k。每一条指令处于不同的执行阶段。
2.4.2 流水线级数的影响
ARM7采用的是三级流水线,arm9采用的是5级流水线,ARM10采用的是6级流水线,ARM11处理器采用的是8级流水线,比以前的ARM内核减少了40%的吞吐量,8级流水线可以同时执行8条指令。
当出现多周期指令时跳转分支指令和中断发生时,流水线都会发生阻塞,而且相邻指令之间也可能因为寄存器冲突导致流水阻塞,从而降低了流水线效率。
同时随着系统的时钟频率的增加,指令执行周期也相应减少对其对硬件要求也更高,而且以内核执行一条指令前需要更多的周期来填充流水线。
Arm流水线的一条指令只有在完全通过执行阶段才会被处理。如果随后的指令需要用到前面指令的执行结果作为输入,它就要等前面的指令执行完。
2.4.3 ARM11处理器中流水线的管理
为了解决延时避免流水线的数据,冲突让前面的指令执行的结果能够快速进入到后面指令的流水线中。Rm11处理器采用了预测技术、存储管理、并行机制等技术来保证最佳的流水线效率。
1 预测技术
对跳转的预测分为两种,静态和动态的预测。
动态预测:在arm处理器中包含了64个4状态跳转地址缓存器,来保存最近使用过的转换地址。通过对这些转换记录地址的查询,处理器就可以预测当前的跳转指令是否会被执行。
静态预测:当采用动态转换预测机制无法在寻址缓冲内找到正确的地址时。ARM11处理器就会从跳转的方式来判断是否执行。静态预测检查分支是向前跳转还是向后跳转。
2. 存储管理
在arm11处理器中,指令和数据可以更长时间地保存在Cache中。
由于物理地质Cache本身的存在,使数据交换避免了反复重载Cache。
3. 流水线的并行机制
在流水线的后端使用了三个并行部件机构。ALU、MAC(乘加)、LS(存取)。
LS流水线是专门用于处理存储操作指令,把数据的存取操作与数据的算术操作的耦合性分隔开来可以更加有效地执行指令。
一旦指令被解码,将根据不同的操作类型发射到不同的执行单元中,考虑到不同的指令需要不同的执行时间。当三类指令先后被发射到流水线中,ALU或者是MAC指令,不会由于LS指令的等待而停下来,他们可以同时被执行,即取址、译码和执行等操作可以重叠执行。
通过技术和机制上的改进,arm11处理器改善了因为级数的增加带来的影响,使系统能在优化到更高的流水线吞吐量的同时,还能保持与以前版本处理器中的流水线同样的有效性。
2.5 ARM工作模式及寄存器组
2.5.1 ARM核工作模式
常规cpu工作核心模块都是由寄存器组和ALU两大模快组成。 Alu完成数据的加工处理,寄存器用来保存数据,这些数据直接参与alu运算。
ARM处理器的寄存器分成许多组,不同的组完成不同环境下的工作,不过他们都共用一套alu数据处理模块。ARM一般有37个寄存器,Cortex A8的ARM核的寄存器则多达40个。
程序状态寄存器是一个32位的寄存器,它指示和记录了当前程序的运行状态。PSR(Program State Register).
2.5.2 ARM寄存器分组
ARM芯片将寄存器分成很多组来运行不同模式下的程序,让cpu运行的更加稳定。
ARM寄存器分为以上8个组,其中User和System两个组的寄存器完全共用,Secure monitor是Cortex A8的ARM核开始追加的模式。
8组寄存器各自拥有可访问的寄存器。
17个寄存器是公用的。另外,多大23个专用的寄存器分散在各个模式组中。
- User模式是程序正常运行的模式,它不需要任何异常的特殊的异常处理。
- Secure monitor模式是安全模式,需要协处理器来启动该模块方能进行工作。
2.5.3 工作模式分析
1. 特权(异常)模式
8组寄存器中,除了User外,其余7种都为特权模式或异常模式。
特权模式有以下规定:
- 对PSR寄存器中的模式位组有修改能力。
- 除了能访问公用寄存器外,还有一些专用寄存器。
除了User模式外,其它的都属于特权模式。
- 异常模式都属于特权模式的类型。
- 异常模式都有自己的专用处理程序入口地址,又称为异常向量表。
2. 中断模式
属于异常模式。ARM核用来接入核外部的异常事件,称为中断。分为FIQ和IRQ两种。
FIQ 快速中断
- 当处理器的快速中断请求引脚有效,而且CPSR寄存器的F控制位被清除时,处理器产生快速中断请求异常中断。
IRQ外部请求中断
- 当处理器的外部中断请求引脚有效。而且CPSR寄存器的I控制位被清除时,处理器产生外部中断请求异常中断。
- 系统中各外设通过该异常中断请求处理中断服务。
2.6 各种模式的工作机制
ARM工作模式的切换分为手动切换与异常切换。
- 在某种特权模式下可以切换到另一个特权模式,但是user不可以。
- 当异常发生时,可以从user模式下切到异常模式下,或切到其它模式下。
2.6.1 CPSR、PC、SPSR_XXX和LR_XX寄存器
当异常发生时LR_XX保存当前PC值,SPSR_XXX保存当前CPSR值。因为此时pc会继续存放异常模式下的指令地址,而CPSR将继续反映异常模式下程序运行状态,所以需要保存。
- 这些动作由硬件自动完成。
异常结束时将CPSR和PC的值恢复,正常程序运行。
- 用户在异常程序的末尾写入一条完成指令PC<=lr,就自动完成CPSR<=SPSR_xxx动作。
2.6.4 Supervisor特权模式
进入Supervisor模式,一般有两种方式,硬件方式芯片复位和软件方式执行swi指令。
2.6.5 Abort特权模式
Abort异常模式由arm11内部硬件自动引发,对内存数据的存储和指令预取失败会引发该异常。
2.6.6 Undefined特权模式
Undefined异常,由arm11内部硬件自动引发,当指令执行一条无法识别的指令时将发生该异常。
2.6.7Secure monitor模式
当有私密的数据程序需要执行时(如货币交易),用户软件调用SMI指令即可进入该模式。
2.6.8 System模式
System特权模式是arm11中的7个特权模式中唯一一个没有自己的程序入口地址的模式,所以它不属于异常模式。
- System模式下的工作寄存器与user用户模式下的寄存器完全一样。
- 但是system模式能操作CPSR_mod权限,也就是说可以切换各种特权模式,访问他们的资源。
2.6.9 ARM中各个异常处理响应优先级
各种异常中断的中断向量地址以及中断的处理优先级如下图。
2.7 进入和退出异常中断的过程
2.7.1 异常中断响应过程
- 保存处理器的当前状态,中断屏蔽位以及各个条件标志位。通过将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中实现。
- 设置当前程序CPSR中相应的位。使处理器进入相应的执行模式。当进入IRQ模式,禁止IRQ中断。当进入FIQ模式,禁止FIQ中断。
- 将寄存器LR-mode(R14)设置成返回地址。R14从R15中得到PC的备份。
- 将程序计数器值pc设置成异常中断的中断向量地址,从而跳转到相应的异常中断处执行。
响应IRQ异常中断处理过程的伪代码:
其它还有响应未定义指令异常中断、响应swi异常中断、响应指令预取中止异常中断、响应数据访问中止异常中断等。
2.7.2 从异常中断处理程序返回
- 恢复被中断的程序的处理器状态。也就是将SPSR-mode的寄存器内容复制到当前程序状态寄存器CPSR中。
- 返回到发生异常中断的指令的下一条指令处执行。也就是将 LR_mode寄存器的内容复制到程序计数器pc中。
3 ARM为处理器的指令系统
3.1 Arm微处理器的指令集概述
3.1.1 Arm微处理器的指令的分类与格式
Arm微处理器的指令集是加载型的,即指令集仅能处理寄存器中的数据,而且处理结果必须要放回寄存器中。
对系统存储器的访问则需要通过专门的加载指令来完成。
ARM微处理器的指令集可以分为跳转指令、数据处理指令、程序状态寄存器处理指令、加载/存储指令,协处理器指令和异常产生指令六大类。
ARM指令集主要包括以下几类:
数据处理指令
- 算术运算指令:
- ADD:将两个操作数相加,并将结果存储在目标寄存器中。例如,
ADD R0, R1, R2
表示将寄存器R1和R2中的值相加,结果存入R0 。 - SUB:执行减法操作。例如,
SUB R3, R4, R5
是用R4中的值减去R5中的值,差存入R3。 - MUL:实现乘法运算。比如
MUL R6, R7, R8
将R7和R8中的值相乘,结果存于R6 。 - ADC:带进位的加法。例如在进行128位加法时,可通过
ADDS R0,R4,R8
;ADCS R1,R5,R9
;ADCS R2,R6,R10
;ADCS R3,R7,R11
实现,其中ADCS
中的S
后缀用于更改进位标志。 - SBC:带进位的减法,可用于有符号数或无符号数的减法运算。
- RSC:带借位的反向减法,用于把操作数2减去操作数1,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。
- ADD:将两个操作数相加,并将结果存储在目标寄存器中。例如,
- 逻辑运算指令:
- AND:对两个操作数按位进行与操作。例如,
AND R9, R10, R11
将R10和R11中的每一位进行与运算,结果放入R9,常用于掩码操作。 - ANDS 是 ARM 指令集中的逻辑与操作指令,并且会更新条件标志位。
- 例如:ANDS R0, R1, #0xF。这条指令将 R1 寄存器的值和立即数 0xF(二进制为 00001111)进行按位逻辑与运算,结果存放在 R0 寄存器中,同时更新条件标志位(N、Z、C、V)。
- ORR:按位进行或运算。如
ORR R12, R13, R14
将R13和R14的每一位进行或操作,结果存入R12,可用于设置某些位的值。 - EOR:执行按位异或操作。例如
EOR R15, R16, R17
把R16和R17按位异或后的结果存到R15,在数据加密等操作中有应用。 - BIC:位清除指令,是在一个字中清除位的一种方法,操作数2是一个32位位掩码。如果在掩码中设置了某一位,则清除这一位,未设置的掩码位指示此位保持不变。
- AND:对两个操作数按位进行与操作。例如,
- 比较指令:
- CMP:用于比较两个操作数的值,它会更新条件标志位,但不存储结果。例如
CMP R1, R2
,通过比较R1和R2的值来设置条件标志,后续可根据这些标志位进行条件跳转等操作。 - CMN:比较两个数的相反数。
- CMP:用于比较两个操作数的值,它会更新条件标志位,但不存储结果。例如
加载/存储指令
- LDR:从内存中将一个32位的数据加载到寄存器中。例如
LDR R0, [R1]
表示从R1所指向的内存地址处读取一个字的数据到R0中。 - STR:将寄存器中的32位数据存储到内存中。例如
STR R2, [R3]
把R2中的数据存储到R3所指向的内存地址。 - LDM:多寄存器加载指令,可以一次性加载多个寄存器的数据。例如
LDMIA R4!, {R5 - R7}
以递增的地址顺序从R4所指向的内存位置加载多个数据到R5 - R7寄存器中,并且R4会根据加载的字节数自动更新。 - STM:多寄存器存储指令,一次性存储多个寄存器的数据。例如
STMDB R8!, {R9 - R11}
以递减的地址顺序将R9 - R11寄存器中的数据存储到R8所指向的内存位置,R8也会自动更新。
MOV 指令
-
立即数移动:可以将一个 8 位的立即数移动到寄存器中。例如MOV R0, #42,将立即数 42 移动到寄存器R0中
-
寄存器间数据移动:把一个寄存器的值移动到另一个寄存器。如MOV R1, R0,将寄存器R0的值赋给R1
-
寄存器与立即数的逻辑操作后移动:先将一个寄存器里保存的值与一个立即数做逻辑运算,然后将结果移动到目标寄存器里。例如MOV R1, R0 or #0x7,将R0与立即数0x7进行逻辑或操作后的结果存入R1
-
寄存器移位后移动:对一个寄存器的值进行移位操作,并将结果存储到另一个寄存器。比如MOV R1, R0, LSL #2,将R0的值左移 2 位后存入R1
分支指令
- B:无条件分支指令,直接跳转到指定的地址。
- 它可以使程序流程无条件地跳转到指定的目标地址。目标地址是通过将指令中的相对偏移量与当前程序计数器(PC)的值相加得到的。
- 示例:B label,其中label是一个标号,表示要跳转的目标位置。假设在一个简单的程序中,有一段初始化代码,完成后想要直接跳转到主程序部分,就可以这样写:
MOV R0, #0 ;初始化操作
MOV R1, #1
B main_loop ;跳转到main_loop标号处的主程序
main_loop:
;主程序代码
-
BEQ:相等则跳转,即当比较结果相等时,跳转到指定地址。
-
BNE:不相等则跳转,当比较结果不相等时,跳转到指定地址。
-
BGE:大于或等于跳转。
-
BGT:大于跳转。
-
BLE:小于或等于跳转。
-
BLT:小于跳转。
-
BL:带链接的相对跳转指令,在跳转前会将当前的PC值保存到链接寄存器R14中,以便后续返回。
- 示例:BL subroutine,当执行这条指令时,程序会跳转到subroutine标号所代表的子程序处,同时把返回地址(也就是BL指令后的下一条指令地址)保存到 LR 中。在子程序结束时,可以使用MOV PC, LR指令返回。
-
BLX:带链接的切换跳转,除了保存PC值到R14外,还可以切换指令集。
- 如果当前处理器处于 ARM 状态,它会先判断目标地址的最低位:若为 1,则切换到 Thumb 状态,并将目标地址与 0xFFFFFFFE 进行与操作得到实际的跳转地址,然后跳转到该地址执行 Thumb 指令;
- 若为 0,则保持 ARM 状态,直接跳转到目标地址执行 ARM 指令 。在跳转之前,会将 PC 的当前值(即 BLX 指令的下一条指令地址)保存到 LR 寄存器中。
- 当子程序执行完毕后,可以通过将 LR 寄存器的值复制到 PC 中来实现返回。
ARM_CODE:
MOV R0, #1
BLX thumb_func ; 跳转到thumb_func,并切换到Thumb状态,保存返回地址到LR
MOV R1, #2
...
thumb_func:
ADD R0, R0, #1
BX LR ; 返回调用处,切换回ARM状态
- BX:切换跳转,用于在ARM和Thumb指令集之间切换。
程序状态寄存器(PSR)处理指令
- MRS:把状态寄存器的值送到通用寄存器。
- 它在特权模式下使用,对于操作系统内核、设备驱动程序等需要访问和修改处理器状态的程序非常重要。
- 例如:MRS R0, CPSR,这条指令将 CPSR 的值传送到 R0 寄存器中。
- 应用:保存处理器状态,异常发生时,处理器状态可能会被改变,保存原始状态有助于在异常处理完成后恢复现场。
IRQ_Handler:
MRS R1, CPSR
; 进行中断处理的其他操作
- MSR:把通用寄存器的值传送到状态寄存器PSR。
- PSR 是一个关键的寄存器,包含了如处理器模式、中断使能位、条件标志位(N、Z、C、V)等重要信息。
- 基本格式有两种:
MSR{cond} psr_fields, #immediate
MSR{cond} psr_fields, Rm
- 其中,
{cond}
是条件码。和其他ARM指令一样,它可以指定指令执行的条件,例如EQ
(相等)、NE
(不相等)等条件。如果不满足条件,指令将不被执行。 psr_fields
表示目标程序状态寄存器的字段。它可以是以下几种:- CPSR(Current Program Status Register):这是当前程序状态寄存器,包含了所有状态信息。
- SPSR(Saved Program Status Register):用于在异常处理过程中保存CPSR的内容,在异常返回时可以恢复处理器状态。
- CPSR_f或SPSR_f:代表访问标志位字段,主要包括N(Negative)、Z(Zero)、C(Carry)和V(oVerflow)这些标志位,用于反映算术和逻辑运算的结果状态。
- CPSR_c或SPSR_c:用于访问控制位字段,像中断使能位、处理器模式位等控制信息都在这里。例如,在ARM处理器中有多种运行模式,如用户模式、系统模式、管理模式等,这些模式的切换就是通过修改控制位来实现的。
- CPSR_s或SPSR_s:访问状态位字段。
- CPSR_x或SPSR_x:访问扩展位字段。
协处理器指令
Arm微处理器可支持多达16个协处理器。用于各种协处理操作。在程序执行过程中,每个鞋处理器只执行对自身的斜处理指令,忽略按摩处理器以及其他协处理器的指令。
Arm的协处理器指令主要用于arm处理器初始化ARM协处理器的数据处理操作,以及 arm处理器的寄存器和谐处理器的寄存器之间进行数据传递。
- CDP、CDP2:协处理器数据处理操作。
- LDC、LDC2:从协处理器取一个或多个32位值。
- STC、STC2:从协处理器中把一个或多个32位值存到内存。
- MCR、MCR2、MCRR:从寄存器送数据到协处理器。
- MRC、MRC2、MRRC:从协处理器传送数据到寄存器 。
异常产生指令
- BKPT:断点指令,用于设置断点,使程序在执行到该指令时暂停,以便进行调试。
- SWI:软件中断指令,用于产生软件中断,将执行转移到内存地址0x00000008处,并切换到管理模式,可用于实现操作系统调用等功能。
3.1.2 指令的条件域
当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行。当指令的执行条件满足时指令被执行,否则指令被忽略。
ARM指令的条件码
- 定义与作用
- ARM指令的条件码是一种机制,用于根据处理器的状态标志来决定指令是否执行。这些标志存储在程序状态寄存器(PSR)中,主要包括N(Negative)、Z(Zero)、C(Carry)和V(oVerflow)等标志位。通过条件码,程序可以根据之前操作的结果,如比较运算、算术运算等,有条件地执行后续指令,从而实现高效的分支和控制流程。
- 条件码的种类及其含义
条件码共有16种,每种条件码可用两个字符表示。这两个字符可以添加在指令助记符的后面和指令同时使用。- EQ(Equal):当Z标志位被设置为1时满足条件,即表示上一次操作的结果为零。例如,在执行比较指令
CMP R0, R1
后,如果R0
和R1
的值相等,那么Z标志位会被置1,后续带有EQ
条件码的指令就会执行。常用于判断两个数是否相等,如在循环中判断计数器是否达到某个特定值来决定是否退出循环。 - NE(Not Equal):当Z标志位为0时满足条件,意味着上一次操作的结果非零。例如,在
CMP
指令比较两个不相等的寄存器值后,Z = 0,带有NE
条件码的指令可以执行。它可以用于在一组数据中筛选出与特定值不相等的数据。 - CS/HS(Carry Set/High or Same):当C标志位为1时满足条件。在无符号数的算术运算中,比如加法
ADD
指令,如果产生进位,C标志位会被置1。这在处理无符号数的大小比较和加法运算的溢出判断等场景中很有用,例如判断两个无符号数相加是否超过了寄存器所能表示的范围。 - CC/LO(Carry Clear/Low):当C标志位为0时满足条件。例如在减法运算中,如果没有产生借位,C标志位为0,带有
CC/LO
条件码的指令可以执行。 - MI(Minus):当N标志位为1时满足条件,N标志位表示运算结果的符号位。如果运算结果为负数,N标志位会被置1,带有
MI
条件码的指令就可以执行,用于判断运算结果是否为负。 - PL(Plus):当N标志位为0时满足条件,即运算结果为正数或者零,用于区分非负的运算结果。
- VS(oVerflow Set):当V标志位为1时满足条件。在有符号数的算术运算中,如果发生溢出,V标志位会被置1。例如,在有符号数的加法运算中,当两个正数相加结果为负数,或者两个负数相加结果为正数时,V标志位会被置1,带有
VS
条件码的指令就可以执行,用于检测有符号数运算的溢出情况。 - VC(oVerflow Clear):当V标志位为0时满足条件,用于判断有符号数运算没有溢出的情况。
- HI(Higher):满足
C = 1
且Z = 0
的条件。在无符号数比较中,用于判断一个无符号数是否大于另一个无符号数。例如,在CMP
指令比较两个无符号数R2
和R3
后,如果R2
大于R3
,则满足HI
条件,带有HI
条件码的指令可以执行。 - LS(Lower or Same):满足
C = 0
或者Z = 1
的条件。在无符号数比较中,用于判断一个无符号数是否小于或等于另一个无符号数。 - GE(Greater than or Equal):在有符号数比较中,对于
SUB
或CMP
等指令,当N
和V
标志位相同(N == V
)时满足条件。用于判断一个有符号数是否大于或等于另一个有符号数。 - LT(Less than):在有符号数比较中,当
N
和V
标志位不同(N!= V
)时满足条件。用于判断一个有符号数是否小于另一个有符号数。 - GT(Greater than):满足
Z = 0
且N == V
的条件。在有符号数比较中,用于判断一个有符号数是否严格大于另一个有符号数。 - LE(Less than or Equal):满足
Z = 1
或者N!= V
的条件。在有符号数比较中,用于判断一个有符号数是否小于或等于另一个有符号数。
- EQ(Equal):当Z标志位被设置为1时满足条件,即表示上一次操作的结果为零。例如,在执行比较指令
ARM指令的条件码在寄存器中位置
在ARM的程序状态寄存器(PSR)中,条件码标志的存储位置如下 :
- N标志位:位于PSR的第31位,它表示指令结果的符号位。若运算结果为负数,则N标志位被置1;若结果为非负数,则N标志位被置0 。例如,在执行减法操作
R1 - R2
后,如果结果是负数,那么N标志位会被设置为1。 - Z标志位:处于PSR的第30位,当指令的运算结果为零时,Z标志位被置1;反之,若结果不为零,则Z标志位被置0。比如,在执行比较指令
CMP R3, R4
后,若R3
和R4
的值相等,此时Z标志位就会被置为1。 - C标志位:存于PSR的第29位,其设置情况较为复杂,具体有以下几种情形 :
- 在加法(包括比较指令
CMN
)运算中,如果产生无符号溢出,即加法运算的结果超出了无符号数所能表示的范围,C标志位会被置1;否则置0。例如,两个无符号8位寄存器R5
和R6
相加,结果大于255时,C标志位为1。 - 在减法(包括比较指令
CMP
)运算中,若产生借位,C标志位被置1;否则置0。如R7 - R8
时,若R7
的值小于R8
的值,就会产生借位,C标志位为1。 - 对于包含移位操作的非加减运算,C标志位被设置为移位器移出的最后一位。
- 对于其他的非加减运算,C标志位通常保持不变。
- 在加法(包括比较指令
- V标志位:位于PSR的第28位,在有符号数的算术运算中,如果发生符号溢出,即两个有符号数相加或相减的结果超出了有符号数所能表示的范围,且符号位发生错误时,V标志位被置1;否则置0 。比如,两个有符号8位寄存器
R9
和R10
,分别存储的值为127和1,执行加法操作后结果为-128,此时就发生了符号溢出,V标志位会被置为1 。
不同版本的ARM架构以及不同的处理器模式下,PSR寄存器可能会有一些细微的差异,但上述条件码标志位的基本位置和含义是相对固定的.
3.2 ARM指令寻址方式
寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式。
3.2.1· 立即寻址
- 定义
- 立即寻址是ARM指令集中一种基本的寻址方式。在这种寻址方式中,操作数本身作为指令的一部分直接包含在指令代码中,这个操作数被称为立即数。立即数是一个固定的值,在指令执行过程中,处理器直接使用这个包含在指令中的值进行操作,而不需要从寄存器或者内存中获取。
- 格式与示例
- 在ARM指令格式中,立即数通常跟在操作码之后。例如,在指令
MOV R0, #0x12
中,#0x12
就是立即数。这条指令的功能是将十六进制数0x12(十进制为18)传送到寄存器R0中。这里的MOV
是操作码,表示数据传送操作,R0
是目标寄存器,#0x12
是立即数,也就是要传送到R0中的数据。
- 在ARM指令格式中,立即数通常跟在操作码之后。例如,在指令
- 立即数的编码规则
- ARM指令中的立即数并不是任意的32位数值。它是通过一种特定的编码方式来表示的,这种编码方式允许在指令中高效地表示一个8位的常数,并可以通过循环右移偶数位(0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30)来得到一个32位的立即数。例如,一个合法的立即数可以是
0x0000000F
(它本身就是8位的0xF循环右移0位得到的),但像0x12345678
这样的数就不能直接作为立即数,因为它不符合ARM的立即数编码规则。
- ARM指令中的立即数并不是任意的32位数值。它是通过一种特定的编码方式来表示的,这种编码方式允许在指令中高效地表示一个8位的常数,并可以通过循环右移偶数位(0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30)来得到一个32位的立即数。例如,一个合法的立即数可以是
- 应用场景
- 常量赋值:常用于初始化寄存器。例如,在设置中断向量表的起始地址时,可能会使用立即寻址来将一个特定的内存地址值赋给寄存器。比如,将中断向量表的起始地址
0x00000000
赋给某个寄存器(假设为R1),可以使用指令MOV R1, #0x00000000
。 - 计数操作:在循环中作为计数器的初始值。比如,实现一个简单的循环,循环次数为10,可使用指令
MOV R2, #10
来初始化计数器R2,然后在循环体中通过递减R2并判断是否为0来控制循环的结束。 - 设置模式位或控制位:在系统初始化或者模式切换时,用于设置处理器的某些控制寄存器的特定位。例如,设置处理器进入某种特权模式,可能需要将一个特定的立即数写入模式控制寄存器的相应位。
- 常量赋值:常用于初始化寄存器。例如,在设置中断向量表的起始地址时,可能会使用立即寻址来将一个特定的内存地址值赋给寄存器。比如,将中断向量表的起始地址
3.2.2 寄存器寻址
- 原理:操作数的值存放在寄存器中,指令中的地址码字段指出的是寄存器编号,指令执行时直接取出寄存器中的值来进行操作。
- 示例:
MOV R1, R2
,这条指令将寄存器R2
的值存入R1
中;SUB R0, R1, R2
则是将R1
的值减去R2
的值,结果保存到R0
。
3.2.3 寄存器移位寻址
- 原理:这是ARM指令集特有的寻址方式。当第2个操作数是寄存器移位方式时,第2个寄存器操作数在与第1个操作数结合之前,先进行移位操作。
- 示例:
MOV R0, R2, LSL #3
,表示将R2
的值左移3位,结果放入R0
中,即R0 = R2 × 8
;`
3.2.4 寄存器间接寻址
- 原理:指令中的地址码给出的是一个通用寄存器的编号,所需的操作数保存在该寄存器指定地址的存储单元中,即寄存器作为操作数的地址指针。
- 示例:
LDR R1, (R2)
,其功能是将R2
指向的存储单元的数据读出并保存在R1
中;
3.2.5 基址寻址
- 原理:将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址。常用于查表、数组操作、功能部件寄存器访问等。
- 示例:
LDR R2, (R3, #0x0C)
,该指令会读取R3+0x0C
地址上的存储单元的内容,并放入R2
中;
3.2.6 多寄存器寻址
- 原理:一次可传送多个寄存器的值,允许一条指令传送16个寄存器的任何子集或所有寄存器。
- 示例:
LDMIA R1!, {R2-R7, R12}
,这条指令可以将寄存器R2
至R7
、R12
的值保存到R1
指向的存储单元中,并且R1
的值会自动增加,以指向下一个存储单元。
3.2.7 堆栈寻址
- 原理:使用一个专门的寄存器(堆栈指针)指向一块存储区域(堆栈),指针所指向的存储单元即是堆栈的栈顶。根据堆栈的生长方向和指针指向的不同,可以分为满递增、空递增、满递减、空递减四种类型 。
- 示例:
PUSH {R4-R8}
,该指令会将寄存器R4
至R8
的值依次存储到堆栈中,堆栈指针会自动调整;POP {R4-R8}
则是从堆栈中依次弹出数据,并将其存入寄存器R4
至R8
中。
3.2.8 块拷贝寻址
- 原理:多寄存器传送指令用于将一块数据从存储器的某一位置拷贝到另一位置。
- 示例:通过使用特定的指令格式和寄存器组合,可以实现将一段连续的内存数据批量地从一个地址范围拷贝到另一个地址范围,但具体指令因不同的ARM架构和应用场景可能会有所不同 。
3.2.9 相对寻址
- 原理:相对寻址是基址寻址的一种变通方式。由程序计数器
PC
提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。 - 示例:
LDR R1, (PC, #8)
,它会从PC+8
所指向的地址中加载数据到R1
中。这种寻址方式常用于实现程序中的相对跳转和位置无关代码。
3.3 ARM指令集补充
3.3.1 直接写跳转指令
概念
在 ARM 架构中,程序计数器(PC)用于保存下一条要执行指令的地址。通过直接向 PC 写入一个新的地址,可以实现程序流程的跳转。这个新地址可以是绝对地址,也可以是相对地址。当向 PC 写入跳转地址时,处理器会在下一个指令周期从新的地址开始取指令并执行,从而改变程序的执行路径。
方式
使用指令直接写入:可以使用一些能够对寄存器进行写操作的指令来修改 PC 的值。
例如,在某些特殊情况下,通过MOV指令直接将目标跳转地址写入 PC。不过这种方式比较少见,因为直接操作 PC 可能会导致一些不可预测的问题,而且在很多 ARM 处理器的指令集中,对 PC 的直接写入有一些限制。
例如,MOV PC, #addr(其中#addr是目标跳转地址),但在实际应用中,这种简单的方式可能因为对齐问题或者指令流水线等因素而不能正常工作。
3.3.2 数据处理指令
ARM指令中的数据处理指令大致可分为以下几类:
- 数据传送指令:
- MOV:将立即数或寄存器中的数据传送到目标寄存器。例如
MOV R1, #0x10
,将立即数0x10传送到寄存器R1中;MOV R0, R1
,则是将寄存器R1中的值传送到寄存器R0 。 - MVN:将立即数或寄存器中的数据按位取反后传送到目标寄存器。如
MVN R1, #0xff
,会将0xff取反后的值0xffffff00传送到寄存器R1中;MVN R1, R2
,把寄存器R2中的值取反后存到R1。
- MOV:将立即数或寄存器中的数据传送到目标寄存器。例如
- 算术逻辑运算指令:
- 加法运算指令 ADD:将operand2数据与Rn的值相加,结果保存到Rd寄存器。例如
ADD R1, R1, #1
,实现R1 = R1 + 1;ADD R1, R1, R2
,则是R1 = R1 + R2 。 - 减法运算指令 SUB:用寄存器Rn减去operand2,结果保存到Rd中。如
SUB R0, R0, #1
,即R0 = R0 - 1;SUB R2, R1, R2
,得到R2 = R1 - R2 。 - 逆向减法指令 RSB:用寄存器operand2减去Rn,结果保存到Rd中。像
RSB R3, R1, #0xff00
,结果为R3 = 0xff00 - R1;RSB R1, R2, R2, LSL #2
,即R1 = R2 << 2 - R2 = R2 × 3 。 - 带进位加法指令 ADC:将operand2的数据与Rn的值相加,再加上CPSR中的C条件标志位,结果保存到Rd寄存器。常与ADD指令结合使用实现64位加法,如
ADDS R0, R0, R2
配合ADC R1, R1, R3
,实现(R1、R0)=(R1、R0)+(R3、R2) 。 - 带进位减法指令 SBC:用寄存器Rn减去operand2,再减去CPSR中的C条件标志位的非,结果保存到Rd中。可与SUBS指令联合实现64位减法,如
SUBS R0, R0, R2
配合SBC R1, R1, R3
,实现(R1, R0)-(R3, R2) 。 - 带进位逆向减法指令 RSC:用寄存器operand2减去Rn,再减去CPSR中的C条件标志位,结果保存到Rd中。可用于求64位数值的负数,如
RSBS R2, R0, #0
配合RSC R3, R1, #0
。 - 逻辑与操作指令 AND:将operand2值与寄存器Rn的值按位作逻辑与操作,结果保存到Rd中。例如
ANDS R0, R0, #x01
,取出R0的最低位数据;AND R2, R1, R3
,则是R2 = R1 & R3 。 - 逻辑或操作指令 ORR:将operand2的值与寄存器Rn的值按位作逻辑或操作,结果保存到Rd中。如
ORR R0, R0, #x0f
,将R0的低4位置1;MOV R1, R2, LSR #4
配合ORR R3, R1, R3, LSL #8
,将近R2的高8位数据移入到R3低8位中 。 - 逻辑异或操作指令 EOR:将operand2的值与寄存器Rn的值按位作逻辑异或操作,结果保存到Rd中。比如
EOR R1, R1, #0x0f
,将R1的低4位取反;EOR R2, R1, R0
,即R2 = R1 ^ R0 。 - 位清除指令 BIC:将寄存器Rn的值与operand2的值的反码按位作逻辑与操作,结果保存到Rd中。例如
BIC R2, R1, #0x0f
,将R1的低4位清零,其它位不变 。
- 加法运算指令 ADD:将operand2数据与Rn的值相加,结果保存到Rd寄存器。例如
- 比较指令:
- CMP:使用寄存器Rn的值减去operand2的值,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。本质上等同于SUBS指令,只是不存储运算结果
- 如
CMP R1, R2
,根据比较结果设置CPSR中的标志位,若标志位Z = 1,则两者相等;若Z = 0,则两者不相等 。
- 如
- CMN:将寄存器中的值加上表示的数值,根据操作的结果更新CPSR中的条件标志位,也是默认更新,无需添加“S”。常用于比较两个数的大小关系及后续的条件判断.
- TST:将表示的数值与寄存器的值按位做逻辑与操作,根据操作的结果更新CPSR中相应的条件标志位。通常用于测试寄存器中某些位是1还是0,一般会与BEQ或者BNE指令配合使用完成条件判断跳转功能.
- TEQ:将表示的数值与寄存器的值按位做逻辑异或操作,根据操作的结果更新CPSR中相应的条件判断位。常用来判断两个数是否相等,但这种比较操作不会影响到CPSR寄存器中的V位跟C位;同时,TEQ指令也可用于比较操作数符号是否相同,该指令执行后,CPSR寄存器中的N位为两个操作数符号位做异或的结果.
- CMP:使用寄存器Rn的值减去operand2的值,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。本质上等同于SUBS指令,只是不存储运算结果
3.3.3 乘法与乘加指令
- 乘法指令(MUL)
- 功能描述:MUL指令用于实现两个寄存器中的无符号整数相乘,并将结果存储在目标寄存器中。
- 指令格式:
MUL{cond}{S} Rd, Rm, Rs
。其中,{cond}
是条件码,用于指定指令执行的条件;{S}
表示是否更新标志位;Rd
是目标寄存器,用于存放乘法运算的结果;Rm
和Rs
是源寄存器,分别提供两个相乘的操作数。 - 示例及应用场景:
- 例如,
MUL R0, R1, R2
,这条指令将寄存器R1
和R2
中的无符号整数相乘,结果存放在R0
中。在计算数组元素的偏移量、进行简单的图形缩放(如在二维图形中,将坐标乘以缩放因子)等场景中经常使用。假设要计算一个二维数组array[10][10]
中某个元素的地址,已知每行的元素个数为10,元素大小为4字节,数组的起始地址存放在R3
中,要计算array[i][j]
的地址(i
存于R1
,j
存于R2
),可以使用乘法指令:MUL R4, R1, #40
(计算行偏移量,每行40字节);MUL R5, R2, #4
(计算列偏移量,每个元素4字节);ADD R6, R3, R4
(加上行偏移量);ADD R6, R6, R5
(加上列偏移量得到元素地址)。
- 例如,
- 乘加指令(MLA)
- 功能描述:MLA指令在乘法的基础上增加了加法操作。它先将两个源寄存器中的无符号整数相乘,然后将乘积与第三个源寄存器中的值相加,最后将结果存储在目标寄存器中。
- 指令格式:
MLA{cond}{S} Rd, Rm, Rs, Rn
。其中,Rn
是提供相加操作数的寄存器,其他参数含义与MUL指令相同。 - 示例及应用场景:
- 例如,
MLA R0, R1, R2, R3
,先将R1
和R2
中的值相乘,然后将乘积与R3
中的值相加,结果存于R0
。在数字信号处理中的滤波算法中应用广泛。例如,一个简单的有限脉冲响应(FIR)滤波器的计算,假设滤波器系数存放在coeff
数组中,输入信号样本存放在input
数组中,中间计算结果和最终结果存放在result
数组中。对于某个时刻k
,计算滤波器输出可以使用乘加指令:LDR R1, =coeff[0]
(加载第一个滤波器系数);LDR R2, =input[k]
(加载当前输入样本);MLA R3, R1, R2, #0
(计算第一个乘积项)。然后通过循环,依次加载其他系数和样本,使用MLA指令进行乘加运算,最终得到滤波器输出。
- 例如,
- 有符号乘法指令(SMUL)和有符号乘加指令(SMLA)
- 功能描述:这两个指令与MUL和MLA类似,但是用于有符号整数的运算。它们在处理有符号数时,会考虑操作数的符号位,按照有符号数的乘法和乘加规则进行运算。
- 指令格式和应用场景:
- 指令格式与无符号数对应的指令格式相似,如
SMUL{cond}{S} Rd, Rm, Rs
和SMLA{cond}{S} Rd, Rm, Rs, Rn
。在涉及有符号数的数学计算、数值分析等领域应用较多。例如,在计算带有正负权重的加权求和时,需要使用有符号乘法和乘加指令。假设weight
数组存储有符号的权重值,value
数组存储相应的数据值,计算加权和sum
:MOV R0, #0
(初始化结果寄存器);LDR R1, =weight[0]
;LDR R2, =value[0]
;SMLA R0, R1, R2, R0
(开始计算加权和),然后通过循环处理其他数组元素。
- 指令格式与无符号数对应的指令格式相似,如
- 长乘法指令(UMULL、UMLAL、SMULL、SMLAL)
- 功能描述:
- UMULL(无符号长乘法):用于实现两个32位无符号整数相乘,得到64位的乘积结果。乘积的低32位存储在一个指定的寄存器中,高32位存储在另一个指定的寄存器中。
- UMLAL(无符号长乘加):先执行两个32位无符号整数的乘法,得到64位乘积,然后将这个乘积与另外两个32位寄存器中的64位无符号数相加,结果的低32位和高32位分别存储在指定的寄存器中。
- SMULL(有符号长乘法):和UMULL类似,但是用于有符号整数的乘法,会考虑操作数的符号位,得到64位的有符号乘积结果并存储。
- SMLAL(有符号长乘加):和UMLAL类似,用于有符号整数的乘加运算,将两个32位有符号整数相乘得到的64位乘积与另外两个32位有符号数相加,结果的低32位和高32位分别存储在指定的寄存器中。
- 指令格式和应用场景:
- 以UMULL为例,指令格式为
UMULL{cond}{S} RdLo, RdHi, Rm, Rs
,其中RdLo
存放乘积的低32位,RdHi
存放乘积的高32位。长乘法指令在高精度计算、密码学中的大整数运算(如RSA算法中的大整数乘法)等场景中非常有用。例如,在实现一个简单的64位无符号整数乘法时:UMULL R0, R1, R2, R3
,将R2
和R3
中的32位无符号整数相乘,结果的低32位存于R0
,高32位存于R1
。
- 以UMULL为例,指令格式为
- 功能描述:
3.3.7 数据交换指令
-
SWP(交换)指令
- 指令格式:
SWP{cond}{B} Rd, Rm, [Rn]
。{cond}
:条件码,用于指定指令执行的条件,如EQ
(相等)、NE
(不相等)等。{B}
:字节操作选项。如果有{B}
,则进行字节交换;无{B}
则默认进行32位字交换。Rd
:目标寄存器,用于存放从内存读取的数据,同时其原有数据将写入内存。Rm
:备份寄存器,用于提供要写入内存的数据。[Rn]
:以Rn
寄存器的值为地址的内存单元,该内存单元的数据将与Rd
的数据交换。
- 工作原理:首先,将
Rn
指向的内存单元中的数据读取并存储到Rd
中。然后,把Rm
中的数据写入Rn
指向的内存单元,以此完成数据交换。例如SWP R0, R1, [R2]
,先把R2
指向内存的数据存入R0
,再把R1
的数据写入R2
指向的内存。 - 应用场景:
- 互斥锁实现:用于实现多任务环境下的互斥锁。例如,有一个内存地址存储互斥锁变量
lock
(初始值为0,表示未锁定)。当任务要获取锁时,执行SWP R0, R1, [R3]
(R3
存储lock
地址,R1
初始化为1),若R0
读取为0,则获取锁成功并将lock
设为1;若R0
读取为1,则表示锁已被占用,任务等待。 - 数据同步更新:在多处理器共享数据场景下,确保数据更新的一致性。例如双处理器系统更新共享内存数据结构,通过SWP指令可避免一个处理器更新时被另一个干扰。
- 互斥锁实现:用于实现多任务环境下的互斥锁。例如,有一个内存地址存储互斥锁变量
- 注意事项:
- 原子性:SWP指令的原子性很重要,保证了数据交换过程不被中断,避免多任务或多处理器环境下的数据不一致。
- 数据大小选择:根据数据类型(字节或32位字)正确选择是否使用
{B}
选项。
- 指令格式:
-
SWPB(交换字节)指令
- 指令格式:
SWPB{cond} Rd, Rm, [Rn]
。与SWP指令类似,但专门用于字节交换。 - 应用场景:适用于处理字节类型数据交换。例如在简单的通信协议中,接收和发送字节数据并在寄存器和内存间交换字节时非常有用。
- 指令格式:
-
字节序交换指令(如REV、REV16、REVSH)
- REV指令:
- 功能:用于反转32位寄存器中的字节顺序。例如,将寄存器中的数据从大端序转换为小端序,或者反之。
- 指令格式:
REV Rd, Rm
,其中Rd
是目标寄存器,Rm
是源寄存器。 - 应用场景:在处理不同字节序的数据时非常有用。例如,当从一个大端序存储的设备读取数据到ARM处理器(通常采用小端序),可以使用REV指令进行字节序转换。
- REV16指令:
- 功能:对寄存器中的两个半字(16位)分别进行字节序反转。
- 指令格式:
REV16 Rd, Rm
。 - 应用场景:适用于处理半字数据,如在某些音频或视频处理应用中,对16位音频样本或像素数据进行字节序调整。
- REVSH指令:
- 功能:对寄存器中的低半字(16位)进行字节序反转,并将结果符号扩展到32位,用于有符号半字数据的字节序转换。
- 指令格式:
REVSH Rd, Rm
。 - 应用场景:在处理有符号半字数据的字节序转换,如在一些通信协议中处理有符号短整型数据时很有用。
- REV指令:
3.3.8 移位指令
- 逻辑左移(LSL)指令
- 功能描述:
- LSL(Logical Shift Left)指令用于将寄存器中的数据向左移动指定的位数。左移时,低位补0,高位的数据会因为移动而丢失。移动的位数可以是一个立即数或者由另一个寄存器指定。
- 指令格式:
LSL{cond}{S} Rd, Rm, #shift_imm
或LSL{cond}{S} Rd, Rm, Rs
。其中,{cond}
是条件码,用于指定指令执行的条件;{S}
表示是否更新条件码标志位;Rd
是目标寄存器,用于存储移位后的结果;Rm
是源寄存器,包含要移位的数据;#shift_imm
是立即数,表示移位的位数(范围通常是0 - 31);Rs
是一个寄存器,其值用于指定移位的位数。
- 示例及应用场景:
- 例如,
LSL R0, R1, #2
,将寄存器R1
中的数据向左移动2位,结果存放在R0
中。这相当于将R1
中的值乘以4,在乘法运算中,如果乘数是2的幂次方,就可以用LSL指令来高效地实现。例如,在计算内存地址偏移量时,如果每个元素的大小是4字节(32位),要访问第n
个元素的地址,可以先将元素索引n
存放在一个寄存器中,然后使用LSL指令将其左移2位,得到正确的偏移量。
- 例如,
- 功能描述:
- 逻辑右移(LSR)指令
- 功能描述:
- LSR(Logical Shift Right)指令是将寄存器中的数据向右移动指定的位数。右移时,高位补0,低位的数据会因为移动而丢失。
- 指令格式和应用场景:
- 格式与LSL类似,如
LSR{cond}{S} Rd, Rm, #shift_imm
或LSR{cond}{S} Rd, Rm, Rs
。它常用于将一个无符号数进行除法运算,当除数是2的幂次方时,通过右移相应的位数来实现。例如,LSR R2, R3, #3
相当于将R3
中的无符号数除以8,结果存放在R2
中。在处理位域(bit - field)提取时,也可以使用LSR指令将需要的位移动到最低位,方便后续操作。
- 格式与LSL类似,如
- 功能描述:
- 算术左移(ASL)指令
- 功能描述:
- ASL(Arithmetic Shift Left)指令在功能上和LSL指令基本相同,也是将数据向左移动,低位补0,高位的数据会丢失。在一些ARM处理器的指令集中,ASL是LSL的别名,它们执行相同的操作。
- 功能描述:
- 算术右移(ASR)指令
- 功能描述:
- ASR(Arithmetic Shift Right)指令用于将寄存器中的有符号数据向右移动指定的位数。右移时,符号位(最高位)保持不变,用于保持数据的符号性质,低位的数据会因为移动而丢失。
- 指令格式和应用场景:
- 格式如
ASR{cond}{S} Rd, Rm, #shift_imm
或ASR{cond}{S} Rd, Rm, Rs
。在有符号数的除法运算中,如果除数是2的幂次方,可以使用ASR指令来实现。例如,ASR R4, R5, #2
可以将有符号数R5
除以4,结果存放在R4
中,同时保持符号位不变。在对有符号数进行位域操作,如提取符号位扩展后的数值部分时,ASR指令也很有用。
- 格式如
- 功能描述:
- 循环左移(ROL)指令
- 功能描述:
- ROL(Rotate Left)指令将寄存器中的数据向左循环移动指定的位数。即移出的高位数据会循环移到低位,形成一个循环移位的效果。
- 指令格式和应用场景:
- 格式类似其他移位指令,如
ROL{cond}{S} Rd, Rm, #shift_imm
或ROL{cond}{S} Rd, Rm, Rs
。在加密算法(如简单的位循环加密)、数据编码等领域有应用。例如,在一个简单的位循环编码算法中,通过ROL指令将数据循环左移一定位数来实现编码操作。
- 格式类似其他移位指令,如
- 功能描述:
- 循环右移(ROR)指令
- 功能描述:
- ROR(Rotate Right)指令是将寄存器中的数据向右循环移动指定的位数。移出的低位数据会循环移到高位。
- 指令格式和应用场景:
- 格式为
ROR{cond}{S} Rd, Rm, #shift_imm
或ROR{cond}{S} Rd, Rm, Rs
。在数据校验、错误检测等场景中有应用。例如,在一些简单的循环冗余校验(CRC)算法的实现中,可能会用到ROR指令来对数据进行循环右移操作。
- 格式为
- 功能描述:
- 带扩展的循环右移(RRX)指令
- 功能描述:
- RRX(Rotate Right with eXtension)指令将寄存器中的数据向右循环移动1位,并且将CPSR(程序状态寄存器)中的C(Carry)位的值移到最高位,同时将最低位的值移到C位。
- 指令格式和应用场景:
- 格式为
RRX{cond}{S} Rd, Rm
。在多字节加法、减法运算中,用于处理进位或借位的循环传递,确保运算结果的准确性。例如,在实现高精度的有符号数加法或减法运算时,RRX指令可以帮助处理进位和借位的传递。
- 格式为
- 功能描述:
3.4 Thumb指令及应用
-
定义与背景
- Thumb指令集是ARM架构下的一种指令集。ARM处理器传统上使用32位的ARM指令集,但为了提高代码密度(在给定的存储容量下能存储更多的指令),ARM公司开发了Thumb指令集。Thumb指令集的指令长度主要是16位,相比32位的ARM指令,它可以使程序占用更少的存储空间。
-
指令特点
- 代码密度高:由于指令长度较短,通常为16位,在存储容量有限的情况下,可以存储更多的指令。这对于一些对存储资源较为敏感的应用场景,如嵌入式系统中的小型设备,具有很大的优势。例如,在一个资源受限的物联网传感器节点中,使用Thumb指令集能够在有限的闪存空间中存储更复杂的程序逻辑。
- 执行效率权衡:Thumb指令的执行效率相对ARM指令在某些情况下可能会稍低。因为Thumb指令集是ARM指令集的一个子集,并且为了缩短指令长度,有些操作可能需要更多的指令来完成相同的功能。不过,在现代处理器设计中,通过指令流水线和缓存技术等优化手段,这种效率差异在很多情况下可以被减小。
- 所有的Thumb指令都有对应的arm指令,当处理器在执行Arm程序段时称arm处理器处于arm工作状态,当处理器在执行Thumb程序段时,称arm处理器处于Thumb工作状态。
- 与ARM指令集的兼容性:Thumb指令集能够很好地与ARM指令集配合使用。ARM处理器可以在ARM指令集和Thumb指令集之间进行切换,这使得程序员可以根据实际应用场景灵活选择使用哪种指令集。例如,在对性能要求较高的关键代码部分使用ARM指令集,而在对代码密度要求更高的部分使用Thumb指令集。
- 与arm指令集相比,Thumb指令集的数据处理指令的操作位数仍是32位,指令地址也是32位,但是指令集实现了16位的指令长度。舍弃了一些arm指令集的特性,如大多数的Thumb指令集是无条无条件执行的。
- 由于Thumb指令的长度是16位,也就是只用到了arm指令一半的位数来实现相同的功能,所以要实现特定的程序的功能。所需要的Thumb指令的条数就要比Arm的指令条数多。
- Thumb指令与ARM指令的时间效率和空间效率
4. 若使用16位的存储器,samba代码比rm代码快约40%~50%。
5 与Arm代码比较,使用上部代码存储器的功耗会降低一个30%。
- 如果对系统的性能要求比较高,应该使用32位的存储系统和arm指令集。如果对系统的成本和功耗有较高要求,则应该使用16位的存储系统和Thumb指令集。
-
指令格式与类型
- 数据处理指令:
- 包括算术运算指令(如加法、减法等)和逻辑运算指令(如与、或、异或等)。例如,Thumb指令集中有
ADD
指令用于两个操作数相加,但其指令格式和操作数的指定方式与ARM指令集有所不同。它可以实现如寄存器与寄存器相加、寄存器与立即数相加等操作,用于简单的数值计算。 - 还有数据传送指令,用于在寄存器之间或者寄存器与内存之间传输数据。像
MOV
指令可以将一个寄存器的值传送到另一个寄存器,或者将立即数传送到寄存器,这在数据初始化和变量赋值等操作中经常使用。
- 包括算术运算指令(如加法、减法等)和逻辑运算指令(如与、或、异或等)。例如,Thumb指令集中有
- 分支指令:
- 提供了无条件分支和条件分支指令。例如
B
(无条件跳转)指令可以使程序跳转到指定的地址。BEQ
(相等则跳转)、BNE
(不相等则跳转)等条件分支指令则根据之前比较操作的结果(通过设置的条件码标志位来判断)决定是否跳转,这些指令用于实现程序的分支结构,如循环和条件判断。
- 提供了无条件分支和条件分支指令。例如
- 加载/存储指令:
- 用于从内存加载数据到寄存器或者将寄存器中的数据存储到内存。例如
LDR
(加载字)指令可以从内存地址中读取32位的数据到寄存器,STR
(存储字)指令则相反,将寄存器中的32位数据存储到指定的内存地址,这对于操作内存中的数据(如数组、结构体等)是必不可少的。
- 用于从内存加载数据到寄存器或者将寄存器中的数据存储到内存。例如
- 数据处理指令:
-
应用场景
- 嵌入式系统:在资源受限的嵌入式设备中广泛应用,如智能手表、智能家居传感器等。这些设备通常具有较小的内存空间和较低的功耗要求,Thumb指令集的高代码密度特性有助于在有限的存储资源内实现更多的功能。
- 移动设备:早期的移动电话等移动设备也大量使用Thumb指令集。因为在这些设备中,存储容量和电池电量都是宝贵的资源,Thumb指令集能够在保证一定性能的前提下,减小程序占用的空间和功耗。
4 S3C6401处理器
- 概述
- S3C6410是三星(Samsung)公司开发的一款基于ARM1176JZF - S内核的高性能、低功耗32位RISC(精简指令集计算机)微处理器。它主要应用于移动设备、嵌入式系统等领域,如智能手机、PDA(个人数字助理)和便携式多媒体播放器等。
- 处理器核心特性
- 高性能内核:ARM1176JZF - S内核具有较高的处理能力。它采用了哈佛架构(Harvard architecture),这种架构将指令和数据存储在不同的存储空间中,使得数据的读取和指令的执行可以同时进行,从而提高了处理速度。例如,在执行复杂的多媒体任务时,如视频解码,能够快速地获取指令和数据,提升系统性能。
- 工作频率:该处理器可以在不同的电压下运行不同的频率。在ARM Core电压为1.1V时,能够运行到553MHz;在1.2V的情况下,可以运行到667MHz,能够根据系统的功耗需求和性能要求灵活调整。
- 内部总线结构:通过AXI(Advanced eXtensible Interface)、AHB(Advanced High - performance Bus)和APB(Advanced Peripheral Bus)组成的64/32bit内部总线与外部模块相连。AXI总线提供了高性能、高带宽的通信通道,适用于高速数据传输的模块,如内存控制器等;AHB总线用于连接一些对带宽要求较高的外设;APB总线则主要用于连接一些低速的外围设备,这种分层的总线结构优化了系统的整体性能。
- 存储系统
- 内存控制器:
- 拥有两个片选,能够支持多种类型的内存,包括SDRAM(同步动态随机存取存储器)、DDR SDRAM(双倍数据速率同步动态随机存取存储器)、mobile SDRAM和mobile DDR SDRAM。每个片选最大支持256MB的内存容量,这为系统提供了足够的内存扩展空间。例如,在智能手机应用中,可以使用DDR SDRAM来满足系统运行多个应用程序和处理多媒体数据的内存需求。
- NandFlash控制器:
- 支持SLC(Single - Level Cell)/MLC(Multi - Level Cell)NandFlash。SLC NandFlash具有写入速度快、寿命长的特点,而MLC NandFlash存储容量大、成本低。它还支持512/2048Bytes Page的Nandflash,支持8 - Bit Nandflash,并且能够进行1/4/8 - Bit ECC(Error - Correcting Code)校验,用于检测和纠正数据存储过程中的错误,保证数据的完整性。同时,支持NandFlash Boot功能,使得系统可以直接从NandFlash启动。
- OneNAND控制器:
- 支持2个OneNAND控制器,可外接16 - Bit OneNand Flash。它可以支持同步异步读取数据,并且具备OneNAND Boot功能,在一些对数据存储速度和可靠性有要求的应用场景中发挥作用。
- SROM控制器:
- 具有六个片选,支持SRAM(静态随机存取存储器)、ROM(只读存储器)和NOR Flash。支持8/16 - Bit数据位宽,每个片选支持128MB,为系统提供了多样化的存储选择,方便存储程序代码和数据。
- 内存控制器:
- 系统外设
- RTC(实时时钟):
- 系统掉电时由备份电池支持,确保时间信息不会丢失。需要外接32.768KHz时钟,年/月/日/时/分/秒等时间信息都是以BCD(Binary - Coded Decimal)码格式存储,这种格式便于时间信息的处理和显示。
- PLL(锁相环):
- 支持三个PLL,分别是APLL、MPLL和EPLL。APLL为ARM提供时钟,产生ARMCLK;MPLL为所有和AXI/AHB/APB相连的模块提供时钟,产生HCLK和PCLK;EPLL为特殊的外设提供时钟,产生SCLK。通过PLL可以灵活地调整系统各个部分的时钟频率,以满足不同模块的性能需求,同时降低功耗。
- TIMER/PWM(定时器/脉冲宽度调制):
- 支持5个32 - Bit Timer。其中Timer0和Timer1具有PWM功能,可用于控制电机转速、调节灯光亮度等应用。而Timer2、3、4没有输出管脚,为内部Timer,主要用于系统内部的定时操作,如定时中断等。
- WATCHDOG(看门狗定时器):
- 可当作16 - Bit的内部定时器。它的主要作用是防止系统出现故障或死锁,当系统在规定时间内没有对看门狗定时器进行复位操作时,会触发系统复位,从而提高系统的可靠性。
- DMA(直接内存访问):
- 支持4个DMA控制器,每个控制器包含8个通道,支持8/16/32 - Bit传输,并支持优先级,其中通道0优先级最高。DMA可以在不需要CPU干预的情况下,直接在内存和外设之间传输数据,大大提高了数据传输效率,减轻了CPU的负担,尤其适用于高速数据传输的场景,如音频、视频数据的传输。
- KEYPAD(键盘):
- 支持8x8键盘,与GPIO(通用输入输出端口)复用。按下和抬起按键都可产生中断,方便系统对键盘输入进行及时响应,可应用于设备的按键输入功能。
- RTC(实时时钟):
- 通信接口
- I2S(Inter - IC Sound):
- 用于和外接的Audio Codec传输音频数据,支持普通的I2S双通道以及5.1通道I2S传输。音频数据可以是8/16/32 - Bit,采样率从8KHz到192KHz,能够满足高质量音频传输的需求,适用于多媒体设备中的音频处理部分。
- I2C(Inter - Integrated Circuit):
- 支持2个I2C控制器。I2C是一种简单的双向二线制同步串行总线,主要用于连接微控制器及其外围设备,在连接一些低速设备,如传感器、EEPROM(电可擦可编程只读存储器)等方面应用广泛。
- UART(通用异步收发传输器):
- 支持4个UART口,支持DMA和Interrupt模式,UART0/1/2还支持IrDA1.0功能,UART最高速度达3Mbps。UART是一种通用的串行通信接口,常用于设备之间的异步通信,如与PC进行调试信息的传输等。
- GPIO(通用输入输出端口):
- 提供了通用的输入输出功能,其端口功能可以复用,通过软件配置可以实现多种功能,如作为输入读取外部信号,或者作为输出控制外部设备等,是连接外部设备的重要接口。
- IrDA(红外数据协会):
- 独立的IrDA控制器,兼容IrDA1.1,支持MIR(中等速率红外)和FIR(高速红外)模式,用于实现设备之间的红外通信,在一些近距离无线数据传输的应用场景中使用。
- SPI(串行外设接口):
- 支持全功能的SPI。SPI是一种高速的全双工串行通信接口,主要用于连接微控制器和外围设备,如闪存、传感器等,能够实现高速的数据传输。
- MODEM(调制解调器):
- 内置8KB SRAM用于S3C6410和外接Modem交换数据,该SRAM还可为Modem提供Boot功能,方便系统通过调制解调器进行通信,如在移动设备中实现数据的远程传输。
- USB OTG(USB On - The - Go):
- 支持USB OTG 2.0,同时支持Slave和Host功能,最高速度480Mbps。USB OTG功能使得设备可以在主机和从机模式之间灵活切换,方便设备之间的数据共享和通信,例如连接外部USB存储设备或与其他USB设备进行数据交换。
- USB HOST:
- 独立的USB Host控制器,支持USB Host 1.1,用于连接USB设备,如USB鼠标、键盘等,扩展了设备的输入输出功能。
- MMC/SD(多媒体卡/安全数字卡):
- SD/MMC控制器,兼容SD Host 2.0,SD Memory Card 2.0,SDIO Card 1.0和High - Speed MMC。用于连接SD卡或MMC卡,实现大容量数据存储,如存储照片、视频等多媒体数据。
- PCM AUDIO(脉冲编码调制音频):
- 支持两个PCM Audio接口,传输单声道16 - Bit音频数据,主要用于音频数据的传输和处理,是音频系统的重要组成部分。
- AC97(Audio Codec '97):
- AC97控制器,支持独立的PCM立体声音频输入,单声道MIC输入和PCM立体声音频输出,通过AC - Link接口与Audio Codec相连,用于音频信号的输入输出处理,提供了高质量的音频功能。
- I2S(Inter - IC Sound):
- 多媒体处理
- TFT LCD(薄膜晶体管液晶显示器)控制器:
- 支持TFT 24 - Bit LCD屏,分辨率能支持到1024x1024,用于驱动液晶显示屏,实现高质量的图像显示,在便携式设备的显示屏驱动方面发挥关键作用,如智能手机、平板电脑等设备的屏幕显示。
- TFT LCD(薄膜晶体管液晶显示器)控制器:
4.5 存储器映射
- 概述
- S3C6401的存储器映射是指将不同类型的存储器和外围设备映射到处理器的地址空间中,使得处理器能够通过统一的地址访问这些资源。这对于系统的集成和软件编程是非常重要的。
S3C6401的主存地址范围是0x0000_0000至0x6FFF_FFFF,此范围又被划分为以下几个区域 :
- S3C6401的存储器映射是指将不同类型的存储器和外围设备映射到处理器的地址空间中,使得处理器能够通过统一的地址访问这些资源。这对于系统的集成和软件编程是非常重要的。
- 引导镜像区:地址范围从0x0000_0000至0x07FF_FFFF,大小为128MB,但实际上这个区域并没有真实对应的存储器设备,它存有引导芯片的信息,复位后处理器的程序计数器PC会跳到0x0000_0000处开始运行指令.
- 内部内存区:地址范围是0x0800_0000至0x0FFF_FFFF,共128MB,内部的ROM和SRAM都分布在这个区间。其中,0x0800_0000至0x0BFF_FFFF对应着内部ROM,不过实际的内部ROM只有32KB是有实际存储介质的只读区,存放IROM方式下的启动代码;0x0C00_0000至0x0FFF_FFFF对应内部SRAM,实际可用的SRAM一般为4KB.
- 静态存储区:地址范围从0x1000_0000至0x3FFF_FFFF,共3*128MB。这个区域用于访问挂在外部总线上的设备,如SRAM、NOR flash、oneNand等,该区域被分割为6个bank,每个bank为128MB,数据宽度最大支持16bit.
- 动态存储区:物理地址为0x4000_0000至0x6FFF_FFFF,共3*256MB,其中第一个256MB为保留区,实际使用的动态内存区为0x5000_0000至0x6FFF_FFFF,又分为2个区间,分别占256MB,可以通过DMC的Xm1CS(1:0)来进行这2个区间的选择,此内存区主要是用于扩展DRAM,最大可扩展512MB的DRAM.
-
内存区域映射
- SDRAM区域:S3C6401的内存控制器可以支持SDRAM(同步动态随机存取存储器)。SDRAM区域在存储器映射中有特定的地址范围,用于存储程序代码和运行时的数据。例如,它可能被映射到从某个起始地址(如0x30000000)开始的一段连续地址空间,其大小取决于系统配置和SDRAM芯片的容量。这样,当处理器需要访问存储在SDRAM中的数据或指令时,就可以通过这个地址范围进行读取和写入操作。
- NAND Flash区域:NAND Flash用于存储大量的数据,如系统的启动代码、操作系统内核等。在存储器映射中,NAND Flash也有自己的地址空间。通常,处理器通过特殊的NAND Flash控制器来访问NAND Flash。它可能会被映射到与SDRAM不同的地址范围,比如从0x00000000开始(这是一种常见的映射方式,实际情况可能因系统设计而不同)。由于NAND Flash的读写特性与SDRAM不同,所以处理器在访问时需要使用专门的指令或驱动程序。
- 其他存储区域:还包括SRAM(静态随机存取存储器)等存储设备的映射区域。SRAM速度快,常被用于存储一些对读写速度要求极高的临时数据。它在存储器映射中的地址范围会根据系统设计来确定,可能与SDRAM和NAND Flash的地址区域相互独立,方便处理器进行区分访问。
-
外围设备映射
- GPIO(通用输入输出端口)区域:GPIO用于连接外部设备,如按键、LED等。在存储器映射中,GPIO寄存器会被分配一个特定的地址范围。例如,这些寄存器可能被映射到0x7F008000 - 0x7F008FFF之间的地址。通过对这些地址进行读写操作,软件可以控制GPIO引脚的输入输出状态。比如,向某个GPIO寄存器写入数据可以设置引脚为高电平或低电平,从而控制外部设备的开启或关闭。
- UART(通用异步收发传输器)区域:UART用于串行通信,在存储器映射中有对应的寄存器地址。这些寄存器存储了UART的配置信息,如波特率设置、数据格式(数据位、停止位、奇偶校验位等)、发送和接收缓冲区等。当处理器需要发送数据时,它会将数据写入UART的发送寄存器,而接收数据时则从接收寄存器读取。其地址可能在0x7F005000 - 0x7F005FFF左右的范围,通过对这些地址的操作可以实现处理器与外部设备(如PC通过串口进行通信)的串行通信。
- I2C、SPI等接口区域:I2C和SPI也是常用的外围接口。I2C用于连接低速的外围设备,如温度传感器、EEPROM等;SPI主要用于连接高速的外围设备,如闪存芯片。它们在存储器映射中都有各自的寄存器地址范围。对于I2C,其寄存器可能位于0x7F00B000 - 0x7F00BFFF附近,通过对这些寄存器的操作可以配置I2C总线的工作模式、发送和接收数据等。SPI的寄存器地址范围根据系统设计而定,通过操作这些寄存器可以控制SPI设备的通信,如设置时钟频率、数据传输模式等。
-
特殊功能寄存器区域
- S3C6401还有一些特殊功能寄存器,用于控制处理器的各种核心功能,如时钟管理、中断控制等。这些寄存器在存储器映射中有特定的位置。例如,时钟管理寄存器可能用于设置处理器各个模块的时钟频率,它们的地址可能在0x7E00F000 - 0x7E00FFFF之间。通过对这些寄存器的操作,可以调整系统的时钟配置,以满足不同的性能和功耗需求。中断控制寄存器则用于管理和响应外部和内部的中断请求,其地址范围也在特定的区域,通过对这些寄存器的读写可以设置中断优先级、使能或禁止中断等操作。
5 GPIO接口
-
GPIO概述
- GPIO(General - Purpose Input/Output)即通用输入/输出端口,是S3C6401芯片中非常重要的外设接口。它提供了一种灵活的方式来连接外部设备,使得芯片能够与各种外部组件(如按键、LED、传感器等)进行通信。
-
引脚数量和分组
- S3C6401有多个GPIO端口,这些端口被分组为Port A - Port H等。每个端口包含数量不等的引脚。例如,Port A可能有23个引脚,Port B可能有11个引脚等,具体的引脚数量和功能定义是由芯片的硬件设计决定的。
- 总体而言,S3C6401大约有128个左右的GPIO引脚。这些引脚可以灵活地配置为输入、输出或者复用功能模式,以满足各种不同的应用场景需求,如连接外部设备(如按键、LED、传感器等)或者实现通信接口(如UART、I2C、SPI等)的功能。
-
功能模式
- 输入模式:当GPIO引脚被配置为输入模式时,它可以读取外部设备的信号状态。例如,连接一个按键到GPIO引脚,当按键按下或松开时,会产生不同的电平信号,通过将该引脚设置为输入模式,芯片就能够检测到这些电平变化,从而实现对按键事件的监测。
- 输出模式:在输出模式下,芯片可以通过GPIO引脚向外部设备输出电平信号。例如,连接一个LED到GPIO引脚,通过将引脚设置为输出模式,并控制引脚的电平为高或低,就可以实现LED的点亮或熄灭。
- 复用功能模式:除了基本的输入输出功能外,GPIO引脚还可以被配置为复用功能模式。在这种模式下,引脚可以用于实现一些特殊的功能,如UART(通用异步收发传输器)、I2C(内部集成电路)、SPI(串行外设接口)等通信接口的功能。这些通信接口在芯片内部有对应的硬件模块,当需要使用这些接口时,相关的GPIO引脚就会被配置为复用功能模式,以满足通信的需求。
-
寄存器配置
- 控制寄存器:S3C6401通过一系列的寄存器来配置GPIO引脚的功能和状态。其中,控制寄存器用于设置引脚的工作模式(如输入、输出、复用等)。每个端口组通常都有自己对应的控制寄存器,通过向这些寄存器写入特定的值,可以对该组内的引脚进行统一的模式配置。例如,写入0x0表示将引脚配置为输入模式,写入0x1表示配置为输出模式等。
- 数据寄存器:数据寄存器用于在输出模式下设置引脚的输出电平,或者在输入模式下读取引脚的输入电平。对于输出模式,向数据寄存器的相应位写入1或0,可以使对应的GPIO引脚输出高电平或低电平。对于输入模式,读取数据寄存器的相应位可以获取外部设备输入到引脚的电平状态。
- 上拉/下拉电阻寄存器:为了确保输入信号的稳定性,GPIO引脚还可以配置上拉或下拉电阻。上拉电阻寄存器和下拉电阻寄存器用于设置引脚是否连接上拉电阻或下拉电阻,以及电阻的大小(如果有多种可选电阻值的话)。通过合理配置上拉/下拉电阻,可以防止引脚处于浮空状态,避免信号干扰。
-
电气特性
- GPIO引脚具有特定的电气特性,包括输出驱动能力、输入电压范围等。输出驱动能力决定了引脚能够提供的最大电流,这对于连接一些需要较大电流驱动的外部设备(如大功率LED)非常重要。如果输出驱动能力不足,可能无法正常驱动外部设备。输入电压范围则规定了引脚能够正确识别的输入电平范围,超出这个范围的电压可能会导致引脚损坏或读取错误的信号。
-
中断功能
- S3C6401的GPIO引脚还可以支持中断功能。当引脚配置为输入模式并且使能中断后,外部设备输入信号的变化(如电平从高到低或从低到高的跳变)可以触发中断请求。在芯片内部,有相应的中断控制器来处理这些GPIO中断。一旦发生中断,芯片可以暂停当前的任务,转而执行中断服务程序,以快速响应外部设备的变化,这种中断机制在实时监测外部事件(如按键按下、传感器触发等)方面非常有用。
8 ADC和触摸屏接口
采样转换的4个过程采样、保持、量化以及编码。
S3C6410的ADC具有8位通道模拟输入10位转换精度的cmos型模数转换器,它将模拟的输入信号转换成数字编码。最大转换速率是500ksps和2.5MHz的ADC时钟。
-
概述
- S3C6410集成了模数转换器(ADC),它用于将外部的模拟信号转换为数字信号,从而使得处理器能够对这些模拟信号进行数字处理。这在很多应用场景中非常重要,如温度传感器、光照强度传感器等模拟信号的采集。
-
主要特性
- 分辨率:S3C6410的ADC通常具有一定的分辨率,例如10 - bit分辨率。这意味着它可以将模拟输入信号量化为2^10(即1024)个不同的数字级别。较高的分辨率能够更精确地表示模拟信号的变化,但同时也可能需要更多的处理时间和存储空间来处理这些更精细的数据。
- 转换通道数量:它拥有多个ADC转换通道,这些通道可以同时或分别连接不同的外部模拟信号源。例如,可能有8个或更多的通道,方便同时采集多个传感器的模拟信号,如在一个环境监测系统中,可以通过不同通道分别采集温度、湿度、光照等模拟信号。
- 转换速度:具有一定的转换速度范围,以每秒转换次数(SPS - Samples Per Second)来衡量。其转换速度可以满足多种应用的需求,如对于一些变化缓慢的模拟信号(如温度信号),较低的转换速度就可以满足要求;而对于一些快速变化的信号(如音频信号),则可能需要较高的转换速度。
-
工作原理
- 采样阶段:ADC首先对外部模拟信号进行采样。它通过内部的采样保持电路,在一个特定的时间点获取模拟信号的瞬时值。这个采样时间是可以配置的,合适的采样时间能够确保采集到准确的模拟信号值。
- 量化和编码阶段:采样后的模拟信号值被转换为数字值。在量化过程中,根据ADC的分辨率,将模拟信号的幅度范围划分为多个离散的区间。例如,对于10 - bit分辨率的ADC,模拟信号的幅度范围被划分为1024个区间。然后,每个区间被赋予一个唯一的数字编码,这个编码就是最终转换得到的数字信号。
-
寄存器配置
- 控制寄存器:通过对控制寄存器的操作来配置ADC的工作模式。例如,可以设置转换通道、转换速度、采样时间等参数。用户可以根据具体的应用需求,向控制寄存器写入合适的值来启动和控制ADC转换过程。
- 数据寄存器:转换完成后,数字结果被存储在数据寄存器中。处理器可以读取这个寄存器来获取转换后的数字信号,然后进行后续的数字处理,如计算、存储或传输等操作。
-
应用场景
- 环境监测:在环境监测设备中,ADC可用于采集温度传感器(如热敏电阻)、湿度传感器、光照传感器等的模拟信号。将这些模拟信号转换为数字信号后,通过软件算法进行处理,从而得到环境参数的数值,并可以进一步实现环境参数的监测、记录和报警等功能。
- 电池电量监测:用于监测电池的电压,将电池的模拟电压信号转换为数字信号,以便系统能够准确地判断电池的剩余电量,从而实现电池管理功能,如低电量报警、充电控制等。
9 Andrioid系统开发概述
9.1 Android的发展
最初的安卓系统由美国的一家小型公司设计开发,公司名称也叫做安卓。
安卓的名称来源于法国作家利尔亚于1886年发表的科幻小说未来夏娃。小说中那个类人机器的名字就是安卓。
2005年,谷歌收购了安卓公司,继续进行安卓系统的研发。
2007年,谷歌正式向外界展示了安卓的移动设备操作系统平台。同年,谷歌宣布建立一个全球性的联盟组织,即开放手机联盟OHA。
- 该组织由34家手机制造商软件开发商电信运营商以及芯片制造商共同组成。
- 该组织将支持谷歌发布的手机操作系统以及应用软件,将共同开发安卓系统的开放源代码。
- 中国三大电信运营商都是OHA成员,移动还是创始成员。
在2008年的谷歌大会上,谷歌提出了安卓HAL架构图。同年安卓获得了美国联邦通信委员会的批准,2008年9月,谷歌正式发布了安卓系统1.0。
2008年,以智能手机为代表的移动设备仍然是以诺基亚的塞班系统为主。在短短几年内,安卓系统迅速成长为一个完整的系统平台,并成为市场占有率最高的系统。
在最初的谷安卓系统1.0发布之后,谷歌又发布了多个安卓版本。每个版本还具有与之对应的api级别,这里的api级别通常是指安卓系统中Java层的api接口。
作为一个具有开放开源属性的系统,安卓具有较多优势。它允许任何移动设备厂商使用安卓系统,从而获得了更多的开发者获得更多的移动应用支持。
此外,安卓移动应用不受审查限制,可以从不同的安卓应用商店下载。由于安卓系统发展过程中得到了众多软硬件厂商运营商的支持,因此安卓系统的发展极为迅速。
安卓系统并不是完全免费的。对于操作系统底层的驱动通常是根据移动设备进行定制开发,这部分是不开源的。
安卓应用主要使用Java语言开发,其运行效率和硬件消耗一直对系统性能具有较大的影响。
9.2 Android系统架构
安卓将系统架构分为4层,从上到下,依次为应用程序层、应用程序框架层、系统运行库层以及Linux内核层。
- 安卓系统的Linux内核层由c语言实现,运行于操作系统的内核空间。
- 系统运行库层由c和c+加实现。
- 应用程序框架层和应用程序层主要有Java语言实现。
从Linux操作系统的角度来看,是内核空间与用户空间的分界线。系统运行库存和应用程序框架层之间是本地代码层和Java代码层的接口。应用程序框架层和应用程序之间是安卓的系统api接口。
9.2.1 应用程序层
应用程序层包含一个核心应用集合,联系人管理、浏览器等,同时开发人员利用Java语言设计和编写的程序也都运行在这一层上,如游戏和音乐播放器。
也就是说安卓系统自带的应用和第三方开发的应用都位于这个层次上。
- 安卓系统的应用是由谷歌官方提供的,会使用一些隐藏的类。
- 而第三方开发的应用是以安卓系统的sdk为基础进行开发的,以Java作为编程语言来编写的应用程序。
安卓系统通过本机开发程序包NDK,提供了从应用层穿越Java框架层与底层包含了JNI接口的c/c++库直接通信的方法。
安卓的应用程序是指主要用Java语言编写的用户程序。其中还包括各种的资源文件等相关资源经过编译后最终生成的一个apk包。
一个完整的安卓应用程序要包括一个或几个组件:
-
Activity(活动)
- 功能概述:Activity是安卓应用中最基本的组件,用于实现用户界面。它就像是一个单独的屏幕,用户可以在上面进行各种交互操作,如点击按钮、输入文字等。例如,在一个购物应用中,商品展示页面、购物车页面、支付页面等都是不同的Activity。
- 生命周期管理:Activity有完整的生命周期,包括onCreate(创建)、onStart(启动)、onResume(恢复)、onPause(暂停)、onStop(停止)、onDestroy(销毁)等方法。这些方法在Activity的不同状态转换时被调用,用于管理资源的分配和释放。例如,在onCreate方法中可以进行视图的初始化和数据的加载,在onDestroy方法中可以释放一些占用的资源,如关闭数据库连接等。
- Intent启动方式:Activity可以通过Intent(意图)进行启动。显式Intent明确指定要启动的Activity类,用于在应用内部进行页面跳转。隐式Intent则通过指定动作(action)和数据(data)来启动能够处理该意图的Activity,这种方式可以用于跨应用启动Activity。例如,一个地图应用可以通过隐式Intent接收来自其他应用的位置信息,并显示对应的地图位置。
-
Service(服务)
- 功能概述:Service是一种在后台运行的组件,没有用户界面。它主要用于执行长时间运行的操作,如音乐播放、文件下载、数据同步等。例如,在音乐播放应用中,播放音乐的功能通常是由Service来实现的,即使应用的界面被切换或者手机屏幕关闭,音乐依然可以继续播放。
- 启动方式和生命周期:Service可以通过startService(启动服务)或bindService(绑定服务)方法启动。startService方法启动的Service会一直运行直到被停止,而bindService方法启动的Service与调用者绑定,当所有绑定的组件都解绑后,Service会被销毁。Service的生命周期包括onCreate(创建)、onStartCommand(启动命令)、onBind(绑定)、onUnbind(解绑)、onDestroy(销毁)等阶段。
- 与其他组件的交互:Service可以通过Broadcast Receiver接收广播消息来执行相应的操作,也可以与Activity等组件进行通信。例如,一个文件下载Service可以在下载完成后通过广播通知Activity更新界面显示下载完成的信息。
-
Broadcast Receiver(广播接收器)
- 功能概述:Broadcast Receiver用于接收系统或应用发出的广播消息。广播消息可以是系统事件,如开机完成、网络连接变化、电池电量变化等,也可以是应用自定义的广播。例如,当手机的网络从Wi - Fi切换到移动数据时,系统会发送一个网络连接变化的广播,Broadcast Receiver可以接收到这个广播并采取相应的措施,如提醒用户网络已切换。
- 注册方式:Broadcast Receiver有两种注册方式,静态注册和动态注册。静态注册是在AndroidManifest.xml文件中声明,这种方式的Broadcast Receiver可以在应用未启动时接收到广播。动态注册是在代码中通过registerReceiver方法进行注册,通常在Activity或Service的生命周期内有效,当组件被销毁时,需要通过unregisterReceiver方法注销,否则可能会导致内存泄漏。
- 应用场景:广播接收器在很多场景下都有应用,如推送通知接收、系统事件响应、应用间通信等。例如,在推送服务中,当服务器推送一条消息到手机时,应用内的Broadcast Receiver可以接收这个消息并在通知栏显示推送内容。
-
Content Provider(内容提供者)
- 功能概述:Content Provider用于在不同的应用之间共享数据。它将数据封装在一个标准的接口后面,其他应用可以通过这个接口来访问和操作数据。例如,安卓系统中的联系人应用通过Content Provider来允许其他应用访问和修改联系人信息。
- 数据共享方式:Content Provider使用统一资源标识符(URI)来标识数据资源。其他应用通过ContentResolver对象,使用Content Provider提供的查询(query)、插入(insert)、更新(update)、删除(delete)等方法来操作数据。例如,一个第三方的短信备份应用可以通过Content Provider访问短信应用中的短信内容,并将短信备份到云端。
- 数据安全与权限管理:Content Provider可以通过权限设置来控制对数据的访问。只有具有相应权限的应用才能访问特定的Content Provider提供的数据。这可以保护数据的安全性和用户的隐私。例如,系统的短信Content Provider通常会设置只有具有读取短信权限的应用才能访问短信内容。
应用程序在安卓系统中运行的主要特征为每一个应用程序都默认运行在一个Linux进程中。安卓系统会自动为需要运行的应用程序启动一个进程,这个进程会一直存在直到说到包含要求退出的信息的代码。
- 安卓系统的每个进程互不干涉都运行在一个相对独立的Dalvik a virtual machine中.
- 在默认情况下,一般每个应用程序都被给予唯一的一个Linux user id。
- 在极端特殊的情况下,两个应用程序也可以用一个id,但是在共用的时候,他们之间是可以彼此访问的。
9.2.2 应用程序框架层
应用程序框架层是安卓应用开发的基础,开发者可以使用谷歌发布的api框架进行程序开发。Api框架简化了程序开发的架构设计,但开发者必须遵守开发的规则。
安卓系统的应用程序框架层包含的组件包含:
-
Activity Manager(活动管理器)
- 功能概述:Activity Manager主要负责管理Activity的生命周期。它决定了一个Activity何时创建、启动、暂停、停止和销毁。例如,当用户从一个应用的主界面跳转到另一个功能界面时,Activity Manager会协调旧Activity的暂停和新Activity的启动过程。
- 任务栈管理:它还负责维护任务栈(Task Stack)。任务栈是一种后进先出(LIFO)的数据结构,用于存储用户打开的Activity序列。例如,当用户依次打开了应用A中的三个Activity(A1、A2、A3),它们会按照A1、A2、A3的顺序堆叠在任务栈中。当用户按下返回键时,Activity Manager会按照栈的顺序,先销毁A3,然后是A2,以此类推。
-
Window Manager(窗口管理器)
- 功能描述:Window Manager负责管理窗口的显示和布局。它控制着屏幕上所有窗口的位置、大小和层级关系。例如,在多窗口模式下,Window Manager会确定各个应用窗口的排列方式,是平铺还是层叠,以及每个窗口的大小和位置等。
- 与视图系统的协作:它和视图系统(View System)紧密协作。视图系统负责构建和绘制用户界面的各个组件,而Window Manager则负责将这些视图组件正确地显示在屏幕上。例如,当一个Activity的视图结构发生变化,如添加或移除了一个视图,Window Manager会根据新的视图布局要求重新调整窗口的显示。
-
Content Providers(内容提供者)
- 数据共享功能:Content Providers是用于在不同应用之间共享数据的组件。它将应用内部的数据封装起来,通过统一的接口供其他应用访问。例如,安卓系统中的联系人应用通过Content Providers允许其他应用(如拨号应用、短信应用等)访问联系人数据。
- 数据访问机制:其他应用通过ContentResolver来与Content Providers交互,使用查询(query)、插入(insert)、更新(update)、删除(delete)等操作来获取或修改共享的数据。例如,一个第三方的联系人管理应用可以使用ContentResolver从系统的联系人Content Providers中读取联系人信息,并进行显示或修改。
-
View System(视图系统)
- 用户界面构建:View System是构建安卓应用用户界面的基础。它包含了各种视图组件(Views)和视图组(View Groups)。视图组件如TextView(用于显示文本)、Button(用于触发操作)等,视图组如LinearLayout(线性布局)、RelativeLayout(相对布局)等用于组织和管理多个视图组件。例如,在一个简单的登录界面中,用户名和密码输入框可以是EditText视图组件,登录按钮是Button组件,它们通过LinearLayout或RelativeLayout等视图组进行布局。
- 事件处理机制:View System还具备事件处理机制,能够处理用户在视图上的各种操作,如点击、滑动、长按等。当用户在一个视图上执行操作时,相应的事件会在视图层次结构中传播,直到被合适的事件处理器(如OnClickListener、OnTouchListener等)处理。例如,当用户点击一个按钮时,按钮的OnClickListener会被触发,执行相应的点击事件处理代码。
-
Package Manager(包管理器)
- 应用管理功能:Package Manager负责安卓系统中所有应用程序包(APK)的管理。它能够获取应用的各种信息,如应用的名称、版本号、权限要求、安装位置等。例如,在应用商店中,当用户查看一个应用的详细信息时,这些信息就是由Package Manager提供的。
- 安装、卸载和更新:它还负责应用的安装、卸载和更新操作。当用户从应用商店下载并安装一个应用时,Package Manager会解析APK文件,将应用安装到合适的位置,并注册应用相关的组件(如Activity、Service等)。在应用更新时,它会比较新旧版本的差异,进行相应的更新操作。
-
Telephony Manager(电话管理器)
- 电话功能支持:Telephony Manager提供了对手机通信功能的管理和访问接口。它可以获取手机的信号强度、网络运营商信息、通话状态(如正在通话、来电等待等)等。例如,在一个手机信号检测应用中,可以通过Telephony Manager获取信号强度数据并显示给用户。
- 与通信相关的服务:它还用于支持一些与电话通信相关的服务,如短信发送和接收的底层管理。虽然具体的短信发送和接收功能通常由其他组件(如短信应用的Activity和Content Providers)实现,但Telephony Manager在其中起到了协调和提供基础数据的作用。
-
Location Manager(位置管理器)
- 位置信息获取:Location Manager用于获取设备的地理位置信息。它可以通过多种方式获取位置,如GPS(全球定位系统)、Wi - Fi定位、基站定位等。例如,在地图应用中,Location Manager提供的位置信息可以帮助应用显示用户当前的位置,并提供导航等服务。
- 位置更新和精度控制:它能够根据应用的需求设置位置更新的频率和精度。例如,一个运动健身应用可能需要高精度的位置更新,以准确记录用户的运动轨迹,而一个基于位置的广告推送应用可能只需要较低精度的位置信息,并且更新频率也可以较低,以节省电量和网络流量。
9.2.3 系统运行层库
-
概述
- Android系统运行层库是Android系统架构中的重要组成部分,它为应用程序的开发提供了丰富的功能支持。这些库以C/C++编写,通过Android的应用程序框架(Application Framework)为Java等高级语言编写的应用程序提供服务,并且借助Android运行时环境(Android Runtime)来执行。
-
主要的系统运行层库
-
SQLite
- 功能:SQLite是一个轻量级的关系型数据库管理系统。它允许应用程序存储和管理结构化的数据。例如,在许多Android应用中,如联系人管理应用、待办事项应用等,SQLite用于存储用户数据。它支持标准的SQL语法,能够进行数据的插入、查询、更新和删除操作。
- 优势:SQLite的优点在于其占用资源少、易于嵌入应用程序。它不需要单独的服务器进程,所有的数据存储和管理操作都在本地文件系统上完成,这使得它非常适合移动设备这种资源有限的环境。
-
OpenGL ES
- 功能:OpenGL ES(OpenGL for Embedded Systems)是用于嵌入式系统的图形处理库。它提供了2D和3D图形渲染的功能,使开发者能够创建具有高质量视觉效果的应用程序。例如,在3D游戏开发中,OpenGL ES用于渲染游戏场景中的各种3D模型、纹理和光照效果。
- 版本与特性:Android支持多个版本的OpenGL ES,如OpenGL ES 1.0、1.1、2.0等。随着版本的升级,功能不断增强,如OpenGL ES 2.0引入了可编程渲染管线,让开发者能够更灵活地控制图形渲染过程,实现更加复杂的图形效果。
-
FreeType
- 功能:FreeType是一个用于字体渲染的库。它允许Android应用程序正确地显示各种字体。在文字处理应用、阅读应用等众多应用中,FreeType发挥着关键作用。它可以将字体文件(如TrueType字体)转换为可以在屏幕上显示的位图,并且支持字体的缩放、旋转等操作。
-
Libc(C标准库)
- 功能:Libc为应用程序提供了基本的C语言函数支持,如字符串处理函数(strcpy、strcat等)、内存管理函数(malloc、free等)、数学函数(sqrt、sin等)。这些函数是很多应用程序正常运行的基础。例如,在进行数据处理、算法实现等过程中,都需要用到这些基本的C函数。
- 与系统结合:Android中的Libc库是经过优化的,以适应移动设备的特点。它与Android的系统调用和底层硬件相结合,确保应用程序在不同的Android设备上都能够高效地运行。
-
Media Framework Libraries
- 功能:这是一组用于处理音频和视频的库。它支持多种音频和视频格式的播放、录制和编码/解码。例如,在视频播放应用中,Media Framework Libraries负责对视频文件(如MP4、AVI等格式)进行解码,并将解码后的视频帧和音频数据进行同步播放。在视频录制应用中,它则用于对摄像头和麦克风采集的音频和视频信号进行编码,生成相应的文件。
- 格式支持:它支持的音频格式包括MP3、AAC等,视频格式包括H.264、VP8等主流格式,并且随着Android系统的更新,不断增加对新格式的支持,以满足用户对多媒体内容的多样化需求。
-
桌面管理器
- 在 Android 系统运行库层中,桌面管理器(Launcher)是一个关键组件。它主要负责管理和显示设备的主屏幕(Home Screen)和应用程序抽屉(App Drawer),是用户与设备上各种应用程序进行交互的重要入口。
-
Web浏览器引擎web kit
- 一个开源的浏览器引擎
-
SGL库
- 基本都2D图形引擎
-
9.2.4 Linux内核层
安卓以Linux内核为基础,借助Linux内核提供核心系统服务,例如安全性内存管理以及进程管理等方面。 Linux内核作为一个抽象层,存在于硬件和软件栈之间。
- 在安卓4.0之前,基于Linux2.6系列内核。
- 在安卓4.0之后使用更新的Linux3.x内核。
- Linux3.3内核中包含了一些安卓代码,可以让开发人员利用Linux内核运行于安卓系统。
- Linux3.4增加了电源管理等更多功能,以增加与安卓的硬件兼容性。
安卓在Linux内核的基础上进行了增强,增加了一些面向移动计算的特有功能。
- 这些内核的增强是安卓在继承Linux内核安全机制的同时,进一步提升了内存管理进程间通信。等方面的安全性。
- 需要注意的是,由于安卓系统使用的Linux内核做了定制化的修改和优化,从内核代码上来说很难融合到Linux的主线内核中。
- 因此,在2010年安卓系统的Linux内核曾被Linux kernel2.6.33版代码库移除,直到2012年才重新回归到Linux3.3代码库。
安卓中主要包含了以下的驱动程序。
-
显示驱动(Display Driver)
- 功能:负责控制和管理屏幕显示相关的功能。它与图形处理单元(GPU)紧密合作,将图形数据转换为屏幕可以显示的信号。例如,在手机屏幕上显示应用程序的用户界面、视频播放内容、游戏画面等。它支持多种显示技术,如液晶显示器(LCD)、有机发光二极管(OLED)等不同类型屏幕的显示控制,包括对屏幕分辨率、刷新率、色彩模式等参数的设置。
- 应用场景:在任何需要在屏幕上显示内容的场景中都发挥关键作用。从简单的系统界面显示,如主屏幕、通知栏等,到复杂的多媒体内容展示,如高清视频播放、3D游戏运行等,都离不开显示驱动的支持。
-
触摸驱动(Touch Driver)
- 功能:主要用于处理触摸屏幕输入的信号。它能够检测触摸屏幕上的触摸位置、触摸压力(如果屏幕支持压力感应)、触摸手势(如滑动、点击、长按、缩放等)等信息,并将这些信息传递给上层的系统软件和应用程序。触摸驱动通过与硬件传感器紧密配合,实现高精度的触摸输入检测,以提供良好的用户触摸交互体验。
- 应用场景:是所有触摸式安卓设备(如智能手机、平板电脑)正常使用的关键驱动。用户在操作设备时,无论是通过触摸屏幕打开应用程序、浏览网页、进行游戏操作还是输入文字等,触摸驱动都在背后负责准确地获取和传递触摸信息。
-
摄像头驱动(Camera Driver)
- 功能:控制摄像头硬件,实现拍照和录像功能。它管理摄像头的各种参数,如焦距、光圈、曝光时间、白平衡等,以确保拍摄出高质量的照片和视频。摄像头驱动还负责将摄像头捕获的光信号转换为数字图像或视频数据,并将这些数据传递给上层的媒体处理库或应用程序进行进一步的处理,如图像编辑、视频录制和直播等。
- 应用场景:在相机应用、视频通话应用、监控应用等众多需要使用摄像头的应用程序中发挥核心作用。例如,当用户使用手机相机拍摄风景照片或者进行视频会议时,摄像头驱动负责启动摄像头、设置拍摄参数,并将拍摄的数据传输给应用程序。
-
音频驱动(Audio Driver)
- 功能:管理音频设备,包括扬声器、麦克风等。它负责音频信号的输入和输出处理。对于音频输出,它将音频数据转换为模拟信号,驱动扬声器播放声音,支持多种音频格式和音频效果,如音量调节、均衡器设置等。对于音频输入,它从麦克风获取声音信号,将其转换为数字音频数据,用于录音或语音识别等应用。
- 应用场景:在音频播放应用(如音乐播放器、视频播放器)、录音应用、语音助手等应用中是必不可少的。例如,当用户使用手机播放音乐时,音频驱动将音乐文件中的音频数据进行解码并输出到扬声器;当用户使用语音助手进行语音输入时,音频驱动从麦克风获取语音信号并传递给语音识别软件。
-
传感器驱动(Sensor Drivers)
- 功能:安卓设备中包含多种传感器,如加速度计、陀螺仪、地磁传感器、环境光传感器、距离传感器等,每种传感器都有对应的驱动程序。这些驱动程序负责与传感器硬件通信,获取传感器测量的数据,并将其传递给上层的系统服务或应用程序。例如,加速度计驱动可以获取设备的加速度信息,用于屏幕自动旋转、运动检测等功能;环境光传感器驱动能够获取环境光照强度,用于自动调节屏幕亮度。
- 应用场景:广泛应用于各种需要感知环境或设备状态的应用场景。如在运动类游戏中,通过加速度计和陀螺仪驱动获取设备的运动状态来控制游戏角色;在自动亮度调节功能中,环境光传感器驱动提供的数据用于根据环境光照强度动态调整屏幕亮度。
-
蓝牙驱动(Bluetooth Driver)
- 功能:实现安卓设备与其他蓝牙设备之间的通信。它负责管理蓝牙设备的配对、连接、数据传输等操作。蓝牙驱动支持多种蓝牙协议和配置文件,如蓝牙音频传输协议(A2DP)用于连接蓝牙耳机,蓝牙人机接口设备协议(HID)用于连接蓝牙键盘、鼠标等。
- 应用场景:在无线音频设备连接(如蓝牙耳机、蓝牙音箱)、无线输入设备连接(如蓝牙键盘、蓝牙鼠标)、文件传输(如与其他蓝牙设备之间传输照片、文档等)等场景中发挥作用。
-
Wi - Fi驱动(WiFi Driver)
- 功能:使安卓设备能够连接到Wi - Fi网络。它负责Wi - Fi设备的初始化、扫描可用网络、连接网络、传输数据等操作。Wi - Fi驱动与底层的Wi - Fi硬件芯片紧密配合,支持不同的Wi - Fi标准,如802.11a/b/g/n/ac/ax等,以实现高速、稳定的无线网络连接。
- 应用场景:在几乎所有需要通过Wi - Fi连接网络的场景中都必不可少,如浏览网页、下载应用程序、在线视频播放、云存储等,只要设备需要通过Wi - Fi与互联网或本地网络中的其他设备进行通信,就需要Wi - Fi驱动的支持。
-
存储驱动(Storage Driver)
- 功能:管理存储设备,如内部闪存、外部SD卡等。存储驱动负责存储设备的初始化、读写操作、分区管理等功能。它将文件系统的操作请求转换为对存储硬件的实际读写操作,确保数据能够正确地存储和读取。例如,它支持常见的文件系统,如FAT32、exFAT、ext4等,以满足不同存储设备和应用场景的需求。
- 应用场景:在设备的所有存储相关操作中都发挥作用,包括安装应用程序、存储用户数据(如照片、视频、文档等)、系统更新等。无论是内部存储用于保存系统文件和应用数据,还是外部存储用于扩展设备的存储容量,都需要存储驱动来进行管理。
9.3 Android系统内核
安卓采用了Linux的内核,所以具有Linux内核的一些特性:
- 强大的内存管理和进程管理方案。
- 基于权限的安全模式。
- 支持共享库。
- 经过认证的驱动模型。
- Linux本身就是开源的项目。
9.3.1 Linux内核结构
Linux的内核主要由5个子系统组成,一是进程调度,二是内存管理,三是虚拟文件系统,四是网络接口,五是进程间通讯。
-
进程管理子系统
- 进程创建与终止:
- 进程是程序的一次执行过程,Linux内核通过
fork()
系统调用创建新进程。例如,当用户在终端输入一个命令来启动一个程序时,内核就会使用fork()
为这个程序创建一个进程。fork()
创建的子进程几乎是父进程的副本,它会复制父进程的地址空间(包括代码段、数据段、堆栈等)。 - 当进程完成任务或者遇到错误等情况时,内核会通过
exit()
系统调用终止进程。在这个过程中,内核会回收进程占用的资源,如内存空间、打开的文件描述符、占用的CPU时间等,以避免资源浪费。
- 进程是程序的一次执行过程,Linux内核通过
- 进程调度:
- 内核采用多种调度算法来分配CPU时间片。如时间片轮转(RR)调度算法,每个进程会被分配一个固定的时间片来使用CPU。假设时间片为10ms,当一个进程的时间片用完后,内核会暂停这个进程,将CPU资源分配给下一个进程,从而实现多个进程的并发执行。
- 优先级调度也是常用的算法之一。进程可以有不同的优先级,内核会优先将CPU资源分配给优先级高的进程。例如,对于实时性要求高的进程(如音频播放进程),内核会赋予它较高的优先级,确保它能及时获得CPU资源,保证音频的流畅播放。
- 进程间通信(IPC):
- 管道(Pipe)是一种简单的IPC机制,用于在具有亲缘关系(父子进程)的进程之间传递数据。例如,在一个命令管道中,一个进程的输出可以作为另一个进程的输入,就像
ls | grep ".txt"
这个命令,ls
进程的输出通过管道传递给grep
进程进行筛选。 - 消息队列(Message Queue)允许不同进程之间发送和接收消息。进程可以将消息发送到消息队列中,其他进程可以从队列中读取消息。这种方式支持消息的异步传递,适用于需要在多个进程之间进行通信和协调的场景。
- 共享内存(Shared Memory)机制允许多个进程共享同一块物理内存区域。这可以大大提高数据共享的效率,因为进程之间不需要进行数据的复制。但是,为了避免多个进程同时访问共享内存时出现冲突,需要配合信号量(Semaphore)等同步机制来进行访问控制。
- 管道(Pipe)是一种简单的IPC机制,用于在具有亲缘关系(父子进程)的进程之间传递数据。例如,在一个命令管道中,一个进程的输出可以作为另一个进程的输入,就像
- 进程创建与终止:
-
内存管理子系统
- 虚拟内存管理:
- 虚拟内存是Linux内存管理的核心概念。每个进程都有自己独立的虚拟地址空间,通过内存分页(Paging)技术与实际的物理内存进行映射。例如,在32位系统中,进程的虚拟地址空间通常为4GB,其中一部分用于用户空间,另一部分用于内核空间。当进程访问虚拟内存中的某个页面时,内核会根据页表(Page Table)来查找对应的物理内存页面。如果该页面不在物理内存中,内核会从磁盘等存储设备中将页面调入物理内存(这个过程称为页面调入,Page - in)。
- 内存分配与回收:
- 内核通过系统调用(如
kmalloc()
、vmalloc()
等函数)为内核空间分配内存,通过malloc()
等函数为用户空间分配内存。当进程申请内存时,内核会从空闲内存列表中查找合适的内存块进行分配。例如,malloc()
函数会在堆(Heap)上为应用程序分配内存,应用程序可以使用这些内存来存储数据结构、变量等。 - 当内存不再需要时,内核会回收这些内存。对于用户空间的内存,应用程序可以通过
free()
函数释放内存。内核会将释放的内存标记为空闲状态,以便后续重新分配。同时,为了避免内存碎片问题,内核采用了一些策略,如伙伴系统(Buddy System)来管理内存块的分配和合并。
- 内核通过系统调用(如
- 内存保护:
- 借助内存管理单元(MMU)等硬件设施,内核实现了内存保护机制。每个进程只能访问自己的虚拟内存空间,当一个进程试图访问不属于它的内存区域时,MMU会触发一个异常,如缺页中断(Page Fault)。内核会处理这些异常,可能是将需要的页面调入物理内存,或者判断为非法访问而终止进程,从而确保不同进程之间的内存互不干扰,提高系统的安全性和稳定性。
- 虚拟内存管理:
-
文件系统子系统
- 文件系统支持:
- Linux内核支持多种文件系统,如ext4、XFS、Btrfs等。ext4是一种广泛使用的文件系统,它具有良好的性能和稳定性,支持文件的创建、删除、读写等基本操作,还支持目录结构、文件权限管理等功能。例如,用户在ext4文件系统的磁盘分区上创建一个新文件,内核会在文件系统的元数据区域记录文件的相关信息(如文件名、文件大小、创建时间等),并在数据区域为文件分配存储空间。
- XFS文件系统适用于高性能的存储环境,它在处理大型文件和高并发读写操作方面表现出色。Btrfs文件系统具有一些先进的特性,如支持快照(Snapshot)功能,用户可以对文件系统的某个状态进行快照,方便数据备份和恢复。
- 虚拟文件系统(VFS)层:
- VFS是Linux文件系统的一个抽象层,它为上层应用程序提供了一个统一的文件操作接口。无论底层使用的是哪种实际的文件系统,应用程序都可以通过相同的系统调用(如
open
、read
、write
、close
等)来操作文件。例如,当应用程序调用open
函数打开一个文件时,VFS层会根据文件所在的文件系统类型(如ext4或XFS),调用相应文件系统的打开文件操作函数来完成文件的打开过程。
- VFS是Linux文件系统的一个抽象层,它为上层应用程序提供了一个统一的文件操作接口。无论底层使用的是哪种实际的文件系统,应用程序都可以通过相同的系统调用(如
- 文件缓存管理:
- 为了提高文件系统的性能,内核会对文件数据进行缓存。当应用程序读取文件时,内核首先会检查缓存中是否已经存在所需文件的数据。如果缓存命中,就直接从缓存中获取数据,而不需要从磁盘等存储设备中读取,大大加快了文件访问速度。例如,对于经常访问的系统配置文件,内核可能会将其数据缓存在内存中,下次读取时就可以快速获取。同时,内核会根据一定的策略(如最近最少使用,LRU)来管理缓存的更新和清理,以确保缓存的有效性和内存的合理利用。
- 文件系统支持:
-
设备驱动子系统
- 设备驱动框架:
- Linux内核提供了一个设备驱动框架,用于编写和管理各种硬件设备的驱动程序。这个框架定义了设备驱动的基本结构和接口,包括设备的初始化、操作函数(如读写操作)、中断处理等。例如,对于一个网卡设备,网卡驱动程序需要按照内核的设备驱动框架编写,实现初始化网卡、发送和接收网络数据包、处理网卡中断等功能。
- 设备驱动加载与卸载:
- 内核能够动态地加载和卸载设备驱动程序。当一个新的设备插入系统(如通过USB接口插入一个移动硬盘)时,内核会检测到设备的接入,并根据设备的类型和识别信息,尝试从已有的驱动程序库中加载合适的驱动程序。在加载过程中,内核会为设备分配资源,如内存空间、中断号等。当设备拔出时,内核会卸载相应的驱动程序,释放设备占用的资源。
- 设备抽象与统一接口:
- 通过设备驱动子系统,内核将不同类型的硬件设备抽象为统一的设备接口。对于上层的软件(如应用程序、系统服务)来说,它们可以以相同的方式访问不同的设备。例如,无论是对磁盘设备(属于块设备)还是对终端设备(属于字符设备)进行读写操作,应用程序都可以使用
read
和write
系统调用,内核会根据设备的类型,通过设备驱动将这些操作转换为对具体设备的实际操作。
- 通过设备驱动子系统,内核将不同类型的硬件设备抽象为统一的设备接口。对于上层的软件(如应用程序、系统服务)来说,它们可以以相同的方式访问不同的设备。例如,无论是对磁盘设备(属于块设备)还是对终端设备(属于字符设备)进行读写操作,应用程序都可以使用
- 设备驱动框架:
-
网络子系统
- 网络协议栈:
- Linux内核包含完整的网络协议栈,支持多种网络协议。从底层的物理层协议(如以太网协议)到链路层、网络层(如IP协议)、传输层(如TCP和UDP协议),再到应用层协议(如HTTP、FTP等)。例如,当一个应用程序通过HTTP协议发送一个网页请求时,请求数据会首先在应用层进行封装,然后依次经过传输层(TCP会对数据进行分段和编号,确保可靠传输)、网络层(IP会为数据添加源地址和目的地址等信息)、链路层(以太网协议会将数据封装为帧,并添加MAC地址等信息),最后通过物理层(网卡)发送到网络中。
- 网络设备管理:
- 内核有效地管理各种网络设备,包括网卡、无线网络设备等。它可以配置网络设备的参数,如IP地址、子网掩码、网关等。例如,通过
ifconfig
或ip
命令,用户可以设置网络设备的IP地址和子网掩码等信息。内核还可以对网络流量进行监控和管理,通过netstat
等工具可以查看网络连接状态和网络流量统计信息。同时,内核支持网络设备的热插拔,当插入一个新的网卡时,内核能够自动检测并配置这个网卡。
- 内核有效地管理各种网络设备,包括网卡、无线网络设备等。它可以配置网络设备的参数,如IP地址、子网掩码、网关等。例如,通过
- 网络服务与应用支持:
- 为网络服务(如Web服务、邮件服务等)和网络应用(如浏览器、邮件客户端等)提供支持。内核通过网络协议栈接收和发送网络数据包,使得网络服务能够提供服务,网络应用能够与服务器进行通信。例如,一个基于Linux的Web服务器(如Apache、Nginx)可以通过内核的网络管理系统接收来自客户端浏览器的HTTP请求,然后将网页内容发送回客户端。
- 网络协议栈:
9.3.2 Android内核和驱动
安卓采用了以Linux为基础的操作系统内核,同时也对Linux内核作了修改,以适应移动设备上的应用。
- 内核就相当于一个介于硬件层和系统中其他软件组之间的一个抽象层次。但是不算做Linux操作系统。
- 因为安卓系统的Linux内核是由标准的Linux内核修改而来的。
安卓系统在Linux内核的基础上增加了称为Dalvik的Java虚拟机和安卓runtime运行环境,构成了安卓的运行库。
-
每个安卓的应用都运行在自己的进程上使用由环由运行环境分配的专有实例。
-
安卓系统中的Dalvik的Java虚拟机被修改为支持。多个虚拟机高效运行在同一设备上。
-
Dalvik的Java虚拟机执行的是Dalvik格式的可执行文件.dex。该格式经过优化,将内存耗用降到了最低。
-
Java编译器将Java的源文件转化为class文件。class文件又被内置的dx工具转化为dex格式文件。这种文件在Dalvik虚拟机上注册并运行。
-
安卓系统的应用软件都是运行在Dalvik上的Java软件,而dalvik可是运行在Linux中的。
- 在一些底层功能,例如线程和低内存管理方面到位,可虚拟机是依赖于Linux内核的。
-
可以认为,安卓是运行在Linux内核之上的操作系统。
安卓系统是为移动设备设计和实现的系统,尽管安卓系统采用了Linux内核作为操作系统的最基本内核,但仍然与Linux内核有较多的差别。
-
内核功能裁剪
- 安卓系统:安卓中的Linux内核是经过裁剪的。由于安卓设备主要是移动设备,如智能手机和平板电脑,其硬件资源有限,并且功能需求相对集中。因此,在安卓的Linux内核中,一些在传统桌面Linux系统中可能用到的功能被移除。例如,对于服务器相关的高级网络服务功能(如复杂的网络文件系统支持)可能会被裁剪掉,因为安卓设备一般不作为文件服务器使用。同时,一些对移动设备不太重要的设备驱动也会被精简,以减小内核体积,节省存储空间和内存资源。
- 传统Linux系统:传统Linux内核功能更全面,旨在支持各种各样的应用场景。它不仅要支持桌面计算机的功能,如多用户登录、复杂的图形界面系统、大量的外部设备连接(包括各种专业设备如打印机、扫描仪等),还要满足服务器的需求,如提供各种网络服务(Web服务、邮件服务、文件共享服务等)。所以,传统Linux内核包含了完整的设备驱动集、网络协议栈和文件系统支持,以适应不同用户在不同环境下的使用。
-
电源管理方面
- 安卓系统:安卓系统的Linux内核针对移动设备的电池供电特点进行了电源管理方面的优化。它具有专门的电源管理模块,用于动态调整硬件设备的电源状态。例如,当屏幕关闭一段时间后,内核可以自动降低CPU的频率,关闭一些不必要的硬件模块(如Wi - Fi、蓝牙等,在未使用时),以延长电池续航时间。同时,安卓系统还可以根据应用程序的使用情况来管理电源,比如当某个应用在后台运行但没有进行重要任务时,内核会限制其对硬件资源的使用,从而降低功耗。
- 传统Linux系统:传统Linux内核虽然也有电源管理功能,但重点更多地放在桌面计算机或服务器的电源管理上。在桌面环境中,电源管理主要关注的是系统在不同电源模式(如睡眠、待机、关机)之间的切换,以及对硬件设备的简单节能措施(如硬盘在空闲一定时间后自动停止旋转)。在服务器环境中,电源管理相对简单,因为服务器通常是一直插电运行的,主要考虑的是硬件设备的散热和整体能耗效率,而不是像安卓设备那样对电池续航的精细管理。
-
硬件抽象层(HAL)与设备驱动集成
- 安卓系统:安卓系统在Linux内核之上引入了硬件抽象层(HAL)。HAL的作用是在Linux内核的设备驱动和安卓的上层应用框架之间提供一个中间层。这样做的好处是,当硬件发生变化或者需要更新设备驱动时,只需要修改HAL和驱动的接口部分,而不会影响到上层的应用程序。例如,对于摄像头硬件的升级,只要新的摄像头驱动和HAL之间的接口保持一致,应用程序就可以继续正常使用摄像头功能,无需重新编写。
- 传统Linux系统:传统Linux系统通常直接通过设备驱动与上层软件进行交互。当硬件更新或者驱动需要修改时,可能会影响到上层的应用程序,特别是那些直接与硬件设备打交道的软件。例如,在Linux系统中,如果更新了声卡驱动,一些音频播放软件可能会因为驱动接口的变化而出现兼容性问题,需要重新进行适配。
-
安全机制侧重点
- 安卓系统:安卓系统中的Linux内核安全机制更侧重于保护用户的隐私和应用程序的隔离。它采用了多种安全措施,如应用沙盒(App Sandbox)机制,每个安卓应用在安装后都运行在自己独立的沙盒环境中,不能随意访问其他应用的资源和数据。同时,安卓系统的Linux内核在权限管理方面也更加精细,用户可以在安装应用时或者在系统设置中对应用的各种权限(如访问摄像头、麦克风、通讯录等)进行管理。
- 传统Linux系统:传统Linux系统的安全机制主要基于用户和用户组的权限管理。不同的用户具有不同的权限,以访问不同的文件和执行不同的命令。例如,普通用户可能无法修改系统的关键文件和执行一些需要管理员权限的命令,而管理员(root)用户则拥有最高权限。虽然传统Linux系统也注重安全,但在应用程序隔离和隐私保护方面的机制不像安卓系统那样专门针对移动应用进行精细设计。
-
启动流程和系统初始化
- 安卓系统:安卓系统的启动流程相对复杂,除了Linux内核的启动阶段外,还包括安卓特有的初始化过程。在启动过程中,首先是引导加载程序(Bootloader)加载Linux内核,然后内核启动后会挂载根文件系统,并启动安卓的init进程。init进程会负责启动一系列安卓系统服务,如Zygote进程(用于孵化应用程序进程)、SurfaceFlinger(用于管理图形显示)等,这些服务协同工作来建立起安卓的运行环境。
- 传统Linux系统:传统Linux系统的启动主要是围绕初始化硬件设备、挂载文件系统、启动系统服务(如网络服务、登录服务等)和启动用户登录界面等。它的启动过程相对简单直接,重点是为用户提供一个可以登录并使用的桌面或者服务器环境,没有安卓系统中那么多针对移动应用的复杂初始化环节。
-
安卓系统中采用了安卓Binder
- 安卓Binder是基于openBinder的框架的驱动。主要用于安卓应用程序之间以及应用程序与系统服务之间的通信。
- 在安卓的架构中,为了实现组件化开发,不同的应用程序(如一个相机应用和一个图像编辑应用)或者应用程序与系统服务(如应用与电量管理服务)需要进行高效、安全的通信,Android Binder 就是为此而设计的。
-
安卓系统中的存储管理进行了较多的优化。
- 安卓系统将应用数据存储划分为多个分区,如内部存储(Internal Storage)和外部存储(External Storage)。
- 内部存储是应用的私有存储区域,其他应用通常无法访问,这为应用数据提供了安全性。例如,应用的数据库文件、配置文件等重要数据一般存储在内部存储。
- 对于内部存储,安卓还进一步细分了缓存(Cache)和数据(Data)区域。
- 缓存区域用于存储临时数据,如网络请求的缓存结果,这些数据可以在需要时被快速访问,并且系统可以根据存储空间的紧张程度自动清理缓存。
- 数据区域则用于存储应用的持久化数据,如用户设置、游戏进度等,这些数据除非用户手动删除或者应用被卸载,否则会一直保存。
- 外部存储则可以用于存储一些共享的、大容量的数据,如照片、视频等。这种分区规划有助于合理利用存储空间,并且方便用户对数据进行管理。
- 安卓系统会对应用的数据存储大小进行限制。
- 当应用的数据存储超过一定限额时,系统会提醒用户清理数据或者自动采取一些清理措施。
- 例如,对于一些长期未使用的应用,系统可能会自动清理其缓存数据,以释放存储空间。
- 同时,应用开发者也可以在应用中实现自己的数据清理功能,如在应用设置中提供 “清除缓存” 选项,方便用户手动清理。
-
安卓系统中采用了 yaffs2作为MTD NAND flash文件系统。
- yaffs2是一个快速稳定的应用于n的flash的跨平台的嵌入式设备文件系统。
- 从其他flash文件系统相比,yaffs2能使用更小的内存来保存其运行状态,因此占用内存小。
- Yaffs2的垃圾回收非常简单而迅速,因此能达到更好的性能,非常适合大容量的flash存储。
-
安卓系统还提供了很多机制来进行系统的优化,或者为系统提供新的功能。
- 安卓Logger 是一个轻量级的日志设备,能用于抓取安卓系统的各种日志。
- 安卓alarm提供了一个定时器,用于把设备从睡眠状态唤醒。
- 安卓usb gadget驱动是一个基于标准Linux usb gadget驱动框架的设备驱动。
- 用于调试功能的安卓的ram console允许将调试日志信息写入一个被称为ram console的设备中。
9.4 系统移植的概念和驱动开发的方法
嵌入式操作系统与桌面操作系统以及服务器操作系统最显著的区别就是它的可移植性。
安卓系统的移植是为了在特定的硬件平台上运行安卓系统。
安卓中具有很多组件,但并不是每一个部件都需要移植。一些纯软的组件就没有移植的必要。
- 例如浏览器引擎虽然需要下层网络的支撑,但是并非直接为其移植网络接口。而是通过无线局域网或者电话系统数据连接来完成标准网络接口的移植。
- 安卓系统的移植并不需要精通安卓的每一个部分。需要考虑的仅仅是安卓系统的硬件抽象层HAL和Linux中的相关设备驱动程序。
安卓系统硬件抽象层运行于用户空间,介于驱动程序和安卓系统之间。
-
驱动程序是硬件和上层软件的接口,Linux的驱动运行在内核空间。在安卓手机系统中需要的机的屏幕、触摸屏、键盘、以及音频摄像头、电话、WiFi、蓝牙等多种设备的驱动程序。
-
具有了特定的硬件系统之后,通常在Linux中需要实现其他的驱动程序。这些驱动程序通常是Linux的标准驱动程序在安卓平台和其他的Linux平台上的功能基本相同。
-
安卓的移植主要可以分成几个类型:基本图形用户界面就是GUI的部分;硬件加速部分包括媒体编解码和openGL;音视频输入输出环节,包括音频、视频输出和摄像头部分;连接部分包括无线局域网、蓝牙和gps。
10 Android系统开发环境
10.1 交叉开发环境
10.1.1 交叉开发环境概述
移动设备本质上来说也是嵌入式系统的一种。移动设备主要是通过电池进行供电,因此在设计时需要考虑传统桌面系统。不同的用户交互设计能耗和重量问题。
不能直接在移动设备上进行软件开发,需要采用基于宿主机-目标机开发环境的交叉开发模式。
交叉开发模式是嵌入式系统领域常用的开发模式,其本质是在一台设备上进行开发和调试开发出来的软件在另一台设备上存储和运行。
- 宿主机就是我们的计算机。
- 目标机就是我们最终的运行设备。
我们要做的就是基于宿主目标机的开发环境,在宿主机上使用既有的工具,面向目标机上有限的硬件资源,为目标机定制软件系统。
- 在交叉环境下开发的时候,需要借助宿主机的编译环境。
- 由于不同架构的处理器有不同的指令集,因此不同架构的处理器需要对应不同的编译器。
- 相对于交叉编译,在本机编译出能在同样架构计算机和操作系统上运行的编译称为本地编译。
- 而交叉编译就是在一种处理器架构的计算机下编译成另外一种处理器架构的目标文件。
10.1.2 宿主机与目标机的连接
宿主主机和目标机的连接方式有4种,分别是串口、以太网接口、usb接口和JTAG接口。
-
大多数的操作系统中,串口可以作为终端使用。宿主机可以利用串口给目标机发送命令,同时也可以接收目标及返回的信息并显示。
-
以太网是当今局域网采用的最通用的通信协议标准。基本上这个传输速率都很快,但就是网络驱动的实现比较复杂。
-
Usb现在已经成为了桌面计算机的通用接口,很多基于usb标准的设备被广泛应用。它是一种快速灵活的总线接口,与其他通信接口相比,比较简单用。
- 另外,usb还支持热插拔,同时无需要无需用户自己配置,系统会自动搜索程序并安装。
-
JTAG 是一种国际标准测试协议 (ieee 1149.1兼容),主要用于芯片内部测试及对系统进行仿真调试。
- 几乎所有的处理器都支持JTAG,调试器的单步调试和断点都需要与基JTAG交互。另外还可以通过JTAG将程序写到目标机上。
10.1.3 宿主机环境
由于目标机的实际操作系统不提供编译器或者开发环境不完整,甚至没有操作系统,通常采用交叉编译的方式产生目标代码。
宿主机所使用的主流操作系统包含种类丰富的开发工具,在这些操作系统中,Linux操作系统是使用最广泛的操作系统之一
- 有大量开源开放的软件可供使用。Shell、glibc、gcc和gdb等。
- 还有许多功能强大的编译工具,如Vim、Emacs和gedit等。
Linux操作系统成为交叉开发模式向宿主端的主要操作系统平台。
- 此外,目标机需要通过通信接口向宿主机提出请求,如ip地址分配和文件传输等。就需要做主机提供相应的服务,如DHCP和TFTP等。
串口终端
- 作为一种宿主机与目标机之间的连接方式,串口并不适用于传输大量数据的场合,更多是作为终端来使用。
- 窗口终端主要用来控制和管理嵌入式系统,如管理bootloader、输入命令等。
- 串口终端的使用非常广泛,因此很多操作系统都已经集成串口终端工具,如windows的超级终端和Linux的minicom。
- Windows的超级终端是有gui的。
- Linux的minicom采用的是命令行用户界面。主要的优点是占用的系统资源少,而且操作方便。
BOOTP
-
- BOOTP(Bootstrap Protocol)概述
- BOOTP是一种基于UDP(用户数据报协议)的网络协议,主要用于无盘工作站或刚启动的网络设备从服务器获取IP地址等网络配置信息。
- 它在网络启动过程中扮演着关键的角色,使设备能够在没有预先配置IP地址的情况下接入网络并获取必要的配置参数,从而正常启动并与其他网络设备通信。
- BOOTP最初是在RFC951中定义。使用的是 UDP67/68两个通信端口。
-
工作原理
- 请求阶段
- 当一个无盘工作站或新启动的网络设备(如某些网络打印机、网络摄像头等)启动时,它会在本地网络中广播一个BOOTP请求消息。这个请求消息包含了设备的硬件地址(如MAC地址),因为此时设备还没有IP地址,只能通过硬件地址来唯一标识自己。广播的目的是让网络中的BOOTP服务器能够接收到这个请求。
- 目标机的BOOTP是由Bootloader启动的,此时他广播的ip地址是0.0.0.0。
- 例如,在一个企业局域网中,一台新连接的无盘工作站启动后,它会在子网内发送BOOTP请求,这个请求会被发送到子网内的所有设备,但只有配置为BOOTP服务器的设备会对这个请求进行处理。
- 当一个无盘工作站或新启动的网络设备(如某些网络打印机、网络摄像头等)启动时,它会在本地网络中广播一个BOOTP请求消息。这个请求消息包含了设备的硬件地址(如MAC地址),因为此时设备还没有IP地址,只能通过硬件地址来唯一标识自己。广播的目的是让网络中的BOOTP服务器能够接收到这个请求。
- 响应阶段
- BOOTP服务器接收到请求后,会根据请求消息中的硬件地址来查找对应的网络配置信息。这些配置信息通常是预先配置在服务器中的,包括IP地址、子网掩码、默认网关、DNS服务器地址等。
- 然后,BOOTP服务器会向发出请求的设备发送一个BOOTP响应消息。这个响应消息包含了设备所需的网络配置信息。设备收到响应后,就可以使用这些配置信息来配置自己的网络接口,从而能够在网络中正常通信。例如,服务器会将分配的IP地址和其他网络参数发送给无盘工作站,工作站就可以利用这些参数来完成网络初始化。
- 请求阶段
-
与DHCP(动态主机配置协议)的关系
- BOOTP可以看作是DHCP的前身。DHCP在BOOTP的基础上进行了扩展和改进。两者都用于网络设备的IP配置,但DHCP具有更多的功能,如动态分配IP地址(可以在一定时间内租用IP地址)、支持更多的配置选项、能够自动更新IP地址租约等。
- 不过,BOOTP仍然在一些特定的场景下使用。例如,在一些对网络配置稳定性要求较高、不希望IP地址频繁变化的网络环境中,或者在一些简单的网络设备启动配置场景中,BOOTP的简单性和确定性(设备每次启动可能获得相同的IP地址)可能更具优势。
-
BOOTP消息格式
- BOOTP消息是通过UDP数据包来传输的,其消息格式相对固定。消息分为多个字段,包括操作码(用于区分请求和响应)、硬件类型(如以太网类型)、硬件地址长度(如MAC地址长度为6字节)、事务标识符(用于匹配请求和响应)、秒数(表示自客户端开始尝试获取配置信息以来经过的秒数)等。
- 消息的数据部分包含了服务器返回的具体配置信息,如IP地址、子网掩码等。这些字段的定义和格式使得BOOTP能够准确地传递网络配置信息,并且不同的网络设备和服务器能够按照统一的标准来进行通信。
-
应用场景
- 无盘工作站环境:在无盘工作站网络中,BOOTP是必不可少的。因为无盘工作站没有本地磁盘来存储操作系统和网络配置信息,它需要通过BOOTP从服务器获取IP地址等配置,然后从服务器下载操作系统和其他应用程序来启动运行。
- 简单网络设备配置:对于一些简单的网络设备,如早期的网络打印机、网络摄像头等,它们可能没有复杂的网络配置界面,通过BOOTP可以方便地从网络中获取配置信息,快速接入网络并开始工作。
TFTP协议
-
TFTP(Trivial File Transfer Protocol)概述
- TFTP是一种简单的文件传输协议,它基于UDP(用户数据报协议),主要用于在网络上进行文件的简单传输。与其他复杂的文件传输协议(如FTP)相比,TFTP具有简单、轻量级的特点,适用于资源有限的设备和简单的文件传输场景。
-
协议特点
- 简单性
- TFTP的设计非常简洁,它只定义了几种基本的操作类型,包括文件读取(RRQ,Read Request)、文件写入(WRQ,Write Request)、数据传输(DATA)、确认(ACK)和错误(ERROR)。这种简单的操作集使得TFTP易于实现和理解。例如,在一个小型的嵌入式系统中,开发人员可以相对容易地在设备上实现TFTP客户端或服务器功能,用于更新系统固件或传输配置文件。
- 它的消息格式也比较简单,没有像FTP那样复杂的用户认证、目录浏览等功能。TFTP消息通常由操作码、文件名、模式(如ASCII或二进制模式)和数据块组成。这种简单的格式减少了协议的开销,使得文件传输能够快速进行。
- 基于UDP
- TFTP运行在UDP之上,这使得它与基于TCP(传输控制协议)的文件传输协议有所不同。UDP是一种无连接的协议,不像TCP那样提供可靠的连接建立、数据确认和重传机制。因此,TFTP自身需要处理一些可靠性问题。例如,TFTP通过在每个数据块传输后等待接收方的确认消息(ACK)来确保数据的正确传输,如果在一定时间内没有收到确认,发送方会重新发送数据块。
- 无用户认证和有限的安全性
- TFTP通常没有用户认证机制,这意味着任何能够访问TFTP服务器端口(默认是69端口)的客户端都可以尝试进行文件传输。这种设计虽然方便了简单的文件传输操作,但也带来了安全风险。例如,如果TFTP服务器暴露在不安全的网络环境中,可能会被恶意用户滥用,用于非法获取或篡改文件。
- 简单性
-
工作模式和操作流程
- 文件读取模式(RRQ)
- 当客户端想要从服务器读取一个文件时,它会向服务器发送一个RRQ消息。这个消息包含文件名和文件传输模式(如ASCII或二进制)。服务器收到RRQ消息后,如果文件存在且有读取权限,就会开始发送文件数据。服务器将文件数据分成一个个固定大小的数据块(通常是512字节),并为每个数据块编号。
- 客户端收到每个数据块后,会向服务器发送一个ACK消息,确认收到的数据块编号。这样,通过ACK和数据块的交互,文件数据从服务器逐步传输到客户端。如果客户端没有收到某个数据块的ACK,服务器会根据超时机制重新发送该数据块。
- 文件写入模式(WRQ)
- 在文件写入模式下,客户端首先向服务器发送一个WRQ消息,包含文件名和传输模式。服务器收到WRQ后,如果允许文件写入,会发送一个ACK消息,表示可以开始接收文件数据。然后客户端将文件数据分成数据块发送给服务器,每个数据块发送后等待服务器的ACK。服务器收到数据块后,会检查数据的正确性,并发送ACK确认。如果数据块有问题,服务器可能会发送ERROR消息,要求客户端重新发送数据块。
- 文件读取模式(RRQ)
-
应用场景
- 嵌入式系统和网络设备固件升级:在许多嵌入式系统和网络设备(如路由器、交换机)中,TFTP被广泛用于固件升级。设备可以通过TFTP从服务器获取新的固件文件,进行系统更新。这种方式简单高效,不需要复杂的用户认证和文件系统操作,适用于资源有限的设备。
- 小型网络中的文件共享和配置备份:在小型的局域网环境中,TFTP可以用于简单的文件共享。例如,将一些常用的配置文件(如网络设备的配置备份)存储在TFTP服务器上,方便网络管理员在需要时进行访问和恢复。不过,由于其安全性较低,这种应用场景通常在相对安全的内部网络中使用。
TFTP协议与FTP协议的区别
-
协议基础与连接方式
- TFTP(Trivial File Transfer Protocol)
- 基于UDP协议:TFTP是基于UDP(用户数据报协议)的简单文件传输协议。UDP是一种无连接的协议,它不提供像TCP(传输控制协议)那样复杂的连接建立、维护和拆除机制。这使得TFTP在传输文件时相对简单快速,但也意味着它需要自己处理数据传输的可靠性问题。例如,在TFTP中,如果一个数据块丢失,发送方需要通过超时重传机制来重新发送数据块,因为UDP本身不会对丢失的数据进行重传。
- 简单的连接模式:TFTP采用简单的请求 - 响应模式进行文件传输,没有用户权限管理的功能。当客户端想要进行文件传输时,它直接向服务器发送请求,不需要像FTP那样先建立复杂的控制连接和数据连接。这种模式使得TFTP在轻量级的文件传输场景中非常高效,如在小型的嵌入式系统或简单的网络设备之间进行文件传输。
- FTP(File Transfer Protocol)
- 基于TCP协议:FTP是基于TCP的文件传输协议。TCP提供了可靠的、面向连接的通信服务。在文件传输前,FTP会先建立控制连接用于传输命令和响应,然后根据需要建立数据连接用于实际的文件传输。这种基于TCP的连接方式保证了文件传输的可靠性和顺序性,因为TCP会自动处理数据的丢失、重复和乱序等问题。例如,在网络状况不稳定的情况下,TCP会通过重传机制确保数据的完整性。
- 复杂的双连接机制:FTP使用两个独立的连接,即控制连接和数据连接。控制连接用于在客户端和服务器之间传递命令(如登录、文件目录操作、文件传输模式选择等)和响应,数据连接用于传输文件数据。这种双连接机制使得FTP能够灵活地处理各种文件传输情况,但也增加了协议的复杂性和资源消耗。
- TFTP(Trivial File Transfer Protocol)
-
功能特性与复杂性
- TFTP
- 功能简单:TFTP的功能相对较少,主要专注于基本的文件传输操作,即文件的读取(RRQ)和写入(WRQ)。它没有像FTP那样丰富的用户认证、文件目录浏览和管理等功能。TFTP的消息格式也比较简单,主要包括操作码、文件名、模式(如ASCII或二进制)和数据块等部分。例如,在TFTP中,客户端不能像在FTP中那样方便地浏览服务器上的文件目录,只能通过事先知道文件名来请求文件。
- 易于实现和使用:由于其简单性,TFTP易于在资源有限的设备上实现。对于小型的嵌入式系统或简单的网络设备,开发人员可以相对容易地编写TFTP客户端或服务器代码,用于设备的固件升级或简单的文件传输。同时,TFTP的使用也很简单,不需要用户了解复杂的命令和参数,只需要发送基本的文件读取或写入请求即可。
- FTP
- 功能丰富:FTP提供了多种功能,包括用户认证(支持用户名和密码登录)、文件目录操作(如列出目录内容、切换目录、创建和删除目录等)、文件传输模式选择(ASCII模式用于文本文件传输,二进制模式用于非文本文件传输)等。这些功能使得FTP能够满足复杂的文件管理和传输需求,如在大型的文件服务器和客户端之间进行文件存储和共享。
- 复杂的命令和参数:FTP使用一套复杂的命令和参数来实现各种功能。例如,用户需要使用“USER”命令输入用户名,“PASS”命令输入密码进行登录,使用“LIST”命令列出服务器上的文件目录内容等。这种复杂性要求用户或开发人员熟悉FTP的命令语法,同时也增加了客户端和服务器软件的开发难度。
- TFTP
-
安全性与应用场景
- TFTP
- 安全性较低:TFTP通常没有用户认证机制,这意味着任何能够访问TFTP服务器端口(默认是69端口)的客户端都可以尝试进行文件传输。这种设计虽然方便了简单的文件传输操作,但也带来了安全风险。例如,如果TFTP服务器暴露在不安全的网络环境中,可能会被恶意用户滥用,用于非法获取或篡改文件。
- 应用场景受限:由于安全性和功能的限制,TFTP主要应用于内部网络中的简单文件传输和设备固件升级。例如,在一个小型的局域网中,TFTP可以用于将配置文件从服务器传输到网络设备,或者在嵌入式系统中用于更新固件。但在需要安全的用户认证和复杂文件管理的场景下,TFTP不太适用。
- FTP
- 安全性相对较高(可配置):FTP支持用户认证,通过用户名和密码的方式可以对用户进行身份验证,限制文件访问权限。此外,还可以通过配置FTP服务器,使用SSL/TLS(安全套接字层/传输层安全)协议对传输的数据进行加密,增强数据传输的安全性。这种安全性措施使得FTP可以在需要保护数据隐私和访问权限的场景中使用。
- 广泛的应用场景:FTP的功能丰富和相对较高的安全性使其在各种网络环境中有广泛的应用。它可以用于大型文件服务器与客户端之间的文件共享、网站的文件上传和下载(如通过FTP客户端管理网站的文件)、企业内部的文件存储和管理等场景。
- TFTP
交叉编译工具链
交叉编译工具链主要包含标准库编译器,连接器,汇编器和调试器。
可以在x86平台上编译出能够在arm平台上运行的程序。比如在ARM平台下选择arm Linux gcc。
通常交叉编译工具链的构件有以下三种方法由易到难分别为下载使用、脚本编译和从头编译。
通常交叉编译工具链的构建有以下三种方法:
1. 下载并使用预构建的交叉编译工具链
- 优点:
- 简单便捷:这是最方便快捷的方式。对于许多常见的目标平台和处理器架构,都可以找到已经构建好的工具链。例如,针对ARM架构的嵌入式开发,有许多官方或者社区提供的预构建工具链。使用者只需要从相关网站下载合适的版本,解压后配置好环境变量,就可以直接使用。
- 节省时间:不需要自己进行繁琐的编译过程,能够快速投入开发工作,尤其适合初学者或者项目时间紧张的情况。
- 缺点:
- 灵活性有限:预构建的工具链可能无法满足一些特殊的需求,例如对工具链版本、编译选项、支持的库有特殊要求时,可能无法通过简单的配置来实现。
- 可能存在兼容性问题:有时候下载的工具链与开发主机或者目标设备的系统版本、硬件细节等可能存在兼容性问题,导致工具链无法正常工作或者出现一些难以调试的错误。
2. 使用交叉编译工具链构建脚本(如crosstool - ng)
- 优点:
- 高度定制化:像crosstool - ng这样的工具允许用户根据自己的具体需求来定制工具链。用户可以选择特定的编译器版本(如GCC的某个特定版本)、目标架构(如ARMv7 - a、MIPS等)、C库(如uClibc、glibc等)以及其他相关的工具和库。
- 自动化构建过程:它通过脚本的方式自动化了工具链构建的大部分流程,减少了人工操作失误的可能性。用户只需要按照自己的需求配置好相应的参数,脚本就会自动下载源代码、进行编译和安装。
- 方便更新和维护:当需要更新工具链的某个组件或者调整配置时,使用构建脚本相对比较容易操作。只需要修改相关的配置参数,然后重新运行构建脚本即可。
- 缺点:
- 学习成本:需要学习和掌握构建脚本(如crosstool - ng)的使用方法和配置参数,对于初学者来说可能有一定的难度。
- 依赖环境较多:构建脚本在运行过程中可能依赖于主机系统的一些软件包、库和环境变量。如果主机环境配置不当,可能会导致构建过程失败。
3. 手动构建交叉编译工具链
- 优点:
- 完全控制构建过程:开发人员对工具链的每一个组件的来源、版本、编译选项等都有绝对的控制权。可以根据项目的特殊需求,如对安全性、性能、体积等方面的要求,精细地调整工具链的构建。
- 深入理解工具链构建原理:通过手动构建,能够深入了解交叉编译工具链各个组件之间的关系、编译过程中涉及的各种参数和环境变量,有助于解决在开发过程中遇到的一些深层次的工具链相关问题。
- 缺点:
- 构建过程复杂且耗时:需要手动下载各个工具链组件的源代码,如编译器(GCC)、二进制工具(binutils)、C库(如glibc)等,并且要按照正确的顺序和配置进行编译和安装。这个过程非常繁琐,容易出现错误,而且可能会花费大量的时间。
- 需要丰富的知识和经验:要求开发人员对编译器、操作系统、硬件架构等多个领域有深入的了解,包括但不限于编译原理、链接过程、库的安装和配置等知识,否则很难成功构建一个可用的工具链。
10.1.4 目标机环境
目击标机环境目前有两个重要的部分,分别是JTAG接口和bootloader。
- JTAG最初用于对芯片进行测试。基本原理就是在器件内部定义一个测试访问接口(TAP test access port),通过专用的JTAG测试工具对内部节点进行测试。
- JTAG测试允许多个器件通过机JTAG接口串联在一起,形成一个JTAG链。能实现对各个器件的分别测试。
- 后来JTAG接口还常用于实现在线编程,也就是isp。
- 标准的JTAG接口有四线: TMS、TCK、TDI、TDO,分别是模式选择线、时钟线、数据输入线和数据输出线。现在常用的JTAG接口有三种标准,即10针、14针和20针。
BootLoader:
- BootLoader是系统加电后操作系统内核运行之前执行的一段小程序。
- 这段程序可以进行硬件设备的初始化建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便最终调用操作系统内核准备好正确的环境。
- 在桌面计算机中起到类似作用的引导代码一般。由bios和位于MBR的操作系统引导程序组成。
- 通常来说,bootloader是严重依赖于硬件环境的,建立一个通用的bootloader几乎是不可能的,每种不同的cpu体系都有不同的bootloader。
- 对于两块不同的嵌入式板,即使是基于同一种cpu建立的bootloader程序在移植过程中也需要修改源代码,才能运行在一块板子上。
10.2 Linux操作系统及其开发工具。
10.2.1 Linux操作系统及其概述
操作系统是计算机必不可少的组成部分。操作系统的功能用一句话来说就是管理与控制计算机资源的软件。
- 目前比较流行的操作系统包括UNIX系统、类UNIX的Linux系统、Windows系统,以及一些嵌入式的操作系统。
Linux是一类UNIX系统的统称。最初由Linus Torvalds带头开发,并由全世界众多爱好者共同维护的操作系统。
- Linux的设计源于UNIX,实现了UNIX操作系统的API。
- 与其它的类UNIX操作系统不同, Linux系统并不是直接修改UNIX系统源代码而来的,而是对Unix系统的重新实现。
- 严格来说,Linux只是一个操作系统内核,所以通常说的不同版本的Linux是指由JNU软件和Linux内核构成的完整的操作系统。
Linux操作系统具有许多优秀的特性,以下是详细介绍:
1. 开放性和自由性
- 开源本质:Linux操作系统的核心代码是开源的,这意味着任何人都可以查看、修改和分发代码。这种开放性促进了全球范围内的开发者共同参与改进系统,使得Linux能够快速吸收各种优秀的技术和想法。例如,Linux内核的开发是一个全球性的协作项目,有众多开发者提交代码补丁、修复漏洞和添加新功能。
- 自由定制:用户可以根据自己的需求自由定制Linux系统。无论是用于服务器、桌面还是嵌入式设备,都可以对系统进行裁剪和配置。比如,在构建一个嵌入式Linux系统时,可以选择只包含特定的设备驱动、应用程序和库,以减少系统体积并满足硬件资源有限的要求。
2. 高度的稳定性和可靠性
- 系统健壮性:Linux系统设计之初就注重稳定性,能够长时间稳定运行而不出现故障。在服务器领域,许多重要的服务器(如Web服务器、数据库服务器等)都运行Linux操作系统,它们可以连续运行数月甚至数年而无需重启。例如,大型互联网公司的数据中心采用Linux服务器来处理海量的用户请求,这些服务器能够承受高负载和复杂的网络环境。
- 错误处理能力:Linux具有强大的错误处理和恢复机制。当系统遇到软件错误或者硬件故障时,能够采取相应的措施来保护数据和系统的正常运行。例如,在文件系统出现错误时,Linux的文件系统检查工具(如e2fsck)可以在系统启动时自动检测并修复文件系统中的问题。
3. 多用户和多任务处理能力
- 多用户支持:Linux系统可以同时支持多个用户登录和使用。每个用户都有自己的账户和权限,可以独立地运行程序和访问资源。这种特性在服务器环境中非常重要,多个系统管理员或者用户可以同时远程登录到服务器进行操作。例如,在一个大学的计算中心服务器上,不同的教师和学生可以使用自己的账户登录,进行教学、科研或者学习相关的操作。
- 多任务处理:Linux能够高效地同时运行多个任务。它通过进程管理和调度机制,合理分配CPU时间和系统资源给各个任务。例如,在一个桌面Linux系统中,用户可以同时打开多个应用程序,如浏览器、文本编辑器、音乐播放器等,这些程序可以同时运行而不会互相干扰,并且系统能够根据任务的优先级和资源需求进行动态调整。
4. 强大的网络功能
- 网络协议支持:Linux支持几乎所有的网络协议,包括TCP/IP、UDP、HTTP、FTP、SMTP等常见协议,以及一些特殊用途的协议。这使得Linux系统可以方便地构建各种网络应用,从简单的文件共享服务器到复杂的云计算平台。例如,使用Linux搭建的邮件服务器可以通过SMTP、POP3和IMAP协议来发送、接收和管理电子邮件。
- 网络服务性能:Linux在网络服务方面表现出色,能够高效地处理大量的网络连接和数据传输。许多高性能的网络服务器软件(如Apache、Nginx等)最初都是为Linux系统开发的,并且在Linux上能够发挥出最佳性能。例如,Nginx在Linux服务器上可以快速地处理大量的HTTP请求,实现高性能的Web服务。
5. 安全性能高
- 用户权限管理:Linux采用了严格的用户权限管理机制。系统中的每个文件和目录都有所有者、所属组和其他用户的权限设置,包括读、写、执行三种权限。这种权限管理可以有效地防止用户对系统文件和其他用户文件的非法访问。例如,普通用户无法修改系统关键文件,除非获得管理员权限。
- 安全更新机制:Linux发行版通常有完善的安全更新机制。一旦发现安全漏洞,发行商和社区会及时发布安全补丁,用户可以通过系统更新工具方便地安装这些补丁。例如,Debian和Ubuntu等发行版会定期推送安全更新,用户可以使用apt - get或者图形化的更新管理器来更新系统,提高系统的安全性。
6. 丰富的软件生态系统
- 软件包管理系统:Linux拥有多种强大的软件包管理系统,如Debian/Ubuntu使用的apt,Red Hat/CentOS使用的yum等。这些软件包管理系统可以方便地安装、更新和卸载软件。例如,在Ubuntu系统中,用户只需要使用“sudo apt - get install [软件名]”命令就可以轻松安装所需软件。
- 软件多样性:涵盖了从服务器软件(如数据库管理系统、邮件服务器)到桌面应用(如办公软件、图形编辑软件)以及开发工具(如编译器、集成开发环境)等各个领域的软件。无论是用于企业级的服务器部署,还是个人的日常办公和开发,都能找到合适的软件。例如,对于开发者来说,Linux下有丰富的编程语言环境,如Python、Java、C/C++等的开发工具和库。
10.2.2 Linux操作系统核心与驱动程序
Linux的内核从逻辑上可以分为进程调度、进程间通讯、内存管理、虚拟文件系统和网络5个部分。
-
进程调度
- 基本概念:在Linux系统中,进程调度是内核的关键部分,它负责决定哪个进程在什么时候使用CPU资源。Linux是一个多任务操作系统,多个进程同时存在,而CPU在某一时刻只能执行一个进程的指令,因此需要合理的调度机制。例如,当你在Linux系统中同时打开浏览器、文本编辑器和音乐播放器,进程调度器会决定这些进程在CPU上的执行顺序和时间分配。
- 调度策略:Linux采用多种调度策略,如完全公平调度(CFS - Completely Fair Scheduler)。CFS的目标是公平地分配CPU时间给每个进程。它通过虚拟运行时间(vruntime)来衡量进程应该获得的CPU时间。每个进程都有一个虚拟运行时间,这个时间是根据进程的实际运行时间和优先级等因素动态计算的。优先级高的进程会有相对较小的vruntime增长速度,这样它们会更频繁地获得CPU时间,但CFS也会保证其他进程不会被“饿死”,即每个进程都能在一定时间内获得CPU资源。
- 调度时机:进程调度主要发生在进程状态转换的时候,如进程从运行态转为等待态(例如等待I/O操作完成),或者新进程被创建、唤醒时。当一个进程由于等待I/O(如等待从硬盘读取数据)而阻塞时,调度器会选择另一个就绪进程来运行,从而提高CPU的利用率。
-
进程间通讯(IPC)
- 管道(Pipe)
- 定义与用途:管道是Linux中最基本的进程间通信机制之一,它提供了一个单向的数据通道,用于具有亲缘关系(如父子进程)的进程之间传输数据。例如,在命令行中使用“ls | grep test”,“|”就是管道的操作符,这里“ls”命令的输出通过管道作为“grep test”命令的输入,实现了两个进程之间的数据传递。
- 实现方式:管道在系统内部是通过内核缓冲区来实现的。当一个进程向管道写入数据时,数据被存储在这个缓冲区中,另一个进程可以从缓冲区读取数据。管道的大小通常是有限制的,当缓冲区满时,写入进程会被阻塞,直到有足够的空间可以写入;当缓冲区为空时,读取进程会被阻塞,直到有数据可读。
- 消息队列(Message Queue)
- 定义与用途:消息队列是一种进程间通信机制,它允许不同进程通过发送和接收消息来进行通信。消息队列的优势在于它不像管道那样是一次性的通信方式,而且可以在多个不相关的进程之间使用。例如,在一个分布式系统中,不同的服务器进程可以通过消息队列来交换任务请求和执行结果。
- 实现方式:消息队列在内核中有一个消息队列标识符,进程通过这个标识符来访问消息队列。消息被发送到队列中,每个消息有自己的类型标识。接收进程可以根据消息类型有选择地接收消息,这样可以实现消息的分类处理。
- 共享内存(Shared Memory)
- 定义与用途:共享内存是一种高效的进程间通信方式,它允许多个进程共享同一块物理内存区域。由于进程可以直接访问共享内存区域,避免了数据在不同进程空间之间的多次复制,大大提高了通信效率。例如,在数据库系统中,多个进程可能需要同时访问数据库的缓存数据,通过共享内存可以快速地实现数据共享和更新。
- 实现方式:在Linux中,通过shmget、shmat等系统调用实现共享内存的操作。首先,通过shmget系统调用创建或获取共享内存段,然后使用shmat系统调用将共享内存段映射到进程的地址空间。多个进程可以通过相同的操作将共享内存映射到自己的地址空间,之后就可以对共享内存区域进行读写操作。为了防止多个进程同时访问共享内存区域导致数据不一致,通常需要配合使用信号量等同步机制。
- 信号量(Semaphore)
- 定义与用途:信号量主要用于进程间的同步和互斥,它是一个计数器,用于控制多个进程对共享资源的访问。例如,在多个进程同时访问一个文件或者一个共享内存区域时,信号量可以确保在同一时刻只有一个进程对资源进行操作,避免数据冲突。
- 实现方式:Linux提供了一组系统调用(如semget、semop等)来操作信号量。通过这些系统调用可以创建信号量集、对信号量进行加1或减1操作等。当一个进程要访问共享资源时,它会先对信号量进行减1操作,如果信号量的值大于等于0,则表示可以访问资源;如果信号量的值小于0,则表示资源正在被其他进程占用,该进程需要等待。当进程访问完资源后,会对信号量进行加1操作,以允许其他等待的进程访问资源。
- 管道(Pipe)
-
内存管理
- 物理内存管理
- 内存分配与回收:Linux内核负责将物理内存分配给各个进程。它把物理内存划分为固定大小的页面(通常是4KB),通过页表来记录页面的使用情况。当一个进程需要内存时,内核会从空闲的页面中分配给它。例如,当你启动一个新的应用程序,内核会根据程序的内存请求,从空闲页面列表中分配足够的页面给这个程序。当一个进程结束运行时,内核会回收该进程占用的页面,将这些页面重新标记为空闲状态,以便分配给其他进程。
- 内存映射(Memory Mapping):物理内存管理还涉及内存映射,这是一种将文件或设备的内容直接映射到内存中的机制。例如,在加载一个可执行文件时,内核会将文件的内容映射到物理内存的某个区域,这样进程就可以直接从内存中读取可执行文件的指令和数据,而不是通过文件系统的读取操作。这种方式可以提高程序的运行效率,特别是对于大型文件或频繁访问的文件。
- 虚拟内存管理
- 虚拟地址空间与物理地址空间的关系:每个进程在Linux系统中有自己独立的虚拟地址空间。虚拟地址空间比实际的物理地址空间大得多,它为进程提供了一个统一的、连续的内存视图。例如,在32位的Linux系统中,每个进程的虚拟地址空间大小为4GB。内核通过页表机制将虚拟地址转换为物理地址。当进程访问一个虚拟地址时,内核会根据页表中的映射关系找到对应的物理地址。如果访问的虚拟地址对应的物理页面不在内存中(例如,页面被交换到磁盘上的交换空间),会触发缺页中断,内核会从磁盘交换空间中将页面调入内存,然后继续执行进程。
- 页面置换算法(Page Replacement Algorithms):由于物理内存是有限的,当内存不足时,需要将一些页面置换到磁盘上的交换空间。Linux使用了多种页面置换算法,如最近最少使用(LRU - Least Recently Used)算法的变种。这种算法会选择最近一段时间内最少被访问的页面进行置换。通过这种方式,可以在内存资源有限的情况下,尽可能地保证系统的性能,使那些经常被访问的页面保留在内存中。
- 物理内存管理
-
虚拟文件系统(VFS)
- 文件系统抽象层:VFS是Linux内核中用于统一管理各种文件系统的抽象层。它隐藏了不同文件系统(如ext4、NTFS、FAT等)的具体细节,为用户和应用程序提供了一个统一的文件操作接口。例如,无论底层是本地的ext4文件系统还是通过网络挂载的CIFS文件系统,应用程序都可以使用相同的系统调用(如open、read、write等)来操作文件。VFS定义了一组通用的文件系统操作函数,如文件的打开、读取、写入、关闭等,所有支持的文件系统都需要实现这些函数,以适应VFS的接口。
- 文件系统挂载(Mount)和卸载(Unmount):在Linux中,文件系统需要挂载到系统的目录树上才能被访问。VFS负责管理文件系统的挂载和卸载过程。当你插入一个USB存储设备并将其挂载到系统中(例如挂载到“/media/usb”目录),VFS会识别设备上的文件系统类型(如FAT32或NTFS),并将其与挂载点相关联。这样,用户就可以通过挂载点目录访问设备上的文件。当你要拔出设备时,需要先卸载文件系统,以确保数据的完整性和设备的安全移除。
- 文件系统缓存(Cache)机制:VFS还包括文件系统缓存机制,用于提高文件访问的性能。当应用程序读取文件时,内核会将文件的内容缓存到内存中。如果后续还有对同一文件的访问,就可以直接从缓存中获取数据,而不需要再次从磁盘读取。这种缓存机制可以大大加快文件访问的速度,特别是对于频繁访问的文件。同时,内核也会根据一定的策略(如文件的访问频率、缓存的大小限制等)来管理缓存,当缓存空间不足时,会淘汰一些不常用的缓存数据。
-
网络部分
- 网络协议栈:Linux内核包含了完整的网络协议栈,从物理层协议(如以太网协议)到网络层协议(如IP协议),再到传输层协议(如TCP和UDP协议)以及应用层协议(如HTTP、FTP等)。例如,当你在浏览器中输入一个网址并发送请求时,请求会首先经过应用层的HTTP协议进行封装,然后通过传输层的TCP协议添加端口号等信息,接着在网络层通过IP协议确定目标主机的IP地址并进行路由选择,最后通过物理层的以太网协议将数据发送到网络上。
- 网络设备驱动与接口:Linux内核支持多种网络设备,如以太网卡、无线网卡等。网络设备驱动负责与这些硬件设备进行交互,实现数据的发送和接收。内核为网络设备提供了统一的接口,使得应用程序可以通过这些接口来访问网络设备。例如,当网卡接收到一个数据包时,网卡驱动会将数据包传递给内核的网络协议栈进行处理;当应用程序要发送一个数据包时,会通过系统调用将数据包交给内核,内核再通过网络设备驱动将数据包发送到网络设备上。
- 网络套接字(Socket)接口:Socket是Linux网络编程的基础接口,它提供了应用程序与网络协议栈之间的通信接口。应用程序可以通过Socket接口创建TCP或UDP套接字,进行网络连接、数据发送和接收等操作。例如,在编写一个简单的网络服务器程序时,会使用Socket接口来监听特定的端口,当有客户端连接时,通过Socket接收客户端的数据并进行处理。Socket接口使得网络编程更加方便和统一,无论底层是何种网络协议和网络设备,应用程序都可以使用相同的方式来进行网络通信。
驱动程序也是Linux系统中的一个重要组成部分。在目前的Linux内核的源代码中,移动程序占了大部分。在Linux操作系统中,系统调用是应用程序和内核之间的接口,而设备驱动程序是操作系统内核和机器硬件之间的接口。
在Linux系统中,驱动程序分成了三种基本的类型:
在Linux操作系统中,驱动程序主要有以下三种基本类型:
1. 字符设备驱动(Character Device Driver)
- 定义与特点
- 字符设备是以字节流的方式进行数据传输的设备,其数据处理没有固定的块大小要求。字符设备驱动主要用于处理像串口、终端设备(如键盘、显示器对应的设备文件)、打印机等设备。这些设备通常按照字符或者字节为单位依次读取或写入数据。
- 例如,键盘作为字符设备,当用户按下一个键时,字符设备驱动就会将这个按键对应的字符代码(如ASCII码)传递给系统,系统根据这个字符代码进行相应的处理,比如在文本编辑器中显示出对应的字符。
- 数据传输方式
- 字符设备驱动提供了顺序访问的接口,应用程序通过系统调用(如read、write)按顺序读取或写入数据。读取或写入操作通常是阻塞式的,即如果没有数据可读或没有空间可写,进程会被阻塞直到条件满足。
- 以串口通信为例,当应用程序通过串口发送数据时,字符设备驱动会将数据一个字节一个字节地发送到串口硬件缓存中,如果缓存满了,发送操作就会阻塞,直到有空间可以继续发送;当从串口接收数据时,驱动会等待数据到达,一旦有数据进入缓存,就会将数据按字节读取并传递给应用程序。
- 设备文件表示
- 字符设备在Linux文件系统中有对应的设备文件,通常位于“/dev”目录下,文件类型标记为“c”。设备文件的主设备号(major number)用于标识设备驱动类型,次设备号(minor number)用于区分同一类型的不同设备实例。
- 例如,系统中的多个串口设备可能具有相同的主设备号(表示它们使用相同的驱动程序类型),但不同的次设备号来区分是COM1、COM2等不同的串口。
2. 块设备驱动(Block Device Driver)
- 定义与特点
- 块设备主要用于存储大量的数据,数据的读写是以固定大小的块(通常是512字节的倍数,如常见的4KB)为单位进行的。像硬盘、固态硬盘(SSD)、USB闪存驱动器等存储设备都属于块设备。块设备驱动的主要任务是高效地管理这些存储设备上的数据读写操作。
- 例如,当操作系统要从硬盘读取一个文件时,它会以块为单位向硬盘块设备驱动发送读取请求。块设备驱动会根据请求的块地址,从硬盘的相应位置读取数据块并返回给操作系统。
- 数据传输方式
- 块设备驱动通过缓存机制来提高性能。它会将经常访问的数据块缓存在内存中,当应用程序再次请求这些数据块时,可以直接从缓存中获取,而不必从物理设备中读取,从而加快数据访问速度。同时,块设备驱动支持随机访问,即可以不按照顺序访问设备中的数据块。
- 例如,在数据库系统中,频繁访问的数据表块可能会被块设备驱动缓存到内存中。当数据库应用程序需要读取某个数据表记录时,如果对应的块在缓存中,就可以快速获取;而且可以通过指定块地址的方式随机读取不同位置的数据块。
- 设备文件表示
- 块设备在Linux文件系统中的设备文件也位于“/dev”目录下,文件类型标记为“b”。和字符设备类似,块设备也通过主设备号和次设备号来区分设备类型和不同的设备实例。
- 例如,系统中的多个硬盘分区可能具有不同的次设备号,但它们的主设备号相同,表示它们属于同一类块设备驱动管理的设备。
3. 网络设备驱动(Network Device Driver)
- 定义与特点
- 网络设备驱动用于控制和管理网络接口设备,如以太网卡、无线网卡等,使Linux系统能够通过这些设备连接到网络并进行数据通信。网络设备驱动与字符设备和块设备驱动不同,它不直接对应到文件系统中的设备文件进行读写操作,而是通过网络协议栈来传输数据。
- 例如,以太网卡的网络设备驱动负责将网络协议栈处理后的数据包发送到物理网络介质(如网线)上,同时也负责接收从网络介质传来的数据包,并将其传递给网络协议栈进行处理。
- 数据传输方式
- 网络设备驱动在数据传输过程中需要处理网络协议相关的操作。当发送数据时,驱动会将从网络协议栈接收到的数据包封装成适合网络介质传输的格式(如以太网帧),并添加源和目的MAC地址等信息;当接收数据时,驱动会从网络介质接收到的数据包中提取有效数据,去除帧头帧尾等信息,然后将数据传递给网络协议栈进行上层协议(如IP、TCP等)的处理。
- 例如,在一个基于TCP/IP协议的网络通信中,当应用程序通过Socket发送数据时,数据经过网络协议栈的层层封装后到达网络设备驱动,驱动将其转换为以太网帧格式发送出去。当收到以太网帧时,驱动会进行相反的操作,将数据传递给网络协议栈进行解包处理。
- 设备表示与管理
- 网络设备在Linux系统中有自己的设备名称(如eth0表示第一个以太网接口、wlan0表示第一个无线接口),通过特定的命令(如ifconfig或ip命令)可以对这些网络设备进行配置和管理。虽然网络设备没有像字符设备和块设备那样在“/dev”目录下对应的传统设备文件,但它们在系统内部通过特殊的设备驱动接口与网络协议栈相连。
- 例如,使用“ifconfig eth0 192.168.1.100 netmask 256.256.256.0”命令可以设置以太网接口eth0的IP地址和子网掩码,这个过程就是通过系统与网络设备驱动的交互来实现的。
10.2.3 Linux交叉编译工具链
交叉编译工具链是一个由编译器连接器和解释器等组成的集成开发环境。以上功能主要由glibc、gcc、binuntils和gdp这4个软件包提供。
-
Glibc(GNU C Library)
- 定义与功能
- Glibc是GNU项目发布的C标准库的实现,几乎所有的Linux程序都依赖它。它提供了一系列的函数,用于字符串处理、数学计算、文件操作、内存管理等诸多方面。例如,像
printf()
函数用于格式化输出,malloc()
和free()
函数用于动态内存分配和释放,这些常用的函数都是Glibc提供的。 - 它还包含了对系统调用的封装,使得应用程序可以以更方便、更抽象的方式使用操作系统提供的底层服务。比如,在进行文件读写时,通过Glibc提供的
fopen()
、fread()
和fwrite()
等函数,开发者可以不用直接处理底层的系统调用细节。
- Glibc是GNU项目发布的C标准库的实现,几乎所有的Linux程序都依赖它。它提供了一系列的函数,用于字符串处理、数学计算、文件操作、内存管理等诸多方面。例如,像
- 重要性
- Glibc是Linux系统中程序运行的基础。没有它,很多应用程序将无法正常编译和运行。它保证了不同的Linux软件在遵循C标准的基础上,能够有统一的函数库来调用,从而实现软件的可移植性。例如,一个在使用Glibc的Ubuntu系统上编译的C程序,在很大程度上可以很容易地移植到另一个同样使用Glibc的CentOS系统上。
- 版本差异
- 不同版本的Glibc可能会有一些函数的行为变化或者新增功能。这可能会导致一些旧的程序在新的Glibc版本下出现兼容性问题,或者一些新的程序需要较新的Glibc版本才能充分利用其新特性。例如,较新的Glibc版本可能会对安全性方面进行增强,如改进内存分配函数的安全性检查。
- 定义与功能
-
GCC(GNU Compiler Collection)
- 定义与功能
- GCC是一个功能强大的编译器集合,它支持多种编程语言,如C、C++、Objective - C、Fortran、Ada等。对于C和C++语言,它可以将源程序(
.c
或.cpp
文件)编译成目标机器能够理解的机器语言(二进制文件)。例如,当你编写一个简单的C程序hello.c
,使用gcc -o hello hello.c
命令,GCC就会将hello.c
编译成一个名为hello
的可执行文件。 - GCC包含了多个编译阶段,包括预处理(处理
#include
、#define
等预处理指令)、编译(将预处理后的代码转换为汇编语言)、汇编(将汇编语言转换为机器语言)和链接(将多个目标文件和库文件链接成一个可执行文件)。在每个阶段,GCC都提供了丰富的选项来控制编译过程,以满足不同的需求。
- GCC是一个功能强大的编译器集合,它支持多种编程语言,如C、C++、Objective - C、Fortran、Ada等。对于C和C++语言,它可以将源程序(
- 优化功能
- GCC具有强大的优化功能,可以生成高效的机器代码。它可以根据不同的优化级别(如
-O0
、-O1
、-O2
、-O3
)对代码进行优化。例如,在-O2
优化级别下,GCC会对循环展开、函数内联等操作进行优化,以提高程序的执行速度和减小程序的体积。这些优化可以使程序在运行时更加高效,减少CPU和内存的消耗。
- GCC具有强大的优化功能,可以生成高效的机器代码。它可以根据不同的优化级别(如
- 在开发中的应用
- 在Linux软件开发中,GCC是最常用的编译器。无论是开发系统软件、应用程序还是内核模块,GCC都发挥着关键作用。开发者可以通过编写符合编程语言标准的源程序,利用GCC的各种功能来生成高质量的可执行文件或者库文件。例如,在开发一个Linux驱动程序时,需要使用GCC将驱动程序的源文件编译成可以加载到内核中的模块文件。
- 定义与功能
-
Binutils(Binary Utilities)
- 定义与功能
- Binutils是一组二进制工具集,主要用于处理目标文件、可执行文件和库文件。它包含了诸如
as
(汇编器)、ld
(链接器)、objdump
(反汇编工具)、readelf
(查看ELF文件信息工具)等工具。例如,as
工具可以将汇编语言文件(.s
文件)转换为目标文件(.o
文件),ld
工具可以将多个目标文件和库文件链接成一个完整的可执行文件。 objdump
工具是一个非常有用的工具,它可以查看目标文件或者可执行文件的详细信息,包括反汇编后的汇编代码、符号表信息等。例如,当你想要查看一个可执行文件内部的函数实现细节,或者检查程序中是否存在未定义的符号时,objdump
就可以派上用场。
- Binutils是一组二进制工具集,主要用于处理目标文件、可执行文件和库文件。它包含了诸如
- 在编译过程中的角色
- 在整个编译过程中,Binutils与GCC紧密配合。GCC在编译阶段生成目标文件后,Binutils中的链接器
ld
会将这些目标文件以及相关的库文件(如Glibc库)链接在一起,形成最终的可执行文件。在调试和分析程序时,Binutils提供的工具可以帮助开发者了解程序的内部结构和运行机制。
- 在整个编译过程中,Binutils与GCC紧密配合。GCC在编译阶段生成目标文件后,Binutils中的链接器
- 重要性
- Binutils提供的工具对于理解和操作二进制文件是不可或缺的。在软件开发过程中,特别是在处理底层代码、进行性能优化或者调试程序时,这些工具能够帮助开发者深入了解程序的行为。例如,在分析程序的内存布局或者查找程序崩溃的原因时,Binutils中的工具可以提供关键的信息。
- 定义与功能
-
GDB(GNU Debugger)
- 定义与功能
- GDB是一个强大的调试器,用于调试用C、C++等语言编写的程序。它允许开发者在程序运行过程中观察程序的状态,包括变量的值、函数的调用栈、程序的执行流程等。例如,当程序出现错误或者行为不符合预期时,开发者可以使用GDB来找出问题所在。
- GDB提供了多种调试功能,如设置断点(在程序的特定位置暂停执行)、单步执行(逐行执行程序代码)、查看变量的值(可以在程序执行的任何时刻查看变量的当前值)和查看调用栈(了解函数之间的调用关系)。通过这些功能,开发者可以深入了解程序的运行过程,发现和修复程序中的错误。
- 调试过程示例
- 假设你有一个简单的C程序,其中有一个函数出现了错误。你可以使用GDB来调试这个程序。首先,使用
gcc -g
命令编译程序,这个选项会在生成的可执行文件中包含调试信息。然后,启动GDB并加载这个可执行文件。在GDB中,你可以在怀疑有问题的函数入口处设置断点,当程序运行到这个断点时,就会暂停执行。此时,你可以查看变量的值,检查是否符合预期,然后通过单步执行来观察程序的后续行为,直到找到错误的根源。
- 假设你有一个简单的C程序,其中有一个函数出现了错误。你可以使用GDB来调试这个程序。首先,使用
- 在开发中的价值
- 在复杂的Linux软件开发中,GDB是必不可少的工具。它可以大大提高调试的效率,尤其是对于大型项目或者涉及多线程、动态库等复杂情况的程序。通过使用GDB,开发者可以更快地定位和解决程序中的错误,从而提高软件的质量。
- 定义与功能
10.3 Android系统开发工具
10.3.1 Android代码目录结构
安卓代码的组织方式是进行安卓系统开发的重要步骤。
安卓代码包含三个部分,分别是核心工程、扩展工程和包。
- 核心工程是建立安卓系统的基础,在跟目录的各个文件夹中。安卓的核心工程包含了对安卓系统基本运行的支持,工程内容包括bootloader、build 、kernel和system等。
- 扩展工程使用的是其他开源项目,扩展功能包含在external文件夹中。是一些经过修改后适应安卓系统的开源工程。
- 包提供了安卓应用程序和服务,其中既包含要在安卓设备上运行的代码,还包括主机编译工具仿真环境等。
第一级别包含的目录和文件如下:
Makefile、bionic、Bootloader、build、dalvik、development、device、external、frameswork、hardware、kernel、packages、prebuilt、recovery、system、vendor。
上面的目录可能在相应的sdk中不存在,或者在sdk包含的目录在该列表中不存在,因此需要查看具体开发的机器和平台。
一般来说,sdk主体就是上述的16个目录,在上述的目录中sdk称为Software Develop Kit,指Android开发完整软件包。
- CSP是CPU Support package是针对某操作系统而适配的与cpu紧密相关的代码与库。
- BSP是Board support package 是针对某操作系统而适配的与具体开发的pcb板紧密相关的代码与库。
编译完成后将在根目录中生成一个out文件夹,生成的所有的安卓代码结构内容均放置在这个文件夹中:
- Out文件夹包含以下目录和文件:CaseCheck.txt、casecheck.txt、host、common、linux-x86、target、product。
- 其中,最主要的两个目录为host和target,前者表示在主机x86生成的工具后者表示目标基于armV5运行的内容。
10.3.2 Ubuntu与虚拟机
为移动设备进行程序设计和开发,同样使用交叉开发模式。
- 目前宿主机上的操作系统一般使用Linux系统,编译采用安卓操作系统,一个真正的Linux环境是必不可少的。
谷歌的官方推荐Ubuntu系统作为安卓的开发机,其名称来自于非洲南部祖鲁语ubuntu。虽然其他的Linux发行版也可以作为开发机,但是需要手动配置大量的库。
- ubuntu是一个以桌面应用为主的开源Linux操作系统,基于Debian GNU/Linux,支持x86、amd64(x64)和pcc架构。
如果设定windows为开发操作系统,工作难度会很大。虽然使用gcc可以在windows下交叉编译,但是安卓自带的很多开发工具如模拟器、rom生成脚本等移植的windows下十分困难。
因此,如果要使用windows作为开发系统,就需要通过虚拟机安装Linux虚拟机是独立运行与主机操作系统的离散环境。
- 加载虚拟机后,计算机就可以运行自己的操作系统和应用程序,并能够在运行于桌面上的多台虚拟器之间进行切换,通过一个网络共享虚拟机挂起、恢复和退出虚拟机。
- Vm ware就是一个常用的虚拟机平台。
- Virtual box是另一个可以采用的x86或者是x64虚拟化产品选择virtual box作为windows下虚拟Linux的虚拟化平台,是因为virtual box是唯一免费开源的虚拟机。同时virtual box还提供高性能的全硬件虚拟化功能。
如果习惯使用windows操作系统可以完成在虚拟机的安装,后在虚拟机支持下安装ubuntu操作系统,然后在ubuntu操作系统中完成安卓系统及开发工作。
- 安装虚拟机和ubuntu之前,要充分注意是否有充足的硬盘空间。
- 安卓对于jdk的版本十分敏感,需要确保安装了正确的gdk版本才能正确的编译安卓的源代码。
10.3.3 安卓系统及开发工具链
安卓系统运行于Linux kernel之上,但并不等同于Linux。
- Cairo、X11、Alsa、FFmpeg、GTK等都被安卓系统摘出来。
- 安卓系统中以bionic取代glibc、以Skia取代Cairo,以OpenCoRE取代FFmpeg。
- 安卓为了达到商业应用的目的,必须移除被GUN GPL授权证约束的部分。
- 将驱动程序移动到userspace。
- 使Linux drive与Linux kernel彻底分开。
安卓系统使用git作为代码管理工具。
安卓所用的交叉编译工具链可以在网上下载获得。如果下载了完整的安卓项目的源代码,则可以在相应的系统目录下找到交叉编译工具。
- 安卓并没有使用glibc作为c库,而是采用了谷歌自己开发的bionic Libc作为C库。
- 安卓系统的官方交叉编译工具链也是基于bionic Libc的。
所以使用或其移植其他工具链来学安卓就比较麻烦。如果要支持安卓的应用程序开发,建议使用官方或具有的安卓系统及开发工具链来进行开发。
- 安卓系统及开发包括内核的移植或者驱动程序的开发,涉及内核的编译。
- 先进行Android系统级的程序开发,然后在编译之前进行配置,再使用build系统进行编译,最后生成启动配置文件。
安卓编译系统。Build system用来编译安卓系统。
- 该系统主要由make文件shell脚本以及Python脚本组成,支持多架构多语言多目标的编译方式。
- Build系统中最主要的处理逻辑都在make文件中,而其他的脚本文件只起到一些辅助作用。
整个Build系统的make文件可以分为三类。
-
顶层Makefile
- 功能概述
- 顶层Makefile是整个Build系统的核心控制文件。它主要负责定义整个项目的构建目标和构建策略,从宏观角度把控项目的构建流程。例如,它会确定是要构建一个完整的产品(如完整的软件系统或硬件设备固件),还是只构建其中的某个模块;还会决定构建的版本类型,是用于开发调试的版本,还是用于发布的稳定版本。
- 项目级别的配置与依赖管理
- 在这个Makefile中,会处理项目级别的配置选项。这些配置选项可能涉及到编译器的选择、编译优化级别、目标平台等众多因素。例如,它可以通过定义变量来指定使用GCC编译器,并根据目标平台(如ARM架构的设备或x86架构的模拟器)来设置不同的编译参数。同时,它会管理项目的全局依赖关系,明确各个模块之间的依赖顺序,确保在构建过程中,先构建被依赖的模块。
- 与子Makefile的协调
- 顶层Makefile会调用和协调各个子Makefile。它就像是一个总指挥,将构建任务分解并分配给不同的子系统。例如,在一个复杂的软件项目中,可能有专门负责用户界面模块、数据库模块、网络通信模块的子Makefile。顶层Makefile会通过一定的规则和命令来触发这些子Makefile的执行,并且收集它们的构建结果,最终组合成完整的产品。
- 功能概述
-
模块级Makefile
- 模块定义与构建规则
- 模块级Makefile主要针对项目中的各个独立模块进行构建规则的定义。每个模块都有自己的功能和结构,模块级Makefile会根据这些特点来确定如何将模块中的源文件编译成目标文件。例如,对于一个图形处理模块,它会规定如何将图形算法的源文件(如
.cpp
文件)编译成对应的目标文件(.o
文件),包括指定合适的头文件路径、编译选项等。
- 模块级Makefile主要针对项目中的各个独立模块进行构建规则的定义。每个模块都有自己的功能和结构,模块级Makefile会根据这些特点来确定如何将模块中的源文件编译成目标文件。例如,对于一个图形处理模块,它会规定如何将图形算法的源文件(如
- 局部依赖处理
- 这个Makefile会处理模块内部的依赖关系。一个模块可能包含多个源文件和头文件,这些文件之间存在相互依赖的情况。例如,一个源文件可能会包含其他头文件,模块级Makefile需要确保在编译这个源文件时,所依赖的头文件是最新的。如果头文件发生变化,相关的源文件会被重新编译。同时,对于模块内的资源文件(如图像、配置文件等),也会明确它们与目标文件的依赖关系。
- 接口与对外提供的目标
- 模块级Makefile还会定义模块的对外接口,即该模块向外提供的目标文件、库文件或者可执行文件。这些接口是其他模块与该模块进行交互的关键。例如,一个模块可能会构建出一个共享库(
.so
文件),其他模块可以通过链接这个共享库来使用该模块提供的功能。模块级Makefile会确保这些对外提供的目标按照正确的方式构建,并遵循一定的命名和版本规则。
- 模块级Makefile还会定义模块的对外接口,即该模块向外提供的目标文件、库文件或者可执行文件。这些接口是其他模块与该模块进行交互的关键。例如,一个模块可能会构建出一个共享库(
- 模块定义与构建规则
-
子目录Makefile(递归Makefile)
- 目录结构与构建顺序
- 子目录Makefile用于处理项目子目录中的构建任务。在一个大型项目中,通常会按照功能或者层次结构划分为多个子目录。子目录Makefile会根据子目录的内容和在项目中的位置来确定构建顺序。例如,在一个包含底层驱动子目录、中间件子目录和应用层子目录的项目中,底层驱动子目录的构建可能需要先完成,因为其他子目录中的模块可能依赖于底层驱动的功能。
- 继承与扩展顶层和模块Makefile规则
- 子目录Makefile会继承和扩展顶层Makefile和模块Makefile的规则。它可以复用一些通用的构建规则,同时根据子目录的特殊情况进行补充和修改。例如,在顶层Makefile中定义了通用的编译器选项,子目录Makefile可以在这个基础上,根据本子目录中的源文件特点(如需要特殊的预处理指令或者包含特定的库)来添加额外的编译选项。
- 相对路径与文件查找
- 子目录Makefile在处理文件时,需要考虑相对路径的问题。由于它是在子目录中运行,源文件、头文件和目标文件的路径与顶层目录或者其他子目录有所不同。它会通过相对路径来查找和引用这些文件,确保构建过程能够正确地找到所需的资源。例如,在一个子目录中,它会使用相对路径来指定本目录下的源文件和头文件,同时也会考虑如何将生成的目标文件放置在合适的位置,以便于其他子目录或者模块能够正确地链接和使用。
- 目录结构与构建顺序
整个系统中包含了大量的模块,每一个模块都有一个专门的make文件,这类文件的名称统一为Android.mk,该文件中定义了如何编译当前模块。
Build系统会在整个源码数中扫描名称为Android.md的文件,并根据其中的内容执行模块的编译。
- 在build的产物中,最重要的是三个镜像文件。三个文件分别是 system.img、ramdisk.img和userdata.img。
- system.img中包含了Android OS的系统文件、库、可执行文件、及预置的应用程序将,被挂载为根分区。
- ramdisk.img在启动时将被Linux内核挂在为只读分区,它包含了初始化的一些文件和配置文件,用来挂载其他系统镜像并启动init进程。
- userdata.img将被挂在位/data,包含了应用程序相关的数据以及和用户相关的数据。
make命令是编译过程中一个非常重要的编译命令。利用make工具可以将大型的开发项目分解成更多易于管理的模块。对于一个包含几百个源文件的应用程序,使用make和Matkefile工具就可以简洁明快的理顺各个源文件之间纷繁复杂的关系。
执行make命令的时候需要一个make file文件以告诉make命令如何去编译和链接程序。
以下是一个简单的示例Makefile文件,用于展示如何编译和链接C语言源文件的基本规则。假设我们有两个C语言源文件 main.c
和 utils.c
,它们共同构建一个可执行程序,以下是对应的Makefile内容:
# 定义编译器,这里使用gcc,你也可以根据需要更换为其他编译器,比如clang等
CC = gcc
# 定义编译选项,例如开启警告信息等,可根据实际情况调整
CFLAGS = -Wall -g
# 定义要生成的最终可执行文件名
TARGET = my_program
# 列出所有的源文件,这里是示例的两个源文件,可以根据实际项目添加更多
SRC_FILES = main.c utils.c
# 根据源文件生成对应的目标文件(.o文件),这里使用了一个通配符规则
# %.o表示以.o结尾的目标文件,其依赖于对应的.c源文件
# 编译命令使用了前面定义的编译器和编译选项
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 定义最终可执行文件的构建规则,它依赖于所有的目标文件(.o文件)
$(TARGET): $(SRC_FILES:.c=.o)
$(CC) $(CFLAGS) $^ -o $@
# 添加一个伪目标clean,用于清除编译生成的中间文件和最终可执行文件
clean:
rm -f $(TARGET) $(SRC_FILES:.c=.o)
# 添加一个伪目标help,用于显示帮助信息
help:
@echo "Available targets:"
@echo " make - Build the $(TARGET) program."
@echo " make clean - Remove generated files."
@echo " make help - Show this help message."
以下是对这个Makefile各部分的详细解释:
1. 变量定义部分
CC
变量:用于指定编译器,这里将其设置为gcc
,如果想使用其他编译器,比如clang
,可以修改为CC = clang
。CFLAGS
变量:用来设置编译选项。-Wall
表示开启大多数警告信息,帮助我们发现代码中潜在的问题;-g
则是添加调试信息,方便后续使用调试工具(如gdb
)调试程序。可以根据实际需求添加或修改其他编译选项,比如-O2
可以开启一定级别的优化。TARGET
变量:定义了最终要生成的可执行文件的名称,在这里我们将可执行文件命名为my_program
。SRC_FILES
变量:列出了项目中所有的C语言源文件。在这个示例中有main.c
和utils.c
,如果项目有更多的源文件,只需按照空格分隔添加在这里即可。
2. 目标文件生成规则(编译规则)
%.o: %.c
这一行定义了一个模式规则,用于从.c
源文件生成对应的.o
目标文件。%
是一个通配符,匹配相同的文件名部分。例如,main.c
会对应生成main.o
,utils.c
会生成utils.o
。$(CC) $(CFLAGS) -c $< -o $@
是具体的编译命令。$(CC)
使用前面定义的编译器,$(CFLAGS)
应用设定的编译选项,-c
表示只进行编译操作,生成目标文件,$<
表示规则中的第一个依赖文件(也就是对应的.c
源文件),-o $@
表示输出的目标文件是当前规则的目标(也就是对应的.o
文件)。
3. 可执行文件构建规则(链接规则)
$(TARGET): $(SRC_FILES:.c=.o)
定义了最终可执行文件my_program
(由$(TARGET)
指定)的构建依赖于所有由源文件生成的目标文件(通过$(SRC_FILES:.c=.o)
将源文件列表中的.c
替换为.o
得到目标文件列表)。$(CC) $(CFLAGS) $^ -o $@
是链接命令,$(CC)
和$(CFLAGS)
含义同前,$^
表示所有的依赖文件(即所有的.o
目标文件),-o $@
表示输出的文件是当前规则的目标(也就是最终的可执行文件my_program
)。
4. 伪目标部分
clean
伪目标:用于清理编译过程中生成的中间文件(.o
文件)和最终的可执行文件。rm -f $(TARGET) $(SRC_FILES:.c=.o)
这条命令使用rm
命令强制删除指定的文件。当执行make clean
命令时,就会执行这个清理操作。help
伪目标:用于显示一些帮助信息,告诉用户可以使用的make
命令目标以及它们的作用。使用@echo
可以在执行make help
时输出相应的提示内容。
使用这个Makefile时,可以在包含该Makefile文件以及源文件的目录下,在终端中输入以下命令:
make
:执行编译和链接操作,生成最终的可执行文件my_program
。make clean
:清除之前编译生成的文件,方便重新编译或者整理项目目录。make help
:显示帮助信息,了解可用的make
命令操作。
你可以根据实际项目的具体情况,比如更多的源文件、不同的编程语言、需要链接的库文件等,对这个Makefile进行相应的修改和扩展。例如,如果要链接外部库,只需要在链接规则的命令中添加对应的库文件名(例如 -l[库名]
的形式)即可。
11 Boot Loader
BootLoader是安卓系统的重要组成部分,负责安卓移动设备的启动|image的烧写、关机、充电等。
11.1 Boot Loader概述
11.1.1 BootLoader主要功能
操作系统可以就地在rom和flash中执行,从而可以不需要任何的引导装入程序。
- 但是大多数嵌入式系统还是采用bootloader的。
- Bootloader的目的就是要准备好初始的运行环境。
嵌入式系统家电或者是复位之后,cpu通常都从cpu制造商预先安排的地址上取地址。
- Arm处理器在复位时通常都从0x00处取第1条指令。
- 基于处理器构建的嵌入式系统通常都有某种类型的固态存储设备被映射到这个预先安排的地址上。
- 因此,如果将boot loader映射到安装地址上,则系统加电之后,cpu将首先执行bootloader程序。
具体来说,bootloader的功能大致分为以下几个部分。
- 对pll时钟进行初始化。处理器在启动的时候,为了获得更好的这个设备兼容性及工作频率一般都很低,在bootloader程序中会提高处理器的时钟频率,以加快运行速度。速度一旦调好就不会发生改变,随着锁相环时钟频率的提高,运行速度加快,系统耗电量也会增加。
- 初始化SDRAM内存控制器。 bootloader自身也需要用到内存,大多数的bootloader都会将自己加载到内存中内存的配置一般包括行地址和列地址的配置以及自动刷新频率的配置。
- 初始化中断控制器和中断服务程序。
- 初始化各地址空间的片选地址寄存器和读写程序。
- 初始化堆栈寄存器。例如x86说实话的esp寄存器。
- Bootloader中需要访问的其他硬件设备进行初始化。
- 将bootloader加载到内存的过程中如果需要解压,还需要完成解压操作。
- 加载需要运行的应用程序,并最终运行到被加载的应用程序。
嵌入式bootloader也支持多种体系的cpu。例如,U-boot就同时支持arm体系结构和MIPS体系结构的cpu。
在嵌入式系统领域中,arm处理器系列的bootloader,大多数以U-boot为基础。U-boot是一个开源的项目,读者可以从网络上下载到最新的源代码。
11.1.2 Boot Loader操作模式
大多数的put load都包含两种不同的操作模式,即启动加载模式和下载模式。这两种仅对于开发人员有意义。
1. 启动加载模式
启动加载模式也称自主模式。即Bootloader从目标机上的某个固态存储器设备上将操作系统加载到ram中运行。
- 操作系统需要运行在rom中,因为rom相对于flash的读写速度更快,所以先把映像从flash复制到ram中,再从ram空间运行是非常高效的,且在调试阶段可以暂时修改代码而不损坏原始映像。
- 操作系统的映像通常处于压缩状态。在解压后,映像会被搬运至rmb中,复制和解压都需要一段程序来配置,这段程序只能在ram中就地执行。
- 之所以压缩是为了降低存储空间。在嵌入式产品启动时,bootloader都工作在这种模式下。
2. 下载模式
在这种模式下,目标机上的bootloader将通过串口连接或者网络连接等通信手段从主机下载文件。
从主机下载的文件通常首先被bootloader保存到目标机的RAM中,然后再被bootloader写到目标机的flash中。bootloader这种模式通常在第1次安装内核与根文件系统时被使用,以后的系统更新时也会使用。
下载模式的bootloader通常会向它的终端用户提供一个简单的命令行接口。
像Blob或U-Boot这样功能强大的bootloader通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。
11.1.3 BootLoader通信
所有主机和目标机之间一般通过串口建立连接。
bootloader在执行时通常会对串口进行io操作,如输出打印信息到窗口,从窗口读取用户控制字符等。
传输协议通常是xmodem、ymodem和zmodem协议中的一种。
串口传输的速度是有限的,因此通过以太网连接并借助TFTP下载文件是更好的选择。
- 如果通过以太网连接和TFTP下载文件时,主机平台中必须有一个软件用来提供 TFTP服务。
11.2 BootLoader的工作过程
11.2.1 BootLoader工作过程概述
从固态存储设备上启动的bootloader大多数都有两个阶段的启动过程,即启动过程可以分为阶段一和阶段二。
- 依赖于cpu体系结构的代码通常都放在阶段一中,而且通过汇编语言来实现。
- 硬件设备初始化。
- 为加载bootloader的阶段二准备ram空间。
- 复制bootloader的阶段二到ram空间。
- 设置堆栈
- 跳转到阶段2的c入口。
- 阶段二通常使用c语言来实现,这样可以实现一些更加复杂的功能。
- 初始化本阶段要使用的硬件设备。
- 检测系统内存映射。Memory Map
- 将内核映像和根文件系统映像从flash读到ram空间。
- 为内核设置启动参数。
- 调用内核。
11.2.2 BootLoader 阶段1
-
硬件复位与基本初始化
- 复位操作
- 当设备上电或复位时,Bootloader阶段一首先被触发。此时,系统硬件会经历一个复位过程,包括CPU和各种外围设备。这个复位操作将硬件的状态设置为一个已知的初始状态,例如,CPU内部的寄存器被清零或者设置为预定义的初始值,各种控制信号被复位,这为后续的有序启动奠定了基础。
- 屏蔽所有中断为中断提供服务通常是操作系统设备驱动程序的责任,因此在bootloader的阶段可以不必响应任何中断。
- 在一些简单的微控制器中,会设置CPU的时钟源,选择内部时钟或者外部晶振作为时钟信号源,并且设置一个基础的时钟频率,以确保CPU能够开始执行指令。
- 同时,会对一些关键的寄存器进行初始化,如设置堆栈指针(SP)寄存器,为程序运行提供基本的栈空间。
- 对于内存(RAM)系统,也会进行简单的初始化。虽然此时可能不会进行完整的内存检测和复杂的内存控制器配置,但会设置一些基本的内存访问参数,确保可以对内存进行最基本的读写操作。例如,在一些简单的嵌入式系统中,会设置内存的初始读写模式和基本的地址映射,使得后续可以将代码和数据加载到内存中。
- 复位操作
-
为加载阶段2准备ram空间
- 为了达到更快的执行速度,通常把阶段二加载到ram空间中执行。因此,必须为加载bootloader的阶段二准备好一段可用的ram空间。
- 由于阶段2通常是c代码执行的,因此在考虑空间大小时除了阶段2可执行映像的大小,还必须把堆栈空间也考虑进来。
- 一般而言,1M 的ram空间已经足够了。
- 必须保证安排的地址范围是可读写的ram空间。这个检测每一个page开始的两个字是否是可读写的,记这个算法为test-mp。
- 为了得到一段干净的ram范围,也可以对安排的ram空间范围进行清零操作。
-
加载初始代码片段
- 代码存储位置识别
- Bootloader阶段一需要确定初始代码片段的存储位置。在不同的设备中,这个初始代码可能存储在不同的地方。例如,在一些基于闪存(Flash)的系统中,初始代码存储在闪存的特定区域,这个区域通常是在设备制造过程中预先编程好的,并且有固定的起始地址。Bootloader阶段一要能够识别这个起始地址,并且知道如何从这个存储位置读取代码。
- 代码加载到内存
- 一旦确定了存储位置,就需要将初始代码片段加载到内存中。由于此时内存可能尚未完全初始化,这个加载过程可能比较简单。例如,通过直接内存访问(DMA)或者简单的指令读取 - 存储操作,将闪存中的初始代码逐字节或逐字地移动到内存的特定位置。这个位置通常是在之前初始化的内存区域中指定的,并且要保证代码能够在这个位置正确地执行。
- 代码存储位置识别
-
初步的异常和中断设置
- 异常向量表初始化
- 在Bootloader阶段一,要建立初步的异常向量表。异常向量表是一个存储了各种异常处理程序入口地址的表格,这些异常包括复位异常、硬件故障异常、软件中断异常等。例如,当系统发生复位异常时,CPU会根据异常向量表中的复位异常入口地址,跳转到相应的复位处理程序。在这个阶段,会将一些最基本的异常处理程序入口地址填充到异常向量表中,如将复位异常处理程序的地址设置为刚刚加载到内存中的初始代码片段的起始地址。
- 中断优先级和屏蔽设置
- 对于中断系统,也会进行初步的设置。这包括设置一些基本的中断优先级。例如,对于一些关键的硬件中断,如电源故障中断,会设置较高的优先级,确保在发生紧急情况时能够及时响应。同时,可能会对一些暂时不需要的中断进行屏蔽,以避免在初始启动阶段受到不必要的干扰。不过,在这个阶段,通常不会进行完整的中断服务程序的设置,只是为后续更详细的中断系统配置打下基础。
- 异常向量表初始化
-
设置堆栈指针
- 设置堆栈指针是为执行c语言代码做好准备,通常可以把sp的值设置为之前的(ram空间结尾-4)。记在前面安排的1M的ram空间的最顶端向下生长。
- 设置堆栈指针是为执行c语言代码做好准备,通常可以把sp的值设置为之前的(ram空间结尾-4)。记在前面安排的1M的ram空间的最顶端向下生长。
-
跳转到阶段2入口
在RAM系统中,可以通过修改pc寄存器为合适的地址来实现这个操作。
11.2.3 BootLoader阶段2
代码通常是c语言实现的,以便实现更加复杂的功能和更好的可疑之性。但与普通的c语言应用程序不同的是,在编译和链接bootloader的程序中不能使用glibc库中的任何支持函数。这就带来一个问题,那就是从哪里跳转进去main函数。
用一段trampoline汇编小程序为m函数的外部包裹。
BootLoader阶段2的主要内容:
-
初始化阶段要用到的硬件设备。
- 初始化至少一个串口
- 初始化计时器等。
- 这个时候可以把led灯点亮,表示已经进入了main函数的执行。
-
检测系统的内存映射
- 内存映射是指在整个4GB的物理地址空间中,哪些地址被分配用作寻址系统的RAM单元。
- 具体的嵌入式系统往往只把cpu预留的全部ram地址空间中的一部分映射到ram单元上。
- Bootloader的第2个阶段必须在执行任何操作之前检测整个系统的内存映射情况,也就是必须知道cpu预留的全部RAM地址空间中哪些被真正映射到ram地址单元。
- 这个检测系统的内存映射情况的算法,也可以将内存映射的详细信息打印到窗口。
-
加载内核映像和根文件系统映像
- 规划内存占用的布局。这里包括两个方面:第1内存内核映像所占用的内存范围。第2根文件系统映像所占用的内存范围。
- 对于内核映象,一般将其复制到从(MEM_START+0x8000)这个基地址开始的大约1M大小的内存范围内。 之所以空出来32k的一个内存,主要是因为Linux内核要在这段内存中存放一些全局数据结构。例如启动参数和内核页表等信息。
- 对于跟文件系统的映射,一般将其复制到MEM_START+0x100000开始的地方。如果用RAM disk作为根文件系统映像,则其解压后的大小一般是1MB。
- 从Flash上复制
- 由于像arm这样的嵌入式cpu通常是在同一的内存地址空间中寻址flash等固态存储设备,因此从flash上读取数据和从RAM地址单元中读取数据并没有什么不同。
- 规划内存占用的布局。这里包括两个方面:第1内存内核映像所占用的内存范围。第2根文件系统映像所占用的内存范围。
-
设置内核的启动参数。
- 在将内核映像和根文件系统映像复制到ram地址空间之后,就可以准备启动Linux内核了。
- 但是在调用内核之前应该做一步准备工作,即设置Linux内核的启动参数。
- 通过设置启动参数来进行启动的配置。Linux内核在启动时可以以命令行参数的形式来接收信息。
-
调用内核。
- BootLoader调用Linux内核的方法就是直接跳转到内核的第1条指令处,即直接跳转到MEM_START+0x8000地址处。
- 跳转时要满足的条件:
11.3 U-Boot启动流程分析
11.3.1 U-Boot概述
U-Boot(Universal Boot Loader)是遵循GPL条款的开放源码项目。
U-Boot不仅支持Linux系统的引导,还支持NetBSD、VxWorks、QNX等嵌入式系统。它支持很多处理器,如x86、ARM、MIPS等。
U-Boot支持ip和MAC等的预制功能,这一点和其他的BootLoader类似。U-Boot还有以下特有功能:
- 在线读写flash、IIC和eerom以及RTC以及IDE等。
- 支持kermit和S-record串行口下载代码。 U-boot可以将elf32位格式的可执行文件转换成S-record格式,直接从串口下载并执行。
- 识别二进制、ELF32、uImage格式的Image,对Linux引导有特别的支持。
- 单任务软件运行环境。 Uboot可以动态加载和运行独立的应用程序。
- 监控命令集:读写io、内存、寄存器、外设测试功能等。
- 脚本语言支持。类似Bash脚本。
- 支持watchdog、 lcd logo和状态指示灯功能。
- 支持MTD(内存技术设备)和文件系统。
- 支持中断。由于传统的bootloader分为阶段一和阶段二,因此在阶段二中添加中断处理服务非常困难。但是U-boot将两部分放到了一块,所以添加中断服务程序比较方便。
10.具有详细的开发文档。
13.3.2 U-boot代码结构
13.3.3 U-Boot启动流程分析
现在的安卓手机都具有两个处理器,应用处理器AP和通信处理器CP。
- Ap负责主操作系统与用户交互的应用程序的执行。
- Cp负责无线电话通信功能,并通过ap向安卓终端用户提供相应的无线通信功能。
- Cp的工作复杂,而机密一般由芯片厂商适配,所以用户主要注意ap。
按代码执行顺序和代码编写语言划分,U-Boot的启动过程可以分为两个阶段
- 第一阶段是汇编代码阶段,在这个阶段,由于要考虑存储空间受限和启动速度要快,所以代码并行采用效率更高的汇编语言。
- 二阶段c代码阶段到此阶段已经可以开始与用户交互,而且机器的硬件大多激活,所以用c语言编写。
第1阶段的代码在start.s中实现。在这段代码里面,U- boot要完成ddr land的flash等基础硬件的初始化。