Linux设备驱动中的异步通知与同步I/O

在设备驱动中使用异步通知可以使得对设备的访问可进行时,由驱动主动通知应用程序进行访问。因此,使用无阻塞I/O的应用程序无需轮询设备是否可访问,而阻塞访问也可以被类似“中断”的异步通知所取代。异步通知类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步I/O”。

9.1 异步通知的概念和作用

  • 异步通知:一旦设备就绪,则主动通知应用程序,该应用程序无需查询设备状态
  • 几种通知方式比较:
    • 阻塞I/O :一直等待设备可访问后开始访问
    • 非阻塞I/O:使用poll()查询设备是否访问
    • 异步通知 :设备主动通知用户应用程序
    • -

9.2 linux异步通知编程

9.2.1 linux信号

  • 作用:linux系统中,异步通知使用信号来实现
  • Linux信号及其定义如下:
信号含义
SIGHUP1挂起
SIGINT2终端中断
SIGQUIT3终端退出
SIGILL4无效命令
SIGTRAP5跟踪陷阱
SIGIOT6IOT陷阱
SIGBUS7BUS错误
SIGFPE8浮点异常
SIGKILL9强行终止
SIGUSR110用户定义信号1
SIGSEGV11无效的内存段处理
SIGUSR212用户定义信号2
SIGPIPE13半关闭管道的写操作已经发生
SIGALRM14计时器到时
SIGTERM15终止进程
SIGSTKFLT16堆栈错误
SIGCHLD17子进程已经停止或退出
SIGCONT18如果停止了,继续执行
SIGSTOP19停止执行
SIGTSTP20终端来的停止信号
SIGTTIN21后台进程读终端
SIGTTOU22后台进程写终端
SIGURG23I/O紧急信号
SIGXGPU24CPU时限超时
SIGXFSZ25文件长度过长
SIGVTALRM26虚拟计时器到时
SIGPROF27统计分布图用计时器到时
SIGWINCH28窗口大小发生变化
SIGIO29描述符上可以进行I/O
SIGPWR30断电重启

9.2.2 信号的接收

  • 信号捕获函数signal()
    • 参数:
      • signum:信号值
      • handler:针对signum的处理函数
        • 若为SIG_IGN:忽略该信号
        • 若为SIG_DFL:系统默认方式处理
        • 若为用户自定义函数:信号被捕获,该函数被执行
    • 返回值
      • 成功:最后一次为信号signum绑定的处理函数的handler值
      • 失败:返回SIG_ERR
    • sigaction()
      • 作用:改变进程接收到特定信号后的行为
      • 参数
        • signum:信号值
          • 除SIG_KILL及SIG_STOP以外的一个特定有效的信号
        • act:指向结构体sigaction的一个实例的指针
          • 在结构体sigaction中,指定了处理信号的函数,若为空则进程会以缺省值的方式处理信号
        • oldact:保存原来对应的信号的处理函数,可设为NULL
int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact);
  • 实例:使用信号实现异步通知
    • 在用户空间处理设备释放信号的准备工作
      • 通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,以使信号被本进程捕获
      • 通过F_SETFL IO控制命令设置设备文件以支持FASYNC,及异步通知模式
      • 通过signal()函数连接信号和信号处理函数
//启动信号机制

void sigterm_handler(int sigo)
{
    char data[MAX_LEN];
    int len;
    len = read(STDIN_FILENO,&data,MAX_LEN);
    data[len] = 0;
    printf("Input available:%s\n",data);
    exit(0);
}

int main(void)
{
    int oflags;
    //启动信号驱动机制

    signal(SIGIO,sigterm_handler);
    fcntl(STDIN_FILENO,F_SETOWN,getpid());
    oflags = fcntl(STDIN_FILENO,F_GETFL);
    fctcl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
    //建立一个死循环,防止程序结束

    whlie(1);

    return 0;
}

