嵌入式系统设计A 复习笔记

嵌入式系统设计A 复习笔记

我太难了,背不完了,实验课全挤在考试周
……
一堆背不出来,凭感觉写。最后卷面82

嵌入式系统设计基础

- 嵌入式系统的前世今生及应用领域

世界首个是阿波罗登月舱;第一个批量是美国1966军事导弹;
可用于有苛刻要求、尺寸和重量、功率消耗、震动和冲击、温度和湿度

- 嵌入式系统的发展阶段

  • 无操作系统阶段
    将嵌入式软件固化在只读内存,汇编编程
  • 简单操作系统阶段
    开发简单的应用软件(相对高级语言,例如搭载JVM虚拟机)
  • 实时操作系统阶段
    实时控制:软实时,硬实时;增加用户体验
  • 面向Internet阶段
    传感网络,物联网,工业物联网,CPS(信息物理社会)

嵌入式系统的定义

嵌入式系统是以应用为中心,以计算机技术为基础,采用可剪裁软硬件,适用于对功能、可靠性、成本、体积、功耗等有严格要求,负责控制、监视或者辅助设备、机器和车间运行的专用计算机系统。

嵌入式系统一般由嵌入式微处理器、外围硬件设备、嵌入式操作系统(可选),以及用户的应用软件系统等四个部分组成

嵌入式系统与桌面通用系统的区别

嵌入式处理器和处理器体系结构类型多,一般对实时性,可靠性,功耗,可用资源都有严格限制要求。一般是实时操作系统,开发需要专用工具和特殊方法,是一项综合的计算机应用技术

通用计算机采用少数的处理器类型和体系结构,主要都在少数大公司的掌握中,而嵌入式系统可采用多种类型的处理器和体系结构。微处理器产业链上有巨大产业链,有千种处理器和几十种体系结构可选择。

实时系统:指系统能够在限定的响应时间内提供所需水平的服务。

软件一般是固化运行或直接加载到内存中运行,具有快速启动的功能。并对实时的强度要求各不一样,可分为硬实时和软实时。

嵌入式处理器的基本特征

嵌入式处理器除了集成CPU核心、Cache、MMU、总线结构等部分外,还集成了各种外部接口和设备,如中断控制器、MA、定时器、UART等,符合嵌入式的低成本和低功耗需求。

组成

控制单元、算术逻辑单元和寄存器。

  • 控制单元:取指、译码、取操作数,发送主要的控制指令。
    • 程序计数器PC:记录下一条程序指令的位置
    • 指令寄存器IR:存放所取的指令
  • 算术逻辑单元:
    • 数学运算:加、减、除或数值的比较
    • 逻辑运算:AND、OR、XOR或NOT
  • 寄存器:用于存储暂时性的数据。

存储器包括主存和外存

主存存储器的特点数据快,一般采用ROM、EPROM、NOR Flash、SRAM、DRAM等存储器件
嵌入式系统中一般不采用硬盘而采用电子盘外存:采用flash芯片存储数据,体积小、功耗低、抗震

嵌入式系统的大多数输入、输出接口和部分设备已经集成在嵌入式微处理器中

相对通用处理器,嵌入式处理器有以下特点

  • 体积小、集成度高、价格较低
  • 可扩展的处理器结构
  • 功耗低
  • 对实时多任务有很强的支持能力
  • 具有功能很强的存储保护功能

嵌入式处理器的种类

单片机功能太简单, 性能太差
DSP太专用, 可以看成一个外设
通用处理器与SOC是主要发展方向

嵌入式微处理器 MPU

Power PC,MIPS,ARM

  • 在嵌入式应用中,将微处理器装配在专门设计的电路板上,只保留和嵌入式应用紧密相关的功能硬件,去除其它冗余的功能,以最低功耗和成本实现特定的需求。
  • 为了满足嵌入式应用的特殊要求,在工作温度、抗电磁干扰、可靠性等方面对CPU做了各种增强
  • 微处理器装配在专门设计的电路板上,电路板上必须包括ROM,RAM,总线接口,各种外设等器件,降低了系统的可靠性,技术保密性也较差;
  • 不是为任何已有的特定计算目的而设计的芯片,与其他嵌入式处理器相比,处理性能高,但价格也高

