回顾:
1.linux内核等待队列机制
1.1.回顾进程休眠的方法
应用:sleep
驱动:msleep/ssleep/schedule/schedule_timeout
缺陷:随时随地休眠做不到随时随地被唤醒正常运行
办法:等待队列
1.2.等待队列特点
能够让进程在内核空间休眠,唤醒(随时随地)
只能用于进程,中断不可用
有中断必然有等待队列,有等待队列不一定有中断
1.3.等待队列编程方法:两种
方法1:步骤
定义初始化等待队列头对象(全局)
定义初始化装载休眠进程的容器对象(局部)
将当前进程添加到等待队列中
设置进程休眠的类型:可中断
让进程进入真正休眠,等待被唤醒,代码停止不前
进程被唤醒,设置为运行态
从队列中移除
判断进程唤醒的原因
事件触发,硬件准备继续,中断触发主动唤醒休眠的进程
方法2:步骤
定义初始化等待队列头对象(全局)
调用高度封装方法决定进程是否休眠
事件触发,硬件准备继续,中断触发主动唤醒休眠的进程
编程框架:
//休眠代码
休眠
置假
//唤醒代码
置真
唤醒
-------------------------------------------------------------
2.linux内核按键去抖动
2.1.按键抖动产生的原因
按键属于机械结构,势必产生抖动,接触不良现场
2.2.按键抖动实际产生的波形
参见:按键去抖动.png
去除抖动方案:两种
抖动产生之后,前后两次中断的时间间隔经验值:5~10ms
硬件去抖动:硬件上添加滤波电路
优点:去抖动效果最好
缺点:费钱
软件去抖动:
裸板程序:忙延时去抖动,极大浪费CPU资源
linux内核:采用软件定时器
优点:节省成本
缺点:去抖动效果没有硬件好
2.3.掌握linux内核利用软件定时器去抖动
面试时边说边画图展示去抖动原理
添加定时器,利用定时器和超时处理函数唤醒进程
参见:按键去抖动.png
--------------------------------------------------------------
3.linux内核地址映射
3.1.明确:在linux系统中,不管是应用程序还是驱动程序
都不允许直接访问外设的物理地址,要想访问必须将
物理地址映射到用户虚拟地址或者内核虚拟地址
一旦映射完毕,应用或者内核程序访问映射的虚拟地址
就是在访问实际的物理地址
明确:在linux系统中,4G虚拟地址空间的划分
用户虚拟地址范围:0x00000000~0xBFFFFFFF
内核虚拟地址范围:0xC0000000~0xFFFFFFFF
问:如何将物理地址映射到内核虚拟地址?
答:利用大名鼎鼎的ioremap函数
问:如何将物理地址映射到用户虚拟地址?
答:利用mmap
结论:一个物理地址可以有多个虚拟地址
一个虚拟地址不能有多个物理地址
3.2.ioremap/iounmap函数
函数原型:
void *ioremap(unsigned long phy_address, unsigned long len)
函数功能:将物理地址映射到内核虚拟地址
phy_address:传递要映射的起始的物理地址
len:传递要映射的物理地址空间的大小
返回值:返回映射的起始内核虚拟地址
void iounmap(void *vir_address)
函数功能:解除物理地址和内核虚拟地址的映射关系
vir_address:传递映射好的起始内核虚拟地址
参考代码:
回顾:ARM裸板开关灯
寄存器 物理地址
GPIOCALTFN0 0xC001C020
GPIOCOUTENB 0xC001C004
GPIOCOUT 0xC001C000
//四选一
GPIOCALTFN0 &= ~(3 << 24);
GPIOCALTFN0 |= (1 << 24);
//二选一
GPIOCOUTENB |= (1 << 12);
//输出1和0
GPIOCOUT |= (1 << 12);
GPIOCOUT &= ~(1 << 12);
在linux内核中,开关灯,有两种方案:
1.采用内核提供的GPIO操作库函数
优点:代码极其简单
缺点:很神秘,看不到寄存器的操作细节
只能做输入或者输出操作
如果作为UART的TX或者RX
如果作为I2C的SCL或者SDA等
GPIO库函数毫无办法,无法使用!
只能自己软件操作寄存器来实现,必须采用方案2
代码:
gpio_direction_output(PAD_GPIO_C + 12, 1);
gpio_direction_output(PAD_GPIO_C + 12, 0);
2.采用直接操作寄存器
优点:很直白
缺点:代码极其繁琐
代码:
寄存器 物理地址 内核虚拟地址
GPIOCALTFN0 0xC001C020 gpiocaltfn0
GPIOCOUTENB 0xC001C004 gpiocoutenb
GPIOCOUTENB 0xC001C000 gpiocout
//地址映射,两种方案:
方案1:
unsigned long *gpiocout, *gpiocoutenb, *gpiocaltfn0;
gpiocout = ioremap(0xC001C000, 4);
gpiocoutenb = ioremap(0xC001C004, 4);
gpiocaltfn0 = ioremap(0xC001C020, 4);
方案2:由于寄存器的物理地址空间都是连续的,所以连续映射:
void *gpiobase; 使用无类型指针地址换算可读性好
unsigned long *gpiocout, *gpiocoutenb, *gpiocaltfn0;
gpiobase = ioremap(0xC001C000, 0x24);
//地址换算,使用无类型指针可直接通过手册的地址换算
若不使用无类型指针,例如使用unsigned long *gpiobase
gpiocout = (unsigned long *)(gpiobase + 1);可读性差
gpiocout = (unsigned long *)(gpiobase + 0x00);
gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
//四选一
*gpiocaltfn0 &= ~(3 << 24);
*gpiocaltfn0 |= (1 << 24);
//二选一
*gpiocoutenb |= (1 << 12);
//输出1和0
*gpiocout |= (1 << 12);
*gpiocout &= ~(1 << 12);
案例:编写LED混杂设备驱动,提供ioctl接口实现开关某个灯
不允许使用GPIO库函数,通过直接访问寄存器来实现
1 #include<linux/init.h>
2 #include<linux/module.h>
3 #include<linux/fs.h>
4 #include<linux/miscdevice.h>
5 #include<linux/io.h> //ioremap/iounamp
6 #include<linux/uaccess.h>
7 //定义寄存器对应的内核虚拟地址的指针变量
8 static void *gpiobase;
9 static unsigned long *gpiocaltfn0, *gpiocoutenb, *gpiocout;//功能选择,输出使能,输出
10
11 #define LED_ON 0x100001
12 #define LED_OFF 0x100002
13 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long buf) {
14 int kindex;
15 copy_from_user(&kindex, (int *)buf, sizeof(kindex));
16 switch(cmd){
17 case LED_ON:
18 if(kindex == 1){
19 *gpiocout &= ~(1 << 12);
20 }
21 break;
22 case LED_OFF:
23 if(kindex == 1){
24 *gpiocout |= (1 << 12);
25 }
26 break;
27 }
28 //调试信息
29 printk("ALT:%#X, OUTENB:%#X, OUT:%#X\n", *gpiocaltfn0, *gpiocoutenb, *gpiocout);
30 return 0;
31 }
32
33 static struct file_operations led_fops = {
34 .unlocked_ioctl = led_ioctl
35 };
36
37 static struct miscdevice led_misc = {
38 .name = "myled",
39 .minor = MISC_DYNAMIC_MINOR,
40 .fops = &led_fops
41 };
42
43 static int led_init(void){
44 misc_register(&led_misc);
45 //将寄存器的物理地址映射到内核虚拟地址上
46 gpiobase = ioremap(0xC001C000, 0x24);
47 //换算三个寄存器的内核虚拟地址
48 gpiocout = (unsigned long *)(gpiobase + 0x00);
49 gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
50 gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
51 //四选一
52 *gpiocaltfn0 &= ~(3 << 24);
53 *gpiocaltfn0 |= (1 << 24);
54 //二选一
55 *gpiocoutenb |= (1 << 12);
56 //默认输出1
57 *gpiocout |= (1 << 12);
58
59 return 0;
60 }
61
62 static void led_exit(void){
63 *gpiocout |= (1 << 12);
64 //解除地址映射
65 iounmap(gpiobase);
66
67 misc_deregister(&led_misc);
68
69 }
70
71 module_init(led_init);
72 module_exit(led_exit);
73 MODULE_LICENSE("GPL");
----------------------------------------------------------------------------------