Hasen的linux设备驱动开发学习之旅--阻塞与非阻塞I/O

2024-06-08 03:32

本文主要是介绍Hasen的linux设备驱动开发学习之旅--阻塞与非阻塞I/O,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

/*** Author:hasen* 参考 :《linux设备驱动开发详解》* 简介:android小菜鸟的linux* 	         设备驱动开发学习之旅* 主题:阻塞与非阻塞I/O* Date:2014-11-05*/阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到条件满足以进行操作为止。驱动程序通常需要提供这样的能力,当应用程序进行read()、write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的xxx_read()、xxx_write()等操作中将进程阻塞直到资源可以获取,此后,应用程序的read()、write()等调用才返回,整个过程仍然进行了正确的设备访问,用户没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read()、xxx_write()等操作应该立即返回,read()、write()等系统调用也随即被返回。阻塞的进程会进入休眠状态,需要有一个地方来唤醒休眠的进程。唤醒进程的最大可能是在中断里面。下面的代码段分别演示了以阻塞和非阻塞方式读取一个字符的代码。实际的串口编程中,若使用非阻塞模式,还可借助信号(sigaction)以异步方式访问串口以提高CPU的利用率。代码段1:阻塞地读串口一个字符char buf ;fd = open("/dev/ttyS1",O_RDWR) ; /*以阻塞的方式打开串口*/...res = read(fd,&buf,1) ; /*当串口上有输入时才返回*/if(res == 1)printf("%c\n",buf) ;代码段2:非阻塞地读串口一个字符char buf ;fd = open("/dev/ttyS1",O_RDWR|O_NONBLOCK) ;/*以非阻塞的方式打开串口*/...while(read(fd,&buf,1) == 1)continue ; /*串口上无输入也返回,所以要循环尝试读取串口*/printf("%c\n",buf) ;等待队列linux驱动程序中,可以使用等待队列(wait queue)来是实现阻塞进程的唤醒。wait queue很早就作为一个基本的功能单位出现在linux的内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问。信号量在内核中依赖等待队列实现的。等待队列的操作如下:(1)定义“等待队列头”wait_queue_head_t my_queue ;(2)初始化“等待队列头”init_waitqueue_head(&my_queue) ;用下面的宏可以实现定义和初始化“等待队列头”DECLARE_WAIT_QUEUE_HEAD(name) (3)定义等待队列该宏用于定义并初始化一个名为name的等待队列DECLARE_WAITQUEUE(name,tsk)(4)添加/移除等待队列void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait) ;void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait) ;/*add_wait_queue用于将等待队列wait添加到等待队列头q指向的等待队列链表中,而remove_wait_queue用于将等待队列wait从附属的等待队头q指向的等待队列链表中移除*/(5)等待事件wait_event(queue,condition) ;wait_event_interruptible(queue,condition) ;wait_event_timeout(queue,condition,timeout) ;wait_event_interruptible_timeout(queue,condition,timeout) ;等待第一个参数queue作为等待队列头的等待队列被唤醒,而且第二个参数condition必须满足,否则继续阻塞。wait_event()和wait_event_interruptible()的区别在于后者可以被信号打断,而前者不能。加上timeout后意味着阻塞等待的超时时间,以jiffy为单位,在第三个参数的timeout到达时,不论condition是否满足,均返回。(6)唤醒队列void wake_up(wait_queue_head_t *queue) ;void wake_up_interruptible(wait_queue_head_t *queue) ;上述操作会唤醒以queue作为等待队列头的所有等待队列中属于该等待队列头的等待队列对应的进程。wake_up和wait_event或wait_event_timeout成对使用,wake_up_interruptible和wait_event_interruptible或者wait_event_interruptible_timeout成对使用,wake_up可唤醒处于TASK_INTERRUPTIBLE和TASH_UNINTERRUPTIBLE的进程,而wait_event_interruptible仅能唤醒处于TASK_INTERRUPTIBLE的进程。(7)在等待队列上睡眠sleep_on(wait_queue_head_t *q) ;interruptible_sleep_on(wait_queue_head_t *q) ;sleep_on()函数的作用是把目前的进程状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列q,直到资源可或获得,q引导的等待队列被唤醒。interruptible_sleep_on()函数的作用是把目前的进程状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列q,直到资源可或获得,q引导的等待队列被唤醒或者进程收到信号。在许多的linux驱动程序之中,并不调用sleep_on()或者interruptible_sleep_on(),而是直接进行进程状态的改编和切换。代码示例:在驱动代码中改变进程状态并调用schedule()static int xxx_write(struct file *filp,const char *buffer,size_t count,loff_t *ppos){...DECLARE_WAITQUEUE(wait,current) ;/*定义等待队列*/add_wait(&xxx_wait,&wait) ;/*添加等待队列*/ret = count ;/*等待设备缓冲区可写*/do{avail = device_writable(...) ;if(avail < 0)__set_current_state(TASK_INTERRUPTIBLE) ;/*改变进程状态*/if(avail < 0){if(file->f_flags & O_NONOBLOCK){ /*非阻塞*/if(!ret)ret = -EAGAIN ;goto out ;}schedule() ;/*调度其他进程执行*/if(signal_pending(current)){ /*如果是因为信号唤醒*/if(!ret)ret = -ERESTARTSYS ;goto out ;}}}while(avail<0) ;/*写设备缓冲区*/device_write(...) ;out :remove_wait_queue(&xxx_wait ,&wait) ;/*将等待队列移出等待队列头*/set_current_state(TASK_RUNNING) ;/*设置进程状态为TASK_RUNNNIG*/}上述的代码示例对理解进程切换非常重要,所以应该读几遍直至完全领悟,下面是几个要点:(1)如果是非阻塞访问(O_NONOBLOCK被设置),设备忙时,直接返回"-EAGAIN" .(2)对于阻塞访问,会进行状态的切换并显式通过"schedule()"调度其他进程执行。(3)醒来时要注意,由于调度出去的时候,进程的状态是TASK_INTERRUPTIBLE,即浅度的睡眠,因此唤醒它的有可能是信号,因此,我们首先通过"signal_pending(current)"了解是不是信号唤醒的,如果是,立即返回"-ERESTARTSYS" 。轮询操作在用户程序中,select()和poll()也是设备阻塞与非阻塞访问息息相关的论题。使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可以对设备进行无阻塞的访问。select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。epoll()是扩展的poll() .应用程序中最广泛的应用的是select()系统调用,其函数原型是:int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout) ;其中,readfds,writefds,exceptfds分别是被select()监视的读、写、异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文件描述符加1,timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。struct timeval 的定义如下:struct timeval{int tv_sec ;/*秒*/int tv_usec ;/*微秒*/};下面的操作用来设置、清除、判断文件描述符集合:FD_ZERO(fd_set *set) ;/*清除一个文件描述符集*/FD_SET(int fd ,fd_set *set) ;/*将一个文件描述符加入到文件描述符集*/FD_CLR(int fd ,fd_set *set) ;/*将一个文件描述符从文件描述符集中清除*/FD_ISSET(int fd ,fd_set *set) ;/*判断文件描述符是否被置位*/设备驱动中的轮询编程设备驱动中poll()函数的原型是:unsigned int (*poll) (struct poll_table *wait) ;第一个参数为file结构体指针,第二个参数为轮询表指针。这个函数进行两项工作:(1)对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table.(2)返回表示是否能对设备进行无阻塞读、写访问的掩码。关键的用于向poll_table注册等待队列的poll_wait()函数的原型如下:void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait) ;poll_wait()函数的名字非常容易让人产生误会,以为它和wait_event()等一样,会阻塞地等待某时间发生,其实这个函数并不会引起阻塞。poll_wait()函数所作的工作是把当前的进程添加到wait参数指定的等待列表(poll_table)中。驱动程序poll()函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL等宏的位“或”结果。每个宏的含义都是表明设备的一种状态,如POLLIN(定义为0x0001)意味着设备可以无阻塞地读,POLLOUT(定义为0x0004)意味着设备可以无阻塞地写。poll()函数的典型模板static unsigned int xxx_poll(struct file *filp,poll_table *wait){unsigned int mask = 0 ;struct xxx_dev *dev = filp->private_data ;/*获取设备结构体指针*/...poll_wait(filp,&dev->r_wait,wait) ;/*加读等待队列头*/poll_wait(filp,&dev->w_wait,wait) ;/*加写等待队列头*/if(...) /*可读*/mask |= POLLIN | POLLRDNORM ;/*标示数据可获得*/if(...) /*可写*/mask |= POLLOUT | POLLWRNORM ;/*标示数据可写入*/...return mask ;}总结:阻塞与非阻塞访问是I/O操作的两种不同模式,前者在操作暂时不可进行时会让进程睡眠,后者则不然。在设备驱动中阻塞I/O一般基于等待队列来说实现,等待队列可用于同步驱动中事件发生的先后顺序。使用非阻塞I/O的应用程序也可借助轮询函数来查询设备是否能立即被访问,用户空间调用select()和poll()接口,设备驱动提供poll()函数。设备驱动的poll()本身不会阻塞,但是poll()和select()系统调用则会阻塞地等待文件描述符集合中的至少一个可访问或超时。

这篇关于Hasen的linux设备驱动开发学习之旅--阻塞与非阻塞I/O的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1041092

相关文章

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Python使用smtplib库开发一个邮件自动发送工具

《Python使用smtplib库开发一个邮件自动发送工具》在现代软件开发中,自动化邮件发送是一个非常实用的功能,无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们... 目录代码实现与知识点解析1. 导入必要的库2. 配置邮件服务器参数3. 创建邮件发送类4. 实现邮件

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存