用户空间:recv/accept
fd = open("/dev/mybuttons",…);
read(fd,buf,len);//有数据读,无数据等待
驱动程序:
要是实现设备的阻塞方式操作,要使用内核中的等待队列机制
等待队列的核心数据结构:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
使用步骤:
1)定义等待队列头变量
wait_queue_head_t btn_wqh;
2)初始化等待队列头
init_waitqueue_head(&btn_wqh);
以上两步也可以使用DECLARE_WAIT_QUEUE_HEAD(btn_wqh);来替换
3)当设备I/O就需,可以调用以下函数进去睡眠状态
wait_event_interruptible(btn_wqh,condition);//让调用进程进入深度睡眠
wait_event_interruptible(btn_wqh,condition);//让调用者进程进入可中断的睡眠
condition,该表达式为false时,对应的进程进入睡眠状态,true时,以上两个函数直接返回,不睡眠
4)当设备I/O未就绪时,可以调用以下两个函数完成睡眠进程的唤醒
wake_up(&btn_wqh);
wake_up_interruptible(&btn_wqh);
案例代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <mach/platform.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
struct cdev btn_cdev;
dev_t dev;
struct class *cls;
/*定义一个信号量*/
struct semaphore btn_sem;
/*定义按键键值缓冲区*/
unsigned char key_buf;
/*表示按键缓冲区中是否有键值 0,无键值 1,有键值*/
volatile unsigned int ev_press = 0;
/*定义一个等待队列头*/
wait_queue_head_t btn_wqh;
int btn_open(struct inode *inode, struct file *filp)
{
/*获取信号量*/
//down(&btn_sem);
if(down_interruptible(&btn_sem))
{
return -ERESTARTSYS;
}
return 0;
}
int btn_close(struct inode *inode, struct file *filp)
{
up(&btn_sem);
return 0;
}
ssize_t btn_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
int ret = 0;
/*有数据就直接返回给用户空间 无数据就睡眠等待
*等待过程中一旦有数据可供用户空间读,就唤醒睡眠进程
*将数据返回到用户空间
* */
wait_event_interruptible(btn_wqh, ev_press);
/*将键值拷贝到用户空间内存*/
ret = copy_to_user(buf, &key_buf, len);
ev_press = 0;
return len;
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_close,
.read = btn_read,
};
irqreturn_t btn_isr(int irq, void *dev)
{
/*保存按键值*/
key_buf = 0x10;
ev_press = 1;//按键缓冲区中有键值
/*唤醒因无键值而睡眠的进程*/
wake_up_interruptible(&btn_wqh);
return IRQ_HANDLED;
}
int __init btn_drv_init(void)
{
int ret = 0;
/*申请设备号*/
alloc_chrdev_region(&dev, 0, 1, "mybuttons");
/*初始化cdev*/
cdev_init(&btn_cdev, &btn_fops);
/*注册cdev*/
cdev_add(&btn_cdev, dev, 1);
/*创建设备文件*/
cls = class_create(THIS_MODULE, "buttons");
device_create(cls, NULL, dev, NULL, "mybuttons");
/*初始化信号量*/
sema_init(&btn_sem, 1);
/*初始化等待队列头*/
init_waitqueue_head(&btn_wqh);
/*注册中断*/
ret = request_irq(IRQ_GPIO_A_START+28, btn_isr, IRQF_TRIGGER_FALLING, "up", NULL);
return 0;
}
void __exit btn_drv_exit(void)
{
/*注销中断*/
free_irq(IRQ_GPIO_A_START+28, NULL);
/*销毁设备文件*/
device_destroy(cls, dev);
class_destroy(cls);
/*注销cdev*/
cdev_del(&btn_cdev);
/*注销设备号*/
unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
实验:
1)make
2)arm-cortex_a9-linux-gnueabi-gcc test.c -o test
3) cp ../../rootfs
4) 在板子上执行
insmod btn_drv.ko
./test 阻塞睡眠等待
按一下按键 串口中打印了一个或者多个(抖动)键值
test进程继续阻塞
原理:
wait_event_interruptible
__wait_event_interruptible{
/*定义一个等待队列 (链表中的一个节点)
该节点记录要阻塞睡眠的那个进程的相关信息
*/
DEFINE_WAIT(__wait){
/*定义链表节点*/
wait_queue_t name = { \
/*保存要睡眠进程的相关信息
current始终指向真正CPU中执行的那个进程
内核中管理进程会为每个进程创建struct task_struct对象
*/
.private = current, \
.func = function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
}
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE){
/*将定义好的节点,插入到等待队列头指向的链表中去*/
__add_wait_queue(q, wait);
/*将当前进程状态设置TASK_INTERRUPTIBLE(可中断的睡眠状态)*/
set_current_state(state);
}
schedule();//内核中任务调度器
}
}
内核中管理字符设备会为其创建struct cdev类型对象
内核中管理进程会为其创建struct task_struct类型对象
struct task_struct
{
/*保存进程的当前状态*/
volatile long state;
...
}