1、异步通知的概念和作用
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上”中断“的概念,比较准确的称谓是”信号驱动的异步I/O“。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
2、Linux异步通知编程
Linux信号。使用信号在进程间通信(IPC)是UNIX系统中的一种传统机制,Linux系统也支持这种机制。在Linux系统中,异步通知使用信号来实现,Linux系统中可用的信号及其定义如下图所示。
除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他全部的信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程捕获,内核将采取默认处理。
信号的接收。在用户程序中为了捕获信号,可以使用signal()函数来设置对应信号的处理函数,如下所示,第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若位用户自定义的函数,则信号被捕获到后,该函数将被执行。
sighandler_t signal(int signum,sighandler_t handler);
如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数handler值,失败返回SIG_ERR。以下是一个使用信号实现异步通知的例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
len = read(STDIN_FILENO,&data,MAX_LEN);
data[len]=0;
printf("input available:%s\n",data);
}
int main()
{
int oflags;
signal(SIGIO,input_handler);
fcntl(STDIN_FILENO,F_SETOWN,getpid());//设置本进程为标准输入文件的拥有者,指示信号要发送给本进程
//oflags = fcntl(STDIN_FILENO,F_GETFL);
//fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
while(1);
}
在用户空间中能处理一个设备释放的信号必须完成以下工作:
1、通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。
2、通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式。
3、通过signal()函数连接信号和信号处理函数。
信号的释放。在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,应该在合适的时机让设备驱动释放信号,在设备驱动程序中增加信号释放的相关代码。
设备驱动中的异步通知编程比较简单,主要用到一项数据结构和两个函数。数据结构是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);
和其他的设备驱动一样,fasync_struct结构体可以放在设备结构体中:
struct xxx_dev{ struct cdev cdev; ...... struct fasync_struct *async_queue;//异步结构体指针 };
以下是支持异步通知的设备驱动程序fasync()函数的模板:
static int xxx_fasync(int fd,struct file *filp,int mode) { struct xxx_dev *dev = filp->private_data; return fasync_helper(fd,filp,mode,&dev->async_queue); }
在设备资源可获得时,应该调用kill_fasync()释放SIGIO信号,可读时第3个参数设置为POLL_IN,可写时第3个参数设置为POLL_OUT,代码如下:
ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset) { struct globalfifo_dev *dev = filp->private_data; ... //产生异步信号 if(dev->async_queue) { kill_fasync(&dev->async_queue,SIGIO,POLL_IN); } ... }
最后在文件关闭时,应在release函数中调用设备驱动的fasync函数将文件从异步通知的队列中删除:
int globalfifo_release(struct inode *node, struct file *filp) { struct globalfifo_dev *dev = filp->private_data; ... //将文件从异步通知队列中删除 globalfifo_fasync(-1,filp,0); ... return 0; }
支持异步通知的globalfifo的驱动。需要在file_operations中定义 .fasync函数。当应用程序使用fcntl设置FASYNC标志时,驱动程序就会调用定义好的fasync函数,注册异步通知队列。驱动程序在写完数据后,使用kill_fasync函数释放读信号。
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pmbus.h>
#include <linux/util_macros.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#define GLOBALFIFO_SIZE 20
int globalfifo_major=0;
struct class *globalfifo_class;
struct globalfifo_dev{
//struct cdev cdev;//cdev结构体
unsigned int current_len;//fifo有效数据长度
unsigned char mem[GLOBALFIFO_SIZE];//全局内存123
struct semaphore sem;//并发控制用的信号量
wait_queue_head_t r_wait;//阻塞读用的等待队列头
wait_queue_head_t w_wait;//阻塞写用的等待队列头
struct fasync_struct *async_queue;//异步结构指针,用于读
};
struct globalfifo_dev *globalfifo_devp;
int globalfifo_open(struct inode *node, struct file *filp)
{
printk(KERN_INFO"globalfifo_open\n");
filp->private_data = globalfifo_devp;
printk(KERN_INFO"private_data\n");
return 0;
}
int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
printk("globalfifo_fasync");
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
int globalfifo_release(struct inode *node, struct file *filp)
{
printk(KERN_INFO"globalfifo_release\n");
globalfifo_fasync(-1,filp,0);
return 0;
}
ssize_t globalfifo_read(struct file *filp, char *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO"globalfifo_read\n");
int ret;
struct globalfifo_dev *dev = filp->private_data;
printk(KERN_INFO"read try to declare queue\n");
DECLARE_WAITQUEUE(wait,current);
printk(KERN_INFO"read try to get sem\n");
down(&dev->sem);
printk(KERN_INFO"read have got sem\n");
add_wait_queue(&dev->r_wait,&wait);
if(dev->current_len==0)
{
if(filp->f_flags & O_NONBLOCK)
{
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
printk(KERN_INFO"read release sem,begin shcedule\n");
schedule();
if(signal_pending(current))
{
ret = -ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if(size > dev->current_len)
size = dev->current_len;
if(copy_to_user(buf,dev->mem,size))
{
ret = -EFAULT;
goto out;
}
else
{
//printk("%s",kern_buf);
memcpy(dev->mem,dev->mem+size,dev->current_len-size);
dev->current_len-=size;
printk(KERN_INFO"read %d bytes(s),current_len:%d\n",size,dev->current_len);
wake_up_interruptible(&dev->w_wait);
ret = size;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->r_wait,&wait);
set_current_state(TASK_RUNNING);
return size;
}
ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
printk("global_fifo write");
struct globalfifo_dev *dev = filp->private_data;
int ret,i=0,j;
DECLARE_WAITQUEUE(wait,current);
down(&dev->sem);
add_wait_queue(&dev->w_wait,&wait);
if(dev->current_len == GLOBALFIFO_SIZE)
{
if(filp->f_flags & O_NONBLOCK)
{
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
schedule();
if(signal_pending(current))
{
ret = -ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if(size>GLOBALFIFO_SIZE-dev->current_len)
{
size = GLOBALFIFO_SIZE-dev->current_len;
}
//for(i=dev->current_len,j=0;i<dev->current_len+size;i++,j++)dev->mem[i]=buf[j];
if(copy_from_user(dev->mem + dev->current_len,buf,size))
{
ret = -EFAULT;
goto out;
}
else
{
dev->current_len+=size;
printk(KERN_INFO"written %d bytes(s),current_len:%d\n",size,dev->current_len);
wake_up_interruptible(&dev->r_wait);
//产生异步读信号
if(dev->async_queue)
{
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
}
ret = size;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
unsigned int globalfifo_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;
down(&dev->sem);
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
if(dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM;
}
if(dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM;
}
up(&dev->sem);
return mask;
}
struct file_operations globalfifo_drv={
.owner = THIS_MODULE,
.open = globalfifo_open,
.release = globalfifo_release,
.read = globalfifo_read,
.write = globalfifo_write,
.poll = globalfifo_poll,
.fasync = globalfifo_fasync,
};
int globalfifo_init(void)
{
int ret;
//申请设备号
globalfifo_major = register_chrdev(0,"globalfifo",&globalfifo_drv);
globalfifo_class = class_create("globalfifo_class");
if (IS_ERR(globalfifo_class)) {
printk(KERN_ERR "globalfifo_class: failed to allocate class\n");
return PTR_ERR(globalfifo_class);
}
device_create(globalfifo_class,NULL,MKDEV(globalfifo_major,0),NULL,"globalfifo_device");
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev),GFP_KERNEL);
if(!globalfifo_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
memset(globalfifo_devp,0,sizeof(struct globalfifo_dev));
//globalfifo_setup_cdev(globalfifo_devp,0);
sema_init(&globalfifo_devp->sem,1);
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait);
return 0;
fail_malloc:device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
class_destroy(globalfifo_class);
unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
return ret;
}
void globalfifo_exit(void)
{
device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
class_destroy(globalfifo_class);
unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
return;
}
module_init(globalfifo_init);
module_exit(globalfifo_exit);
MODULE_LICENSE("GPL");