引入:
按键驱动方式对比
- 查询:一直读,耗资源
- 中断: 没有超时机制,当没有中断,read函数一直休眠
- poll机制,加入超时机制
以上3种,都是让应用程序主动去读,本节我们学习异步通知,它的作用就是当驱动层有数据时,主动告诉应用程序,然后应用程序再来读, 这样,应用程序就可以干其它的事情,不必一直读
比如:kill -9 pid ,其实就是通过发信号杀死进程,kill发数据9给指定id号进程
进程间发信号
使用 man signal
查看需要头文件 #include <signal.h>
测试程序如下如果用在gcc下编译需要为sleep
函数添加头文件#include <unistd.h>
实例函数如下arm-linux-gcc -o test test.c
1 #include <stdio.h> 2 #include <signal.h> 3 #include <unistd.h> 4 5 //typedef void (*sighandler_t)(int); 6 void my_signal_fun(int signum) 7 { 8 static int cnt=0; 9 printf("signum=%d ,%d times\n",signum,++cnt ); 10 11 } 12 13 int main(int argc, char const *argv[]) 14 { 15 signal(SIGUSR1,my_signal_fun); 16 while(1) 17 { 18 19 sleep(100); 20 } 21 return 0; 22 }
测试使用kill -USR1 pid
后会打印信号
目标
实现异步通知:驱动程序使用信号通知应用程序去读取按键
要求:
- 1.应用程序要实现,有:注册信号处理函数,使用signal函数
- 2.谁来发?驱动来发
- 3.发给谁?驱动发给应用程序,但应用程序必须告诉驱动PID,
- 4.怎么发?驱动程序调用kill_fasync函数
看下别人怎么写的:
搜索下发送信号的函数kill_fasync
,寻找一个字符设备是怎么使用的,在drivers/char/rtc.c
中有如下调用
1 kill_fasync (&rtc_async_queue, SIGIO, POLL_IN); 2 3 //结构体定义如下: 4 static struct fasync_struct *rtc_async_queue; 5 struct fasync_struct { 6 int magic; 7 int fa_fd; 8 struct fasync_struct *fa_next; /* singly linked list */ 9 struct file *fa_file; 10 };
信号的接受者被定义在fasync_struct
中,搜索下其初始化函数,发现函数rtc_fasync
被调用如下形式,也就是类似于open、close
的形式了
1 static const struct file_operations rtc_fops = { 2 .owner = THIS_MODULE, 3 .llseek = no_llseek, 4 .read = rtc_read, 5 #ifdef RTC_IRQ 6 .poll = rtc_poll, 7 #endif 8 .ioctl = rtc_ioctl, 9 .open = rtc_open, 10 .release = rtc_release, 11 .fasync = rtc_fasync, 12 };
函数的原型如下,这个函数用来初始化结构体,应用程序会最终调用他告知驱动应该信号给哪个pid
1 static int rtc_fasync (int fd, struct file *filp, int on) 2 { 3 return fasync_helper (fd, filp, on, &rtc_async_queue); 4 }
也就是是说应用程序通过fasync
来调用驱动具体的rtc_fasync
,这个函数会设置fasync_struct
这个结构体,这个结构体会被当做驱动发送信号函数的参数,也就是说应用程序告诉驱动程序其目标。
步骤:
1.先写驱动程序,在之前的中断程序上修改
1.1定义 异步信号结构体 变量:
1 static struct fasync_struct * button_async;
1.2在file_operations结构体添加成员.fasync函数,并写函数
1 static int fourth_fasync (int fd, struct file *file, int on) 2 { 3 return fasync_helper(fd, file, on, & button_async); //初始化button_async结构体,就能使用kill_fasync()了 4 } 5 6 static struct file_operations third_drv_fops={ 7 .owner = THIS_MODULE, 8 .open = fourth_drv_open, 9 .read = fourth _drv_read, 10 .release= fourth _drv_class, 11 .poll = fourth _poll, 12 .fasync = fourth_fasync //添加初始化异步信号函数 13 };
3.3在buttons_irq中断服务函数里发送信号:
1 static irqreturn_t buttons_irq(int irq, void *dev_id) 2 { 3 4 ...... 5 ev_press = 1; /*表示中断发生了*/ 6 wake_up_interruptible(&button_waitq); /*去队列button_waitq里面唤醒休眠的进程*/ 7 8 /*kill_fasync: 发送信号,发给谁?-->在&button_async中定义*/ 9 /*定义之后,要在fasync_helper中初始化,才可使用*/ 10 kill_fasync (&button_async, SIGIO, POLL_IN); /*SIGIO表有数据可供读写*/ 11 return IRQ_HANDLED; 12 }
驱动程序代码如下:
/*所需头文件,参考其他类似的驱动*/ #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <linux/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> static struct class *forthdrv_class; static struct class_device *forthdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; volatile unsigned long *gpgcon = NULL; volatile unsigned long *gpgdat = NULL; // 定义一个名为`button_waitq`的队列 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static struct fasync_struct *button_async; /*由结构fasync_stuct定义, */ /*中断事件标志,中断服务程序将它置 1 ,forth_drv_read 将它清0 */ static volatile int ev_press = 0; struct pin_desc{ unsigned int pin; unsigned int key_val; }; static unsigned char key_val; /*键值: 按下时,0x01, 0x02, 0x03, 0x04*/ /*键值: 松开时,0x81, 0x82, 0x83, 0x84*/ struct pin_desc pins_desc[4] = { /* pin ,key_val */ {s3c2410_GPF0, 0x01}, {s3c2410_GPF2, 0x02}, {s3c2410_GPG3, 0x03}, {s3c2410_GPG11,0x04}, }; /*确定按键值*/ static irqreturn_t buttons_irq(int irq, void *dev_id) { /*在中断处理函数中如何使用pins_desc[4] */ struct pin_desc *pin = (struct pin_desc *)dev_id; unsigned int pinval; /*系统函数,可读出单个引脚状态值,高低电平*/ pinval = s3c2410_gpio_getpin(pin_desc->pin); /*确定按键值*/ if (pinval) { /* 为1--松开 */ key_val = 0x80 | pin_desc->key_val; } else { /* 0--按下 */ key_val = pin_desc->key_val; } ev_press = 1; /*表示中断发生了*/ wake_up_interruptible(&button_waitq); /*去队列button_waitq里面唤醒休眠的进程*/ /*kill_fasync: 发送信号,发给谁?-->在&button_async中定义*/ /*定义之后,要在fasync_helper中初始化,才可使用*/ kill_fasync (&button_async, SIGIO, POLL_IN); /*SIGIO表有数据可供读写*/ return IRQ_HANDLED; } static int forth_drv_open(struct inode *inode, struct file *file) { //GPF0 GPF2 GPG3 GPG11,使用虚拟地址 /*设置GPF0,2 GPG3,11为中断引脚*/ request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "s2", &pins_desc[0]); request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "s3", &pins_desc[1]); request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "s4", &pins_desc[2]); request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "s5", &pins_desc[3]); return 0; } ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { if(size != 1) return -EINVAL; /*如果没有按键动作,ev_press = 0, 将进程挂在队列里面等待,休眠, 不会执行下面的函数*/ wait_event_interruptible(button_waitq, ev_press); //谁来唤醒进程?-> 中断发生的时候,就唤醒进程 /*/*如果有按键动作,ev_press = 1,会执行到此函数,并返回键值,将变量清零*/*/ copy_to_user(buf, &key_val, 1); ev_press = 0; return 1; } int forth_drv_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT0, &pins_desc[0]); free_irq(IRQ_EINT2, &pins_desc[1]); free_irq(IRQ_EINT11, &pins_desc[2]); free_irq(IRQ_EINT19, &pins_desc[3]); return 0; } /*这个函数用来初始化结构体&button_async,应用程序会最终调用他告知驱动应该信号给哪个pid*/ static int forth_drv_fasync (int fd, struct file *filp, int on) { return fasync_helper (fd, filp, on, &button_async); /**/ } static struct file_operations forth_drv_fops = { .owner = THIS_MODULE, .open = forth_drv_open, .read = forth_drv_read, .release = forth_drv_close, .fasync = forth_drv_fasync, }; int major; /*定义全局变量,存储主设备号*/ static int forth_drv_init(void) { major = register_chrdev(0, "forth_dev", &forth_drv_fops); forthdrv_class = class_create(THIS_MODULE, "forthdrv"); /*先创建一个类class,再在class下面创建一个设备(如下)*/ forthdrv_class_dev = class_device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/xyz */ /*建立地址映射*/ gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); gpgdat = gpgcon + 1; return 0; } static int forth_drv_exit(void) { unregister_chrdev(major, "forth_dev"); class_device_unregister(forthdrv_class_dev); /*卸载设备*/ class_destroy(forthdrv_class); /*销毁创建的类*/ /*解除地址映射*/ iounmap(gpfcon); iounmap(gpfcon); return 0; } module_init(forth_drv_init); module_exit(forth_drv_init); MODULE_LICENSE("GPL");
2.写应用测试程序
步骤如下:
1) signal(SIGIO, my_signal_fun);
调用signal函数,当接收到SIGIO信号就进入my_signal_fun函数,读取驱动层的数据
2) fcntl(fd,F_SETOWN,getpid());
指定进程做为fd文件的”属主”,内核收到F_SETOWN命令,就会设置pid(驱动无需处理),这样fd驱动程序就知道发给哪个进程
3) oflags=fcntl(fd,F_GETFL);
获取fd的文件状态标志
4) fcntl(fd,F_SETFL, oflags| FASYNC );
添加FASYNC状态标志,会调用驱动中成员.fasync函数,执行fasync_helper()来初始化异步信号结构体
这4个步骤执行后,一旦有驱动层有SIGIO信号时,进程就会收到
应用层代码如下:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <poll.h> 6 #include <signal.h> 7 #include <unistd.h> 8 #include <fcntl.h> 9 10 int fd,ret; 11 void my_signal_fun(int signame) //有信号来了 12 { 13 read( fd, &ret, 1); //读取驱动层数据 14 printf("key_vale=0X%x\r\n",ret); 15 16 } 17 18 /*useg: fourthtext */ 19 int main(int argc,char **argv) 20 { 21 int oflag; 22 unsigned int val=0; 23 fd=open("/dev/buttons",O_RDWR); 24 if(fd<0) 25 {printf("can't open!!!\n"); 26 return -1;} 27 28 signal(SIGIO,my_signal_fun); //指定的信号SIGIO与处理函数my_signal_run对应 29 fcntl( fd, F_SETOWN, getip()); //指定进程作为fd 的属主,发送pid给驱动 30 oflag=fcntl( fd, F_GETFL); //获取fd的文件标志状态 31 fcntl( fd, F_SETFL, oflag|FASYNC); //添加FASYNC状态标志,调用驱动层.fasync成员函数 32 33 while(1) 34 { 35 sleep(1000); //做其它的事情 36 } 37 return 0; 38 39 }
3.测试
可以发现打开文件的时候会有提示driver: drv_fasync
,说明App会主动调用.fasync
1 # insmod dri.ko 2 # 后台运行 3 # ./test & 4 # driver: drv_fasync 5 #实际休眠状态的 6 7 # ps 8 PID Uid VSZ Stat Command 9 798 0 1308 S ./test 10 .... 11 12 # 按键按下正常打印 13 # irq55 14 key_val: 0x3 15 irq55 16 key_val: 0x83 17 irq18
参考: