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设备驱动中的异步通知机制的主要内容,如果未能解决你的问题,请参考以下文章

Linux之异步通知机制分析

Linux通信之异步通知模式

[架构之路-38]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之(并发与互斥阻塞与非阻塞异步通知)

[架构之路-38]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之(并发与互斥阻塞与非阻塞异步通知)

Linux驱动开发异步通知

Linux驱动开发异步通知