9.2.3 信号的释放 (在设备驱动端释放信号)

  • 为了使设备支持异步通知机制,驱动程序中涉及以下3项工作
    • 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应的进程ID。不过此项工作已由内核完成,设备驱动无须处理
    • 支持F_SETFL命令处理,每当FASYNC标志改变时,驱动函数中的fasync()函数得以执行。因此,驱动中应该实现fasync()函数
    • 在设备资源中可获得,调用kill_fasync()函数激发相应的信号
  • 设备驱动中异步通知编程:
    • 处理FASYNC标志变更函数:fasync_helper()
    • 释放信号的函数:kill_fasync()
int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);

 void kill_fasync(struct fasync_struct **fa,int sig,int band);
  • 将fasync_struct结构体指针放到设备结构体中是最佳的选择
//异步通知的设备结构体模板
struct xxx_dev{
    struct cdev cdev;
    ...
    struct fasync_struct *async_queue;//异步结构体指针
};
  • 在设备驱动中的fasync()函数中,只需简单地将该函数的3个参数以及fasync_struct结构体指针的指针作为第四个参数传入fasync_helper()函数就可以了,模板如下
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,GIGIO,POLL_IN);
    ...
}
  • 最后在文件关闭时,要将文件从异步通知列表中删除
int xxx_release(struct inode *inode,struct file *filp)

{
    //将文件从异步通知列表中删除
    xxx_fasync(-1,filp,0);
    ...
    return 0;
}

9.3 linux异步I/O

9.3.1 AIO概念与GNU C库 AIO

9.3.1.1 AIO概念
  • 同步I/O:linux系统中最常用的输入输出(I/O)模型是同步I/O,在这个模型中,当请求发出后,应用程序就会阻塞,知道请求满足

  • 异步I/O:I/O请求可能需要与其它进程产生交叠

  • Linux 系统中最常用的输入/输出(I/O)模型是同步 I/O

    • 在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止
    • 调用应用程序在等待 I/O 请求完成时不需要使用任何中央处理单元(CPU)
    • 在某些情况下,I/O 请求可能需要与其他进程产生交叠,可移植操作系统接口(POSIX)异步 I/O(AIO)应用程序接口(API)就提供了这种功能
9.3.1.2 AIO系列API:
  • aio_read–异步读
    • 作用:请求对一个有效的文件描述符进行异步读写操作
      • 请求进行排队之后会立即返回
      • 这个文件描述符可以表示一个文件、套接字,甚至管道
    • 参数aiocb:结构体包含了传输的所有信息,以及为AIO操作准备的用户空间缓存区
    • 返回值
      • 成功:返回0
      • 失败:返回-1,并设置errno的值
int aio_read( struct aiocb *aiocbp );
  • aio_write–异步写
    • 作用:请求一个异步写操作
      • 请求进行排队之后会立即返回
      • 这个文件描述符可以表示一个文件、套接字,甚至管道
    • 参数aiocb:结构体包含了传输的所有信息,以及为AIO操作准备的用户空间缓存区
    • 返回值
      • 成功:返回0
      • 失败:返回-1,并设置errno的值
int aio_write( struct aiocb *aiocbp );
  • aio_error
    • 作用:确定请求的状态
    • 参数aiocb:结构体包含了传输的所有信息,以及为AIO操作准备的用户空间缓存区
    • 返回值
      • EINPROGRESS:说明请求尚未完成
      • ECANCELED:说明请求被应用程序取消
      • 失败:返回-1,并设置errno的值
int aio_error( struct aiocb *aiocbp );
  • aio_return–获得异步操作的返回值
    • 异步 I/O 和标准块 I/O 之间的另外一个区别是不能立即访问这个函数的返回状态,因为并没有阻塞在 read()调用上
    • 在标准的 read()调用中,返回状态是在该函数返回时提供的。但是在异步 I/O 中,我们要使用 aio_return()函数
    • 只有在 aio_error()调用确定请求已经完成(可能成功,也可能发生了错误)之后,才会调用这个函数
    • 参数aiocb:结构体包含了传输的所有信息,以及为AIO操作准备的用户空间缓存区
    • 返回值
      • 成功:返回所传输的字节数
      • 失败:返回-1
