字符设备驱动------异步通知 按键之使用异步通知(详解)

引入:

按键驱动方式对比

  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 }

 驱动程序代码如下:

/*所需头文件,参考其他类似的驱动*/
#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 = 0return 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");
forth_drv.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

参考:

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

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

猜你喜欢

转载自www.cnblogs.com/y4247464/p/10110160.html
今日推荐