Linux 驱动框架---驱动中的异步

  异步IO时对阻塞和轮叙IO的机制补充,所谓异步IO就是在设备数据就绪时主动通知所属进程进行处理的机制。之所以说是异步是相对与被通知的进程,因为进程不知道也无法知道什么时候会被通知;这一机制非常类似于硬件上的中断一样。异步IO的实现也依赖于Linux内核进程的信号机制,因为异步IO就是通过SIGIO信号通知的进程,而进程在受到信号后就会像中断一样直接跳转去执行之前就注册好的信号处理接口函数。

用户空间  

       注册信号接口函数有两个版本的接口signal(int signal,sighandler_t handler)其中signal为内核定义的信号类型目前支持30种信号,而每种信号也有其缺省的处理方式如果当前进程未指定某一信号的处理方式的话就会按系统缺省的方式处理。还有一个接口比较新功能也更加强大一点的就是int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact))他们的详细使用和定义内容如下:

signal:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler));

void (*signal(int signum, void (*handler))(int)))(int);

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理方式或接口:忽略该信号(参数设为SIG_IGN)可以采用系统默认方式处理信号(参数设为SIG_DFL)也可以自己实现处理方式(参数指定一个函数地址)signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值,失败则返回SIG_ERR。

sigaction:

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

//struct sigaction 结构体
struct sigaction {
    union {
      __sighandler_t _sa_handler;
      void (*_sa_sigaction)(int, struct siginfo *, void *);
    } _u;
    sigset_t sa_mask;
    unsigned long sa_flags;
    void (*sa_restorer)(void);
};

//__sighandler_t
void (*__sighandler_t)(int );
//
typedef struct siginfo {
    int si_signo;
    int si_errno;
    int si_code;
    int __pad0;

    union {
        int _pad[SI_PAD_SIZE];

        /* kill() */
        struct {
            pid_t _pid;     /* sender's pid */
            uid_t _uid;     /* sender's uid */
        } _kill;

        /* POSIX.1b timers */
        struct {
            timer_t _tid;       /* timer id */
            int _overrun;       /* overrun count */
            char _pad[sizeof(__ARCH_SI_UID_T) - sizeof(int)];
            sigval_t _sigval;   /* must overlay ._rt._sigval! */
            int _sys_private;   /* not to be passed to user */
        } _timer;

        /* POSIX.1b signals */
        struct {
            pid_t _pid;     /* sender's pid */
            uid_t _uid;     /* sender's uid */
            sigval_t _sigval;
        } _rt;

        /* SIGCHLD */
        struct {
            pid_t _pid;     /* which child */
            uid_t _uid;     /* sender's uid */
            int _status;        /* exit code */
            clock_t _utime;
            clock_t _stime;
        } _sigchld;

        /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */
        struct {
            void __user *_addr; /* faulting insn/memory ref. */
            int _imm;       /* immediate value for "break" */
            unsigned int _flags;    /* see below */
            unsigned long _isr; /* isr */
            short _addr_lsb;    /* lsb of faulting address */
        } _sigfault;

        /* SIGPOLL */
        struct {
            long _band; /* POLL_IN, POLL_OUT, POLL_MSG (XPG requires a "long") */
            int _fd;
        } _sigpoll;
    } _sifields;
} siginfo_t;
sigaction的第一个参数和signal相同,第二和第三个参数都是 struct sigaction类型的指针,第一个为给这个信号新安插的处理接口,而第三个用来接收之前给这个信号安插的处理接口的返回。
这个结构体中的union 是信号处理接口函数,使用的是哪一种取决与flags变量的bit标志,而sa_mask则负责在信号处理过程中对指定信号的掩蔽类似避免中断嵌套。最后一个sa_restorer现在已经
废弃不建议再使用最后就是siginfo_t 结构体他是sigaction接口高级于signal接口的另一个力证。其中主要的成员是意义使用起来比较复杂在专门的地方学习,今天主要学习驱动部分的异步IO
实现机制。

内核空间
前面说了应用层的信号使用和异步IO的工作原理,接下来就是说明驱动也就是内核空间的异步IO实现机制了。在用户空间是负责接收信号那么内核空间肯定就是负责信号的释放,但是要能释放信号给
指定的进程还要满足几个条件:

1、文件是异步方式打开的。

2、当前进程是文件的属主。

其中第一个条件可以同通过打开文件时指定FASYNC标志或通过fcntl(fd,F_SETFL,...)接口修改文件标志以支持异步通知。第二个则是通过使用接口fcntl(fd,F_SETOWN,getpid());进行设置。

而在内核部分的实现主要依赖一个数据结构和两个接口 

struct fasync_struct {
    spinlock_t      fa_lock;
    int         magic;
    int         fa_fd;
    struct fasync_struct    *fa_next; /* singly linked list */
    struct file     *fa_file;
    struct rcu_head     fa_rcu;
};
//释放信号
void kill_fasync(struct fasync_struct **, int, int);
//处理文件FASYNC 状态变更的接口
int fasync_helper(int, struct file *, int, struct fasync_struct **);

文件操作接口中有一个fasync接口函数在文件ASYNC状态变更时会被调用所以需要实现它,的实现模版大致如下:

ststic int xxx_async(int fd,struct file* filp,int mode)
{
  ...
  ...
  //async 为struct fasync_struct 的指针
  return fasync_helper(fd,filp,mode,&async)
  
}

然后就是释放信号的的操作,这一部分有可能发生在中断也有可能发生的其他的接口函数中,比如写接口中可以释放IO可以读的就绪信号给进程,而读接口可以释放可以写的接口给进程等,具体的使用就是:

ststic int xxx(int fd,struct file* filp,...)
{
  ...
  ...
  //async 为struct fasync_struct 的指针 
  kill_fasync(&async,signum,POLL_IN);
  ...
  ...
}

kill_fasync 第一个接口参数为 异步结构体指针而 第二个参数为信号值一般为SIGIO,第三个为IO事件可有如下值
//events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

最后需要注意的是,在通过xxx_async接口把文件加入到异步通知列表后如果关闭文件或取消异步时需要通过这个接口把FD文件描述符设置为-1和async_queue设为None将文件从异步通知列表中移除。

//
static int xxx_release(struct inode* inode,struct file *filp)
{
     ...
    xxx_fasync(-1,filp,0);
     ...
   return 0; 
}

参考:

https://blog.csdn.net/Fury97/article/details/83041810

 
原文地址:https://www.cnblogs.com/w-smile/p/13418232.html