首页 > 代码库 > 按键驱动异步通知

按键驱动异步通知

在此以前,我们都是让应用程序主动去读按键的状态,有没有一种情况,当驱动程序有数据时,主动去告诉应用程序,告诉它,有数据了,你赶紧来读吧。这种情况在linux里的专业术语就叫异步通知。

在按键的例子中异步通知可以理解为:当按键按下时,驱动程序会提醒(即触发)应用程序(通过信号signal来实现)。

举一个例子:进程之间发信号 

原来我们常用  kill 这个命令 :

kill       -9    pid   kill这个命令就是一个发信号 

发送者  :   kill       

接收者  :   pid 

 

信号测试代码

#include<stdio.h>
#include<signal.h>


void mysignal (int sinum)
{
    static int cnt=0;
    printf("signal = %d  ,%d times\n",sinum,++cnt);
}
int main(int argc,char** argv)
{
     signal(SIGUSR1,mysignal);//设置信号处理函数  SIGUSR1是个信号  发送这个信号给这个应用程序就可以调用mysignal

    while(1)
        {
        sleep(1000);//休眠
        }
    return 0;
}

效果:

 技术分享

技术分享

kill -10 3439 的意思就是kill -USR1 3439 

 

接下来继续按键的代码

如何实现异步通知,有哪些要素?

一、应用程序要实现有:注册信号处理函数,app: 使用signal函数

二、谁来发?驱动来发       驱动中:kill_fasync(&button_async, SIGIO, POLL_IN);

三、发给谁?发给应用程序,但应用程序必须告诉驱动PID    应用程序里面(app):  :fcntl(fd, F_SETOWN, getpid());// 告诉内核,发给谁

四、怎么发?驱动程序使用kill_fasync函数      

 

可以在sourceinsight中搜索函数kill_fasync的使用规则

 

应该在驱动的哪里调用kill_fasync函数?

kill_fasync函数的作用是,当有数据时去通知应用程序,理所当然的应该中断处理函数里调用。

 

为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:
1. 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。
不过此项工作已由内核完成,设备驱动无须处理。
2. 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。
驱动中应该实现fasync()函数。

3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号


应用程序:
fcntl(fd, F_SETOWN, getpid()); // 告诉内核,发给谁

Oflags = fcntl(fd, F_GETFL); 
fcntl(fd, F_SETFL, Oflags | FASYNC);  // 改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct  //FASYNC改变的时候应用程序调这个函数的时候,就会调驱动中的fasync-->fasync_helper

#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 <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#define DEVICE_NAME "mybutton"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
static struct class *button_class;
static struct class_device *button_dev_class;
int major;
static struct fasync_struct *button_async;

static volatile int press_cnt=0;/* 按键被按下的次数(准确地说,是发生中断的次数) */


static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//定义等待队列

/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;

static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    // volatile int *press_cnt = (volatile int *)dev_id;
     press_cnt =press_cnt + 1; /* 按键计数加1 */
     ev_press = 1;                /* 表示中断发生了 */
     wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
     kill_fasync(&button_async, SIGIO, POLL_IN);当有数据时去通知应用程序button包含进程的id表示发给谁  SIGIO:要发送的信号 POLL_IN表示由数据等待要读取

     return IRQ_RETVAL(IRQ_HANDLED);//中断处理程序应该返回一个值,用来表明是否真正处理了一个中断,如果中断例程发现其设备的确要处理,则应该返回IRQ_HANDLED, //否则应该返回IRQ_NONE,我们可以通过这个宏来产生返回值,不是本设备的中断应该返回IRQ_NONE

}
/* 应用程序对设备文件/dev/xxx 执行open(...)时,
 * 就会调用button_open函数
 * 就会调用button_open函数
 */
static int button_open (struct inode *inode, struct file *filep)  
{   
    int err;
    err=request_irq(IRQ_EINT2,buttons_interrupt,IRQF_TRIGGER_FALLING,"KEY3",NULL);
    
    if (err) {
        // 释放已经注册的中断
         free_irq(IRQ_EINT2, NULL);
        return -EBUSY;
    }
    
    return 0;  
}  

