Linux下的异步通知


前言

中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。
阻塞、非阻塞都是通过主动查询实现的,如果可以类似中断一样,设备能主动告诉我们可以访问就好了。
信号为此应运而生,软件层次上类似硬件的中断,驱动主动向应用程序发送信号的方式报告自己可以访问了


一、信号是什么?

异步通知的核心就是信号,在 include/uapi/asm-generic/signal.h 头文件中定义了 Linux 所支
持的信号,这些信号如下所示:

	#define SIGHUP 1 /* 终端挂起或控制进程终止 */
	#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
	#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
	#define SIGILL 4 /* 非法指令 */
	#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
	#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
	#define SIGIOT 6 /* IOT 指令 */
	#define SIGBUS 7 /* 总线错误 */
	#define SIGFPE 8 /* 浮点运算错误 */
	#define SIGKILL 9 /* 杀死、终止进程 */
	#define SIGUSR1 10 /* 用户自定义信号 1 */
	#define SIGSEGV 11 /* 段违例(无效的内存段) */
	#define SIGUSR2 12 /* 用户自定义信号 2 */
	#define SIGPIPE 13 /* 向非读管道写入数据 */
	#define SIGALRM 14 /* 闹钟 */
	#define SIGTERM 15 /* 软件终止 */
	#define SIGSTKFLT 16 /* 栈异常 */
	#define SIGCHLD 17 /* 子进程结束 */
	#define SIGCONT 18 /* 进程继续 */
	#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
	#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
	#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
	#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
	#define SIGURG 23 /* 有"紧急"数据 */
	#define SIGXCPU 24 /* 超过 CPU 资源限制 */
	#define SIGXFSZ 25 /* 文件大小超额 */
	#define SIGVTALRM 26 /* 虚拟时钟信号 */
	#define SIGPROF 27 /* 时钟信号描述 */
	#define SIGWINCH 28 /* 窗口大小改变 */
	#define SIGIO 29 /* 可以进行输入/输出操作 */
	#define SIGPOLL SIGIO
	/* #define SIGLOS 29 */
	#define SIGPWR 30 /* 断点重启 */
	#define SIGSYS 31 /* 非法的系统调用 */
	#define SIGUNUSED 31 /* 未使用信号 */

上述信号除了SIGKILL(9)和 SIGSTOP(19)不能忽略,其它都可以,这些信号就相当于中断号,驱动可以向应用程序发送不同信号来实现不同功能。
应用程序中使用 signal 函数来设置指定信号的处理函数, signal 函数原型如下所示:

sighandler_t signal(int signum, sighandler_t handler)
函数参数和返回值含义如下:
signum:要设置处理函数的信号。
handler: 信号的处理函数。
返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。

信号处理函数原型如下所示:
typedef void (*sighandler_t)(int)

常规用法:

void signtest(int num)
{
	;	//按ctrl+c即会执行函数中的内容
}

signal(SIGINT, signtest);

二、驱动中的信号处理

1.fasync_struct 结构体

源码在include/linux/fs.h 头文件中:

struct fasync_struct {
	spinlock_t fa_lock;
	int magic;
	int fa_fd;
	struct fasync_struct *fa_next; /* singly linked list */
	struct file *fa_file;
	struct rcu_head fa_rcu;
};

一般放在驱动程序的设备信息结构体中。

2.fasync 函数

如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数:

int (*fasync) (int fd, struct file *filp, int on)

fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针, fasync_helper 函数原型如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,第四个参数就是要初始化的 fasync_struct 结构体指针变量。
当应用程序通过“ fcntl(fd, F_SETFL, flags | FASYNC) ”改变 fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行。

示例:

struct xxx_dev{
	struct fasync_struct *async_queue;
};

int xxx_fasync (int fd, struct file *filp, int on)
{
	struct xxx_dev *dev = (struct xxx_dev *)filp->private_data;
	int fasync_helper(fd,filp,on, &dev->async_queue)
}

//关闭驱动时release也要释放fasync_struct,释放函数同样为fasync_helper
static int xxx_release(struct inode *inode, struct file *filp)
{
	xxx_fasync(-1,filp,0);	删除异步通知
}

3.kill_fasync 函数

当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。
kill_fasync 函数负责发送指定的信号, kill_fasync 函数原型如下所示:

void kill_fasync(struct fasync_struct **fp, int sig, int band)
函数参数和返回值含义如下:
fp:要操作的 fasync_struct。
sig: 要发送的信号。
band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
返回值: 无。

使用举例:
结构体中定义struct fasync_struct *async_queue,
文件描述符中,fasync中执行fasync_help fasync_helper(fd, b, c, &key.async_queue);
中断有执行,则发送kill_fasync信号 kill_fasync(&key.async_queue, SIGIO, POLL_IN);
release中执行fasync通过fasync_help完成释放工作(key_fasync(-1,filp,0))

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fcntl.h>
 
#define KEY_MAJOR	201
#define KEY_MINOR	0
#define KEY_NAME	"led_init"

enum key_status{
	KEY_UP,
	KEY_DOWN,
	KEY_KEEP,
};

// extern u64 __jiffy_data jiffies_64;

struct key_dev {
	struct class * fclass;
	struct device * fdevice;
	struct cdev * cdev;
	dev_t dev_num;
	struct device_node * nd; 
	int major;
	int minor;
	int gpio;
	int irq_num;
	spinlock_t lock;
	struct timer_list timer;
	int period;
	wait_queue_head_t r_wait;
	struct fasync_struct *async_queue;
};

