阻塞的必要性
当一个设备无法立刻满足用户的读写请求时应当如何处理?例如:调用read时,设备没有数据提供,但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。当上述情况发生的时候,驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求可以得到满足。
根据驱动的读写规范,驱动的读写都应该按照阻塞模型进行设计。
在实现阻塞型驱动程序中,需要有一个“等待空间”来安排被阻塞的进程来让其进行等待。当唤醒它们的条件成熟时,则可以从中将它们唤醒。这个等待空间就是等待队列。
使用等待队列首先要对其进行定义并初始化
①定义等待队列:wait_queue_head_t my_queue
②初始化等待队列:init_waitqueue_head(&my_queue)
③定义+初始化等待队列:DECLARE_WAIT_QUEUE_HEAD(my_queue)
等待队列的定义与初始化一般在驱动程序的加载函数处完成。
进入等待队列,睡眠的函数有三个:
①wait_enent(queue,condition)
queue:等待队列变量;
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE(不可唤醒、不可打断)模式的睡眠,并挂载queue参数所指定的等待队列上。
②wait_event_interruptible(queue,condition)
queue:等待队列变量;
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE(可中断)的睡眠模式,并挂在queue参数所指定的等待队列上。
③wait_event_killable(queue,condition)
queue:等待队列变量;
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠模式,并挂在queue参数所指定的等待队列上。
睡眠函数通常使用在read,write等函数里面,进行检测。若因为condition不为真使得进程进入睡眠模式,则需要执行唤醒函数才能使进程退出睡眠模式继续执行。
从等待队列中唤醒进程:
①wake_up(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
②wake_up_interruptible(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_INTERRUPTIBLE的进程。
在中断程序中,唤醒函数通常执行在中断处理函数中。
使用代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#define GPFCON 0x56000050
#define GPFDAT 0x56000054
struct work_struct *work1;
struct timer_list buttons_timer;
unsigned int *gpio_data;
unsigned int key_num = 0;
wait_queue_head_t key_q;
void work1_func(struct work_struct *work)
{
mod_timer(&buttons_timer, jiffies + (HZ /10));
}
void buttons_timer_function(unsigned long data)
{
unsigned int key_val;
key_val = readw(gpio_data)&0x1;
if (key_val == 0)
key_num = 4;
key_val = readw(gpio_data)&0x4;
if (key_val == 0)
key_num = 3;
wake_up(&key_q);
}
irqreturn_t key_int(int irq, void *dev_id)
{
//1. 检测是否发生了按键中断
//2. 清除已经发生的按键中断
//3. 提交下半部
schedule_work(work1);
//return 0;
return IRQ_HANDLED;
}
void key_hw_init()
{
unsigned int *gpio_config;
unsigned short data;
gpio_config = ioremap(GPFCON,4);
data = readw(gpio_config);
data &= ~0b110011;
data |= 0b100010;
writew(data,gpio_config);
gpio_data = ioremap(GPFDAT,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;
}
struct file_operations key_fops =
{
.open = key_open,
.read = key_read,
};
struct miscdevice key_miscdev = {
.minor = 200,
.name = "key",
.fops = &key_fops,
};
static int button_init()
{
int ret;
ret = misc_register(&key_miscdev);
if (ret !=0)
printk("register fail!\n");
//注册中断处理程序
request_irq(IRQ_EINT0,key_int,IRQF_TRIGGER_FALLING,"key",(void *)4);
request_irq(IRQ_EINT2,key_int,IRQF_TRIGGER_FALLING,"key",(void *)3);
//按键初始化
key_hw_init();
//. 创建工作
work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(work1, work1_func);
/* 初始化定时器 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
/* 向内核注册一个定时器 */
add_timer(&buttons_timer);
/*初始化等待队列*/
init_waitqueue_head(&key_q);
return 0;
}
static void button_exit()
{
misc_deregister(&key_miscdev);
}
module_init(button_init);
module_exit(button_exit);
当应用程序调用read函数读取数据时,内核调用key_read函数,key_read函数在开头就调用等待函数判断是否需要进行进程阻塞,若不需要则进行下一行代码的执行,若需要则阻塞进程,并等待条件满足;当按键按下时产生中断,中断函数最终调用唤醒函数唤醒进程,从而key_read函数得以继续执行。