0 Linux 操作系统知识
Linux是一个操作系统。
优点:
- 免费;
- 丰富的文档和社区支持;
- 跨平台移植;
- 源代码开放;
- 有许多免费开源软件。
家用电脑用Windows,服务器端用Linux。
操作系统分为桌面、服务器、嵌入式(手机)。
桌面:给用户使用。例:Windows,MacOS,Linux,XP;
服务器和嵌入式:绝大多数Linux。
服务器放在机房里,24小时运行,需要稳定性。
操作系统支持实现多任务并发;提供内存管理机制。
操作Ubuntu:
- 图形界面操作:图形界面消耗性能,消耗显卡,没必要。
- 命令行操作:通过SSH客户端连接远程服务器
即打开Linux终端,输入命令行。
常用Linux命令查询。
1 Linux 设备驱动概述及开发环境构建
1.1 设备驱动的作用
系统没有操作系统,根据硬件设备的特点自行定义接口,如对串口定义SerialSend()、SerialRecv(),对LED 定义 LightOn()、LightOff(),对 Flash 定义 FlashWr()、FlashRd() 等。
系统有操作系统,驱动的架构则由相应的操作系统定义。
1.2 无操作系统时的设备驱动
可以不存在操作系统,但必须存在设备驱动。
例如:ASIC 内部、公交车的刷卡机、电冰箱、微波炉、简单的手机和小灵通等。
一般情况下,每一种设备驱动都会定义为一个软件模块,包含 .h 文件和 .c 文件。
- .h 文件定义该设备驱动的数据结构并声明外部函数;
- .c 文件进行驱动的具体实现。
此时为单任务软件架构。无操作系统情况下硬件、设备驱动与应用软件的关系如图所示。
应用软件直接访问设备驱动的接口;驱动包含的接口函数与硬件的功能直接吻合,没有任何附加功能。
1.3 有操作系统时的设备驱动
如图为硬件、驱动、操作系统和应用程序的关系。
存在操作系统时,驱动连接硬件和内核。
设备驱动被附加更多的代码和功能,把单一的“驱使硬件设备行动”变成了操作系统内与硬件交互的模块,对外呈现为操作系统的 API。
驱动按照操作系统给出的独立于设备的接口设计,应用程序可使用统一的系统调用接口来访问各种设备。
1.4 Linux设备驱动
1.4.1 设备的分类及特点
计算机系统的硬件主要由 CPU、存储器和外设组成。
驱动针对的对象是存储器和外设(包括 CPU 内部集成的存储器和外设),而不是针对CPU 内核。
Linux 将存储器和外设分为 3 个基础大类:
- 字符设备:必须以串行顺序依次进行访问的设备。
eg: 触摸屏、磁带驱动器、鼠标等。 - 块设备:可以按任意顺序进行访问,以块为单位进行操作。
eg: 硬盘、eMMC 等。
前两者都要使用文件系统的操作接口 open()、close()、read()、write() 等进行访问。 - 网络设备:面向数据包的接收和发送而设计。内核与网络设备的通信使用套接字接口。
注:
串、并行和顺序、并发区别。
对于块的解释。
1.4.2 Linux 设备驱动与整个软硬件系统的关系
2 驱动设计的硬件基础
2.1 处理器
2.1.1 通用处理器
目前主流的通用处理器(GPP) 多采用 SoC(片上系统)的芯片设计方法。
SOC既MCU那样有内置RAM、ROM同时又像MPU那样强大。
- 从 体系结构角度,中央处理器可以分为两类,一类为冯·诺依曼结构,另一类为哈佛结构。哈佛结构效率更高。
冯·诺依曼结构 | 哈佛结构 | 改进哈佛结构 |
---|---|---|
程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置 | 程序指令和数据分开存储 | |
程序指令和数据的宽度相同 | 指令和数据可以有不同的数据宽度 | |
– | – | – |
独立的程序总线和数据总线 | 独立的地址总线和数据总线, | |
两条总线由程序存储器和数据存储器分时共用 |
- 从指令集的角度来讲,中央处理器也可以分为两类,即 RISC(精简指令集计算机)和 CISC(复杂指令集计算机)。
2.1.1 数字信号处理器(DSP)
DSP 分为两类,一类是定点 DSP,另一类是浮点 DSP。
- 浮点 DSP 的浮点运算用硬件来实现,可以在单周期内完成,因而其浮点运算处理速度高于定点 DSP。
- 定点 DSP 只能用定点运算模拟浮点运算。
2.1.2 其他处理器
- 专用处理器(ASP):针对特定领域而设计的。
- 网络处理器:一种可编程器件,应用于电信领域。可与硬件协处理器搭配提高处理性能。
- 专用集成电路(ASIC) :专门针对特定应用而设计,不具备也不需要灵活的编程能力。
小结:
2.2 存储器
一、非易失性存储器(NVM)
1、存储器主要可分类为只读储存器(ROM)、闪存(Flash)、随机存取存储器(RAM)、光 /磁介质储存器。
2、ROM 还可再细分为不可编程 ROM、可编程 ROM(PROM)、 可 擦 除 可 编 程 ROM(EPROM)和电可擦除可编程 ROM(E2PROM),E2PROM 完全可以用软件来擦写,已经非常方便了。
3、NOR(或非)和 NAND(与非)是市场上两种主要的 Flash 闪存技术。
- NOR Flash 的特点
可芯片内执行(eXecute In Place,XIP);
程序可以直接在NOR 内运行。
目前 NOR Flash 可以使用 SPI 接口进行访问以节省引脚。
- NAND Flash的特点
和 CPU 的接口必须由相应的控制电路进行转换,也可以通过地址线或 GPIO 产生 NAND Flash 接口的信号。NAND Flash
以块方式进行访问,不支持芯片内执行。
NAND Flash 较 NOR Flash 容量大,价格低。
NAND Flash 的擦除、编程速度远超过 NOR Flash。
4、类 SRAM 接口和公共闪存接口(CFI)、IDE接口
类SRAM接口
包含信号: 数据总线、地址总线、读信号、写信号、片选、字节使能、就绪/忙、中断(仅对 I/O 片)。
CFI: 是一个从 NOR Flash 器件中读取数据的公
开、标准接口。它可以使系统软件查询已安装的 Flash 器件的各种参数,包括器件阵列结构
参数、电气和时间参数以及器件支持的功能等。
包含信号: I/O 总线(地址、指令和数据通过这组总线传输)、芯片启动(Chip Enable,CE#)、写使能 (Write Enable,WE#)、读使能(Read Enable,RE#)、指令锁存使能 (Command Latch Enable,CLE)、地址锁存使能 (Address Latch Enable,ALE)、就绪/忙( Ready/Busy,R/B#)。
IDE接口:可连接硬盘控制器或光驱。
包含信号: 与SRAM类似。
二、掉电丢失数据的 RAM
1、RAM 也可再分为静态 RAM(SRAM)和动态 RAM(DRAM)。
2、DRAM 以电荷形式进行存储,数据存储在电容器中。需要定期刷新。
DRAM 存储单元由 1 个晶体管和 1 个电容器组成。
包括 SDRAM、DDR SDRAM。
3、SRAM 是静态的,只要供电它就会保持一个值,没有刷新周期。
每个SRAM 存储单元由 6 个晶体管组成。
4、特定类型的 RAM
- DPRAM:双端口 RAM
特点:
可以通过两个端口同时访问;
具有两套完全独立的数据总线、地址总线和读写控制线,通常用于两个处理器之间交互数据;
**优点:**是通信速度快、实时性强、接口简单,而且两边处理器都可主动进行数据传输。
- CAM:内容寻址 RAM
特点: 以内容进行寻址的存储器,是一种特殊的存储阵列 RAM;
主要工作机制是同时将一个输入数据项与存储在 CAM 中的所有数据项自动进行比较,判别该输入数据项与 CAM 中存储的数据项是否相匹配,并输出该数据项对应的匹配信息。
优点: 用于数据检索的优势是软件无法比拟的,它可以极大地提高系统性能。
- FIFO:先进先出队列
特点: 先进先出,进出有序。
优点: 多用于数据缓冲。
2.3 接口与总线
2.3.1 串口
RS-232、RS-422 与 RS-485 都是串行数据接口标准。
RS-232:通信距离短、速率低。
RS-422:单机发送、多机接收的单向、平衡传输规范。
RS-485:增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,并扩展了总线共模范围。
RS-232C :是嵌入式系统中应用最广泛的串行接口,它为连接 DTE(数据终端设备)与 DCE(数据通信设备)而制定。
RS-232C 标准接口有 25条线(4 条数据线、11 条控制线、3 条定时线、7 条备用和未定义线),常用的只有 9 根:
组成一个 RS-232C 串口的硬件原理如图。
2.3.2 I2 C
内置集成电路总线是两线式串行总线,用于连接微控制器及其外围设备。
特点:其总线简单而有效,占用的 PCB(印制电路板)空间很小,芯片引脚数量少,设计成本低。
总线支持多主控(Multi-Mastering)模式,任何能够进行发送和接收的设备都可以成为主设备。主控能够控制数据的传输和时钟频率,在任意时刻只能有一个主控。
组成总线的两个信号为数据线 SDA 和时钟 SCL。
为了避免总线信号的混乱,要求各设备连接到总线的输出端必须是开漏输出或集电极开路输出的结构。
总线空闲时,上拉电阻使 SDA 和 SCL 线都保持高电平。根据开漏输出或集电极开路输出信号的“线与”逻辑,总线上任意器件输出低电平都会使相应总线上的信号线变低。
注:“线与”逻辑指的是两个或两个以上的输出直接互连就可以实现“与”的逻辑功能,只有输出端是开漏(对于 CMOS 器件)输出或集电极开路(对于 TTL 器件)输出时才满足此条件。工程师一般以“OC 门”简称开漏或集电极开路。
I2 C设备上的串行数据线 SDA 接口电路是双向的,串行时钟线 SCL 也是双向的。
3 Linux内核及内核编程
3.3 Linux 内核的组成
3.3.1 Linux 内核源代码的目录结构
Linux 内核源代码包含如下目录。
内核一般要做到 drivers 与 arch 的软件架构分离,驱动中不包含板级信息,让驱动跨平台。同时内核的通用部分(如 kernel、fs、ipc、net 等)则与具体的硬件(arch 和 drivers)剥离。
3.3.2 Linux 内核的组成部分
如图所示,Linux 内核主要由进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)5个子系统组成。
- 进程调度
进程调度控制系统中的多个进程对 CPU 的访问,使得多个进程能在 CPU 中“微观串行,宏观并行”地执行。
进程调度处于系统的中心位置,内核中其他的子系统都依赖它,因为每个子系统都需要挂起或恢复进程。
在设备驱动编程中,当请求的资源不能得到满足时,驱动一般会调度其他进程执行,并使本进程进入睡眠状态,直到它请求的资源被释放,才会被唤醒而进入就绪状态。
睡眠分成可中断的睡眠和不可中断的睡眠,两者的区别在于可中断的睡眠在收到信号的时候会醒。
如图为Linux 进程状态转换。
完全处于 TASK_UNINTERRUPTIBLE 状态的进程甚至都无法被“杀死”,TASK_KILLABLE 的 状 态, 等 于“ TASK_WAKEKILL | TASK_UNINTERRUPTIBLE”,可以响应致命信号。
在 Linux 内核中,使用 task_struct 结构体来描述进程,该结构体中包含描述该进程内存资源、文件系统资源、文件资源、tty 资源、信号处理等的指针。
Linux 的线程采用轻量级进程模型来实现,在用户空间通过pthread_create() API 创建线程的时候,本质上内核只是创建了一个新的 task_struct,并将新 task_struct 的所有资源指针都指向创建它的那个 task_struct的资源指针。
绝大多数进程(以及进程中的多个线程)是由用户空间的应用创建的,当它们存在底层资源和硬件访问的需求时,会通过系统调用进入内核空间。有时候,在内核编程中,如果需要几个并发执行的任务,可以启动内核线程,这些线程没有用户空间。
- 内存管理
内存管理的主要作用是控制多个进程安全地共享主内存区域。
当 CPU 提供内存管理单元(MMU)时,Linux 内存管理对于每个进程完成从虚拟内存到物理内存的转换。
- 虚拟文件系统
为所有设备提供了统一的接口;独立于各个具体的文件系统;是对各种文件系统的一个抽象。
- 网络接口
网络接口提供了对各种网络标准的存取和各种网络硬件的支持。
如图所示,在 Linux中网络接口可分为网络协议和网络驱动程序。
网络协议部分负责实现每一种可能的网络传输协议,
网络设备驱动程序负责与硬件设备通信,每一种可能的硬件设备都有相应的设备驱动程序。
- 进程间通信
进程间通信支持进程之间的通信,Linux 支持进程间的多种通信机制,包含信号量、共享内存、消息队列、管道、UNIX 域套接字等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。
Linux 内核 5 个组成部分之间的依赖关系如下。
● 进程调度与内存管理之间的关系:
在多程序环境下,程序要运行,则必须为之创建进程。
创建进程首先就要将程序和数据装入内存。
● 进程间通信与内存管理的关系:
进程间通信子系统要依赖内存管理支持共享内存通信机制,这种机制允许两个进程除了拥有自己的私有空间之外,还可以存取共同的内存区域。
● 虚拟文件系统与网络接口之间的关系:
虚拟文件系统利用网络接口支持网络文件系统(NFS),也利用内存管理支持 RAMDISK 设备。
● 内存管理与虚拟文件系统之间的关系:
内存管理利用虚拟文件系统支持交换,交换进程定期由调度程序调度。当一个进程存取的内存映射被换出时,内存管理向虚拟文件系统发出请求,同时,挂起当前正在运行的进程。
3.3.3 Linux 内核空间与用户空间
- 内核空间:内核驻留和运行的地址空间。
受访问标志保护;内核可以访问整个系统内存,以更高的优先级运行。在内核模式下,CPU可以访问整个内存(内核空间和用户空间)。
- 用户空间:正常程序被限制运行的地址(位置)空间。
在用户模式下,CPU只能访问标有用户空间访问权限的内存。用户空间代码以较低的优先级运行。
内核空间和用户空间这两个名词用来区分程序执行的两种不同状态,它们使用不同的地址空间。
Linux 只能通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。
4 Linux内核模块
4.2 Linux内核模块程序结构
1. 模块加载函数
当通过insmod或modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
模块加载函数以“module init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回0。而在初始化失败时,应该返回错误编码。
在 Linux 内核中,可以使用request module(const char*fmt,)函数加载内核模块,可以通过调用下列代码加载其他内核模块。
request module (module name);
在初始化时内核会通过这些函数指针调用这些__init 函数,并在初始化完成后,释放init 区段(包括nittextinitcall.init等)的内存。
数据也可以被定义为__initdata,对于只是初始化阶段需要的数据,内核在初始化完后,也可以释放它们占用的内存。
Linux内核模块加载函数一般以__exit标识声明。
2. 模块卸载函数
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。
模块卸载函数在模块卸载的时候执行,而不返回任何值,且必须以“module exit( 丽数名)”的形式来指定。
3. 模块许可证声明
许可证(LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,模块被加载时,将收到内核被污染(KernelTainted)的警告。
大多数情况下,内核模块应遵循GPL兼容许可权。
(以下三种结构可选)
4. 模块参数
5. 模块导出符号
3. 模块作者等信息声明
5 Linux文件系统与设备文件
5.2 Linux 文件系统
5.2.1 Linux 文件系统目录结构
Linux没有盘符(eg.C:\)的概念,只有一个根目录(即“/”,Linux文件系统的入口,也是最高一级的目录)。
类似一个树。要把文件存储到一个新盘,就把新盘挂载到新建的文件夹下。