分类: 嵌入式
2015-07-09 17:43:51
原文地址:linux驱动中异步通知应用 作者:xuelei_51
异步通知:
使用异步通知机制可以提高查询设备的效率。通过使用异步通知,应用程序可以在数据可用时收到一个信号,而无需不停地轮询。
设置异步通知的步骤(针对应用层来说的):
1.首先制定一个进程作为文件的属主。通过使用fcntl系统调用执行F_SETOWN命令时,属主进程的ID号就会保存在filp->f_owner中,目的是为了让内核知道应该通知哪个进程。
2.在设备中设置FASYNC标志。通过fcntl调用的F_SETFL来完成。
设置晚以上两步后,输入文件就可以在新数据到达时请求发送一个SIGIO信号,该信号被发送到存放在filp->f_owner中的进程。
实例:启用stdin输入文件到当前进程的异步通知机制
signal(SIGIO,&input_handler);
fcntl(STDIN_FILENO,F_SETOWN,getpid());//设置STDIN_FILENO的属主为当前进程
oflags=fcntl(STDIN_FILENO,F_GETFL);//获得STDIN_FILENO的描述符
fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC);//从新设置描述符
注意的问题:
1.应用程序通常只假设套接字和终端具备异步通知功能。
2.当进程受到SIGIO信号时,它并不知道哪个输入文件有了新的输入。如果有多于一个文件可以异步通知输入的进程,则应用程序必须借助于poll或select来确定输入的来源。
驱动程序的实现:
1、F_SETOWN被调用时对filp->f_owner赋值。
2.在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_owner中的FASYNC标志发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其正确响应。文件打开时,默认fasync标志是被清除的。
3.当数据到达时,所有注册为异步通知的进程都会收到一个SIGIO信号。
linux 这种调用方法基于一个数据结构和两个函数,对于驱动开发而言主要关注两个函数,内核会自己维护该数据结构,为驱动程序服务(书上对该数据结构也并未给出多少解释)。包含在头文件
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
两个函数如下:
int fasnyc_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa,int sig,int band);
当一个打开的文件的FASYNC标志被修改时,调用fasync_helper以便从相关的进程列表中增加或删除文件。当数据到达时,可使用kill_fasync通知所有的相关进程。它的参数是要发送的信号(sig)和带宽(band)。
注意的地方:1.对于struct fasync_struct这个数据结构在编写驱动时并不需要特别关注,它会由内核来维护,驱动程序中调用它即可。如同poll的底层实现上poll_table这个内存页链表也是由内核来维护,驱动程序使用它即可。
2.对于用来通知可读的异步通知:band几乎总是为poll_in
对于用来通知可写的异步通知:band几乎总是为poll_out
3.wait_enevt_interruptible(dev->inq,(dev->rp != dev->wp));
wake_up_interruptible(&dev->inq);
在使进程休眠的时候使用的是值传递,但在唤醒进程的时候使用的指针传递。
实现:
static int scull_p_fasync(int fd,struct file *filp,int mode)
{
struct scull_pipe *dev = filp->private;
fasync_helper(fd,filp,mod,&dev->async_queue);
}
接着当数据到达时,必须执行下面的语句来通知异步读取进程。由于提供scullpipe的读取进程的新数据是在某个进程调用wirte产生的,所以这条语句在scullpipe的write方法中实现:
if(dev->async_queue)
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
最后注意在文件关闭之前,必须调用fasync方法,以便从活动的异步读取进程列表中删除文件。
scull_p_fasync(-1,filp,0);
下面是自己敲了一下scull_pipe的程序,顺便对前边学的复习一下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_p_nr_devs = SCULL_P_NR_DEVS;
int scull_p_buffer = SCULL_P_BUFFER;
module_param(scull_major,int,S_IRUGO);
module_param(scull_minor,int,S_IRUGO);
module_param(scull_p_nr_devs,int,0);
module_param(scull_p_buffer,int,0);
struct scull_pipe {
wait_queue_head_t inq,outq; //定义读写等待队列
char *buffer,*end; //缓冲区开始和结束指针
int buffersize;
char *rp,*wp; //当前的读写位置
int nreaders,nwriters; //读写者计数
sttuct fasync_struct *asynv_queue;
struct semphore sem;
struct cdev cdev;
};
dev_t scull_devno;
static struct scull_pipe *scull_p_devices;
static int scull_p_fasync(int fd,struct file *filp,int mode);
static int spacefree(struct scull_pipe *dev);
static int scull_p_open(struct inode *inode,struct file *filp)
{
struct scull_pipe *dev;
dev = container_of(inode->i_cdev,struct scull_pipe,cdev);
filp->private_data = dev;
if(down_interuptible(&dev->sem))
return -ERESTARTSYS;
if(!dev->buffer){
dev->buffer = kmalloc(scull_p_buffer,GFP_KERNEL);
if(!dev->buffer){
up(&dev->sem);
return -ENOMEM;
}
}
dev->buffersize = scull_p_buffersize;
dev->end = scull_p_buffer + scull_p_buffersize;
dev->rp = dev->wp = dev->buffer;
if(filp->f_mode & FMODE_READ)
dev->nreaders++;
if(filp->f_mode & FMODE_WRITE)
dev->nwriters++;
up(&dev->sem);
return nonseekable_open(inode,filp);
}
static int scull_p_release(struct inode *inode,struct file *filp)
{
struct scull_pipe *dev = filp->private_data;
scull_p_fasync(-1,filp,0);
down_interruptible(&dev->sem);
if(filp->f_mode & FMODE_READ)
dev->nreaders--;
if(filp->f_mode & FMODE_WRITE)
dev->nwriters--;
if(dev->nreaders + dev->nwriters == 0){
kfree(dev->buffer);
dev->buffer =NULL;
}
up(&dev->sem);
return 0;
}
static scull_p_read(struct file *filp,char __user *buf,size_t count,loof_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
if(down_interruptible(&dev->sem))
renturn -ERESTARTSYS;
while(dev->rp == dev->wp){
up(&dev->sem);
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG(....);
if(wait_event_intertuptible(dev->inq,(dev->rp != dev->wp)))
return -ERESTARTSYS;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
....
if(copy_to_user(buf,dev->rp,count){
up(&dev->sem);
return -EFAULT;
}
dev->rp += count;
if(dev->rp == dev->end)
dev->rp = dev->end;
up(&dev->sem);
wake_up_interrputible(&dev->outq); //在读走数据以后,唤醒写等待队列
return count;
}
static int scull_p_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
int err = 0;
if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if(_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
if(_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd));
else if(_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd)):
if(err) return -EFAULT;
这里的access_ok函数,成功返回1,失败返回零。上述代码完成了对cmd的检查确保其唯一性,access_ok是面向内核的 ,所以传输方向与其刚好相反。
switch(cmd){
case SCULL_P_IOCTSIZE: //分别是在头文件中定义的宏命令
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return 0;
}
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
while (spacefree(dev) == 0) { /* full */
DEFINE_WAIT(wait); 设置等待队列入口
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("/"%s/" writing: going to sleep/n",current->comm);
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); //将dev->outq加入到等待队列,并设置其为可中断状态。
if (spacefree(dev) == 0) //这个检查狠重要,确保仍需要休眠
schedule();
finish_wait(&dev->outq, &wait);//schedule()晚以后,马上调用finish_wait
if (signal_pending(current))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
return 0;
}
这里结合scull代码分析下异步通知实现的过程:
异步机制在应用层设计FASYNC标志的置位,驱动层涉及到一个数据结构和两个函数的调用,首先来看驱动层,在scull的驱动代码中用scull_p_fasync函数实现了对fasync_helper的调用,如下:
static int scull_p_fasync(int fd, struct file *filp, int mode)
{
struct scull_pipe *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
在scull_p_write函数中实现了对kill_fasync的调用,如下:
static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
int result;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* Make sure there's space to write */
result = scull_getwritespace(dev, filp);
if (result)
return result; /* scull_getwritespace called up(&dev->sem) */
/* ok, space is there, accept something */
count = min(count, (size_t)spacefree(dev));
if (dev->wp >= dev->rp)
count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
else /* the write pointer has wrapped, fill up to rp-1 */
count = min(count, (size_t)(dev->rp - dev->wp - 1));
PDEBUG("Going to accept %li bytes to %p from %p/n", (long)count, dev->wp, buf);
if (copy_from_user(dev->wp, buf, count)) {
up (&dev->sem);
return -EFAULT;
}
dev->wp += count;
if (dev->wp == dev->end)
dev->wp = dev->buffer; /* wrapped */
up(&dev->sem);
/* finally, awake any reader */
wake_up_interruptible(&dev->inq); /* blocked in read() and select() */
/* and signal asynchronous readers, explained late in chapter 5 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
PDEBUG("/"%s/" did write %li bytes/n",current->comm, (long)count);
return count;
}
在程序的最后调用了kill_fasync,在调用kill_fasync后会向文件的属主进程发送SIGIO信号,从而在驱动层完成了异步机制的设置。下面来看应用程序:
#include
#include
#include
#include
#include
#include
int gotdata=0;
void sighandler(int signo)
{
if (signo==SIGIO)
gotdata++;
return;
}
char buffer[21];
int main(int argc, char **argv)
{
int pipetest0;
int count;
struct sigaction action;
if ((pipetest0 = open("/dev/scullpipe0",O_RDONLY)) < 0) {
printf("open scullpipe0 error! /n");
exit(1);
}
memset(&action, 0, sizeof(action));
action.sa_handler = sighandler;
action.sa_flags = 0;
sigaction(SIGIO, &action, NULL);
fcntl(pipetest0, F_SETOWN, getpid());
fcntl(pipetest0, F_SETFL, fcntl(pipetest0, F_GETFL) | FASYNC);
while(1) {
/* this only returns if a signal arrives */
sleep(86400); /* one day */
if (!gotdata)
continue;
count=read(pipetest0, buffer, 21);
/* buggy: if avail data is more than 4kbytes... */
write(1,buffer,count);
gotdata=0;
break;
}
close(pipetest0 );
printf("close pipetest0 ! /n");
printf("exit !/n");
exit(0);
}
fcntl(pipetest0, F_SETOWN, getpid());
fcntl(pipetest0, F_SETFL, fcntl(pipetest0, F_GETFL) | FASYNC);
完成了应用层异步通知的设置,然后只有在驱动层调用到了kill_fasync从而发出SIGIO信号后,激活sigaction的处理函数,设置了gotdata的值,并得到了POLL_IN状态表明可读了,这是调用read函数读出数据,并通过write将读出的数据发往到终端。以上就是异步交换的整个过程。