DAY5-IO内存映射,混杂设备和竞态

103 阅读6分钟

四.IO内存映射

1.概念

Linux中能够直接访问的是虚拟地址,而访问寄存器是通过物理地址。Linux中物理地址不能直接访问,需要先将硬件寄存器物理地址映射到虚拟地址空间再进行访问。

image-20220801235142392

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(内核地址,用户地址,大小); --------- 用户到内核

注意:这两个拷贝函数调用时 可能导致睡眠,在某些禁止睡眠的场合不能使用。

image-20220801235157281

六.超声波驱动

1.原理

image-20220801235205710

2.硬件连接

由于SR04需要5V供电,可以将其连接到串口电源上,选择con2

image-20220801235209643

3.超声波驱动的设计思路

驱动框架使用混杂设备驱动框架,使用gpio函数操作gpio,加载模块时申请GPIOD19 GPIOD15资源,把GPIOD19配置为输出模式,GPIOD15配置为输入模式。

image-20220801235214228

4.Linux内核延时的使用

需要包含的头文件:

#include <linux/delay.h>

微秒延时 ---------- udelay

毫秒延时 ---------- mdelay

read函数的实现:

   ```c
   GPIOD19拉高 ===> 延时15us ===> GPIOD19拉低 ===>循环等待GPIOD15变高 ===> 
   开始计时 ===> GPIOD15变低停止计时 ===> 通过计时事件计算距离 ===> 上报到用户空间
   ```

练习:

实现超声波的应用程序,读取测量距离

七.内核的竞态和并发

当多个内核对象同时访问系统共享资源时会造成竞态的情形,同时操作共享资源会发生错误。并发是指多个操作之间要按照一定的顺序执行。

1.内核中产生竞态的情形

1.SMP --------- 多核
2.进程与进程之间
3.进程与中断之间
4.中断和中断之间

//访问共享资源的代码叫临界区

2.内核中竞态问题的解决

(1)中断屏蔽

中断屏蔽可以解决除SMP以外的竞态情形

如何使用:

在执行临界区代码之前屏蔽中断,临界区代码执行完毕后解除中断屏蔽

image-20220801235223582

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>

标准自旋锁可以用于中断以外的所有竞态情形,衍生自旋锁可以用于所有的竞态情形。

在访问临界区前先获取锁(加锁),获取不到锁会原地自旋(循环忙等待),临界区执行完毕后释放锁(解锁)。

image-20220801235232063

使用:

数据结构: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内存映射实现超声波驱动,同时使用原子变量实现互斥操作。