4.字符设备驱动------异步通知

引入:

按键驱动方式对比

  1. 查询:一直读,耗资源
  2. 中断: 没有超时机制,当没有中断,read函数一直休眠
  3. 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 }

 驱动程序代码如下:

  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");
sixthdrv.c

 

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 }
forth_test.c

 

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

 

参考:

字符设备驱动(七)按键异步通知

按键之使用异步通知(详解)

 

posted @ 2018-12-12 18:37  朱果果  阅读(255)  评论(0编辑  收藏  举报