ssize_t aio_return( struct aiocb *aiocbp );
  • aio_suspend–挂起异步操作,直到异步请求完成为止
    • 作用:挂起(或阻塞)调用进程,直到异步请求完成为止,调用者提供了一个 aiocb 引用列表,其中任何一个完成都会导致 aio_suspend()返回
int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout );
  • aio_cancel–取消异步请求
    • 作用:允许用户取消对某个文件描述符执行的一个或所有 I/O 请求
    • 要求:
      • 如果要取消一个请求,用户需提供文件描述符和 aiocb 引用
        • 函数返回AIO_CANCELED:请求被成功取消
        • 函数返回AIO_NOTCANCELED:请求完成
      • 如果要取消对某个给定文件描述符的所有请求,用户需要提供这个文件的描述符以及一个对 aiocbp 的 NULL 引用
        • 函数返回AIO_CANCELED:表明所有的请求都取消了
        • 函数返回AIO_NOTCANCELED:表明至少有一个请求没有被取消
        • 函数返回AIO_ALLDONE:表明没有一个请求可以被取消
      • 使用 aio_error()来验证每个 AIO 请求
        • aio_error()返回-1并且设置了errno被设置为ECANCELED:表明某个请求已经被取消了
int aio_cancel( int fd, struct aiocb *aiocbp );
  • lio_listio–同时发起多个传输(一次系统调用可以启动大量的I/O操作)
    • 作用:这个函数非常重要,它使得用户可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作
    • 参数
      • mode:可以是 LIO_WAIT 或 LIO_NOWAIT
        • LIO_WAIT 会阻塞这个调用,直到所有的 I/O 都完成为止
        • 在操作进行排队之后,LIO_NOWAIT 就会返回
      • list :是一个 aiocb 引用的列表,最大元素的个数是由 nent 定义的
        • 如果 list 的元素为 NULL,lio_listio()会将其忽略。
int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig );

9.3.2 Linux内核AIO与libaio

  • linux AIO也可以由内核空间实现,异步I/O是linux2.6以后版本内核的标准特性
  • 对于块设备,AIO可以一次性发出大量的read/write调用并且通过通用块层的I/O调度来获得更好的性能,用户也可以减少过多的同步负载
  • 对网络设备而言,在socket层面上,也可以使用AIO,让CPU和网卡的收发充分交叠以改善吞吐性能
  • 用户空间中一般要结合libaio来进行内核AIO的系统调用
io_setup( )

//Initializes an asynchronous context for the current process

io_submit( )

//Submits one or more asynchronous I/O operations

io_getevents( )

//Gets the completion status of some outstanding asynchronous I/O operations

io_cancel( )

//Cancels an outstanding I/O operation

io_destroy( )

//Removes an asynchronous context for the current process

9.3.3 AIO与设备驱动

  • 用户空间调用io_submit()之后,对应于用户传递的每个iocb结构,内核会生成一个与之对应的kiocb结构
  • 通过is_sync_kiocb判断某kiocb是否为同步I/O请求

    • 如果是返回真,表示为异步I/O请求
  • 字符设备:必须明确应支持AIO(极少数是异步I/O操作)

  • 字符设备驱动程序中file_operations 包含 3 个与 AIO 相关的成员函数
ssize_t (*aio_read) (struct kiocb *iocb, char *buffer, size_t count, loff_t offset);

ssize_t (*aio_write) (struct kiocb *iocb, const char *buffer, size_t count, loff_t offset);

int (*aio_fsync) (struct kiocb *iocb, int datasync);
  • 块设备和网络设备:本身是异步的
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值