前言
中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。
阻塞、非阻塞都是通过主动查询实现的,如果可以类似中断一样,设备能主动告诉我们可以访问就好了。
信号为此应运而生,软件层次上类似硬件的中断,驱动主动向应用程序发送信号的方式报告自己可以访问了
一、信号是什么?
异步通知的核心就是信号,在 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中异步通知及信号的使用,使驱动程序能主动向应用程序发出通知,报告自己可以访问,然后应用程序在从驱动程序中读取或写入数据,类似于在软件上实现中断的效果,制作不易,多多包涵。