按键驱动异步通知

时间:2022-08-30 14:18:30

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

异步通知是指:一旦设备就绪,则主动通知应用程序,应用程序根本就不需要查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。下面我们就看一下在linux中机制的实现方式。

在linux中,异步通知是使用信号来实现的,而在linux,大概有30种信号,比如大家熟悉的ctrl+c的SIGINT信号,进程能够忽略或者捕获除过SIGSTOP和SIGKILL的全部信号,当信号背捕获以后,有相应的signal()函数来捕获信号,函数原型:sighandler_t signal(int signum, sighandler_t handler); 第 一个参数就是指定的信号的值,而第二个参数便是此信号的信号处理函数,当为SIG_IGN,表示信号被忽略,当为SIG_DFL时,表示采用系统的默认方 式来处理该信号。当然,信号处理函数也可以自己定义。当signal()调用成功后,返回处理函数handler值,调用失败后返回SIG_ERR。

在按键的例子中异步通知可以理解为:当按键按下时,驱动程序会提醒(即触发)应用程序(通过信号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 

 

接下来继续按键的代码

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

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

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

三、发给谁?发给应用程序,但应用程序必须告诉驱动PID    应用程序里面  :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()函数激发相应的信号

驱动中的上述3 项工作和应用程序中的3 项工作是一一对应的,如图所示为异步通知处理过程中用
户空间和设备驱动的交互。

按键驱动异步通知

 

 


应用程序:
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

驱动程序:

设备驱动中异步通知编程比较简单,主要用到一项数据结构和两个函数。数据结构是 fasync_struct 结
构体,两个函数分别如下。

处理FASYNC 标志变更的函数。

int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);

释放信号用的函数。

void kill_fasync(struct fasync_struct **fa, int sig, int band);
 
#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
原因是:括号没有匹配,或者少分号   

 

参考:韦东山第一期视频

linux设备驱动开发详解

http://www.cnblogs.com/blogs-of-lxl/p/5879008.html