一、基础知识
信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。
阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。
在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号
kill -9 pid -9就是信号#define SIGKILL 9 /* 杀死、终止进程
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
- 指定信号处理函数,在应用程序中使用
sighandler_t signal(int signum, sighandler_t handler) 函数参数和返回值含义如下: signum:要设置处理函数的信号。 handler:信号的处理函数。 返回值:设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。
- 异步通知结构体
一般将这个结构体定义在设备结构体中。struct fasync_struct { spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; struct file *fa_file; struct rcu_head fa_rcu; };
- 异步通知函数
如果要使用异步通知,需要再设备驱动中实现file_operations操作集合中的fasync函数,函数原型如下int (*fasync)(int fd,struct file *filp,int on)
static int imx6uirq_fasync(int fd, struct file *filp, int on) { struct key_dev *dev = (struct key_dev *)filp->private_data; return fasync_helper(fd,filp,on,&dev->async_queue); }
- 没有按键按下的时候 让当前进程睡眠
ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey)); - 有按键按下的时候 唤醒当前进程
kill_fasync(&dev->async_queue,SIGIO,POLLIN);//释放SIGIO信号
二、驱动编写
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/of_irq.h>
#include <linux/poll.h>
/********************************************************************
* key.c
* 作者:张亚胜
* 版本:V1.0
* 描述:非阻塞io读取按键 异步通知
* 其他:无
* 网站:www.s123.xyz
* 日志:出版V1.0 2020/8/10 张亚胜创建
* ******************************************************************/
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "asyncnoti" /* 设备名字 */
/* 定义按键值 */
#define KEY0VALUE 0X01 /* 按键值 */
#define INVAKEY 0Xff /* 无效键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_keydesc{
int gpio; /* gpio编号 */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int,void *); /* 中断服务函数 */
};
/* keydev设备结构体 */
struct key_dev{
dev_t dev_id; /* 设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* mdev 自动节点文件使用 */
struct device *device; /* class使用 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int key_gpio; /* key gpio 编号 */
atomic_t keyvalue; /* 按键值 */
atomic_t releasekey; /* 标记是否完成一次按键操作 */
struct timer_list timer;/* 定义一个定时器 */
struct irq_keydesc irqkeydesc[KEY_NUM];/* 按键描述数组 */
wait_queue_head_t r_wait; /* 读等待队列头 */
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
struct key_dev keydev; /* key设备 */
//中断服务函数
//处理方法:开定时器,定时10ms,用于去抖
//参数:中断号 设备结构
//返回值:中断执行结果
static irqreturn_t key0_handler(int arg,void *dev_id)
{
struct key_dev *dev = (struct key_dev *)dev_id;
dev->timer.data = (volatile long)dev_id;//把设备结构体传下去
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));//10ms定时
printk("key irq\r\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
//定时器服务函数
//用于按键去抖,定时时间到了以后,再次读取按键值,如果按键还是处于按下状态就表示按键有效
//参数:设备结构体
void timer_function(unsigned long arg)
{
unsigned char value=0;
struct key_dev *dev = (struct key_dev *)arg;
value = gpio_get_value(dev->irqkeydesc[0].gpio);
if(value == 0){
atomic_set(&dev->keyvalue,dev->irqkeydesc[0].value);//按键按下
}else{
atomic_set(&dev->keyvalue,0x80|(dev->irqkeydesc[0].value));//最高位置一 表示按键有效
atomic_set(&dev->releasekey,1);//按下再松开 算是一次按键操作
}
/* 通知进程 */
if(atomic_read(&dev->releasekey)){
if(dev->async_queue){
kill_fasync(&dev->async_queue,SIGIO,POLLIN);//释放SIGIO信号
}
}
}
static int keyio_init(void)
{
int ret;
unsigned char i=0;
/* 获取设备节点 */
keydev.nd = of_find_node_by_path("/key");
if(keydev.nd == NULL){
printk("keydev node can not found!\r\n");
return -EINVAL;
}
/* 获取gpio属性 得到led的gpio编号 */
for (i = 0; i < KEY_NUM; i++)
{
keydev.irqkeydesc[i].gpio = of_get_named_gpio(keydev.nd,"key-gpio",i);
if(keydev.irqkeydesc[i].gpio < 0){
printk("key-gpio get failed!\r\n");
}else {
printk("key-gpio = %d\r\n",keydev.irqkeydesc[i].gpio);
}
}
/* 初始化key所使用的IO,并且设置成中断模式 */
for(i=0;i<KEY_NUM;i++)
{
memset(keydev.irqkeydesc[i].name,0,sizeof(keydev.irqkeydesc[i].name));
sprintf(keydev.irqkeydesc[i].name,"KEY%d",i);
gpio_request(keydev.irqkeydesc[i].gpio,keydev.irqkeydesc[i].name);
gpio_direction_input(keydev.irqkeydesc[i].gpio);
keydev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keydev.nd,i);//获取中断号
#if 0
keydev.irqkeydesc[i].irqnum = gpio_to_irq(keydev.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d,irqnum=%d\r\n",i,keydev.irqkeydesc[i].gpio,keydev.irqkeydesc[i].irqnum);
}
/* 申请中断 */
keydev.irqkeydesc[0].handler = key0_handler;
keydev.irqkeydesc[0].value = KEY0VALUE;
for(i=0;i<KEY_NUM;i++)
{
ret = request_irq(keydev.irqkeydesc[i].irqnum,keydev.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,keydev.irqkeydesc[i].name,&keydev);
if(ret<0){
printk("irq %d request failed!\r\n",keydev.irqkeydesc[i].irqnum);
goto fail_irq;
}
}
/* 创建定时器 */
init_timer(&keydev.timer);
keydev.timer.function = timer_function;
return 0;
fail_irq:
for(i=0;i<KEY_NUM;i++)
{
gpio_free(keydev.irqkeydesc[i].gpio);
}
return ret;
}
static int key_open(struct inode *inode,struct file *filp)
{
filp->private_data = &keydev;
return 0;
}
static ssize_t key_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
int ret=0;
unsigned char keyvalue=0;
unsigned char releasekey=0;
struct key_dev *dev = (struct key_dev *)filp->private_data;
if(filp->f_flags & O_NONBLOCK){ /* 非阻塞访问 */
if(atomic_read(&dev->releasekey) == 0){ /* 没有按键按下 */
return -EAGAIN;
}
} else {
ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
if(ret){
return ret;
}
}
#if 0
/* 等待事件 */
wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /* 等待按键有效 */
#endif
#if 0
DECLARE_WAITQUEUE(wait,current);//定义一个等待队列
if(atomic_read(&dev->releasekey) == 0){//没有按键按下
add_wait_queue(&dev->r_wait,&wait);//添加到等待队列头
__set_current_state(TASK_INTERRUPTIBLE);//设置任务状态
schedule();//进行一次任务切换
if(signal_pending(current)){//判断是否为信号引起的唤醒
ret = -ERESTARTSYS;
goto wait_error;
}
__set_current_state(TASK_RUNNING);//设置为运行状态
remove_wait_queue(&dev->r_wait,&wait);//将等待队列移除
}
#endif
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
//key down
if(releasekey){
atomic_set(&dev->releasekey,0);//按下标志清零
if(keyvalue & 0x80 ){
keyvalue &= ~0x80;
if(keyvalue == KEY0VALUE){
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
} else {
goto data_error;
}
} else {
goto data_error;
}
} else {
goto data_error;
}
//读取按键这里报错 原因是这里忘记返回0了
return 0;
#if 0
set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->r_wait,&wait);
#endif
data_error:
return -EINVAL;
}
unsigned int imx6uirq_poll(struct file *filp,struct poll_table_struct *wait)
{
int mask=0;
struct key_dev *dev = (struct key_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
/* 是否可读 */
if(atomic_read(&dev->releasekey)){//按键按下 可读
mask = POLLIN | POLLRDNORM;//返回POLLIN
}
return mask;
}
/*
* @description : fasync函数,用于处理异步通知
* @param - fd : 文件描述符
* @param - filp : 要打开的设备文件(文件描述符)
* @param - on : 模式
* @return : 负数表示函数执行失败
*/
static int imx6uirq_fasync(int fd, struct file *filp, int on)
{
struct key_dev *dev = (struct key_dev *)filp->private_data;
return fasync_helper(fd,filp,on,&dev->async_queue);
}
/*
* @description : release函数,应用程序调用close关闭驱动文件的时候会执行
* @param - inode : inode节点
* @param - filp : 要打开的设备文件(文件描述符)
* @return : 负数表示函数执行失败
*/
static int imx6uirq_release(struct inode *inode,struct file *filp)
{
return imx6uirq_fasync(-1,filp,0);
}
static struct file_operations keydev_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.poll = imx6uirq_poll,
.fasync = imx6uirq_fasync,
.release = imx6uirq_release,
};
//key_init已经被使用了
static int __init key_init1(void)
{
int ret=0;
/* 注册字符设备驱动 */
if(keydev.major){
keydev.dev_id = MKDEV(keydev.major,0);//构建设备号 子设备号一般设置0
ret = register_chrdev_region(keydev.dev_id,KEY_CNT,KEY_NAME);
}else{
ret = alloc_chrdev_region(&keydev.dev_id,0,KEY_CNT,KEY_NAME);//申请设备号
keydev.major = MAJOR(keydev.dev_id);//获取主设备号
keydev.minor = MINOR(keydev.dev_id);//获取子设备号
}
if(ret < 0){
goto fail_devid;
}
printk("keydev major=%d,minor=%d\r\n",keydev.major,keydev.minor);
/* 初始化cdev */
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev,&keydev_fops);//初始化cdev并绑定设备操作集合
/* 添加一个cdev */
ret=cdev_add(&keydev.cdev,keydev.dev_id,KEY_CNT);
if(ret)
goto fail_cdevadd;
/* 创建类 */
keydev.class = class_create(THIS_MODULE,KEY_NAME);
if(IS_ERR(keydev.class)){
ret = PTR_ERR(keydev.class);
goto fail_class;
}
/* 创建设备节点 mknod 向类中添加设备 */
keydev.device = device_create(keydev.class,NULL,keydev.dev_id,NULL,KEY_NAME);//成功后就会生成/dev/KEY_NAME这个设备文件
if(IS_ERR(keydev.device)){
ret = PTR_ERR(keydev.device);
goto fail_device;
}
ret = keyio_init();
if(ret < 0){
goto fail_keyinit;
}
/* 初始化原子变量 */
atomic_set(&keydev.keyvalue,INVAKEY);
atomic_set(&keydev.releasekey,INVAKEY);
/* 初始化队列头 */
init_waitqueue_head(&keydev.r_wait);
return 0;
fail_keyinit:
fail_device:
class_destroy(keydev.class);
fail_class:
cdev_del(&keydev.cdev);
fail_cdevadd:
unregister_chrdev_region(keydev.dev_id,KEY_CNT);
fail_devid:
return ret;
}
static void __exit key_exit(void)
{
unsigned char i=0;
/* 删除定时器 */
del_timer_sync(&keydev.timer);
/* 释放中断 */
for(i=0;i<KEY_NUM;i++)
{
free_irq(keydev.irqkeydesc[i].irqnum,&keydev);
}
/* 释放gpio */
for(i=0;i<KEY_NUM;i++)
{
gpio_free(keydev.irqkeydesc[i].gpio);
}
/* 注销字符设备 */
cdev_del(&keydev.cdev);
/* 释放设备号 不管是申请的还是注册的 都用下面打函数释放 */
unregister_chrdev_region(keydev.dev_id,KEY_CNT);
/* 删除设备节点 */
device_destroy(keydev.class,keydev.dev_id);
/* 删除类 */
class_destroy(keydev.class);
}
module_init(key_init1);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zys");
三、测试app
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
/********************************************************************
* 文件名:asyncnotiApp.c
* 作者:张亚胜
* 版本:V1.0
* 描述:按键输入测试程序
* 其他:使用方法:./asyncnotiApp /dev/asyncnoti
* 网站:www.s123.xyz
* 日志:出版V1.0 2020/8/10 张亚胜创建
* ******************************************************************/
static int fd = 0;//文件描述符
static void sigio_signal_func(int signum)
{
int err = 0;
unsigned int keyvalue = 0;
err = read(fd,&keyvalue,sizeof(keyvalue));
if(err<0){
//读取错误
}else {
printf("sigio signal key value = %d\r\n",keyvalue);
}
}
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int ret=0;
int flags=0;
char *filename;
if(argc != 2){
printf("params err!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);//阻塞打开
if(fd<0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
/* 设置信号SIGIO处理函数 */
signal(SIGIO,sigio_signal_func);
fcntl(fd,F_SETOWN,getpid());
flags = fcntl(fd,F_GETFD);
fcntl(fd,F_SETFL,flags|FASYNC);
while(1){
sleep(2);
}
ret = close(fd);
if(ret < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return ret;
}