Linux 设备驱动入门
无操作系统
硬件、驱动和应用软件的对应关系
软件设计中的思路:高内聚、低耦合。
有操作系统
操作系统的意义
一个复杂的操作系统需要处理多个并发的任务,没有操作系统,想要完成任务并发时很困难的。并且操作系统给我们提供了内存管理机制。
Linux 设备的分类与特点
-
字符设备:必须以串行顺序依次进行访问的设备,如磁带驱动器、鼠标等。
-
块设备:可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。
-
网络设备。
注:字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲,但是,字符设备与块设备并没有明显的界限,如Flash设备,符合块设备的特点,但是仍然作为一个字符设备来访问。
字符设备与块设备的驱动设计呈现出很大的差异,但是对于用户而言,并无差异。
Linux 设备驱动与整个软硬件系统的关系
应用程序可以使用Linux 的系统调用接口编程,当也可使用C库函数,处于代码的可移植性的目的,后置更值得被推荐。
Linux 设备驱动的重点、难点
Linux 设备驱动的学习是一项好烦的工程,包含如下的重点、难点。
- 编写Linux 设备驱动要求工程师有非常好的演讲基础,懂得SRAM、Flash、SDRAM、磁盘的读写方式,以及UART、USB等设备的接口以及轮询、终端、DMA的原理,PCI总线的工作方式以及CPU的内存管理单元(MMU)等。
- 编写Linux 设备驱动要求工程师有非常好的C语言基础,能灵活运用C语言的结构体、指针、函数指针以及内存动态申请与释放等。
- 编写Linux 设备驱动要求工程师有一定的Linux 内核基础,虽然不要求工程师对内核各个部分有深入的研究,但是至少要明白驱动与核心的接口。
- 编写Linux 设备驱动要求工程师有非常好的多任务并发控制和同步基础,因为在驱动中和大量使用自旋锁、互斥、信号量·、等待队列等并发与同步机制。
驱动设计的硬件基础
通用处理器
通用处理器(GPD)并不针对特定的应用领域进行体系结构和指令集优化,它们具有一般化的通用体系结构和指令集,以求支持复杂的运算并易于添加新开发的功能。
主流的的嵌入式CPU核:
ARM、MIPS、PowerPC、68K/COLDFIRE
从体系架构分类
- 冯诺依曼结构:将程序指令存储器和数据存储器并在一起的存储器结构。程序指令存储地址和数据存储地址只想同一个存储器的不同物理位置,因此程序指令和数据宽度相同。
- 哈佛结构:将程序指令和数据分开存储,指令和数据可以有不同的数据宽度。此外,哈佛结构还采用了独立的程序总线和数据总线,分别作为CPU与每个存储器之间的专用通信路径,具有较高的执行效率。
从指令集分类
- RISC(精简指令集计算机):强调尽可能减少指令集、指令单周期执行,但是目标代码和更大。
- CISC(复杂指令集计算机):强调增强指令集的能力、减少目标代码的数量,但是指令复杂,指令周期长。
ARM、MIPS、PowerPC等CPU内核都采用了RISC指令集。目前RISC和CISC二者的融合非常明显。
存储器
存储器主要分类
只读储存器(ROM)、闪存(Flash)、随机存取存储器(RAM)、光、磁介质存储器。
ROM还可再细分为不可编程ROM、可编程ROM(PROM)、可擦除可编程ROM(EPROM)和电可擦除可编程ROM(EEPROM),EEPROM完全可以用软件来擦鞋,已经非常方便了。
目前ROM有被Flash替代的趋势,NOR(或非)和NAND(与非)是市场上两种主要的Flash闪存技术。
NAND Flash的接口信号
- I/O总线:地址、指令和数据通过这组总线传输,一般为8位或16位。
- 芯片启动(Chip Enable,CE#):如果没有检测到CE信号,那么,NAND器件就保持待机模式,不对任何控制信号做出相应。
- 写使能(Write Enable,WE#):WE#负责将数据、地址或指令写入到NAND中。
- 读使能(Read Enable,RE#):RE#负责允许数据输出。
- 指令锁存使能(Command Latch Enable,CLE):当CLE为高时,在WE#信号的上升沿,指令将被锁存到NAND指令寄存器中。
- 地址锁存使能(Address Latch Enable,ALE):当ALE为高时,在WE#信号的上升沿,地址被锁存到NAND地址寄存器中。
- 就绪/忙(Ready/Busy,R/B#):如果NAND器件忙,R/B#信号将变低。该信号是漏极开路,需要采用上拉电阻。
Flash编程原理
Flash的编程远离都是只能将1写为0,而不能将0写为1。所有在Flash编程之前,必须将对应的块擦除,而擦除的过程就是把所有位都写成1的过程,块内的所有字节都变成0xFF。
特定类型的RAM
-
NVRAM:非易失性RAM
既然是RAM,就是易失性的,为什么有一类非易失性的RAM呢?
实际上NVRAM借助带有备用电源的SRAM或解除NVM(如EEPEAM)存储SRAM的信息并恢复来实现。NVRAM的特点是完全像SRAM一样读写,而且写入的信息掉电不丢失,不需要EEPRAM和Flash的特定擦除和编程操作。NVRAM多用于存放系统中的参数信息。
-
DPRAM:双端口RAM
DPRAM的特点是可以通过2个端口同时访问,具有2套完全独立的数据总线、地址总线和读写控制线,通常用于2个处理器之间的数据交互。
-
CAM:内容寻址RAM
CAM是以内容进行寻址的存储器,是一种特殊的存储阵列RAM,它的主要工作机制就是将一个输入数据项与存储在CAM中的所有数据项自动同时进行比较,判别该输入数据项与CAM中存储的数据项是否匹配,并输出该数据项对应的匹配信息。
串口
RS-232、RS-422与RS-485都是串行数据接口标准,最初都是由电子工业协会(EIA)制订并发布的。
RS-232在1962年发布,命名位EIA-232-E。之后发布的RS-422定义了一种平衡通信接口,它是一种单机发送、多机接收的单向、平衡传输规范,被命名位TIA/EIA-422-A标准。RSS-422改进了RS-232通信距离短、速率低的缺点。为进一步扩展应用范围,EIA又于1983年在RS-422的基础上制定了RS-485标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,并扩展了总线共模范围,被名为为TIA/EIA-485-A标准。
信号定义
- RTS:用来表示DTE请求DCE发送数据,当终端要发送数据时,使该信号有效。
- CTS:用来表示DCE准备好接收DTE发来的数据,是对RTS的相应信号。
- TxD:DTE通过TxD将串行数据发送到DCE。
- RxD:DTE通过RxD接收从DCE发来的串行数据。
- DSR:有效(ON状态)表明DCE可以使用。
- DTR:有效(ON状态)表明DTE可以使用。
- DCD:当本地DCE设备收到对方DCE设备送来的载波信号时,使DCD有效,通知DTE准备接收,并且由DCE将接收到载波信号调节为数字信号,经RXD线送给DTE。
- Ringing-RI:当MODEM收到交换台送来的政令呼叫信号是,是改型号有效(ON状态),通知终端,已被呼叫。
I^2^C
I^2^C(内置集成电路)总线是由Philips公司开发的两线式串行总线,产生于20世纪80年代,用于连接微控制器极其外围设备。I^2^C简单而有效,占用很少的PCB(印刷电路板)空间,芯片管脚数量少,设计成本低。I^2^C支持多主控模式,任何能够进行发送和接收的设备都可以成为主设备。主控能够控制数据的传输和时钟频率,任意时刻只能有一个主控。
组成I^2^C总线的两个信号为数据线SDA和时钟SCL。为了避免总线信号的混乱,要求各设备连接到总线的输出端必须是开漏输出或集电极开路输出的结构。总线空闲时,上拉电阻使SDA和SCL线都保持高电平。根据开漏输出或集电极开路输出信号的“线与”逻辑,I^2^C总线上任意器件输出低电平都会使相应总线上的信号线变低。
注:“线与”逻辑指的使两个或两个以上的输出直接互联就可以实现的“AND”的逻辑功能,只有输出端使开漏(对于CMOS器件)输出或集电极开路(对于TTL器件)输出时才满足此条件,工程师一般以“OC门”简称开漏或集电极开路
以太网接口
以太网接口有MAC(以太网媒体接入控制器)和PHY(物理接口收发器)组成。以太网MAC有IEEE-802.3以太网标准定义,实现了数据链路层。10BaseT和100BaseTX PHY两种实现的帧格式是一样的,但信令机制不同,而且10BaseT采用曼彻斯特编码,100BaseTX采用4B/5B编码。
MAC和PHY之间采用MII(媒体独立接口)连接,它是IEEE-802.3定义的以太网行业标准,包括1个数据接口和1个MAC和PHY之间的管理接口。数据接口包括分别用于发送和接收的两条独立信道,每条信道都有自己的数据、时钟和控制信号,MII数据接口总共需要16个信号。MII管理接口包含两个信号,一个是时钟信号,另一个是数据信号。通过管理接口,上层能监视和控制PHY。
ISA
ISA(工业标准结构总线)起源于1981年IBM生产的以Intel 8088为CPU的IBM-PC为计算机,开始时总线宽度为8位。1984年退出的IBM-PC/AT系统将ISA总线扩充为16位数据总线宽度,同时地址总线宽度也由20位扩充到了24位,气候退出的EISA(扩展的ISA)采用32位地址线,数据总线也扩展位32位,但仍保持了与ISA的兼容。
ISA的信号可以分为:
- 总线基本信号:ISA总线工作所需要的最基本信号,含复位、时钟、电源、地址等。
- 总线访问信号:用于访问ISA总线设备的地址线、数据线以及相应的应答信息。
- 总线控制信号:中断和DMA请求。
上图各个信号的详细定义如下:
- RESET、BCLK:复位及总线基本时钟,BLCK为8MHz。
- SA19~SA0:存储器及I/O空间20位地址,带锁存。
- LA23~LA17:存储器及I/O空间20为地址,不带锁存。
- BALE:宗宪帝制锁存,外部有锁存器的选通。
- AEN:地址允许,表明CPU让出总线,DMA开始。
- SMEMR#、SMEMW#:8位ISA存储器读写控制。
- MEMR#、MEMW#:16位ISA存储器读写控制。
- SD15~SD0:数据总线,访问8位ISA卡时高8位自动传送到SD7~SD0.
- SBHE#:高字节允许,打开SD15~SD8的数据通路。
- MEMCS16#、IOCS16#:ISA卡发出此信号确认可以进行16位传送。
- I/OCHRDY:ISA卡准备信号,可控制插入等待周期。
- NOWS#:有效则暗示不用插入等待周期。
- I/OCHCK#:ISA卡奇偶校验错。
- IRQ15、IRQ14、IRQ12~IRQ9、IRQ7~IRQ3:中断请求。
- DRQ7~DRQ5、DRQ3~DRQ0:ISA卡DMA请求。
- DACK7#~DACK5、DACK3#~DACK0#:DMA请求响应。
- MASTER#:ISA主模块确立信号,ISA发出此信号,与主机DMAC(DMA控制器)配合使ISA卡成为主模块。
PCI和cPCI
PCI外部(外部不见互联)是由Intel于1991年推出的一种局部总线,作为一种通用的总线接口标准,它在目前的计算机系统中得到了非常广泛的应用。PCI提供了一组完整的总线接口规范,其目的是描述如何将计算机系统中的外围设备以一种结构化和可控话的方式连接在一起,给出了外围设备在连接时的电气特性和行为规约,并且详细定义了计算机系统中的各个不同部件之间应该如何正确进行交互。PCI总线具有如下特点。
- 数据总线32位,可扩充到64位。
- 可进行突发(burst)模式传输。
- 总线操作与处理器-存储器子系统操作作并行。
- 总线时钟频率为33MHz或66MHz,最高传输率可达528MB/s。
- 采用中央集中式总线仲裁。
- 支持全自动配置、资源分配,PCI卡内有设备寄存器组为系统提供卡的信息,可实现即插即用。
- PCI总线规范独立于微处理器,通用性好。
- PCI设备可以完全作为主控设备控制总线。
当PCI刚加电时,卡上只有配置空间是可以被访问的,因而PCI卡开始不能由驱动或用户程序访问,这与ISA卡有本质的区别(CPU可直接读取ISA卡在存储空间或I/O空间映射的地址)。PCI配置空间保存着该卡工作所需要的所有信息,如厂家、卡功能、资源要求、处理能力、功能模块数量、主控卡能力等。通过对这个空间信息的读取与编程,可完成对PCI卡的配置。
PCI配置空间空256字节,主要包括如下信息:
- 制造商表示(Vendor ID):由PCI组织分配给厂家。
- 设备标识(Device ID):按产品分类给本卡的编号。
- 分类码(Class Code):本卡的功能的分类码,如图卡、显示卡、解压卡等。
- 申请存储器空间:PCI卡内有存储器或以存储器编址的寄存器和I/O空间,为使驱动程序和应用程序能访问它们,需要申请CPU的一段存储区域以进行定位。配置空间的基地址寄存器用于此目的。
- 申请I/O空间:配置空间的基地址寄存器也用来进行系统I/O空间的申请。
- 中断资源申请:配置空间中的中断引脚和中断向用来向系统申请中断资源。中断资源的申请通过中断引脚(interrupt pin)和中断线(interrupt line)来完成的。偏移3Dh处为中断引脚寄存器,器值表明PCI设备使用了哪一个中断引脚,对应关系为1-INTA#、2-INTB#、3-INTC#、4-INTD#。
PCI总线上的信号大体可分为如下几组。
- 系统接口信号。
- 地址与数据接口信号。
- 接口控制信号。
- 仲裁信号。
- 错误报告信号。
- 中断接口信号。
- 其他接口信号。
如上图,这些信号的详细定义如下:
- CLK:系统时钟。
- AD31~AD0:地址和数据服用信号线信号。
- C/BE3~C/BE:总线命令和地址使能信号。
- PAR:奇偶校验信号。
- FPAM E#:帧周期信号,指示总线操作起始和终止。
- IRDY#:主设备准备好信号。
- TRDY#:目标设备准备好信号。
- STOP#:目标设备要求终止当前数据传输信号。
- DEVSEL#:目标设备选中信号。
- IDSEL:配置空间读写时的片选信号。
- LOCK#:总线锁定信号。
- RET#:复位信号。
- INTA#、INTB#、INTC#和INTD#;中断请求。
- REQ#、GNT#:PCI总线请求与仲裁后的授权。
- AD63-AD32、C/BE7-4等:作用与64为扩展的PCI总线。
cPCI(Compact PCI,紧凑型PCI)是以PCI电气规范为标准的高性能工业用总线,结合了VME(Visa Module Eurocard,维萨信用卡模块欧洲卡)的高性能、可扩展性和可靠性与PCI标准的经济有效和灵活性。cPCI的CPU以及外设与标准PCI是相同的,使用与传统PCI相同的芯片和软件,操作系统、驱动换个应用程序都感觉不到两者的区别。
原理图分析
原理图分析的内容
原理图分析的含义是指通过阅读电路板的原理图获得各种存储器、外设所使用的硬件资源,主要包括存储器和外设控制芯片所使用的片选、中断和DMA资源。通过分析片选得出芯片的内存、I/O基地址,通过分析中断、DMA信号获得芯片使用的中断信号和DMA通道。
原理图的分析方法
原理图的分析方法是以CPU为中心线存储器和外设辐射,步骤如下:
-
阅读CPU部分,获知CPU的哪些片选、中断和集成的外设控制器被使用,列出这些元素a、b、c……
CPU引脚比较多的时候,芯片可能会被分为几个模块单独被画在原理图的不同页上,这时候应该把相应的部分都分析到位。
-
对第一步中列出的元素从原理图中对应的外设和存储器电路中分析出实际的使用情况。
硬件原理图包含如下元素:
- 符号(symbol):symbol描述芯片外围引脚以及引脚的信号,对于复杂的芯片,可能被分割为几个symbol。在symbol中,一般把属于同一个信号群的引脚排列在一起。
- 网络(net):描述芯片、接插件和分离元器件引脚之间的互连关系,每个网络需要根据信号的定义赋予一个合适的名字,如果没有给网络取名字,EDA软件会自动添加一个默认的网络名。
- 描述:原理图中会添加一些文字来辅助描述原理图(类似源代码中的注释),如每页页脚会有该页的功能描述,对重要的信号,在原理图的相应symbol和net也会附带文字说明。
硬件时序分析
时序分析的概念
时序分析的意思是让芯片之间的访问满足芯片手册中时序图信号有效的先后顺序、采样建立时间(setup time)和保持时间(hold time)的要求,在电路板工作不正常的时候,准确地定位时序方面地问题。
- 建立时间:指在触发器地时钟信号边沿到来以前,数据已经保持稳定不变的时间,如果建立时间不够,数据将不能在这个时钟边沿被打入触发器。
- 保持时间:是指在触发器的时钟信号边沿到来以后,数据还需要稳定不变的时间,如果保持时间不够,数据同样不能被打入触发器。
Linux 内核及内核编程
Linux 内核的发展与演变
Linux 操作系统是UNIX操作系统的一种克隆系统,诞生于1991年10月5日(第一次正式向外公布的时间)。Linux 操作系统的诞生、发展核成长过程依赖着5个重要支柱。
-
UNIX操作系统
UNIX操作系统是美国贝尔实验室在1969年夏在DEC PDP-7小型计算机上开发的一个分时操作系统。Linux 可以看作UNIX操作系统的一个克隆版本。
-
Minix操作系统
Minix操作系统也是UNIX的一种克隆系统,它于1987年开发完成。开放源代码Minix系统的出现,使得学习者众多。Linux 刚开始就是参照Minix系统于1991年才开始开发。
-
GNU计划
GNU计划和自由软件基金会(FSF)创办于1984年。GNU是“GNU’s Not UNIX”的缩写。GNU项目开发出了emacs编辑系统、bash shell程序、gcc系列编译程序、gdb调试程序等。这些程序为Linux 操作系统的开发创造了一个合适的环境,是Linux 诞生的基础之一。没有GNU软件环境,Linux 将寸步难行。严格而言,“Linux ”应该被称为“GNU/Linux ”系统。
-
Posix标准
Posix(可以指的操作系统接口)是由IEEE和ISO/IEC开发的一组标准。该标准给予现有的UNIX实践和经验完成,描述了操作系统的条用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植。
-
Internet
如果没有Internet,没有遍布全世界的五十计算机骇客的无私奉献,那么Linux 最多只能发展到0.13(0.95)版地水平。从0.95版开始,对内核的许多改进和扩充均以其他人为主了,而Linus已经其他maintainer的主要工作编程对内核的维护和决定是否采用某个补丁程序。
Linux 2.6内核的特点
- 新的调度器:2.6版本的Linux 内核使用了新的进程调度算法,它在高负载的情况下执行的极其出色,并且有很多处理器是也可以和好地扩充。
- 内核抢占:在2.6版本地Linux 内核中,一个内核任务可以被抢占,从而提高系统地实时性。这样做罪主要地优势在于,可以极大地增强系统用户地交互性,用户将会觉得鼠标单击和击键事件得到了更快速的响应。
- 改进的线程模型:2.6版本的Linux 中线程操作速度得以提高,可以处理任意数目的线程,最大可以到20亿
- 虚拟内存的变化:从虚拟内存的角度来看,新内核融合了r-map(反向映射)技术,显著改善虚拟内存在一定程度负载下的性能。、
- 文件系统:2.6版内核增加了对日志文件系统功能的支持,解决了2.4版在这方面的不足。2.6版内核在文件系统上的关键变化还包括对扩展属性及Posix标准访问控制的支持。etx2/etx3作为大多数Linux 系统缺省安装的文件系统,在2.6版内核中增加了对扩展属性的支持,可以给制定的文件系统中嵌入元数据。
- 音频:新的Linux 音频体系结构ALSA取代了缺陷很多的旧的OSS。新的声音体系结构支持USB音频和MIDI设备,并支持全双工重放等功能。
- 总线:SCSI/IDE子系统经过大幅度的重写,解决和改善了以前的一些问题。比如2.6版内核可以直接通过IDE驱动程序来支持IDE CD/RW设备,而不必像以前一样要使用一个特别的SCSI模拟驱动程序。
- 电源管理支持ACPI(高级电源配置管理界面),用于调整CPU在不同的负载下工作于不同时钟频率以降低功耗。
- 联网和IPSec:2.6内核中加入了对IPSec的支持,删除了原来内核内置的HTTP服务器khttp,加入了对新的NFSv4(网络文件系统)客户机/服务器的支持,并进行对IPv6的支持。
- 用户界面层:2.6内核重写了帧缓冲/控制台层,人机界面层还加入了对近乎所有接口设备的支持。
Linux 内核的编译
在编译内核时,可以使用如下命令:
#make config(基于文本的最为传统的配置界面,不推荐使用)
#make menuconfig(基于文本菜单的配置界面)
#make xconfig(要求安装QT)
#make gconfig(要求安装GTK+)
在配置Linux 内核所使用的make config、make menuconfig、make xconfig、make gconfig中,最值得推荐的是make menuconfig,它不依赖于QT或GTK+,并且非常直观。
内核配置包含的项目相当的多,arch/arm/configs/ldd64101cd_defconfig文件包含了LDD6410的默认配置,因此只需要运行
就可以为LDD6410开发板配置内核。make ldd64101cd_defconfig
编译内核和模块的方法是:
make zImage
make modules
执行完上述命令后,在源代码的根目录下会得到未压缩的内核映像vmLinux 和内核符号表文件System.map,在arch/arm/boot/目录会得到压缩的内核映像zImage,在内核各对应目录的得到选中的内核模块。
Linux 2.6内核的配置系统由以下的3个部分组成。
- Makefile:分布在Linux 内核源代码中的Makefile,定义Linux 内核的编译规则。
- 配置文件(Kconfig):给用户提供配置选择的功能。
- 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面和图形界面)。这些配置工具都是使用脚本语言,如Tcl/TK、Perl等编写。
使用make config、make menuconfig等命令后,会生成一个.config配置文件,记录哪些部分被编译入内核、哪些部分被编译为内核模块。
Kconfig和Makefile
在Linux 内核中增加程序需要完成以下3项工作。
- 将编写的源代码拷入Linux 内核源代码的相应目录。
- 在目录的的Kconfig文件中增加关于新源代码对应项目的编译配置选项。
- 在目录的Makefile文件中增加对新源代码的编译条目
makefile
makefile的语法主要包括如下几个方面:
-
目标定义
目标定义就是用来定义那些内容要作为编译模块,哪些妖编译并连接进内核。
例如:
obj-y += foo.o
表明要由foo.c或foo.s文件编译得到foo.o并连接进内核,而obj-m则要表示该文件要作为模块编译。除了y、m以外的obj-x形式的目标都不会被编译。
-
多文件模块的定义
最简单的Makefile如上一节一句话的形式就够了,如果一个模块由多个文件组成,会稍微复杂一些,这时候应采用模块名加-y或-objs后缀的形式来定义模块组件的组成文件。如下面的例子。
#
#Makefile for the Linux ext2-filesytem routines.
#
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y :=balloc.o dir.o file.o fsync.o ialloc.o inode.o \
iocl.o namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) +=xattr.o xattr_user.o xatte_trused.o
ext2-$(CONFIG_EXT2_FS_POSIX_ACL) +=acl.o
ext2-$(CONFIG_EXT2_FS_SECURITY) +=xattr_security.o
ext2-$(CONFIG_EXT2_FS_XIP) +=xip.o
模块的名字为ext2,由balloc.o、dir.o、file.o等多个目标文件最终链接生成ext2.o直至ext2.ko文件。
-
目录层次的迭代
如下:
obj-$(CONFIG_EXT2_FS) +=ext2/
当CONFIG_EXT2_FS的值为y或m时,kbuild就会把ext2目录列入向下迭代的目标中。
Kconfig
内核配置脚本文件主要包括如下几个方面:
- 菜单入口
- 菜单结构
Linux 内核的引导
引导Linux 系统的过程包括很多阶段,以引导X86 PC为例。
![]()
- 当系统上电或复位时,CPU将会把PC指针赋值为一个特定的地址0xFFFF0并执行该地址处的指令。在PC机中,该地址位于BIOS,它保存在主板上的ROM或Flash中。
- BIOS运行时按照CMOS的设置定义的启动设备顺序来搜索处于活动状态并且可以引导的设备。
- 主引导加载程序查找并加载次引导加载程序。它在分区表中查找活动分区,当找到一个活动分区时,扫描分区表中的其他分区,一确保它们都不是活动的。当这个过程验证完成之后,就将活动分区的引导记录从这个设备中读入RAM中并执行它。
- 次引导加载程序加载Linux 内核和可选的初试RAM磁盘,将控制权交给Linux 内核源代码。
- 运行被加载的内核,并启动用户空间应用程序。
嵌入式系统中的Linux 的引导过程与之类似,但一般更加简洁。不论具体以怎样的方式实现,只要有如下特征就可以称其为Bootloader。
- 可以在系统上电或复位的时候以某种方式执行,这些方式包括被BIOS引导执行、直接在NOR Flash中执行、NAND Flash中的代码被MCU自动拷入内部或外部RAM执行等。
- 能将U盘、磁盘、光盘、NOR/NAND Flash、ROM、SD卡等存储介质,甚至网口、串口中的操作系统加载到RAM并将控制权交给操作系统源代码执行
Linux 下的C编程特点
Linux 程序的命名习惯和Windows程序的命名习惯以及著名的匈牙利命名发有很大的不同。
在Windows程序中,习惯以如下方式命名宏、变量和函数:
#define PI 3.141 592 6
int minValue,maxValue; /*变量:第一个单词全小写,其后的单词第一个字母大写*/
void SendDate(void); /*函数:所有单词第一个字母都大写*/
通过第一个单词的首字母是否大写可以区分名称属于变量还是属于函数。
在Linux 下的命名习惯为:
#define PI 3.141 592 6
int min_value,max_value;
void send_data(void);
总结就是Linux 喜欢使用下划线。
内核下的Documentation/CodingStyle描述了Linux 内核对编码风格的要求,内核下的scripts/checkpath.pl提供了一个检查代码风格的脚本。
GUN C与ANSI C
Linux 上可用的C编译器时GUN C编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。GUN C对标准C进行了一系列扩展,以增强标准C的功能。
-
零长度和变量长度数组
GUN C允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。
例如:
struct var_data{
int len;
char data[0];
};
char data[0]仅仅意味着程序中通过var_data结构体实力的data[index]成员可以访问len之后的第index个地址,它并没有为data[]数组分配内存,因此
sizeo(structvar_data)=sizeof(int)
假设struct var_data的数据域就保存在struct var_data紧接着的内存区域,则通过如下代码可以遍历这些数据:
struct var_data s;
for (i=0; i<s.len; i++)
printf("%x",s.data[i]);
-
case范围
GUN C支持case x…y这样的语法,区间[x,y]的数都会满足这个case条件,如下:
switch (ch){
case '0'...'9': c-='0';
break;
case 'a'...'f': c-='a'-10;
break;
case 'A'...'F'-='A'-10;
break;
}
-
语句表达式
GNU C把包含在括号中的符合语句看做是一个表达式,称之为语句表达式,它可以出现在任何允许表达式的地方。
-
typeof关键字
-
可变参数宏
标准C就支持可变参数函数,意味着函数的参数是不固定的,而在GUN C中宏也可以接受可变数目的参数。
-
标号元素
标准C要求数组或结构体的初始化必须以固定的顺序出现,在GUN C中,通过指定索引或结构体成员名,允许初始化值以任意顺序出现。
指定数组索引的方法是在初始化值前添加"[INDEX]=",当然也可以用"[FIRST...LAST]="的形式指定一个范围。
例如,下面的代码定义一个数组,并把其中的所有元素赋值为"0":
unsigned char data[MAX]={[0 ... MAX-1]};
下面的代码借助结构体成员名初始化结构体:
struct file_oprations ex2_file_operations={
llseek : generic_file_llseek,
read : generic_file_read,
write : generic_file_write,
ioctl : ext2_ioctil,
mmap : generic_file_mmap,
open : generic_file_open,
}
但是,在Linux 2.6推荐类似的代码应该尽量采用标准C的方式。
-
当前函数名
-
特殊属性声明
GUN C允许声明函数、变量和类型的特殊属性,以便进行手工的代码优化和定制代码检查的方法。
-
内建函数
GUN C提供大量内建函数,其中大部分是标准C库函数的GUN C编译器内建版本,与对应的标准C函数功能相同。
do{} while(0)
在Linux 中,会大量使用do{} while(0)这样的语句,在Linux 中这样的书写方式主要是为了保证引用宏定义时较为安全。保证宏定义的使用者能无编译错误地使用宏,它不对其他使用者做任何假设。
goto
Linux 内核源代码中对goto地引用非常广泛,但是一般只限于错误处理中。goto用于错误处理地用法实在是简单而高效,只需要保证在错误处理时注销、资源释放等于正常地注册、资源申请顺序相反。
Linux 内核模块
Linux 内核模块简介
Linux 内核地整体结构已经非常啪嗒,而包含的组件也非常多,我们怎样把需要地部分都包含在内核中呢?
一种方法是把所有需要地功能都编译到Linux 内核。这回导致两个问题,一是生成的内核会很大,而是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。
Linux 提供了这样的一种机制:模块。
模块具有这样的优点。
- 模块本身不被编译进内核映像,从而控制了内核大小。
- 模块一旦被加载,他就和内核中的其他部分完全一样。
Linux 内核模块程序结构
一个Linux 内核模块主要与如下几个部分组成。
-
模块加载函数(必须)
当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
-
模块卸载函数(必须)
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
-
模块许可证声明(必须)
许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将内核收到被污染(kernel tainted)的警告
-
模块参数(可选)
模块参数是模块被加载的时候可以被传递getaway德1值,它本身对应模块内部的全局变量。
-
模块导出符号(可选)
内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。
-
模块作者等信息声明(可选)
模块加载函数
Linux 内核模块加载函数一般以 int标识声明,典型的模块加载函数的形式如下:
static int __int intitializayion_function(void)
{
/*初始化代码*/
}
module_init(initialization_function);
模块加载函数必须以"module_init(函数名)"的形式被指定。它返回整形,若初始化成功,应返回0,而在初始化失败时,应该返回错误编码。在Linux 中错误编码时一个负值,在<Linux /error.h>中定义。
模块卸载函数
Linux 内核模块卸载函数一般以__exit标识声明,典型的模块卸载函数的形式如下:
static void __exit cleanup_function(void)
{
/*释放代码*/
}
module_exit(cleanup_function)
通常来说,模块卸载函数妖完成与模块加载函数相反的功能:
- 若模块加载函数注册了×××,则卸载函数应该注销×××。
- 若模块加载函数动态申请了内存,则模块卸载函数应该释放内存。
- 若模块加载函数申请了硬件资源(中断、DMA通道、I/O内存等)的占用,则模块卸载函数应该释放这些硬件资源。
- 若模块加载函数开启了硬件,则卸载函数中一般要关闭。
模块参数
导出符号
模块声明与描述
模块的使用计数
模块编译
使用模块绕开GPL
Linux 文件系统与设备文件系统
Linux 文件操作
文件操作系统调用
Linux 的文件操作系统系统调用涉及创建、打开、读写和关闭文件。
-
创建
int creat(const char *filename, mode_t mode)
参数mode指定新建文件夹的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变。
-
打开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open()函数有两个形式,其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下方),flags可以是下表所示的一个值或者几个值的组合。
标志 | 含义 |
---|---|
O_RDONLY | 以只读的方式打开文件 |
O_WRONLY | 以只写的方式打开文件 |
O_RDWR | 以读写的方式打开文件 |
O_APPEND | 以追加的方式打开文件 |
O_CREAT | 创建一个文件 |
O_EXEC | 如果使用了O_CREAT而且文件已经存在,就会发生错误 |
O_NOBLOCK | 以非阻塞的方式打开一个文件 |
O_TRUNC | 如果文件已经存在,则删除文件的内容 |
-
读写
在文件打开以后,我们才能对文件进行读写,Linux 中提供文件读写的系统调用是read、write函数:
int read(int fd, const void *buf,size_t length); int write(int fd, const void *buf, size_t length);
其中,参数buf为指向缓冲区的的指针,lenght为缓冲区的大小(以字节为单位)。
-
定位
对于随机文件,我们可以随机地指定位置读写,使用如下函数进行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()将文件对鞋指针相对whence移动offset个字节。操作成功是返回文件指针相对于文件头地位置。参数whence可使用下述值:
SEEK_SET:相对文件开头。
SEEK_CUR:相对文件读写指针的当前位置。
SEEK_END:相对文件末尾。
offset可以取负值。
-
关闭
当我们操作完成以后,需要关闭文件,只要调用close就可以了,其中fd是我们关闭地文件描述符:
int close(int fd);
C库文件操作
C库函数地文件操作实际上是独立于具体地操作系统平台的,不管是在DOS、Windows、Linux 还是在VxWorks中都是这些函数。
-
创建和打开
FILE *fopen(const char *path, const char *mode);
fopen()实现打开指定文件filename,其中mode为打开模式,C库函数中支持的打开模式如下:
标志 含义 r、rb 以只读方式打开 w、wb 以只写方式打开,如果文件不存在,则创建文件,否则文件被截断 a、ab 以追加方式打开,如果文件不存在,则创建文件 r+、r+b、rb+ 以读写方式打开 w+、w+b、wb+ 以读写方式打开,如果文件不存在,则创建文件,否则文件被截断 a+、a+b、ab+ 以读和追加方式打开,如果文件不存在,则创建新文件 其中,b用于区分二进制文件和文本文件,这一点在Windows、DOS中是由区分的,但是在Linux 中不区分二进制文件和文本文件。
-
读写
C库函数支持以字符、字符串等为单位,支持按照某种格式进行文件的读写。
-
关闭
int fclose (FILE *stream)
Linux 文件系统
Linux 文件系统目录结构
进入到Linux 根目录(即"/",Linux 文件系统的入口,也是处于最高一级的目录),运行"ls -1"命令,可以看到Linux 包含以下目录。
-
/bin
包含基本命令,如ls、cp、mkdir等,这个目录中的文件都是可执行的。
-
/sbin
包含系统命令,如modprabe、hwclock、ifconfig等,大多是设计系统管理的名利,这个目录中的文件都是可执行的。
-
/dev
设备文件存储目录,应用程序通过对这些文件的读写和控制就可以访问实际的设备。(驱动对应的最重要的目录)
-
/etc
系统配置文件的所在地,一些服务器的配置为文件也在这里,如用户账号及密码配置文件。busybox的启动脚本也存放在该目录。
-
/lib
系统库文件存放目录,如LDD6410包含libc-2.6.1.so、libpthread-2.6.1.so、libthread_db_1.0.so等。
-
/mnt
/mnt这个目录一般是用于存放挂在储存设备的挂在目录的,比如由cdrom等目录。可以参看/etc/fstab的定义。有时我们需要让系统开机自动挂载文件系统,把挂载点放在这里也是可以的。
-
/opt
opt是"可选"的意思,有些软件包会被安装在这里,例如,在LDD6410的文件系统中,Qt/Embedded就存放在该目录。
-
/proc
操作系统运行时,进程以及内核信息(比如CPU、硬盘分区、内存信息等)存放在这里(伪文件系统)。/proc目录为伪文件系统proc的挂载目录,proc捕食真正的文件系统,它存在于内存之中。
-
/tmp
有时用户运行程序的时候,会产生临时文件,/tmp就用来存放临时文件的。
-
/usr
这个时系统存放程序的目录,比如用户命令、用户库等。LDD3410的usr包括bin、sbin、lib三个子目录。usr/bin中包含diff、which、who、rx、cmp等,usr/sbin中包含chroot、flash_eraseall、inetd等,usr/lib中包含libjpeg.so.62.0.0等。