微控制器(单片机 MCU

以某一种微处理器内核为核心。单片化,体积大大减小,从而使功耗和成本下降、可靠性提高

按位数区分产品,有计算器,仪表盘,傻瓜相机,电表,USB,键盘,录像机,路由器,工作站

嵌入式DSP处理器 EDSP

很流行ARM+DSP结构

采用Harvard(哈弗)结构和专用的硬件乘法器,快速DSP指令(属于RISC精简指令集),适用于处理器运算速度要求较高、向量运算较多的应用领域。

数字信号处理,傅里叶变化,电话,语音识别

片上系统 SoC

很可能是其他处理器的集合体

在一个硅片上实现一个更为复杂的系统,将一个完整产品的各功能集成在一个芯片中。功能可以完全由硬件完成,也可以由软硬件完成。

高密度、高速度、高抗干扰性

典型嵌入式处理器的特点及应用场景

  • ARM
    自己不再制造芯片,而是将芯片的设计方案授权给其他公司生产
    市场特别广

  • MIPS
    无互锁流水级的微处理器:尽量利用软件办法避免流水线中的数据相关问题
    应用于消费类电子、下一代网络、宽带产品、智能卡、机顶盒、数字电视、DVD

  • POWER PC
    可伸缩性好、方便灵活
    应用于DSL调制解调器、SOHO路由器、远程接入服务器、DSLAM、执行局交换机设备、无线基站、企业路由器

  • X86
    处理能力强,适用于高端的应用领域

嵌入式软件系统的体系结构及各个层次的任务

  • 驱动层
    板级初始化程序、与系统软件相关的驱动、与应用程序相关的驱动

  • 硬件抽象层
    通过特定的上层接口与操作系统进行交互,屏蔽了底层硬件的多样性,操作系统不再面对具体的硬件环境,而是面对由这个中间层次所代表的、逻辑上的硬件环境。

  • 操作系统层
    包括嵌入式内核和其他根据需要的系统

  • 中间件层
    在一些复杂的嵌入式系统存在

  • 应用层
    由多个相对独立的应用任务组成,每个应用任务完成特定的工作,如I/O任务、计算的任务、通信任务等,由操作系统调度各个任务的运行。

嵌入式操作系统的主要特性

由于嵌入式系统应用的特点,像嵌入式微处理器一样,嵌入式操作系统也是多姿多彩的

  • 短小精悍的内核以及高度模块化结构以利于不同应用要求的选择和剪裁。(可配置、可剪裁 )
  • 或强或弱的实时性(实时任务调度策略),其调度算法一般采用基于优先级的可抢占的调度算法
  • 嵌入式操作系统按照应用对象有偏控制类和偏人机交互类之分

- 嵌入式操作系统的分类

嵌入式实时系统可分为:

  • 强实时型:响应时间μs~ms级;
  • 一般实时:响应时间ms~s级;
  • 弱实时型:响应时间s级以上。

根据确定性的强弱,可将嵌入式系统分为硬实时、软实时系统:
硬实时:有严格的要求,不能满足将引起系统崩溃
软实时:有要求,但不能满足也不致命

典型嵌入式操作系统的特点及应用场景(Linux、VXwork、QNX、uC/OS)

linux

广泛的硬件支持,内核高效稳定,开放源码,优秀的开发工具,完善的网络通信和文件管理机制

服务器,桌面系统,嵌入式

VxWorks

高可靠性、高实时性、可裁剪性好、但非常昂贵

支持各种实时功能,包括快速多任务处理、中断支持、抢占式和轮转式调度,用于美国航空探测器。

QNX

高可靠性和高安全性

汽车、智能机器、智能仪器仪表、机顶盒、通讯设备、 PDA等应用

μC/OS-II

开源、结构小、可剥夺、实时性好。不支持时间片轮转调度法,所以赋予每个任务的优先级必须是不同的。

控制类等低端应用中。如照相机行业、医疗检测仪器、音响设施等等

ARM微处理器概述与编程模型

ARM微处理器的特点

高性能、低成本、低功耗
多寄存器
非常强大的多寄存器load/store指令
3地址指令(三个源操作数寄存器一个结果寄存器)
每条指令条件执行
协处理器
Thumb体系16位压缩

CISC 与 RISC体系结构的特点及区别

CISC是复杂指令集计算(Complex Instruction Set Computing)

  • 指令的种类繁多(如8086CPU不包括浮点指令在内都有110多条指令)。
  • 指令功能强大,单条指令可完成较为复杂的功能。
  • 指令的机器码长度因指令不同而不同。
  • 指令的执行时间也根据不同的指令有较大的差异。
  • 高性能微指令结构耗用了大量晶体管,造成体积成本增加。

RISC是精简指令集计算(Reduced Instruction Set Computing)

  • 具有一个短小精悍的指令集。不包括衍生指令,大多数RISC微处理器的指令只有几十条。
  • 指令具有相同的机器码位长。常见的为32位。对有些不足32位的指令都将无用位填0。
  • 95%的指令执行时间为一个时钟周期。剩余的5%指令也只需2个时钟周期。
  • 没有采用CISC实现复杂指令必用的微指令(微码)结构。节省了大量晶体管,但无法实现高性能指令。
  • 采用载入/存储(Load/Store)模式, 无法实现单指令存储器—存储器读写功能(存储器访问只能采用Load/Store指令)。

区别:

指 标 RISC CISC
指令集 一个周期执行一条指令,通过简单指令的组合实理复杂操作;指令长度固定。 指令长度不固定,执行需要多个周期。
流水线 流水线每周期前进一步。 指令的执行需要调用微代码的一个微程序。
寄存器 更多通用寄存器。 用于特定目的的专用寄存器。
Load/Store 结构 独立的Load和Store指令完成数据在寄存器和外部存储器之间的传输。 处理器能够直接处理存储器中的数据。

冯.诺依曼结构和哈佛结构的特点及区别

冯.诺依曼结构的处理器指令与数据使用同一个存储器,经由同一个总线传输。指令和数据存储在相同的内存空间,但存储地址不同。处理器利用相同的总线处理内存中的指令和数据,指令和数据具有相同数据宽度,指令与数据无法同时存取

哈佛结构使用两个独立的存储器模块分别存储指令和数据,使用两条独立的总线进行访问。CPU可以程序及数据并行的进行操作,提高处理器的执行效率。

区别:由于冯.诺依曼结构取指令和存数据要从同一个存储空间存取,经由同一总线传输,因而它们无法重叠执行,但哈佛结构取指令和存取数据分别经由不同的存储空间和不同的总线,使得各条指令可以重叠执行,这样,也就克服了数据流传输的瓶颈,提高了运算速度。

ARM微处理器的工作状态及区别

  • ARM状态,此时处理器执行32位的、字对齐的ARM指令;
  • Thumb状态,此时处理器执行16位的、半字对齐的Thumb指令

ARM指令集和Thumb指令集均有切换处理器状态的指令,并可在两种工作状态之间切换,但ARM微处理器在开始执行代码时,应该处于ARM状态

区别:

  • 并非所有的ARM指令都有对应的Thumb指令
  • Thumb代码所需的存储空间约为ARM代码的60%70%
  • Thumb代码使用的指令数比ARM代码多约30%~40%
  • 若使用32位的存储器, ARM代码比Thumb代码快约40%
  • 若使用16位的存储器, Thumb代码比ARM代码快约40%~50%
  • 与ARM代码相比较,使用Thumb代码,存储器的功耗会降低约30%

ARM体系结构的存储器格式

ARM处理器的7种工作模式

程序状态寄存器(CPSR)的结构与作用;(SPSR?)

寄存器 CPSR 为 程序状态寄存器 ,在异常模式中,另外一个寄存器 “ 程序状态保存寄存器( SPSR ) ” 可以被访问。每种异常都有自己的 SPSR ,在因为异常事件而进入异常时它保存 CPSR 的当前值,异常退出时可通过它恢复 CPSR .

异常的响应及返回

对异常的响应

(1)将下一条指令的地址存入相应连接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行。

  • 若异常是从ARM状态进入,LR寄存器中保存的是下一条指令的地址(当前PC+4或PC+8,与异常的类型有关);
  • 若异常是从Thumb状态进入,则在LR寄存器中保存当前PC的偏移量,这样,异常处理程序就不需要确定异常是从何种状态进入的。

(2)将CPSR复制到相应的SPSR中。
(3)根据异常类型,强制设置CPSR的运行模式位。
(4)强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处,同时设置中断禁止位,以禁止中断发生。

异常返回:
⑴将连接寄存器LR的值减去相应的偏移量后送到PC中。
⑵将SPSR复制回CPSR中。
⑶若在进入异常处理时设置了中断禁止位,要在此清除

ARM处理器的寻址方式

目前ARM指令系统支持8种寻址方式:

  • 立即寻址
    MOV R0,#0xFF00
    没有地址,直接写值
  • 寄存器寻址
    MOV R2,R3
    值在寄存器里,寄存器就是地址
  • 寄存器间接寻址
    LDR R1,[R2]
    地址在寄存器里
  • 基址变址寻址
    LDR R0,[R1,#0x08]
    给一个基地址,一个偏移量
    目标地址是基地址+偏移量
  • 寄存器移位寻址
    MOV R0, R1, LSL#2
    给俩寄存器,把第二个寄存器的值移位操作后写入第一个寄存器
    移位有三种:
    LSL:左移
    LSR:右移
    ROR:循环右移
    ASR:最左边不变的右移
  • 多寄存器寻址
    LDMIA R1!,{R2-R4,R5}
    给一个头寄存器,一个输出寄存器列(连续可用‘-’,分隔用‘,’)
    把从头寄存器开始,使用寄存器间接寻址按字长变址读入值存入寄存器列
  • 堆栈寻址
    STMFD SP!,{R1-R7,LR}
    SP是栈顶指针
    SP指向最后压入堆栈的数据时,称为满堆栈(Full Stack),而当指向下一个将要放入数据的空位置时,称为空堆栈(Empty Stack)。
    当堆栈由低地址向高地址生成时,称为递增(Ascending)堆栈,当堆栈由高地址向低地址生成时,称为递减(Decending)堆栈。
  • 相对寻址
    BL NEXT;
    MOV PC,LR;
    给一个偏移量,以PC为基址,相加后为目标地址

ARM的指令格式(立即数、条件码)

<opcode> {<cond>} {S} <Rd> ,<Rn>{,<op2>}

<>必要,{}可选

opcode :指令助记符; cond :执行条件;
S :是否影响CPSR 寄存器的值;
Rd :目标寄存器;Rn :第1 个操作数的寄存器;
op2 :第2 个操作数;

op2有如下三种形式:

  • #immed_8r—— 常数表达式;
  • Rm—— 寄存器方式;
  • Rm,shift—— 寄存器移位方式;

立即数

包含全部1位的数据合法循环右移后可载入一个字节

注:必须偶数次位移

条件码

每一条ARM指令包含4位的条件码,位于指令的最高4位[31:28]

几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件的执行.当指令的执行条件满足时,指令被执行,否则指令被忽略

ARM9的指令系统

跳转指令、数据处理指令+-、存储器访问指令(Load/Store)

LDR rd,<地址>:rd->地址
STR rd,<地址>:rd<-地址

操作符 说明
LDR 字数据
+B 无符号
+T 用户模式
+H 半字
+S 有符号
地址 说明
[Rn] 零偏移
[Rn,offset] {!} 前索引偏移
label 程序相对偏移
[Rn],offset 后索引偏移

• LDR 和 STR 指令应用示例:

1
2
3
4
5
6
7
8
9
10
1.加载/存储字和无符号字节指令
LDR R2,[R5] ;将R5指向地址的字数据存入R2
STR R1,[R0,#0x04] ;将R1的数据存储到R0+0x04地址
LDRB R3,[R2],#-1 ;将R2指向地址的字节数据存入R3,R2=R2-1
STRB R0,[R3,-R8 ASR #2] ;R0->[R3-R8/4],存储R0的最低有效字节

2.加载/存储半字和有符号字节指令
LDRSB R1,[R0,R3] ;将R0+R3地址上的字节数据存入R1,;高24位用符号扩展
LDRH R6,[R2],#2 ;将R2指向地址的半字数据存入R6,高16位用0扩展;读出后,R2=R2+2
STRH R1,[R0,#2]! ;将R1的半字数据保存到R0+2地址,只修改低2字节数据,然后R0=R0+2

多寄存器存取

LDM/STM

后缀 说明
I 增加in
D 减少de
A 之后after
B 之前before

SWP Rd,Rm,Rn
rd<-rn,rn<-rm

+S表示影响标志位

ADDS R1,R1,#1020 ; R1=R1+1020 ,并影响标志位
ADD R1,R1,R2,LSL #2 ;R1=R1+R2<<2

SUBS R0,R0,#240 ;R0=R0-240 ,并影响标志位
SUBS R2,R1,R2 ;R2=R1-R2 ,并影响标志位

B LABEL
跳转
+L 记录当前pc位置(类似子程序,可能还会跳回来)

什么是伪操作,什么是执行指令,区别是?

嵌入式Linux系统

Linux发展的5大支柱;

UNIX,71年发布,73年重写
研发过程诞生了C语言
被反托拉斯司起诉,只能自己使用和发放到大学的科研机构中供研究使用

MINIX 87年大学教授
以UNIX7 为蓝本开发出一个运行于IntelX86 平台上的简化类UNIX系统MINIX(mini-UNIX的意思)来用于教学。完全开源

Linux 91年芬兰学生

GNU 84年
未完成,被 Linux 取代。最大贡献是‘GNU gcc’

  • Unix操作系统。
  • Minix操作系统。
  • GNU计划。
  • POSIX标准。
  • Internet。

Linux的发行版(RedHat家族与Debian家族);

RedHat:使用数量最多的Linux发现版。
Debian:自由社会使用最多的发行版。
Ubuntu:基于Debian的一个发行版。

Linux版本的发展;

从1991年0.01版本到 1999年具有里程碑意义的2.2 版本

2.6 新特性,性能的提升:更好地支持嵌入式系统

  • 内核可抢占
  • 调度算法从O(n)到O(1)
  • 改进的同步机制:“futexes”(快速用户空间互斥)可以使线程串行化以避免竞态条件
  • I/O 性能改进:修改 I/O 调度器来确保不会有进程驻留在队列中过长时间等待
  • 支持无MMU的处理器

可伸缩性增强:更好地支持服务器系统

文件系统改进

命令

文件目录相关命令、压缩打包相关命令

• 目录操作命令
ls pwd cd mkdir rmdir
• 文件操作命令
file touch cp rm mv find
• 文本文件查看命令
cat more less head tail

tar [op] target resource
|附加|说明|
|-|-|
|c|打包|
|z|压缩|
|t|查看|
|x|解压|
|f|指定target文件名|
|v|查看日志(有输出)|
|z|gz|
|j|bz2|

Linux程序开发过程及其使用的工具;

编辑
Vi
set nu 显示行号

编译
GCC

处理 :分析各种预处理命令,如#define,#include,#if
编译 :根据输入文件产生汇编语言程序
汇编 :将汇编语言输入,产生扩展名为.o的目标文件
链接 :以.o目标文件,库文件作为输入,生成可执行文件

#include如果是<>则系统目录,""则先在当前目录下搜索头文件,再在系统默认目录下搜索

运行
./

调试
GDB

调试工具
Make
make [-f Makefile] [option] [target]
make程序:自动编译所有内核代码文件,要使用make工具程序,必须编写一个名称为makefile(或Makefile)的文件。
Makefile文件:主要包含一些make要遵守的执行规则和要求执行的命令等内容,用于告诉make需要对所涉及的源文件做哪些操作和处理以生成相应的目标文件。

GUN make的执行过程分为两个阶段。
第一阶段:读取所有的Makefile文件(包括“MAKIFILES”变量指定的、指示符“include”指定的、以及命令行选项“-f(–file)”指定的Makefile文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。
第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。

Makefile里主要包含了五个东西:显式规则、隐含规则、变量定义、文件指示和注释。
1、显式规则。显式规则说明了,如何生成一个或多个目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2、隐含规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
3、变量的定义。在Makefile中需要定义一系列的变量,变量一般都是字符串,类似C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4、文件指示。有三种情况:第一,在一个Makefile中引用另一个Makefile,就像C语言中的include一样;第二,根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;第三,定义一个多行的命令。
5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++,Java中的“//”一样。

Makefile支持include,前面可以有空字符,不可以是[tab]。多个依赖用空格分隔,可以用通配符

可以通过给VPATH赋值来设置规则中目标文件和依赖文件的搜索目录VPATH:=//c/ming //c/ming/head。Make首先搜索当前目录,如果未找到依赖的文件,make将按照VPATH中给的目录依次搜索。用小写vpath可以特殊指定,如:vpath %.c //c/ming

基本格式
target(目标):prerequisites(先决条件)

command(命令)

1
2
3
4
5
6
7
8
helloworld :hello.o main.o
gcc hello.o main.o -o hello
main.o :main.c hello.h
gcc -c main.c -o main.o
hello.o :hello.c
gcc -c hello.c -o hello.o
clean :
rm -rf *.o helloworld

输入make后
1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2、默认的情况下,make执行的是Makefile中的第一个规则。此规则的第一个目标称之为“最终目的”或者“终极目标”。在上面的例子中,它会找文件中的第一个目标文件(target),即“edit”这个文件,并把这个文件作为最终的目标文件。
3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为该.o文件的依赖规则,如果找到则再根据那一个规则生成.o文件。

.PHONY:
伪目标

嵌入式软件编程技术

汇编语言的简单程序编程;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.global _start
.text
_start:
Mov R8, #20 @低32位初始化为20
Mov R9,#0 @高32位初始化为0
Sub R0,R8,#1 @初始化计数器
Loop
MOV R1,R9 @暂存高位值
UMULL R8,R9,R0,R8 @[R9:R8]=R0*R8
MLA R9,R1,R0,R9 @R9=R1*R0+R9
SUBS R0R0#1 @计数器递减
BNE Loop @计数器不为0时继续循环
.Stop:
B Stop
.end @文件结束

可重入函数及其解决方法;

可重入性(reentrant)
如果某个函数可被多个任务并发调用而不会造成数据错误,则称该函数具有可重入性
可重入函数可在任意时刻被中断,稍后继续运行时不会造成错误

不可重入性(non-reentrant)
不可重入函数不能被多个任务共享,除非采用信号量等机制确保函数的互斥调用,或者在代码的关键部分禁止中断

解决方案:

  1. 全局改局部,各种变量
  2. 加锁,申请/释放信号量

中断及处理的硬件部分及软件部分;

类似异常

硬件部分

  • 复制CPSRSPSR_<mode>
  • 设置正确的CPSR
  • 切换到<mode>
  • 保存返回地址到LR_<mode>
  • 设置PC跳转到相应的异常向量表入口

软件部分

  • SPSRLR压栈
  • 把中断服务程序的寄存器压栈
  • 开中断,允许嵌套中断
  • 中断服务程序执行完后,恢复寄存器
  • 弹出SPSRPC,恢复执行

高级语言与低级语言混合编程;

短的话可以内嵌到C语言
长的按文件加入项目

ATPCS:

  1. 子程序用R0~R3传参。记为A1~A4
  2. ARM状态用R4~R11存局部变量,Thumb状态R4~R7。记为V1~V8(V1~V4)
  3. R12做返回栈指针。记为IP
  4. R13用作数据栈指针。记为SP
  5. R14用作链接寄存器
  6. R15用作程序计数器。记为PC

汇编调用C:

1
2
3
4
int add(int x,int y)
{
return(x+y);
}

汇编程序调用

1
2
3
4
5
6
IMPORT add @声明要调用的C函数
……
MOV r0, 1
MOV r1, 2
;调用C函数add
BL add

C调用汇编:

1
2
3
add ;求和子程序add
ADD r0,r0,r1
MOV pc,lr

1
2
3
4
5
6
7
8
extern int add (int x,int y); //声明add为外部函

void main()
{
int a=1,b=2,c;
c=add(a,b); //调用add子程序
……
}

内敛汇编
asm(
汇编语句模板:
输出部分:
输入部分:
修改部分(要用的寄存器保存下来)
)

1
asm("mov %0, %1, ror #1" : "=r" (result) : "r" (value));

开发环境和调试技术

嵌入式软件开发流程;

交叉编译与本地编译的区别;

交叉编译器和交叉链接器是指能够在宿主机上安装,但是能够生成在目标机上直接运行的二进制代码的编译器和链接器

交叉调试与本地调试的区别;

交叉调试 本地调试
调试器和被调试程序运行在不同的计算机上 调试器和被调试程序运行在同一台计算机上
可独立运行 需要操作系统的支持
被调试程序的装载由调试器完成 被调试程序的装载由 Loader 程序完成
需要通过外部通信的方式来控制被调试程序 不需要通过外部通信的方式来控制被调试程序
可以调试不同指令集的程序 只能调试相同指令集的程序

嵌入式开发环境构建;

交叉编译环境构建
主从机通信环境构建
交叉调试环境构建

主从机通信环境构建;

  1. 串口通讯
    最简单,慢,短。不适合大数据量、长距离数据传输
  2. 网络通讯
    TFTP。大数据量数据传输,可以作为串口通讯的补充
  3. USB
    热插拔,简单易用
  4. JTAG
    写芯片硬件上

开发环境构建中的仿真技术;

开发机和交叉开发环境不一致
API仿真:Cygwin、MinGW
虚拟机:Virtual PC、VMWare、Virtualbox

目标机仿真
硬件仿真:ROM Emulator(ROM模拟)、ICE(在线仿真器)、OCD(CPU芯片提供的一种调试功能)
软件仿真:ARM仿真器Armulator

嵌入式交叉开发环境构建;

arm-linux-gcc
可能要自己生成交叉编译器

远程调试的方法(stub)及常见的三种方法;

  • ROM Monitor调试目标程序
  • KGDB调试系统内核
  • gdbserver调试应用程序(只能实现应用程序级调试)

Linux内核的三种调试方式;

  • Printk:调试内核代码时最常用的技术,在内核代码中加入printk(),可以把所关心的信息打印到屏幕上。
  • KGDB:提供了一种使用GDB调试Linux内核的机制,可以在内核中进行设置断点、检查变量值、单步跟踪程序运行等操作。使用KGDB调试,需要两台机器,一台开发机,另一台目标机,两台机器之间通过串口或网口相连。
  • KDB:由SGI公司开发的遵循 GPL许可证的Linux内核调试工具。(同一台机器调试运行中的内核)

BootLoader

嵌入式系统启动流程;

  1. 硬件加电
  2. 引导加载程序
    Boot代码、Bootloader等
  3. 操作系统内核,如Linux 内核
    根据特定的目标嵌入式硬件系统,定制的内核及启动参数
  4. 加载文件系统
    包括根文件系统以及建立于Flash内存设备上的文件系统
  5. 运行用户程序

典型引导加载方式;

磁盘启动方式

  1. 系统加电启动时,系统BIOS负责检测并启动加载控制卡上的BIOS
  2. BIOS 在完成硬件检测后,将硬盘 MBR 中的 Boot Loader 读到系统的RAM 中,然后将控制权交给 OS Boot Loader
  3. Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,设置相关参数,然后跳转到内核的入口点去运行,即开始启动操作系统

网络启动

Bootloader 安装到板上的 EPROM 或者 Flash 中
Bootloader 通过远程下载 Linux 内核映像或者文件系统

Flash 启动方式

大多数嵌入式系统上都使用 Flash 存储介质。

NOR Flash(线性Flash),支持随机访问,介质上的代码可直接运行:

  • Bootloader 的入口位于处理器上电执行第一条指令的位置。
  • Bootloader 引导 Linux 内核,然后跳转到内核映像入口执行。

NAND Flash、Compact Flash、DiskOnChip等,价格低,存储容量大,通过专用控制器的I/O方式来访问,不随机访问。

  • 在这些芯片上,需要配置专用的引导程序。
  • 通这种引导程序起始的一段代码就把整个引导程序复制到 RAM 中运行。
  • 与磁盘上启动有些相似。

Boot Loader概念;

是在操作系统内核运行之前运行的一段小程序,用于初始化硬件设备、建立内存空间的映射图以及调整系统的软硬件环境,以便操作系统内核启动。一般不通用,依赖于处理器架构和扳级配置,有些对CPU也有要求。

BootLoader操作模式;

启动加载模式
自主(Autonomous)模式,是BootLoader 的正常工作模式

  1. 从目标机某个固态存储设备上将OS加载到 RAM
  2. 准备好内核运行所需的环境和参数
  3. 在RAM运行操作系统内核

下载模式(Downloading)
用户干预进入下载模式,在控制台打印提示信息,等待用户输入
从主机下载的文件首先被 Boot Loader 保存到目标机的 RAM中,然后被 BootLoader 写到目标机上的FLASH 类固态存储设备中,或者直接在RAM中运行

BOOTLOADER的生命周期

  1. 初始化硬件,如检测存储器等
  2. 设置启动参数,告诉内核硬件的信息,如用哪个启动界面,
    波特率.
  3. 跳转到操作系统的首地址.
  4. 消亡

Boot Loader 的 stage1与stage2 主要步骤;

stage1
汇编
简单的硬件初始化

stage2
C语言
复制数据
设置启动参数
串口通信等功能

U-Boot

如何移植U-Boot

系统引导
支持NFS挂载、RAMDISK 系统引导 (压缩或非压缩)形式的根文件系统
支持NFS挂载,从Flash中引导压缩或非压缩系统内核

基本辅助
强大的操作系统接口功能,可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤其对Linux支持最为功能强劲
支持目标板环境参数的多种存储方式,如Flash、NVRAM、EEPROM
CRC32校验,可校验Flash中内核、RAMDISK镜像文件是否完好

设备驱动
串口、SDRAM、Flash、以太网、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持

上电自检功能
SDRAM、Flash大小自动检测;SDRAM 故障检测;CPU型号

特殊功能
XIP内核引导,网络启动

ARM-Linux内核

Linux内核的特点

轻量级的进程管理方式,处理器

包含?主要部分

采用单内核体系结构,并使用模块机制。
不支持完全意义上的用户态线程,但用进程共享
上下文实现LWP(light weight thread)机制。
内核可抢占,允许在内核执行的任务优先执行。
支持对称多处理(symmetric multiprocessing,SMP)
自由

组成五部分:

  • 进程调度程序(SCHED)负责控制进程访CPU。 保证进程能够公平地访问 CPU,同时保证内核可以准时执行一些必需的硬件操作;
  • 内存管理程序(MM)使多个进程可以安全地共享机器的主存系统,并支持虚拟内存;
  • 虚拟文件系统(VFS)通过提供一个所有设备的公共文件接口,VFS 抽象了不同硬件设备的细节。此外,VFS 支持与其他操作系统兼容的不同的文件系统格式;
  • 网络接口(NET)提供对许多建网标准和网络硬件的访问;
  • 进程间通信(IPC)子系统为进程与进程之间的通信提供了一些机制。

内核裁剪

  1. 解压源码
    mv、tar
  2. 配置内核
    make config//文本传统配置
    make menuconfig //文本选单配置裁剪界面
    make xconfig //图像窗口配置
    make oldconfig//小修改
  3. 编译内核和模块
    make
    若有选择加载模块,应对模块编译:
    make modules
    make modules_install 编译模块并安装到标准模块目录
  4. 配置启动文件
    将编译好的内核及内核符号表文件拷贝到/boot目录,修改配置/etc/lilo.conf文件

移植
编写针对特定处理器的代码,关于任务调度、中断处理的代码根据cpu不同可能要重写
编写针对特定硬件平台的引导和初始化代码
编写针对特定外设的设备驱动程序代码

内核空间和用户空间

  • 32位地址形成4GB虚拟地址空间
  • 与x86类似,3G以下是用户空间,3G以上是内核空间
  • ARM将I/O也放在内存地址空间中,所以系统空间的一部分虚拟地址不是映射到物理内存,而是映射到一些I/O。

虚拟内存实现机制间的相互关系

实现机制,五个

首先内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址,在用户程序运行时如果发现程序中要用的虚地址没有对应的物理内存时,就发出了请页要求①;如果有空闲的内存可供分配,就请求分配内存②(于是用到了内存的分配和回收),并把正在使用的物理页记录在页缓存中③(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制,腾出一部分内存④⑤。另外在地址映射中要通过TLB(翻译后援存储器)来寻找物理页⑧;交换机制中也要用到交换缓存⑥,并且把物理页内容交换到交换文件中后也要修改页表来映射文件地址⑦。

Linux进程状态转换

Linux进程创建与终止

fork()
创建普通进程,copy on write(要复制父进程的页表)
vfork()
共享创建,完全无拷贝。(子进程作为父进程的一个单独线程在其地址空间运行,父进程阻塞),几乎与fork函数一样,除了不拷贝父进程的页表项。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec()。子进程不能向地址空间写入。现以彻底没多大用了。
clone()
介于fork()和vfork()之间,可以指定共享什么,拷贝什么。

fork函数返回值
父进程:子进程id
子进程:0
错误:负数

* ARM-Linux模块机制

六个部分构成,加载函数,卸载函数,注册函数,加载/卸载模块时做什么

  1. 模块的加载函数(必须)
    当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
  2. 模块的卸载函数(必须)
    当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
  3. 模块许可证声明
    模块许可证(LICENSE)声明描述内核模块的的许可权限,如果不声明LICENSE,模块被加载时,将接到内核被污染的警告。
  4. 模块参数(可选)
    模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。
  5. 模块导出符号(可选)
    内核模块可以导出符号(symbol,对应于函数或者是变量),这样其他模块就可以使用本模块中的变量或者是函数。
  6. 模块作者等信息声明(可选)
1
2
3
4
5
6
7
8
9
10
module_init(加载函数名);
module_exit(卸载函数名);

int 加载函数名(void){

}

void 加载函数名(void){

}

嵌入式文件系统

* Linux虚拟文件系统

作用,优势

解决了传统Unix文件系统只支持特定的文件系统,无法存取其他的文件系统的问题

解耦编码与文件系统的依赖、减少编程的负担、提高了系统的扩展性

NOR型闪存与NAND型闪存的特点

NOR型闪存的特点:

  • 具有独立的地址线、数据线,支持快速随机访问,容量较小;
  • 具有芯片内执行(XIP, eXecute In Place)的功能,按照字节为单位进行随机写;
  • NOR型闪存适合用来存储少量的可执行代码。

NAND型闪存的特点:

  • 地址线、数据线共用,单元尺寸比NOR型器件小,具有更高的价格容量比,可以达到高存储密度和大容量;
  • 读、写操作单位采用512字节的页面;
  • NAND更适合作为高密度数据存储。

NAND闪存管理中的问题

  • Flash的写入操作只能把对应位置的1修改为0,而不能把0修改为1(擦除Flash就是把对应存储块的内容恢复为1),因此,一般情况下,向Flash写入内容时,需要先擦除对应的存储区间,这种擦除是以块(block)
    为单位进行的。
  • 损耗均衡(Wear Leveling):闪存上的每个块都具有擦除次数的上限,被称为擦除周期计数(erase cycle count)。
  • 坏块处理问题: NAND器件中的坏块是随机分布的,由于消除坏块的代价太高,因而使用NAND器件的初始化阶段进行扫描以发现坏块,并将坏块标记为不可用(非0xFF)。
  • 掉电保护问题:文件系统应能保证在系统突然断电时,最大限度地
    保护数据,使文件恢复到掉电前的一个一致性状态

常见的FLASH文件系统

JFFS/JFFS2
主要用于NOR型闪存,基于MTD驱动层

特点是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃/掉电安全保护,提供“写平衡”支持等。

缺点是:

  • JFFS维护了几个链表来管理擦写块,根据擦写块上内容的不同情况,擦写块会在不同的链表上。回收废弃的块时,以不同的概率对各个链表上的块进行选择。
  • 当文件系统已满或接近满时,因为垃圾收集的关系而使jffs2的运行速度大大放慢。
  • 用概率的方法很难保证磨损均衡的确定性。在某些情况下,可能造成对擦写块不必要的擦写操作,在某些情况下会引起对磨损均衡调整的不及时。

YAFFS/YAFFS2
NAND型闪存而设计的一种日志型文件系统
yaffs与yaffs2的主要区别在于,前者仅支持小页(512 Bytes)NAND闪存,后者则可支持大页(2KB) NAND闪存。同时,yaffs2在内存空间占用、垃圾回收速度、读/写速度等方面均有大幅提升。

CRAMFS
用于保存只读的根文件系统
存储的文件形式是压缩的格式,所以文件系统不能直接在 Flash 上运行.无法对内容进行扩展。

根文件系统的作用及目录结构

根文件系统首先是内核启动时所mount的第一
个文件系统

结构:

  • bin 必要的用户命令(二进制文件)
  • sbin 必要的系统管理员命令
  • usr 大多数用户使用的应用程序和文件目录
  • proc 提供内核和进程信息的 proc文件系统
  • dev 设备文件及其他特殊文件
  • etc 系统配置文件
  • lib 必要的链接库,例如:C链接库、内核模块
  • *boot 引导加载程序使用的静态文件
  • *home 用户主目录
  • mnt 临时挂载的文件系统的挂载点
  • *opt 附加软件的安装目录
  • *root root用户主目录
  • tmp 临时文件目录
  • var 监控程序和工具程序存放的可变数据
    “*” 目录在嵌入式Linux上为可选的。

如何创建根文件系统

参考ppt实验步骤

嵌入式驱动设计

输入、输出方式控制

与外部设备的输入/输出的方式,主要包括:

  • 直接控制I/O方式:通过指令直接对端口进行输入、输出控制。如x86的in、out指令。* 内存映射方式:可以访问较大的地址空间,实现快速数据交换,如:ISA总线可以映射的空间为0xC8000~0xEFFF。
  • 中断方式:采用中断实现数据的输入/输出。
  • DMA方式: 采用DMA控制器,实现数据传输。
    • 数据量不大的情况下,如字符设备,常采用中断方式;
    • 大量数据传输的I/O设备,如磁盘、网卡等设备,常采用DMA方式,以减少CPU资源占用。

驱动程序与应用程序的区别;

设备驱动程序是内核的一部分
设备驱动程序(device driver)是操作系统中唯一知道控制器有几个寄存器及它们用途的程序。
驱动程序是指挥硬件工作的软件。它是应用程序与硬
件之间的一个中层软件层,为应用程序屏蔽硬件的细节

作用:
对设备的初始化和释放

  • 把数据从内核传到硬件/从硬件读数据到内核
  • 读取应用程序传送给设备文件的数据和回送应用程序请求的数据。这需要在用户空间,内核空间,总线以及外设之间传输数据
  • 检测和处理设备出现的错误

区别:

  1. 主动与被动的区别。应用程序有一个main函数,总是从些函数开始主动执行一个任务,而驱动程序安装之后,便停止工作,并等待被应用程序调用。
  2. 使用的库函数不同。
  3. 程序运行的区域不同。驱动程序工作在内核态;应用程序工作在用户态。

设备分类;

块设备 通过buffer或cache进行读写、支持块的随机访问
字符设备 无需缓冲直接读写,字节流一样被访问的设备
网络设备 通过BSD套接口访问,例如:socket、bind、listen、accept、send等系统调用

设备文件和设备号之间的关系;

设备文件
Linux抽象了对硬件设备的访问,可作为普通文件一样访问,使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作

每个设备文件都对应有两个设备号
主设备号,标识设备的种类,使用的驱动程序。2.4内核下是8位,2.6及之后内核为12位
次设备号,标识使用同一设备驱动程序的不同硬件设备。2.4内核下是8位,2.6及之后内核为20位

应用程序通过文件名使用设备文件
设备文件与驱动程序通过主设备号建立联系

设备文件接口(file operation);

标记,如何向系统注册
struct file_operations
系统试图使它对各类设备的输出、输入看起来就象是对普通文件一样。因此,把设备映射为一种特殊的文件

例:

1
2
3
4
5
6
7
static struct file_operations s3c440_fops={
owner:THIS_MODULE,
open:s3c2410_ts_open,
read:s3c2410_ts_read,
write:s3c2410_ts_write,
release:s3c2410_ts_release,
};

分配和释放设备号;

(1)安装驱动,注册主设备号
(2)创建设备文件,指向该设备类型,及主次设备号。
(3)应用程序,调用设备文件。
应用程序fd=open(“/dev/ad_0”,O_RDWR)

MAJOR(dev_t dev) 根据设备号获得主设备号
MINOR(dev_t dev) 根据设备号获得次设备号
MKDEV(int major, int minor) 根据次设备号与主设备号获得设备号

内核空间与用户空间之间复制数据的方法;

从用户态拷贝到内核态:
unsigned long copy_from_user(void to, const void __user from, unsigned long count);
从内核态拷贝到用户态:
unsigned long copy_to_user (void __user to, const void from, unsigned long count);

驱动实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
* a simple char device driver: globalmem without
mutex
*
* Copyright (C) 2014 Barry Song (baohua@kernel.org)
*
* Licensed under GPLv2 or later.
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h> // 文件说明、头文件
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define GLOBALMEM_SIZE 0x1000 // 虚拟设备内存:4K
#define MEM_CLEAR 0x1 // 控制命令
#define GLOBALMEM_MAJOR 230 // 主设备号

static int globalmem_major = GLOBALMEM_MAJOR; // 可选的模块参数,用于改变主设备号
module_param(globalmem_major, int, S_IRUGO);

// 定义结构体(虚拟设备),及对应指针
struct globalmem_dev {
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
};
struct globalmem_dev *globalmem_devp;

static int globalmem_open(struct inode *inode, struct file *filp)
{
filp->private_data = globalmem_devp; // 打开设备函数,私有数据设置
return 0;
}
static int globalmem_release(struct inode *inode, struct file *filp)
{
return 0; // 关闭设备
}

// ioctl()函数(清内存)
static long globalmem_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data; // 获取私有数据指针
switch (cmd) {
case MEM_CLEAR:
// 如果是MEM_CLEAR命令,则清内存
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}

// 读函数
static ssize_t globalmem_read(struct file *filp, char __user * buf,size_t size, loff_t * ppos)
{
unsigned long p = *ppos; //读取位置相对于文件头的偏移量
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_SIZE)//当前读取位置超出文件尾则直接返回0
return 0;
if (count > GLOBALMEM_SIZE - p)
// 如从当前位置开始读取的内容长度超出文件尾,
// 则读取长度为当前位置之后的全部内容
count = GLOBALMEM_SIZE - p;
if (copy_to_user(buf, dev->mem + p, count)) {
ret = -EFAULT;//读取信息,复制到用户空间缓冲区
} else {
*ppos += count;
ret = count;//移动当前读写位置,并提示读到的字节数
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
}
return ret;
}

//写函数
static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t size, loff_t * ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data;
if (p >= GLOBALMEM_SIZE)//当前写入位置超出文件尾则直接返回0
return 0;
if (count > GLOBALMEM_SIZE - p)
//如从当前位置开始写入的数据长度超出文件尾,
//则写入数据长度为全部剩余空间
count = GLOBALMEM_SIZE - p;
if (copy_from_user(dev->mem + p, buf, count)){
ret = -EFAULT;//写入信息,复制到内核空间缓冲区
} else {
*ppos += count;
//移动当前读写位置,并提示写入的字节数
ret = count;
printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
}
return ret;
}

/*
移动当前读写位置函数:seek()函数支持从文件头(orig为0),
以及文件当前位置(orig为1)开始移动读写位置。
*/
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0:
if (offset < 0) {
//从文件头开始,如偏移量为负
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE) {
//从文件头开始,如偏移量超出文件长度
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
//移动当前读写位置
ret = filp->f_pos;
break;
case 1:
if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
//从当前位置开始,如加偏移量超出文件尾
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
//从当前位置开始,如加偏移量小于0
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;//移动当前读写位置
break;
default:
//既不是从文件头开始,也不是从当前位置开始,返回错误标志
ret = -EINVAL;
break;
}
return ret;
}

//操作集定义及赋值
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
//设备注册函数
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);//生成设备号
cdev_init(&dev->cdev, &globalmem_fops);//初始化设备文件结构体
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);//向系统注册设备
if (err)
//如果错误,输出错误信息
printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}

//模块加载函数
static int __init globalmem_init(void)
{
int ret;
dev_t devno = MKDEV(globalmem_major, 0);//生成主设备号
//根据是否已有主设备号,选用不同函数向系统申请注册设备号
if (globalmem_major)
ret = register_chrdev_region(devno, 1, "globalmem");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (ret < 0)
return ret;
globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);//申请设备内存
if (!globalmem_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
globalmem_setup_cdev(globalmem_devp, 0);//注册设备
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(globalmem_init);

//模块卸载函数
static void __exit globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);//注销设备
kfree(globalmem_devp);//释放内存空间
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);//注销设备号
}
module_exit(globalmem_exit);
//作者说明、许可证声明
MODULE_AUTHOR("XXXX");
MODULE_LICENSE("GPL v2");
文章目录
  1. 1. 嵌入式系统设计A 复习笔记
  2. 2. 嵌入式系统设计基础
    1. 2.1. - 嵌入式系统的前世今生及应用领域
    2. 2.2. - 嵌入式系统的发展阶段
    3. 2.3. 嵌入式系统的定义
    4. 2.4. 嵌入式系统与桌面通用系统的区别
    5. 2.5. 嵌入式处理器的基本特征
      1. 2.5.1. 组成
      2. 2.5.2. 存储器包括主存和外存
      3. 2.5.3. 嵌入式系统的大多数输入、输出接口和部分设备已经集成在嵌入式微处理器中
      4. 2.5.4. 相对通用处理器,嵌入式处理器有以下特点
    6. 2.6. 嵌入式处理器的种类
      1. 2.6.1. 嵌入式微处理器 MPU
      2. 2.6.2. 微控制器(单片机 MCU
      3. 2.6.3. 嵌入式DSP处理器 EDSP
      4. 2.6.4. 片上系统 SoC
    7. 2.7. 典型嵌入式处理器的特点及应用场景
    8. 2.8. 嵌入式软件系统的体系结构及各个层次的任务
    9. 2.9. 嵌入式操作系统的主要特性
    10. 2.10. - 嵌入式操作系统的分类
    11. 2.11. 典型嵌入式操作系统的特点及应用场景(Linux、VXwork、QNX、uC/OS)
      1. 2.11.1. linux
      2. 2.11.2. VxWorks
      3. 2.11.3. QNX
      4. 2.11.4. μC/OS-II
  3. 3. ARM微处理器概述与编程模型
    1. 3.1. ARM微处理器的特点
    2. 3.2. CISC 与 RISC体系结构的特点及区别
    3. 3.3. 冯.诺依曼结构和哈佛结构的特点及区别
    4. 3.4. ARM微处理器的工作状态及区别
    5. 3.5. ARM体系结构的存储器格式
    6. 3.6. ARM处理器的7种工作模式
    7. 3.7. 程序状态寄存器(CPSR)的结构与作用;(SPSR?)
    8. 3.8. 异常的响应及返回
    9. 3.9. ARM处理器的寻址方式
    10. 3.10. ARM的指令格式(立即数、条件码)
      1. 3.10.1. 立即数
      2. 3.10.2. 条件码
    11. 3.11. ARM9的指令系统
  4. 4. 嵌入式Linux系统
    1. 4.1. Linux发展的5大支柱;
    2. 4.2. Linux的发行版(RedHat家族与Debian家族);
    3. 4.3. Linux版本的发展;
    4. 4.4. 命令
    5. 4.5. Linux程序开发过程及其使用的工具;
  5. 5. 嵌入式软件编程技术
    1. 5.1. 汇编语言的简单程序编程;
    2. 5.2. 可重入函数及其解决方法;
    3. 5.3. 中断及处理的硬件部分及软件部分;
    4. 5.4. 高级语言与低级语言混合编程;
  6. 6. 开发环境和调试技术
    1. 6.1. 嵌入式软件开发流程;
    2. 6.2. 交叉编译与本地编译的区别;
    3. 6.3. 交叉调试与本地调试的区别;
    4. 6.4. 嵌入式开发环境构建;
    5. 6.5. 主从机通信环境构建;
    6. 6.6. 开发环境构建中的仿真技术;
    7. 6.7. 嵌入式交叉开发环境构建;
    8. 6.8. 远程调试的方法(stub)及常见的三种方法;
    9. 6.9. Linux内核的三种调试方式;
  7. 7. BootLoader
    1. 7.1. 嵌入式系统启动流程;
    2. 7.2. 典型引导加载方式;
      1. 7.2.1. 磁盘启动方式
      2. 7.2.2. 网络启动
      3. 7.2.3. Flash 启动方式
    3. 7.3. Boot Loader概念;
    4. 7.4. BootLoader操作模式;
    5. 7.5. Boot Loader 的 stage1与stage2 主要步骤;
    6. 7.6. U-Boot
  8. 8. ARM-Linux内核
    1. 8.1. Linux内核的特点
    2. 8.2. 内核裁剪
    3. 8.3. 内核空间和用户空间
    4. 8.4. 虚拟内存实现机制间的相互关系
    5. 8.5. Linux进程状态转换
    6. 8.6. Linux进程创建与终止
    7. 8.7. * ARM-Linux模块机制
  9. 9. 嵌入式文件系统
    1. 9.1. * Linux虚拟文件系统
    2. 9.2. NOR型闪存与NAND型闪存的特点
    3. 9.3. NAND闪存管理中的问题
    4. 9.4. 常见的FLASH文件系统
    5. 9.5. 根文件系统的作用及目录结构
    6. 9.6. 如何创建根文件系统
  10. 10. 嵌入式驱动设计
    1. 10.1. 输入、输出方式控制
    2. 10.2. 驱动程序与应用程序的区别;
    3. 10.3. 设备分类;
    4. 10.4. 设备文件和设备号之间的关系;
    5. 10.5. 设备文件接口(file operation);
    6. 10.6. 分配和释放设备号;
    7. 10.7. 内核空间与用户空间之间复制数据的方法;
  11. 11. 驱动实例