[国嵌攻略][124][阻塞型驱动程序设计]
阻塞的必要性
当一个设备无法立刻满足用户的读写请求时应当如何处理?例如,调用read时,设备没有数据提供,但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。当上述情况发生的时候,驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠状态),直到请求可以得到满足。
内核等待队列
在实现阻塞驱动的过程中,需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是内核等待队列。
1.定义等待队列
wait_queue_head_t my_queue
2.初始化等待队列
init_waitqueue_head(&my_queue)
3.定义和初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
4.进入等待队列,睡眠
4.1.wait_event(queue, condition)
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂载queue参数所指定的等待队列上。
4.2.wait_event_interruptible(queue, condition)
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
4.3.int wait_event_killable(queue, condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
5.从等待队列中唤醒进程
5.1.wake_up(wait_queue_t *q)
从等待队列中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
5.2.wake_up_interruptible(wait_queue_t *q)
从等待队列中唤醒状态为TASK_INTERRUPTIBLE的进程。
驱动程序默认都是阻塞型驱动
头文件
#include <linux/sched.h>
error: 'TASK_NORMAL' undeclared
error: 'TASK_UNINTERRUPTIBLE' undeclared
注意:调度文件中可能有别的函数与模块名重名,修改模块名即可。
keydev.c
/******************************************************************** *头文件 *********************************************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/sched.h> /******************************************************************** *宏定义 *********************************************************************/ #define GPGCON 0x56000060 //按键控制寄存器地址 #define GPGDAT 0x56000064 //按键数据寄存器地址 /******************************************************************** *全局变量 *********************************************************************/ unsigned int *keycon; //按键控制指针 unsigned int *keydat; //按键数据指针 struct work_struct *work; //按键工作 struct timer_list timer; //按键定时 wait_queue_head_t queue; //等待队列 int isData; //等待条件 int keyNum; //按键编号 /******************************************************************** *定时处理 *********************************************************************/ //设备定时 void key_timer(unsigned long data){ //读取按键状态 unsigned short datTmp; int key1, key2; datTmp = readw(keydat); //获取GPGDAT值 key1 = datTmp & (1<<0); //获取key1电平 key2 = datTmp & (1<<3); //获取key2电平 //判断按键状态 if(key1 == 0){ keyNum = 1; printk("key1 down!\n"); } if(key2 == 0){ keyNum = 2; printk("key2 down!\n"); } //设置数据状态 isData = 1; wake_up(&queue); } /******************************************************************** *中断处理 *********************************************************************/ //设备中断下部 void key_work(struct work_struct *work){ //启动定时设备 mod_timer(&timer, jiffies + HZ/100); //定时10ms,jiffies表示系统当前嘀嗒数,1HZ = 1s = 1000jiffies } //设备中断上部 irqreturn_t key_irq(int irq, void *dev_id){ //处理硬件相关 //提交硬件无关 schedule_work(work); return 0; } /******************************************************************** *设备方法 *********************************************************************/ //设备读取 ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){ //判断数据状态 wait_event(queue, isData); //读取按键编号 copy_to_user(buf, &keyNum, sizeof(keyNum)); //清零数据状态 isData = 0; return sizeof(keyNum); } //设备打开 int key_open(struct inode *node, struct file *filp){ return 0; } //设备关闭 int key_close(struct inode *node, struct file *filp){ return 0; } //设备方法 struct file_operations key_fops = { .read = key_read, .open = key_open, .release = key_close }; /******************************************************************** *模块安装 *********************************************************************/ //混杂设备 struct miscdevice misdev = { .minor = 200, //次设备号 .name = "keydev", //设备名称 .fops = &key_fops //设备方法 }; //注册硬件 void handware_init(void){ //初始化按键控制寄存器 unsigned int conTmp; keycon = ioremap(GPGCON, 4); //虚拟地址映射 conTmp = readl(keycon); //获取GPGCON值 conTmp &= ~((0x3<<6) | (0x3<<0)); //GPB3[7:6]:00,GPG0[1:0]:00 conTmp |= ((0x2<<6) | (0x2<<0)); //GPB3[7:6]:EINT[11],GPG0[1:0]:EINT[8] writel(conTmp, keycon); //设置GPGCON值 //初始化按键状态寄存器 keydat = ioremap(GPGDAT, 2); //虚拟地址映射 } //安装模块 static int ikey_init(void){ //注册混杂设备 misc_register(&misdev); //注册硬件设备 handware_init(); //注册中断处理 request_irq(IRQ_EINT8, key_irq, IRQF_TRIGGER_FALLING, "keyirq", 0); //下降沿触发,IRQ_EINT8定义在irqs.h文件中 request_irq(IRQ_EINT11, key_irq, IRQF_TRIGGER_FALLING, "keyirq", 0); //注册工作队列 work = kmalloc(sizeof(struct work_struct), GFP_KERNEL); INIT_WORK(work, key_work); //注册定时设备 init_timer(&timer); //初始化定时器 timer.function = key_timer; //添加定时函数 add_timer(&timer); //添加定时设备 //注册等待队列 isData = 0; init_waitqueue_head(&queue); return 0; } //卸载模块 static void ikey_exit(void){ //注销混杂设备 misc_deregister(&misdev); //注销中断处理 free_irq(IRQ_EINT8, 0); free_irq(IRQ_EINT11, 0); } /******************************************************************** *模块声明 *********************************************************************/ MODULE_LICENSE("GPL"); MODULE_AUTHOR("D"); MODULE_DESCRIPTION(""); MODULE_VERSION("v1.0"); module_init(ikey_init); module_exit(ikey_exit);
keyapp.c
mknod /dev/keydev0 c 10 200
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv){ //打开设备 int fd; fd = open("/dev/keydev0", O_RDWR); //读取设备 int keynum; read(fd, &keynum, sizeof(keynum)); printf("key number is %d\n", keynum); //关闭设备 close(fd); }