linux设备驱动中的异步通知机制
Posted 勇士后卫头盔哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux设备驱动中的异步通知机制相关的知识,希望对你有一定的参考价值。
前言
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”.信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
应用层
实现异步通知机制,用户程序涉及2项工作,首先,得指定一个进程作为文件的“属主(owner)”.当应用程序使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中.这一步是必需的,目的是告诉内核将信号发给谁,也就是发给哪个进程.然后为了真正启动异步通知机制,用户程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的,文件打开时,FASYNC标志被默认为是清除的.,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。.执行完这两个步骤之后,内核就可以在新数据到达时通过调用kill_fasync()函数请求发送一个SIGIO信号给应用层,该信号被发送到存放在filp->f_owner中的进程(如果是负值就是进程组)
内核层
根据内核源码来看看调用过程,应用层调用fcntl(),会进行系统调用sys_fcntl(),接着调用do_fcntl(),再根据cmd调用相应的操作函数
sys_fcntl
asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
struct file *filp;
long err = -EBADF;
filp = fget(fd);//通过文件描述符获得相应的文件指针
if (!filp)
goto out;
err = security_file_fcntl(filp, cmd, arg);//调用do_fcntl函数
if (err)
fput(filp);
return err;
err = do_fcntl(fd, cmd, arg, filp);
fput(filp);
out:
return err;
do_fcntl
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
long err = -EINVAL;
......
case F_GETFL: //返回文件标志
err = filp->f_flags;
break;
case F_SETFL:
err = setfl(fd, filp, arg);//转调用setfl函数
break;
case F_SETOWN:
err = f_setown(filp, arg, 1);//转调用f_setown函数
break;
.......
default:
break;
return err;
setfl函数的内部实现
static int setfl(int fd, struct file * filp, unsigned long arg)
struct inode * inode = filp->f_dentry->d_inode;
int error = 0;
……
lock_kernel();
//下面这个判断语句有点意思,是一个边缘触发
//FASYNC标志从0变为1的时候为真
if ((arg ^ filp->f_flags) & FASYNC) //FASYNC标志发生了变化
if (filp->f_op && filp->f_op->fasync)
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);//每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。
if (error < 0)
goto out;
filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
out:
unlock_kernel();
return error;
t
f_setown函数的内部实现
int f_setown(struct file *filp, unsigned long arg, int force)
int err;
err = security_file_set_fowner(filp);
if (err)
return err;
f_modown(filp, arg, current->uid, current->euid, force);//调用f_modown函数
return 0;
static void f_modown(struct file *filp, unsigned long pid,
uid_t uid, uid_t euid, int force)
write_lock_irq(&filp->f_owner.lock);
//设置对应的pid,uid,euid
if (force || !filp->f_owner.pid)
filp->f_owner.pid = pid;
filp->f_owner.uid = uid;
filp->f_owner.euid = euid;
write_unlock_irq(&filp->f_owner.lock);
相关的数据结构和函数
struct fasync_struct
int magic;
int fa_fd;//文件描述符
struct fasync_struct *fa_next; /* singly linked list *///异步通知队列
struct file *fa_file;//文件指针
;
处理FASYNC标志变更的函数
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
struct fasync_struct *fa, **fp;
struct fasync_struct *new = NULL;
int result = 0;
if (on)
new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);//创建对象,slab分配器
if (!new)
return -ENOMEM;
write_lock_irq(&fasync_lock);
//遍历整个异步通知队列,看是否存在对应的文件指针
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next)
if (fa->fa_file == filp) //已存在
if(on)
fa->fa_fd = fd;//文件描述符赋值
kmem_cache_free(fasync_cache, new);//销毁刚创建的对象
else
*fp = fa->fa_next;//继续遍历
kmem_cache_free(fasync_cache, fa);//删除非目标对象
result = 1;
goto out;//找到了
//所谓的把进程添加到异步通知队列中,实则是将文件指针关联到异步结构体对象,然后将该对象挂载在异步通知队列中(等待队列也是这个原理)
if (on) //不存在
new->magic = FASYNC_MAGIC;
new->fa_file = filp;//指定文件指针
new->fa_fd = fd;//指定文件描述符
new->fa_next = *fapp;//挂载在异步通知队列中
*fapp = new;//挂载
result = 1;
out:
write_unlock_irq(&fasync_lock);
return result;
释放信号用的函数
void kill_fasync(struct fasync_struct **fa, int sig, int band);
void kill_fasync(struct fasync_struct **fp, int sig, int band)
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp)
read_lock(&fasync_lock);
/* reread *fp after obtaining the lock */
__kill_fasync(*fp, sig, band);//调用
read_unlock(&fasync_lock);
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
while (fa)
struct fown_struct * fown;
if (fa->magic != FASYNC_MAGIC)
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\\n");
return;
fown = &fa->fa_file->f_owner;//这就是应用层使用F_SETOWN的意义,让其通过异步对象的文件指针知道其主进程
if (!(sig == SIGURG && fown->signum == 0))
send_sigio(fown, fa->fa_fd, band);//向主进程发送信号,也就是向我们的执行了fcntl(fd,F_SETOWN,getpid())命令的应用程序发送信号
fa = fa->fa_next;
当一个打开的文件的FASYNC标志被修改时,调用fasync_helper函数以便从相关的进程表中添加或删除文件.当数据到达时,可使用kill_fasync函数通知所有的相关进程。
以上是关于linux设备驱动中的异步通知机制的主要内容,如果未能解决你的问题,请参考以下文章
[架构之路-38]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之(并发与互斥阻塞与非阻塞异步通知)
[架构之路-38]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之(并发与互斥阻塞与非阻塞异步通知)