1 Linux设备驱动概述及开发环境构建
1.1 设备驱动的作用
- 驱使硬件设备行动
1.2 无操作系统时的设备驱动
- 典型架构:一个无限循环中夹杂着对设备中断的检测或者对设备的轮询
1.3 有操作系统时的设备驱动
并发 、内存管理
1.4 Linux 设备驱动
1.4.1 设备的分类及特点
● 字符设备。
● 块设备。
● 网络设备。
1.4.2 Linux 设备驱动与整个软硬件系统的关系
1.4.3 Linux 设备驱动的重点、难点
● 编写 Linux 设备驱动要求工程师有非常好的硬件基础,懂得 SRAM、 Flash、 SDRAM、磁盘的读写方式,UART、 I2C、 USB 等设备的接口以及轮询、中断、 DMA 的原理,PCI 总线的工作方式以及 CPU 的内存管理单元(MMU)等。
● 编写 Linux 设备驱动要求工程师有非常好的 C 语言基础,能灵活地运用 C 语言的结构体、指针、函数指针及内存动态申请和释放等。
● 编写 Linux 设备驱动要求工程师有一定的 Linux 内核基础,虽然并不要求工程师对内核各个部分有深入的研究,但至少要明白驱动与内核的接口。尤其是对于块设备、网络设备、 Flash 设备、串口设备等复杂设备,内核定义的驱动体系结构本身就非常复杂。
● 编写 Linux 设备驱动要求工程师有非常好的多任务并发控制和同步的基础,因为在驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发与同步机制。
2 驱动设计的硬件基础
2.1 处理器
2.1.1 通用处理器
2.1.2 数字信号处理器
2.2 存储器
2.3 接口与总线
串口 、 I2C I 2 C 、SPI 、USB、以太网 、PCI 和 PCI-E 、SD 和 SDIO
2.4 CPLD 和 FPGA
2.5 原理图分析
- 符号 、网络 、描述
2.6 硬件时序分析
- 时序分析的意思是让芯片之间的访问满足芯片数据手册中时序图信号有效的先后顺序、采样建立时间(Setup Time)和保持时间(Hold Time)的要求
2.7 芯片数据手册阅读方法
2.8 仪器仪表使用
- 万用表 、示波器 、逻辑分析仪
3 Linux 内核及内核编程
3.1 Linux 内核的发展与演变
- 表 3.1 Linux 操作系统版本的历史及特点
版 本 | 时 间 | 特 点 |
---|---|---|
Linux 0.1 | 1991 年 10 月 | 最初的原型 |
Linux 1.0 | 1994 年 3 月 | 包含了 386 的官方支持,仅支持单 CPU 系统 |
Linux 1.2 | 1995 年 3 月 | 第一个包含多平台(Alpha、 Sparc、 MIPS 等)支持的官方版本 |
Linux 2.0 | 1996 年 6 月 | 包含很多新的平台支持,最重要的是,它是第一个支持 SMP(对称多处理器)体系的内核版本 |
Linux 2.2 | 1999 年 1 月 | 极大提升 SMP 系统上 Linux 的性能,并支持更多的硬件 |
Linux 2.4 | 2001 年 1 月 | 进一步提升了 SMP 系统的扩展性,同时也集成了很多用于支持桌面系统的特性: USB、 PC 卡(PCMCIA)的支持,内置的即插即用等 |
Linux 2.6.0 ~ 2.6.39 | 2003 年 12 月~2011 年 5 月 | 无论是对于企业服务器还是对于嵌入式系统, Linux 2.6 都是一个巨大的进步。对高端机器来说,新特性针对的是性能改进、可扩展性、吞吐率,以及对 SMP 机器 NUMA 的支持。对于嵌入式领域,添加了新的体系结构和处理器类型。包括对那些没有硬件控制的内存管理方案的无MMU 系统的支持。同样,为了满足桌面用户群的需要,添加了一整套新的音频和多媒体驱动程序 |
Linux 3.0 ~ 3.19、Linux 4.0-rc1 至今 | 2011 年 7 月至今 | 性能优化等 开发热点聚焦于虚拟化、新文件系统、 Android、新体系结构支持以及 |
3.2 内核组件
1. 进程调度
2. 内存管理
3. 虚拟文件系统
4. 网络接口
5. 进程间通信
- 进程间通信支持进程之间的通信, Linux 支持进程间的多种通信机制,包含信号量、共享内存、消息队列、管道、 UNIX 域套接字等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。在实际的 Linux 应用中,人们更多地趋向于使用 UNIX 域套接字,而不是 System V IPC 中的消息队列等机制。 Android 内核则新增了 Binder 进程间通信方式。
4 内核模块
4.1 模块简介
insmod ./hello.ko
rmmod hello
lsmod
/proc/modules
/sys/module
4.2 模块结构
4.2.1 加载函数
static int __init hello_init(void)
{
...
return 0;
}
module_init(hello_init);
4.2.2 卸载函数
static void __exit hello_exit(void)
{
...
}
module_exit(hello_exit);
4.2.3 许可声明
MODULE_AUTHOR("lin");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple param Module");
MODULE_ALIAS("a simplest module");
- 模块参数
module_param(var, int, S_IRUGO);
- 导出符号
EXPORT_SYMBOL_GPL(func);
(proc/kallsyms)
5 文件系统与设备文件
6 字符设备驱动
6.1 驱动结构
6.1.1 cdev结构体
//生成dev
MKDEV(int major, int minor); //major:0-19 minor:20-31
//获取设备号
MAJOR(dev_t dev)
MINOR(dev_t dev)
//cdev操作
void cdev_init(struct cdev *, struct file_operations *);
struct cdev* cdev_alloc(void);
void cdev_put(struct cdev *);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
6.1.2 设备号分配
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
int unregister_chrdev_region(dev_t from, unsigned count);
6.1.3 file_operations结构体
7 设备驱动中的并发控制
7.1 并发与竞态
- 临界区:访问共享资源的代码段
- 互斥:中断屏蔽、原子操作、自旋锁、信号量、互斥体
7.2 编译乱序和执行乱序
- 表 隔离指令
指令名 | 功能描述 |
---|---|
DMB | 数据存储器隔离。DMB 指令保证: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作。 |
DSB | 数据同步隔离。比 DMB 严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访 问操作——译者注) |
ISB | 指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。 |
7.3 中断屏蔽
local_irq_disable() local_irq_enable() //与自旋锁联合使用
local_irq_save(flags) local_irq_restore(flags)
local_bh_disable() local_bh_enable()
7.4 原子操作
7.4.1 整型原子操作
设置
void atomic_set(atomic_t *v, int i); atomic_t ATOMIC_INIT(int i);
获取
int atomic_read(atomic_t *v);
加减
void atomic_add(int i, atomic_t *v); void atomic_sub(int i, atomic_t *v); void atomic_inc(atomic_t *v); void atomic_dec(atomic_t *v);
操作后测试(为0返回true,非0返回false)
int atomic_inc_and_test(atomic_t *v); int atomic_dec_and_test(atomic_t *v); int atomic_sub_and_test(int i, atomic_t *v);
操作后返回新值
int atomic_add_return(int i, atomic_t *v); int atomic_sub_return(int i, atomic_t *v); int atomic_inc_return(atomic_t *v); int atomic_dec_return(atomic_t *v);
7.4.2 位原子操作
7.5 自旋锁
7.5.1 自旋锁
spinlock_t lock;
spin_lock_init(lock);
spin_lock(lock);
spin_trylock(lock);
spin_unlock(lock);
spin_lock_irq(lock); spin_unlock_irq(lock);
spin_lock__irqsave(lock); spin_unlock_irqrestore(lock);
spin_lock_bh(lock); spin_unlock_bh(lock);
7.5.2 读写锁
7.5.3 顺序锁
- 读执行单元不会被写执行单元阻塞;但写执行单元进行写操作时,其他写执行单元就会自旋。
7.5.4 读-复制-更新
RCU: Read-Copy-Update
7.6 信号量
7.7 互斥体
7.8 完成量
8 阻塞I/O和非阻塞I/O
8.1 阻塞I/O和非阻塞I/O
fd= open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
fcntl(fd, F_SETFL, O_NONBLOCK);
8.1.1 等待队列
//定义
wait_queue_head_t queue_head;
//初始化
init_waitqueue_head(&queue_head);
//定义及初始化
DECLARE_WAIT_QUEUE_HEAD(name)
//队列等待元素
DECLARE_WAITQUEUE(name, tsk)
//操作
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//等待事件
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
//唤醒队列
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);
//睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
...
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&xxx_wait, &wait);
/*等待设备缓冲区可写*/
do {
avail = device_writable();
if (avail < 0) {
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out;
}
}
} while (avail < 0);
device_write();
out:
remove_wait_queue(&xxx_wait, &wait);
set_current_state(TASK_RUNNING);
reutrn ret;
}
8.1.2 支持等待队列的globalfifo
8.2 轮询操作
8.2.1 轮询的概念与作用
9.2.3 信号的释放
异步通知结构体
struct xxx_dev{ struct cdev cdev; ... struct fasync_struct *async_queue; }
- xxx_fasync
static int xxx_fasync(int fd, struct file *filp, int mode) { struct xxx_dev *dev=file->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); }
- 释放读信号
//xxx_write if(dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
- 从异步通知列表删除filp
//xxx_release xxx_fasync(-1, filp, 0);
9.4 Linux异步I/O
9.4.1 AIO
struct aiocb {
int aio_fildes; // File Descriptor
int aio_lio_opcode; // Valid only for lio_listio (r/w/nop)
volatile void *aio_buf; // Data Buffer
size_t aio_nbytes; // Number of Bytes in Data Buffer
struct sigevent aio_sigevent; // Notification Structure
/* Internal fields */
...
};
API 函数 | 说明 |
---|---|
aio_read | int aio_read( struct aiocb *aiocbp ); 请求异步读操作 |
aio_error | int aio_error( struct aiocb *aiocbp ); 检查异步请求的状态 |
aio_return | ssize_t aio_return( struct aiocb *aiocbp ); 获得完成的异步请求的返回状态 |
aio_write | int aio_write( struct aiocb *aiocbp ); 请求异步写操作 |
aio_suspend | int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout ); 挂起调用进程,直到一个或多个异步请求已经完成(或失败) |
aio_cancel | int aio_cancel( int fd, struct aiocb *aiocbp ); 取消异步 I/O 请求 |
lio_listio | int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig ); 发起一系列 I/O 操作 |
9.4.2 内核AIO与libaio
10 中断与时钟
10.1 中断与定时器
11 内存与I/O访问
17 I2C、SPI、USB驱动架构类比
18 ARM Linux设备树
18.1 ARM设备树起源
- 可描述的信息:
- CPU的数量和类别
- 内存基地址和大小
- 总线和桥
- 外设连接
- 中断控制器和中断使用情况
- GPIO控制器和GPIO使用情况
- 时钟控制器和时钟使用情况
18.2 设备树的组成和结构
18.2.1 DTS、DTC和DTB
.dts:device tree source
1.1 Soc共用部分:.dtsi (/include/ “s3c24440.dtsi”)
1.2 模板
/* root节点 */ / { node1 { a-string-property = "A string"; a-string-list-property = "first string", "second string"; a-byte-data-property = [0x01 0x23 0x34 0x56]; child-node1 { first-child-property; second-child-property = <1>; a-string-property = "Hello, world"; }; child-node2 { }; }; node2 { an-empty-property; a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */ child-node1 { }; }; };
.dtc:device tree compiler
.dtb:Device Tree Blob