struct key_dev key = {0};
atomic64_t status;

int key_open (struct inode *a, struct file *b)
{
	unsigned long flags;
	spin_lock_irqsave(&key.lock,flags);
	
	// b->private_data = &key;
	
	spin_unlock_irqrestore(&key.lock,flags);
	
	return 0;
}

ssize_t key_read (struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
	int ret = 0;

	if(filp->f_flags & O_NONBLOCK)	//unblock
	{
		if(atomic64_read(&status) == KEY_KEEP)
			return -1;
	}
	else	//block
		wait_event_interruptible(key.r_wait,atomic64_read(&status) != KEY_KEEP);
	
	ret = copy_to_user(buf,&status,sizeof(status));
	if(!ret)
		printk("key_read copy_to_user failed !\n");
	
	atomic64_set(&status,KEY_KEEP);

	return 0;
}

unsigned int key_poll (struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	
	poll_wait(filp, &key.r_wait, wait);
	
	if(atomic64_read(&status) != KEY_KEEP)
		mask |= POLLIN;
	
	return mask;
}

int key_fasync (int fd, struct file *b, int c)
{
	return fasync_helper(fd, b, c, &key.async_queue);
}

int key_release (struct inode *node, struct file *filp)
{
	key_fasync(-1,filp,0);
	return 0;
}

struct file_operations fop = 
{
	.owner = THIS_MODULE,
	.open = key_open,
	.read = key_read,
	.poll = key_poll,
	.fasync = key_fasync,
	.release = key_release,
};

void time_function(struct timer_list *timer)
{
	static int flag = 1;
	int current_val = 0;
	current_val = gpio_get_value(key.gpio);
	if(current_val == 0 && flag == 1)
	{
		// status = KEY_DOWN;
		atomic64_set(&status,KEY_DOWN);
		wake_up_interruptible(&key.r_wait);
		if(key.async_queue)
			kill_fasync(&key.async_queue, SIGIO, POLL_IN);
	}
		
	else if(current_val == 1 && flag == 0)
	{
		// status = KEY_UP;
		atomic64_set(&status,KEY_UP);
		wake_up_interruptible(&key.r_wait);
		if(key.async_queue)
			kill_fasync(&key.async_queue, SIGIO, POLL_IN);
	}
	else
		atomic64_set(&status,KEY_KEEP);
		
	flag = current_val;
	return;
}


irqreturn_t key_interrupt(int irq, void *arg)
{
	mod_timer(&key.timer,jiffies_64 + msecs_to_jiffies(30));//中断,消抖30ms
	return IRQ_HANDLED;
}

int __init key_test_init(void)
{	
	const char *str = {0};
	int ret = 0;
	unsigned int flag = 0;
	
	key.nd = of_find_node_by_name(NULL,"key");
	if(key.nd == NULL)
	{
		printk("key of_find_node_by_name = NULL !\n");
		return -1;
	}
	
	key.gpio = of_get_named_gpio(key.nd,"key-gpio",0);
	if(!gpio_is_valid(key.gpio))
	{
		printk("key gpio unvalid, failed ~\n");
		return -1;
	}
	
	key.irq_num = irq_of_parse_and_map(key.nd,0);
	
	gpio_request(key.gpio,"key-GPIO");
	gpio_direction_input(key.gpio);
	
	flag = irq_get_trigger_type(key.irq_num);
	if(flag == IRQF_TRIGGER_NONE)
		flag =  IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;

	ret = request_irq(key.irq_num,
				key_interrupt,
				flag,
				"key-interrpt",
				NULL);
	if(ret)
		printk("key request irq failed !\n");

	key.cdev = cdev_alloc();
	key.dev_num = MKDEV(KEY_MAJOR,KEY_MINOR);
	register_chrdev_region(key.dev_num, 1, KEY_NAME);
	cdev_init(key.cdev,&fop);
	cdev_add(key.cdev,key.dev_num,1);
	
	key.fclass = class_create(THIS_MODULE, "key_class");
	key.fdevice = device_create(key.fclass, NULL, key.dev_num, NULL, "key_device");
	
	key.major = KEY_MAJOR;
	key.minor = KEY_MINOR;

	timer_setup(&key.timer,time_function,0);

	atomic64_set(&status,KEY_KEEP);
	init_waitqueue_head(&key.r_wait);
	
	printk("key init success, jiffies_64 = %llu,ms = %d ~~~\n",jiffies_64,jiffies_to_msecs(jiffies_64));

	return 0;
}

void __exit key_test_exit(void)
{
	device_destroy(key.fclass,key.dev_num);
	class_destroy(key.fclass);
	cdev_del(key.cdev);
	unregister_chrdev(KEY_MAJOR, KEY_NAME);
	gpio_free(key.gpio);

	return;
}

module_init(key_test_init);
module_exit(key_test_exit);

MODULE_LICENSE("GPL");

三、应用程序对异步通知的处理

分3步
1、注册信号处理函数
使用signal()来设置信号的处理函数

2、将本应用程序的进程号告诉给内核
使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。

3、开启异步通知
使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 /
fcntl(fd, F_SETFL, flags | FASYNC); /
开启当前进程异步通知功能 */
重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。

整体框架如下:
在这里插入图片描述


总结

以上就是今天要讲的内容,本文简单介绍了Linux中异步通知及信号的使用,使驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据,类似于在软件上实现中断的效果,制作不易,多多包涵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值