/* 应用程序对设备文件/dev/buttons执行close(...)时,
 * 就会调用buttons_close函数
 */
 static int buttons_close(struct inode *inode, struct file *file)
{
    
    free_irq(IRQ_EINT2, NULL);
    return 0;
}

ssize_t button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
     unsigned long err;
    
    /* 如果ev_press等于0,休眠 */
    wait_event_interruptible(button_waitq, ev_press);//阻塞   
     /* 执行到这里时,ev_press等于1,将它清0 */
    ev_press = 0;
     /* 将按键状态复制给用户,并清0 */
    err = copy_to_user(buf, (const void *)&press_cnt,  count);
    //memset((void *)&press_cnt, 0, sizeof(press_cnt));
    return  err ? -EFAULT : 0;
}



static unsigned buttons_poll(struct file *file, poll_table *wait)
{
        unsigned int mask = 0;
        poll_wait(file, &button_waitq, wait); // 不会立即休眠    将进程挂接到button_waitq队列中
          /* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0  
     * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1 
     */  
      if(ev_press)  
    {  
       mask |= POLLIN | POLLRDNORM;  /* POLLIN表示有数据可读   POLLRDNORM表示有普通数据可读*/  
    } 
 /* 如果有按键按下时,mask |= POLLIN | POLLRDNORM,否则mask = 0 */      
    return mask;
}
static int buttons_fasync (int fd, struct file *filp, int on)
{
    printk("driver: buttons_fasync\n");
    return fasync_helper (fd, filp, on, &button_async);//fasync_helper这个函数用来初始化button_async结构体里面包含了进程的id 即你要发给的那个进程
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations button_ops=  
{    
    .owner    =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open     =  button_open,  
    .read      =     button_read,       
    .release  =  buttons_close, 
    .poll     =  buttons_poll,
    .fasync      =  buttons_fasync,
}; 
 
/*
 * 执行insmod命令时就会调用这个函数 
 */
  
static int button_init(void)  
{  
    
    /* 注册字符设备
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */
     major = register_chrdev(0, DEVICE_NAME, &button_ops);
      if (major < 0) 
      {
      printk(DEVICE_NAME  " can‘t register major number number::%d\n",major);
      return 0;
      }
    printk(DEVICE_NAME " initialized1\n");
    button_class = class_create(THIS_MODULE, "button");
    if (IS_ERR(button_class))
        return PTR_ERR(button_class);
    button_dev_class = class_device_create(button_class, NULL, MKDEV(major, 0), NULL, "my_button"); /* /dev/my_button */ 
    
    
    return 0;

}

/*
 * 执行rmmod命令时就会调用这个函数 
 */
static void button_exit(void)
{
    class_device_unregister(button_dev_class);
    class_destroy(button_class);       
     /* 卸载驱动程序 */
    unregister_chrdev(major, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(button_init);  
module_exit(button_exit);  

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");// 驱动程序的作者
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");// 一些描述信息
MODULE_LICENSE("GPL");   // 遵循的协议

test.c 测试程序不会主动读驱动中的数据,当驱动该程序中中断服务程序里面当发现有按键按下了就会给应用程序发送一个信号 (kill_fasync(&button_async, SIGIO, POLL_IN))该信号就触发应用程序调用信号处理函数()signal(SIGIO, my_signal_fun);)--> my_signal_fun()函数

 

#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h> 
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int fd;  
void my_signal_fun(int  signum)
{
    unsigned char key_val;
      read(fd, &key_val, 1);
    printf("key_val: 0x%x\n", key_val);

}


int main(int argc,char**argv)  
{ 
    unsigned char key_val;
    int ret;
    int Oflags;


    signal(SIGIO, my_signal_fun);

    fd=open("/dev/my_button",O_RDWR);
    if (fd < 0)
    {
        printf("can‘t open!\n");
    }


    fcntl(fd, F_SETOWN, getpid());// 告诉内核,发给谁
    Oflags = fcntl(fd, F_GETFL); 

    fcntl(fd, F_SETFL, Oflags | FASYNC);//F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。

    while (1)
    {
        sleep(1000);
    }
    return 0;
}    

 

遇到了一个错误耽误了很久:error: syntax error at end of input 

error: syntax error at end of input
原因是:括号没有匹配,或者少分号   

按键驱动异步通知