前面已经说了阻塞与非阻塞的访问方式,这里我们就继续说下异步通知的机制。
什么是异步通知呢?异步通知的意思就是,一旦设备就绪,则主动通知应用程序,应用程序根本就不需要查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。下面我们就看一下在linux中机制的实现方式。
在linux中,异步通知是使用信号来实现的,而在linux,大概有30种信号,比如大家熟悉的ctrl+c的SIGINT信号,进程能够忽略或者捕获除过SIGSTOP和SIGKILL的全部信号,当信号背捕获以后,有相应的函数来处理它。
在linux中,使用signal()函数来捕获信号,它的函数运行如下:
-
#include
-
-
typedef void (*sighandler_t)(int);
-
-
sighandler_t signal(int signum, sighandler_t handler);
第一个参数就是指定的信号的值,而第二个参数便是此信号的信号处理函数,当为SIG_IGN,表示信号被忽略,当为SIG_DFL时,表示采用系统的默认方式来处理该信号。当然,信号处理函数也可以自己定义。当signal()调用成功后,返回处理函数handler值,调用失败后返回SIG_ERR。下面的函数就是当ctrl+c发生以后,进程捕获别且指定处理函数:
-
signal(SIGINT,sigterm_handler);
这里面SIGINT便是接受的信号,而sigterm_handler就是针对此信号的处理函数,当然处理函数可以自己定义。
下面我们来看一个异步通知的实例:
-
#include
-
#include
-
#include
-
#include
-
#include
-
#include
-
#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);
-
}
-
-
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);
-
}
首先这里面将要进行捕获的信号是SIGIO信号,这里面通过signal()对标准输入文本描述符STDIN_FILENO启动异步信号机制。当用户进行输入以后,进程将捕获到SIGIO信号,然后调用处理函数input_handler()进行处理,当然这个函数是自己定义的。而 fcntl(STDIN_FILENO,F_SETOWN,getpid())设置进程为STDIN_FILENO文件的拥有者,这一步其实是将该信号与进程进行绑定,下面oflags=fcntl(STDIN_FILENO,F_GETFL)和 fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC)是启动异步机制,对设备设置FASYNC标志,程序执行后效果如下:
-
wanchres@wanchres-desktop:~/Linux/test/c code$ ./signal_test
-
wanchres
-
input available:wanchres
当输入一串字符以后,标准输入设备释放SIGIO信号,然后这个信号驱使对应的应用程序中的处理函数得以执行,将输入显示出来。上面的例子很好的说明了一个在用户空间中处理一个释放信号的具体的过程。
而在驱动程序和应用程序的异步通知的交互中,这种仅仅在应用程序端的,因为信号的源头在设备驱动端。因此,应该再合适的时候让设备驱动释放信号,其相关的步骤如下:
1、支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID,此工作已由内核完成。
2、支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动中的fasync()函数将得以执行,所以驱动中应实现fasync()函数。
3、在设备资源可获得时,调用kill_fasync()函数激发相应的信号。
设备驱动中异步通知的编程实现中,主要用到一个结构体fasync_struct,其定义为:
-
struct fasync_struct {
-
int magic;
-
int fa_fd;
-
struct fasync_struct *fa_next;
-
struct file *fa_file;
-
};
fa_file即进程打开设备文件时创建的file结构file.owner即为进程的pid。
处理FASYNC标志变更的函数:
-
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
释放信号的函数:
-
void kill_fasync(struct fasync_struct **fp, int sig, int band)
下面我们来看下支持异步通知的模板。
设备结构体:
-
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信号,可读时第三个参数是POLL_IN,可写时为POLL_OUT.
-
static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
-
{
-
struct xxx_dev *dev = filp->private_data;
-
......
-
if(dev->async_queue)
-
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
-
......
-
}
在release函数中,应调用fasync()函数将文件从异步通知的列表中删除。
-
int xxx_release(struct inode *inode,struct file *filp)
-
{
-
xxx_fasync(-1,filp,0);
-
return 0;
-
}
上面是异步通知在设备驱动中的模板,下面我们来看看在globalmem中添加异步通知后各模块的代码:
-
struct globalmem_dev{
-
struct cdev cdev;
-
unsigned int current_len;
-
unsigned char mem[GLOBALMEM_SIZE];
-
struct semaphore sem;
-
wait_queue_head_t r_wait;
-
wait_queue_head_t w_wait;
-
struct fasync_struct *async_queue;
-
};
-
static int globalmem_fasync(int fd,struct file *filp,int mode)
-
{
-
struct globalmem_dev *dev = filp->private_data;
-
return fasync_helper(fd,filp,mode,&dev->async_queue);
-
}
-
static ssize_t globalmem_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
-
{
-
unsigned long p = *ppos;
-
int ret = 0;
-
struct globalmem_dev *dev = filp->private_data;
-
DECLARE_WAITQUEUE(wait,current);
-
-
down(&dev->sem);
-
add_wait_queue(&dev->w_wait,&wait);
-
-
while(dev->current_len==GLOBALMEM_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(count > GLOBALMEM_SIZE-dev->current_len)
-
count = GLOBALMEM_SIZE-dev->current_len;
-
if(copy_from_user(dev->mem+dev->current_len,buf,count)){
-
ret = -EFAULT;
-
goto out;
-
}else{
-
dev->current_len += count;
-
printk(KERN_INFO "written %d bytes(s),current_len:%d\n",count,dev->current_len);
-
-
wake_up_interruptible(&dev->r_wait);
-
-
if(dev->async_queue)
-
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
-
-
ret = count;
-
}
-
out:up(&dev->sem);
-
out2:remove_wait_queue(&dev->w_wait,&wait);
-
set_current_state(TASK_RUNNING);
-
return ret;
-
}
-
int globalmem_release(struct inode *inode,struct file *filp)
-
{
-
globalmem_fasync(-1,filp,0);
-
return 0;
-
}
代码写好以后,我们还需编写一个在用户空间验证globalmem异步通知的程序,程序的功能是接收到globalmem发出的信号后输出信号值,代码如下:
-
#include
-
#include
-
#include
-
#include
-
#include
-
#include
-
-
void input_handler(int signum)
-
{
-
printf("receive a signal from globalmem,signalnum:%d\n",signum);
-
}
-
-
main()
-
{
-
int fd,oflags;
-
fd = open("/dev/globalmem_sync",O_RDWR,S_IRUSR|S_IWUSR);
-
if(fd != -1){
-
signal(SIGIO,input_handler);
-
fcntl(fd,F_SETOWN,getpid());
-
oflags = fcntl(fd,F_GETFL);
-
fcntl(fd,F_SETFL,oflags|FASYNC);
-
while(1){
-
sleep(100);
-
}
-
}else{
-
printf("device open failure\n");
-
}
-
}
将驱动的内核模块make完成后,将其插入,然后编译用户程序,采用前面介绍过的加载新的设备驱动别且创建设备文件节点(mknod /dev/globalmem),最后再echo向/dev/globalmenm写入新的数据,例如:echo wanchres > /dev/globalmem。