四.IO内存映射
1.概念
Linux中能够直接访问的是虚拟地址,而访问寄存器是通过物理地址。Linux中物理地址不能直接访问,需要先将硬件寄存器物理地址映射到虚拟地址空间再进行访问。
2.IO内存映射的实现
需要包含的头文件
#include <linux/io.h>
#include <mach/platform.h>
(1)建立映射
映射的虚拟地址 = ioremap(IO内存起始地址,映射长度);
//一旦映射成功,访问对应的虚拟地址就相当于访问对应的IO内存
void __iomem *gpioe_base = NULL;
gpioe_base = ioremap(PHY_BASEADDR_GPIOE,SZ_64);
if(IS_ERR_OR_NULL(gpioe_base)){
//......
}
(2)解除映射
iounmap(映射的虚拟地址);
iounmap(gpioe_base);
练习:
使用IO内存映射实现其他三盏灯的驱动
3.IO内存的读写
可以直接使用访问指针的语法对虚拟映射地址进行访问,但是不推荐使用,推荐使用内核提供的IO内存访问函数进行操作。
读:
unsigned int ioread8(IO虚拟地址);
unsigned int ioread16(IO虚拟地址);
unsigned int ioread32(IO虚拟地址);
写:
iowrite8(数据,IO虚拟内存);
iowrite16(数据,IO虚拟内存);
iowrite32(数据,IO虚拟内存);
五.混杂设备驱动(misc)
1.概念
混杂设备是一类无法归类的字符设备,(输入设备,帧缓冲设备,i2c设备,摄像头设备......)
混杂设备的主设备号固定是10
2.使用
需要包含头文件:
#include <linux/miscdevice.h>
(1)分配初始化struct miscdevice结构
struct miscdevice {
//初始化前三个成员
int minor;//次设备号,MISC_DYNAMIC_MINOR表示系统自动分配
const char *name;//设备文件名
const struct file_operations *fops;//操作函数集合
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
umode_t mode;
};
(2)向内核注册 --------------- misc_register
int misc_register(struct miscdevice * misc);
不再使用可以注销 ------------ misc_deregister
int misc_deregister(struct miscdevice *misc);
3.混杂设备驱动的注册函数创建了设备文件和重置了操作函数集合,其他的设备号的申请,设备类的创建,cdev的注册在哪里实现?
以上的操作在内核初始化阶段完成,对应的初始化函数叫misc_init
练习:
使用混杂设备实现beep的驱动
4.内核空间和用户空间的数据交换
需要包含的头文件:
#include <asm/uaccess.h>
Linux中内核空间和用户空间是相互隔离的,互相之间不能直接访问,地址空间相互独立。
4G --------------------- 用户空间(0~3G):0 ~ 0xbfffffff
内核空间(3~4G):0xc0000000 ~ 0xffffffff
内核中提供了 用户空间 <=====> 内核空间 数据拷贝的方法
copy_to_user(用户地址,内核地址,大小); ----------- 内核到用户
copy_from_user(内核地址,用户地址,大小); --------- 用户到内核
注意:这两个拷贝函数调用时 可能导致睡眠,在某些禁止睡眠的场合不能使用。
六.超声波驱动
1.原理
2.硬件连接
由于SR04需要5V供电,可以将其连接到串口电源上,选择con2
3.超声波驱动的设计思路
驱动框架使用混杂设备驱动框架,使用gpio函数操作gpio,加载模块时申请GPIOD19 GPIOD15资源,把GPIOD19配置为输出模式,GPIOD15配置为输入模式。
4.Linux内核延时的使用
需要包含的头文件:
#include <linux/delay.h>
微秒延时 ---------- udelay
毫秒延时 ---------- mdelay
read函数的实现:
```c
GPIOD19拉高 ===> 延时15us ===> GPIOD19拉低 ===>循环等待GPIOD15变高 ===>
开始计时 ===> GPIOD15变低停止计时 ===> 通过计时事件计算距离 ===> 上报到用户空间
```
练习:
实现超声波的应用程序,读取测量距离
七.内核的竞态和并发
当多个内核对象同时访问系统共享资源时会造成竞态的情形,同时操作共享资源会发生错误。并发是指多个操作之间要按照一定的顺序执行。
1.内核中产生竞态的情形
1.SMP --------- 多核
2.进程与进程之间
3.进程与中断之间
4.中断和中断之间
//访问共享资源的代码叫临界区
2.内核中竞态问题的解决
(1)中断屏蔽
中断屏蔽可以解决除SMP以外的竞态情形
如何使用:
在执行临界区代码之前屏蔽中断,临界区代码执行完毕后解除中断屏蔽
unsigned long flags;
local_irq_save(flags);//屏蔽中断同时记录当前标志
//local_ira_disable();
临界区代码;
local_irq_restore(flags);//恢复屏蔽前的状态
//local_irq_enable();
频繁使用中断屏蔽或者长时间屏蔽中断会造成系统响应变慢,甚至导致系统运行故障,尽量少使用中断屏蔽,如果使用也要尽快恢复。
(2)原子操作
需要包含的头文件:
#include <linux/asm/atomic.h>
原子操作可以用于所有情形下的竞态,但是原子操作并不提供处理竞态的机制。
原子操作分为位原子操作和整型原子操作
1)位原子操作
set_bit ------------ 置位
clear_bit ---------- 清0
change_bit --------- 修改
test_bit ----------- 测试
2)整型原子操作
类型:atomic_t
使用:
1.分配初始化
atomic_t t = ATOMIC_INIT(初始值);
//atomic_t t;
//atomic_set(&t,初始值);
2.操作接口
atomic_set
atomic_read
atomic_add
atomic_sub
atomic_inc
atomic_dec
atomic_inc_and_test
atomic_dec_and_test
......
(3)自旋锁 ---------------- spinlock
需要包含的头文件:
#include <linux/spinlock.h>
标准自旋锁可以用于中断以外的所有竞态情形,衍生自旋锁可以用于所有的竞态情形。
在访问临界区前先获取锁(加锁),获取不到锁会原地自旋(循环忙等待),临界区执行完毕后释放锁(解锁)。
使用:
数据结构:spinlock_t
1.分配初始化
spinlock_t lock;
spin_lock_init(&lock);
2.获取锁
spin_lock(&lock);//获取不到忙等待
spin_trylock(&lock);//获取不到也立即返回,成功返回真,失败返回假
3.临界区代码
临界区代码执行必须要很快,不能阻塞/睡眠
4.释放锁
spin_unlock(&lock);
如果竞态中有中断的参与,只能使用衍生自旋锁
获取锁:
spin_lock_irqsave(&lock,flags);
释放锁:
spin_unlock_irqrestore(&lock,flags);
Linux除了自旋锁,还有读写锁和顺序锁。
1)读写锁(rwlock_t)分为读锁和写锁,获取读锁之后允许继续获取读锁,但是不允许获取写锁;获取写锁之后既不允许获取读锁,也不允许获取写锁。
2)顺序锁(seqlock_t)分为读锁和写锁,获取读锁之后允许继续获取读锁,也允许继续获取写锁,但是释放读锁要检查在读过程中是否被获取过写锁,如果获取过写锁,读操作应该重新进行。获取写锁之后既不允许获取读锁,也不允许获取写锁。
作业:
使用IO内存映射实现超声波驱动,同时使用原子变量实现互斥操作。