异步通知的意思是: 一旦设备就绪, 则驱动主动通知应用程序, 这样应用程序根本就不需要查询设备状态, 这一点非常类似于硬件上“中断”的概念, 比较准确的称谓是“信号驱动的异步I/O”。 信号是在软件层次上对中断机制的一种模拟, 在原理上, 一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。 信号是异步的, 一个进程不必通过任何操作来等待信号的到达, 事实上, 进程也不知道信号到底什么时候到达。
linux中的信号:
使用信号进行进程间通信(IPC) 是UNIX中的一种传统机制, Linux也支持这种机制。 在Linux中, 异步通知使用信号来实现(可以理解为异步通知是信号的一种情况), Linux中可用的信号及其定义如表:
除了SIGSTOP和SIGKILL两个信号外, 进程能够忽略或捕获其他的全部信号。 一个信号被捕获的意思
是当一个信号到达时有相应的代码处理它。 如果一个信号没有被这个进程所捕获, 内核将采用默认行为处
理。
信号的接受(应用层)
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler)
第一个参数指定信号的值。
第二个参数指定针对前面信号值的处理函数, 若为SIG_IGN, 表示忽略该信号; 若为SIG_DFL, 表示采用系统默认方式处理信号; 若为用户自定义的函数, 则信号被捕获到后, 该函数将被执行。
如果signal() 调用成功, 它返回最后一次为信号signum绑定的处理函数的handler值, 失败则返回SIG_ERR。
在进程执行时, 按下“Ctrl+C”将向其发出SIGINT信号, 正在运行kill的进程将向其发出SIGTERM信号。
这里先给出信号接收函数。(应用层)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
int fd;
void signal_handler(int signo)
{
char buf[2];
read(fd, buf, 1);
printf("buf = %d\n", buf[0]);
}
int main(int argc,char *argv[])
{
int flag;
fd = open("/dev/button", O_RDWR);
if(fd < 0)
{
printf("open /dev/%s fail\n",argv[1]);
return -1;
}
/* 设置信号处理函数 */
signal(SIGIO, signal_handler);
/* 设置本进程为fd文件的拥有者 */
fcntl(fd , F_SETOWN, getpid());
/* 得到本文件的默认flag */
flag = fcntl(fd, F_GETFL);
/* 启动异步通知fd */
fcntl(fd ,F_SETFL, flag | FASYNC);
while(1)
{
sleep(1);
}
close(fd);
return 0;
}
1.通过signal(SIGIO,signal_handler) 设置对应IO信号的处理函数。
signal(SIGIO, signal_handler);
2.设置本进程为fd文件的拥有者,没有这一步, 内核不会知道应该将信号发给哪个进程。
fcntl(fd , F_SETOWN, getpid());
3.给本文件的flag增加一个异步通知功能。
flag = fcntl(fd, F_GETFL);
fcntl(fd ,F_SETFL, flag | FASYNC);
上面设置的其实是下面标记的两个文件相关的参数
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags; /* 存放文件的flag */
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner; /* 表明该文件当前属于那个进程 */
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
/* 描述文件的从属关系 */
struct fown_struct {
rwlock_t lock; /* protects pid, uid, euid fields */
struct pid *pid; /* pid or -pgrp where SIGIO should be sent 进行号 */
enum pid_type pid_type; /* Kind of process group SIGIO should be sent to */
kuid_t uid, euid; /* uid/euid of process setting the owner */
int signum; /* posix.1b rt signal to be delivered on IO */
};
为了使设备支持异步通知机制, 驱动程序中涉及3项工作。
1) 支持F_SETOWN命令, 能在这个控制命令处理中设置filp->f_owner为对应进程ID。 不过此项工作已由内核完成, 设备驱动无须处理。
2) 支持F_SETFL命令的处理, 每当FASYNC标志改变时, 驱动程序中的fasync() 函数将得以执行。因此, 驱动中应该实现fasync() 函数。
3) 在设备资源可获得时, 调用kill_fasync() 函数激发相应的信号。
驱动中的上述3项工作和应用程序中的3项工作是一一对应的。
其中fcntl函数我们这里不详细分析,这里只列出和上面有关的部分。
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
struct fd f = fdget_raw(fd);
long err = -EBADF;
if (!f.file)
goto out;
if (unlikely(f.file->f_mode & FMODE_PATH)) {
if (!check_fcntl_cmd(cmd))
goto out1;
}
err = security_file_fcntl(f.file, cmd, arg);
if (!err)
err = do_fcntl(fd, cmd, arg, f.file); /* 执行这个 */
out1:
fdput(f);
out:
return err;
}
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
{
long err = -EINVAL;
switch (cmd) {
......
case F_GETFL:
err = filp->f_flags; /* 得到标志 */
break;
case F_SETFL:
err = setfl(fd, filp, arg); /* 设置flag */
break;
......
case F_GETOWN:
/*
* XXX If f_owner is a process group, the
* negative return value will get converted
* into an error. Oops. If we keep the
* current syscall conventions, the only way
* to fix this will be in libc.
*/
err = f_getown(filp); /**/
force_successful_syscall_return();
break;
case F_SETOWN:
err = f_setown(filp, arg, 1); /* 设置拥有者 */
break;
......
default:
break;
}
return err;
}
static int setfl(int fd, struct file * filp, unsigned long arg)
{
struct inode * inode = file_inode(filp);
int error = 0;
/*
* O_APPEND cannot be cleared if the file is marked as append-only
* and the file is open for write.
*/
if (((arg ^ filp->f_flags) & O_APPEND) && IS_APPEND(inode))
return -EPERM;
/* O_NOATIME can only be set by the owner or superuser */
if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME))
if (!inode_owner_or_capable(inode))
return -EPERM;
/* required for strict SunOS emulation */
if (O_NONBLOCK != O_NDELAY)
if (arg & O_NDELAY)
arg |= O_NONBLOCK;
if (arg & O_DIRECT) {
if (!filp->f_mapping || !filp->f_mapping->a_ops ||
!filp->f_mapping->a_ops->direct_IO)
return -EINVAL;
}
if (filp->f_op->check_flags)
error = filp->f_op->check_flags(arg);
if (error)
return error;
/*
* ->fasync() is responsible for setting the FASYNC bit.
*/
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); /* 在这里调用我们的驱动里面的函数 */
if (error < 0)
goto out;
if (error > 0)
error = 0;
}
spin_lock(&filp->f_lock);
filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
spin_unlock(&filp->f_lock);
out:
return error;
}
信号发送函数(异步通知的在驱动中发送)
#include <linux/fs.h> /* 包含file_operation结构体 */
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/highmem.h> /* For wait_event_interruptible */
#include <linux/poll.h>
#include <asm/gpio.h>
#include <asm/uaccess.h>
#include <linux/sysctl.h>
static unsigned int major;
static struct class *button_class;
static struct device *button_dev;
static unsigned char key_val;
static struct fasync_struct *button_fasync = NULL;
struct pin_desc {
unsigned int pin;
unsigned int key_val;
};
/* 按下时 值分别是 0x01 , 0x02 */
/* 松开时 值分别是 0x00 , 0x00 */
static struct pin_desc pins_desc[] = {
{S5PV210_GPH0(2), 0x01},
{S5PV210_GPH0(3), 0x02},
};
static irqreturn_t irq_handler(int irq, void *dev_id)
{
struct pin_desc *p = dev_id;
int pin_val;
pin_val = gpio_get_value(p->pin);
/* 得到键值,判断时按下还是松开 */
if(pin_val)
{
/* 松开 */
key_val &= ~p->key_val;
}
else
{
/* 按下 */
key_val |= p->key_val;
}
/* 产生一个异步读信号 */
kill_fasync(&button_fasync, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
/* open函数 */
static int button_drv_open(struct inode *inode, struct file *file)
{
int ret = 0;
ret = request_irq(IRQ_EINT(2), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint2",&pins_desc[0]);
if(ret)
{
printk(KERN_ERR"request_irq IRQ_EINT(2) fail");
}
ret = request_irq(IRQ_EINT(3), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint3",&pins_desc[1]);
if(ret)
{
printk(KERN_ERR"request_irq IRQ_EINT(3) fail");
}
return 0;
}
static ssize_t button_drv_read(struct file *file, char __user *array, size_t size, loff_t *ppos)
{
int len;
if(size < 1)
{
return -EINVAL;
}
/* 赋值只是为了消除告警 */
len = copy_to_user(array , &key_val, 1);
return 1;
}
int button_drv_fasync (int fd, struct file *file, int on)
{
printk(KERN_INFO"button_drv_fasync\n");
/* 增加一个异步通知到到本设备的异步通知队列中 */
return fasync_helper(fd , file, on, &button_fasync);
}
static int button_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT(2), &pins_desc[0]);
free_irq(IRQ_EINT(3), &pins_desc[1]);
/* 移除一个async */
fasync_helper(-1, file, 0, &button_fasync);
return 0;
}
static const struct file_operations button_drv_file_operation = {
.owner = THIS_MODULE,
.open = button_drv_open,
.read = button_drv_read,
.fasync = button_drv_fasync,
.release = button_drv_close,
};
static int __init button_drv_init(void)
{
/* 获取一个自动的主设备号 */
major = register_chrdev(0,"button_drv",&button_drv_file_operation);
if(major < 0)
{
printk(KERN_ERR"register_chrdev button_drv fail \n");
goto err_register_chrdev;
}
/* 创建一个类 */
button_class = class_create(THIS_MODULE, "button_class");
if(!button_class)
{
printk(KERN_ERR"class_create button_class fail\n");
goto err_class_create;
}
/* 创建从属这个类的设备 */
button_dev = device_create(button_class,NULL,MKDEV(major, 0), NULL, "button");
if(!button_dev)
{
printk(KERN_ERR"device_create button_dev fail \n");
goto err_device_create;
}
return 0;
/* 倒影式错误处理机制 */
err_device_create:
class_destroy(button_class);
err_class_create:
unregister_chrdev(major,"button_drv");
err_register_chrdev:
return -EIO;
}
static void __exit button_drv_exit(void)
{
/* 注销类里面的设备 */
device_unregister(button_dev);
/* 注销类 */
class_destroy(button_class);
/* 注销字符设备 */
unregister_chrdev(major,"button_drv");
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");
我们上面程序中,先是应用程序设置当前设备的FASYNC标志,他会调用驱动里面的fasync函数,而驱动中的fasync则继续调用fasync_helper函数,里面申请一个struct fasync_struct,并把申请的struct fasync_struct加入到设备驱动中定义的struct fasync_struct做为链表头的一个链表上,其中在里面会初始化和绑定好要异步通知的进程等信息。
其中
/*
* fasync_helper() is used by almost all character device drivers
* to set up the fasync queue, and for regular files by the file
* lease code. It returns negative on error, 0 if it did no changes
* and positive if it added/deleted the entry.
* 加入或移除异步通知对于该设备的异步通知链表
*/
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
其中可以看到在一个文件支持异步通知时on传的是非0值,而移除异步通知功能时,是要传入0
/*
* Add a fasync entry. Return negative on error, positive if
* added, and zero if did nothing but change an existing one.
*/
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *new;
new = fasync_alloc(); /* 申请空间 */
if (!new)
return -ENOMEM;
/*
* fasync_insert_entry() returns the old (update) entry if
* it existed.
*
* So free the (unused) new entry and return 0 to let the
* caller know that we didn't add any new fasync entries.
* 如果已经添加过,则返回1,释放掉新申请的
*/
if (fasync_insert_entry(fd, filp, fapp, new)) {
fasync_free(new);
return 0;
}
return 1;
}
/*
* Insert a new entry into the fasync list. Return the pointer to the
* old one if we didn't use the new one.
*
* NOTE! It is very important that the FASYNC flag always
* match the state "is the filp on a fasync list".
*/
struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{
struct fasync_struct *fa, **fp;
spin_lock(&filp->f_lock);
spin_lock(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file != filp) /* 判断有没有已经添加过 */
continue;
spin_lock_irq(&fa->fa_lock);
fa->fa_fd = fd;
spin_unlock_irq(&fa->fa_lock);
goto out; /* 已经添加过,则不用再添加,直接退出 */
}
/* 初始化并绑定到链表上 */
spin_lock_init(&new->fa_lock);
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
rcu_assign_pointer(*fapp, new); /
filp->f_flags |= FASYNC; /* 该文件,增加异步通知标志 */
out:
spin_unlock(&fasync_lock);
spin_unlock(&filp->f_lock);
return fa;
}
int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *fa, **fp;
int result = 0;
spin_lock(&filp->f_lock);
spin_lock(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file != filp)
continue;
spin_lock_irq(&fa->fa_lock);
fa->fa_file = NULL; /* 异步通知链表中的该项先取消和文件关联 */
spin_unlock_irq(&fa->fa_lock);
*fp = fa->fa_next; /* 绑定回掉函数 */
call_rcu(&fa->fa_rcu, fasync_free_rcu);
filp->f_flags &= ~FASYNC; /* 取消该文件对应用进程的的异步通知功能 */
result = 1;
break;
}
spin_unlock(&fasync_lock);
spin_unlock(&filp->f_lock);
return result;
}
/**
* struct callback_head - callback structure for use with RCU and task_work
* @next: next update requests in a list
* @func: actual update function to call after the grace period.
*/
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
};
#define rcu_head callback_head
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; /* 回调函数 */
};
/* 通知该设备的异步通知链表上绑定的进程(设置时在用户空间也设置了fd,通过fd找到用户具体信号处理函数) */
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp) {
rcu_read_lock();
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
rcu_read_unlock();
}
}
通过异步通知我们可以时应用程序不用阻塞,不用查询,得到按键数据。