1.阻塞必要性
- 当一个设备无法立刻满足用户的读写请求时应当如何处理? 例如:调用read时,设备没有数据提供, 但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。当上述情况发生的时候,驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求可以得到满足。
2.内核等待队列
- 在实现阻塞驱动的过程中,需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是内核等待队列。
- 内核等待队列的使用:
- 1.定义等待队列:wait_queue_head_t my_queue
- 2.初始化等待队列:init_waitqueue_head(&my_queue)
- 3.定义和初始化等待队列:DECLARE_WAIT_QUEUE_HEAD(my_queue)
- 4.进入等待队列,睡眠(下面3个函数都可让进程进入睡眠,只是睡眠状态不同)
- wait_event(queue, condition)
- 当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂载queue参数所指定的等待队列上。
- wait_event_interruptible(queue, condition)
- 当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
- int wait_event_killable(queue, condition)
- 当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
- wait_event(queue, condition)
- 5.从等待队列中唤醒进程
- wake_up(wait_queue_t *q)
- 从等待队列中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
- wake_up_interruptible(wait_queue_t *q)
- 从等待队列中唤醒状态为TASK_INTERRUPTIBLE的进程。
- wake_up(wait_queue_t *q)
3.对按键驱动进行阻塞型改造
- key.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#define GPGCON 0x56000060
#define GPGDAT 0x56000064
struct work_struct *key_work;
struct timer_list key_timer;
unsigned int *gpio_data;
unsigned int key_num = 0;
wait_queue_head_t key_q; // 定义等待队列
// 工作队列函数
void key_work_func(struct work_struct *work)
{
mod_timer(&key_timer, jiffies + HZ/10); // 延时100ms
}
// 判断按键是否真正按下
void key_timer_func(unsigned long data)
{
unsigned short key_vall;
key_vall = readw(gpio_data) & 0x01;
if (key_vall == 0)
{
printk("key1 down!\n");
key_num = 1;
}
key_vall = readw(gpio_data) & 0x20;
if (key_vall == 0)
{
printk("key3 down!\n");
key_num = 3;
}
wake_up(&key_q); // 从等待队列中唤醒进程
}
// 中断处理函数
irqreturn_t key_int(int irq, void *dev_id)
{
// 1.检测是否发生了按键中断
// 2.清除已经发生的按键中断
// 3.提交下半部分
schedule_work(key_work);
return IRQ_HANDLED;
}
void key_hw_init()
{
unsigned int *gpio_config;
unsigned int data;
gpio_config = ioremap(GPGCON, 4);
data = readl(gpio_config);
data &= ~0xc03; // 设置GPIO寄存器
data |= 0x802;
writel(data, gpio_config);
gpio_data = ioremap(GPGDAT, 4);
}
int key_open(struct inode *node, struct file *filp)
{
return 0;
}
ssize_t key_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
wait_event(key_q, key_num); // 进入等待队列,睡眠
printk("in kernel :key num is %d\n",key_num);
copy_to_user(buf, &key_num, 4);
key_num = 0; // 清空
return 4;
}
const struct file_operations key_fops =
{
.open = key_open,
.read = key_read,
};
// 初始化miscdevice
struct miscdevice key_miscdev =
{
.minor = 200,
.name = "mykey",
.fops = &key_fops,
};
static int button_init()
{
// 注册miscdevice
misc_register(&key_miscdev);
// 注册中断处理程序
request_irq(IRQ_EINT8, key_int, IRQF_TRIGGER_FALLING, "mykey", 0); // K1
request_irq(IRQ_EINT13, key_int, IRQF_TRIGGER_FALLING, "mykey", 0); // K3
// 按键初始化
key_hw_init();
// 创建工作1
key_work = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(key_work, key_work_func);
// 初始化定时器
init_timer(&key_timer);
key_timer.function = key_timer_func;
// 注册定时器
add_timer(&key_timer);
// 初始化等待队列
init_waitqueue_head(&key_q);
return 0;
}
static void button_exit()
{
// 注销miscdevice
misc_deregister(&key_miscdev);
// 注销中断处理程序
free_irq(IRQ_EINT8, 0);
free_irq(IRQ_EINT13, 0);
}
MODULE_LICENSE("GPL");
module_init(button_init);
module_exit(button_exit);
- key_app.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
int key_num;
// 打开设备
fd = open("/dev/mini2440key",0);
if (fd < 0)
printf("open device fail!\n");
// 读取设备
read(fd, &key_num, 4);
printf("key is %d\n",key_num);
// 关闭设备
close(fd);
}
- 驱动程序默认都是阻塞型驱动