引入:
按键驱动方式对比
- 查询:一直读,耗资源
- 中断: 没有超时机制,当没有中断,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 }
驱动程序代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 #include <linux/poll.h> 13 14 15 static struct class *sixthdrv_class; 16 static struct class_device *sixthdrv_class_dev; 17 18 volatile unsigned long *gpfcon; 19 volatile unsigned long *gpfdat; 20 21 volatile unsigned long *gpgcon; 22 volatile unsigned long *gpgdat; 23 24 25 /* 声明等待队列类型中断 button_wait */ 26 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 27 28 /* 中断事件标志, 中断服务程序将它置1,sixth_drv_read将它清0 */ 29 static volatile int ev_press = 0; 30 31 /* 异步信号结构体变量 */ 32 static struct fasync_struct *button_async; 33 34 35 struct pin_desc{ 36 unsigned int pin; 37 unsigned int key_val; 38 }; 39 40 41 /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */ 42 /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */ 43 static unsigned char key_val; /*定义全局变量key_val,保存key状态*/ 44 45 struct pin_desc pins_desc[4] = { 46 {S3C2410_GPF0, 0x01}, 47 {S3C2410_GPF2, 0x02}, 48 {S3C2410_GPG3, 0x03}, 49 {S3C2410_GPG11, 0x04}, 50 }; 51 52 //static atomic_t canopen = ATOMIC_INIT(1); //定义原子变量并初始化为1 53 54 static DECLARE_MUTEX(button_lock); //定义互斥锁 55 56 /* 57 * 确定按键值 58 */ 59 static irqreturn_t buttons_irq(int irq, void *dev_id) 60 { 61 struct pin_desc * pindesc = (struct pin_desc *)dev_id; 62 unsigned int pinval; 63 64 pinval = s3c2410_gpio_getpin(pindesc->pin); 65 66 if (pinval) 67 { 68 /* 松开 */ 69 key_val = 0x80 | pindesc->key_val; 70 } 71 else 72 { 73 /* 按下 */ 74 key_val = pindesc->key_val; 75 } 76 77 ev_press = 1; /* 表示中断发生了 */ 78 /*去队列button_waitq里面唤醒休眠的进程*/ 79 wake_up_interruptible(&button_waitq); 80 /*kill_fasync: 发送信号,发给谁?-->在&button_async中定义*/ 81 kill_fasync (&button_async, SIGIO, POLL_IN); /*发送SIGIO信号给应用层*/ 82 83 return IRQ_RETVAL(IRQ_HANDLED); 84 } 85 86 static int sixth_drv_open(struct inode *inode, struct file *file) 87 { 88 #if 0 89 if (!atomic_dec_and_test(&canopen)) 90 { 91 atomic_inc(&canopen); 92 return -EBUSY; 93 } 94 #endif 95 96 if (file->f_flags & O_NONBLOCK) 97 { 98 if (down_trylock(&button_lock)) 99 return -EBUSY; 100 } 101 else 102 { 103 /* 获取信号量 */ 104 down(&button_lock); 105 } 106 107 /* 配置GPF0,2为输入引脚 */ 108 /* 配置GPG3,11为输入引脚 */ 109 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]); 110 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]); 111 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]); 112 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]); 113 114 return 0; 115 } 116 117 ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 118 { 119 if (size != 1) 120 return -EINVAL; 121 122 if (file->f_flags & O_NONBLOCK) 123 { 124 if (!ev_press) 125 return -EAGAIN; 126 } 127 else 128 { 129 /*如果没有按键动作,ev_press = 0, 130 *将进程挂在队列里面等待,休眠, 不会执行下面的函数*/ 131 wait_event_interruptible(button_waitq, ev_press); 132 } 133 134 /*如果有按键动作,ev_press = 1, 135 *会执行到此函数,并返回键值,将变量清零*/ 136 copy_to_user(buf, &key_val, 1); 137 ev_press = 0; 138 139 return 1; 140 } 141 142 143 int sixth_drv_close(struct inode *inode, struct file *file) 144 { 145 //atomic_inc(&canopen); 146 free_irq(IRQ_EINT0, &pins_desc[0]); 147 free_irq(IRQ_EINT2, &pins_desc[1]); 148 free_irq(IRQ_EINT11, &pins_desc[2]); 149 free_irq(IRQ_EINT19, &pins_desc[3]); 150 up(&button_lock); 151 return 0; 152 } 153 154 static unsigned sixth_drv_poll(struct file *file, poll_table *wait) 155 { 156 unsigned int mask = 0; 157 poll_wait(file, &button_waitq, wait); // 不会立即休眠 158 159 if (ev_press) 160 mask |= POLLIN | POLLRDNORM; 161 162 return mask; 163 } 164 165 static int sixth_drv_fasync (int fd, struct file *filp, int on) 166 { 167 printk("driver: sixth_drv_fasync\n"); 168 169 //初始化button_async结构体,就能使用kill_fasync()了 170 return fasync_helper (fd, filp, on, &button_async); 171 } 172 173 174 static struct file_operations sencod_drv_fops = { 175 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 176 .open = sixth_drv_open, 177 .read = sixth_drv_read, 178 .release = sixth_drv_close, 179 .poll = sixth_drv_poll, 180 .fasync = sixth_drv_fasync, //初始化异步信号函数 181 }; 182 183 184 int major; /*定义全局变量,存储主设备号*/ 185 static int sixth_drv_init(void) 186 { 187 major = register_chrdev(0, "sixth_drv", &sencod_drv_fops); 188 /*先创建一个类class,再在class下面创建一个设备(如下)*/ 189 sixthdrv_class = class_create(THIS_MODULE, "sixth_drv"); 190 191 sixthdrv_class_dev = class_device_create(sixthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */ 192 /*建立地址映射*/ 193 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); 194 gpfdat = gpfcon + 1; 195 196 gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); 197 gpgdat = gpgcon + 1; 198 199 return 0; 200 } 201 202 static void sixth_drv_exit(void) 203 { 204 unregister_chrdev(major, "sixth_drv"); 205 class_device_unregister(sixthdrv_class_dev); 206 class_destroy(sixthdrv_class); 207 iounmap(gpfcon); 208 iounmap(gpgcon); 209 return 0; 210 } 211 212 213 module_init(sixth_drv_init); 214 215 module_exit(sixth_drv_exit); 216 217 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信号时,进程就会收到
应用层代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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
参考:
字符设备驱动(七)按键异步通知
按键之使用异步通知(详解)