I/O口范文
I/O口范文(精选9篇)
I/O口 第1篇
网友采取的方法
通过上网查阅,网友给出了两种方案:一种是更换三极管的类型,把NPN类型的8050 更换为PNP类型的8550;另外一种方法是为IO添加下拉电阻。
我对这两个方法都进行了实验:当我把三极管8050 改为8550 时,利用程序设置“OUT”初始状态为高电平,Q1截止,按下“ON”键后,Q1 导通;当需要关断三极管时,让“OUT”输出高电平5V,三极管却没有被关断。仔细分析后可知,本电路采用双电源,VC1 采用12V电源,单片机供电为5V,当输出高电平5V时,仍然满足饱和的外部条件而不能退出饱和。因此,第一种方法只能适用于三极管、单片机使用同一5V电压时的情况。
对于第二种解决方法,网友做了一些实验,实验原理图如图2 所示,其实验过程及结果为:为IO添加一个5k的下拉电阻R65,测试有效,上电瞬间不是高电平了;更换R65 为30k下拉电阻,依然有效,上电瞬间IO的电压被拉到低了;再换为60k电阻,也可以;但后面程序运行时候现象就不同了,如果使用5k电阻,那么I/O(DO2)无法启动9013,而30k、60k可以正常启动或关闭9013. 还有一个现象,就是如果使用万用表电压档对DO2 处进行电压测量,会导致9013 断开,而后再无法通过控制IO电平启动9013,必须电路板重新上电才能再启动。
从图2 电路可知,由于单片机的驱动能力有限,如果加下拉电阻,会降低其驱动能力,会对电路造成影响。
对单片机瞬间高电平的测试
为解决这一问题,我用示波器对这一高电平测试并进行录像,逐帧观察。在电路中,P3.7 的初始状态设置为低电平,当按下“ON”时,转为高电平使Q1 饱和导通。
在通电瞬间,用示波器测出的波形如图3 所示。经计算可以得出这一高电平的时间大约为40ms,在这一时间内Q1 饱和导通会使继电器闭合。
利用电容吸收瞬时高电平
如何降低瞬时高电平呢?我想到了利用电容的暂稳态把这一高电平吸收,使输出的高电平降低,不足以使三极管导通。测试电路如图4 所示,在图中P3.7 端口并联了一个电解电容C7。
工作过程:当单片机得电后未运行程序的瞬间,P3.7
输出高电平,但由于电容C7 两端的电压不能突变,所以C7 正极的电压将缓慢增大,在单片机输出高电平的时间内,升高的电压如果达不到三极管的导通电压,则Q1 不会产生误动作。当P3.7 通过软件变为低电平时,C7 上的电压回流到单片机,对电路不会造成影响。
电容器参数的选择实验
由原理可以得出,在51 单片机的I/O外部并接一个电解电容可以把瞬间高电平吸收掉,但是电容器的参数对这一吸收影响较大,我选用了10μF、47μF和100μF的三个电容器。
■ 1、外部并接10μF的电解电容
当在外部并接10μF的电容器时,输出电压的峰值接近3.5V,这一电平能使三极管导通;另外充完后的电压回流时间较短,电容器上的电压基本上是瞬时消失。
■ 2、外部并接47μF的电解电容
当在外部并接47μF的电容器时,输出电压的峰值接近2.3V,这一电平还能使三极管导通;另外充完后的电压回流时间变长,但电容器上的电压会迅速降低到0.3V以下。
■ 3、外部并接100μF的电解电容
当在外部并接100μF的电容器时,输出电压的峰值接近0.5V,这一电平已经不能使三极管导通;另外充完后的电压回流时间变得更长,但不会超过5ms。
实验结果
通过实验,在单片机I/O口并接电解电容后,可以消除其输出的瞬时高电平,符合我设计的电路要求,不过这种方法也存在一定的弊端:通电瞬间,后级电路不会因为单片机的瞬时高电平而得电,但在电路接通时会有一定的延时通电,单片机控制断电时不会即时断电,也会有一定的延时。
Python 文件I/O 第2篇
Python提供了必要的函数和方法进行默认情况下的文件基本操作。你可以用file对象做大部分的文件操作。
open函数
你必须先用Python内置的open()函数打开一个文件,创建一个file对象,相关的辅助方法才可以调用它进行读写。
语法:
file bject = open(file_name [, access_mode][, buffering])
各个参数的细节如下:
file_name:file_name变量是一个包含了你要访问的文件名称的字符串值。
access_mode:access_mode决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。
buffering:如果buffering的值被设为0,就不会有寄存。如果buffering的值取1,访问文件时会寄存行。如果将buffering的值设为大于1的整数,表明了这就是的寄存区的缓冲大小。如果取负值,寄存区的缓冲大小则为系统默认。
不同模式打开文件的完全列表:
基于I2C总线的I/O口扩展设计 第3篇
关键词:I/O口,MCU,PCA9554/A,LabVIEW
0引言
随着电子技术的飞速发展,各类电子产品像雨后春笋般大量的进军电子市场,其中也包括正在蓬勃发展的汽车电子行业。一般汽车电子行业对这些电子产品的要求比较高,会有很多的技术指标去规范它们,其中很重要的一环便是环境实验。出于对工作效率的考虑,一个环境测试台架往往要求能对多个产品进行同时测试。而这些产品的引脚数量一般也会比较多,这样一来,测试台架上所需的测试通道数量就会很多,往往多达几百个。
一般多数用于汽车电子产品的环境测试台架里面会集成有NI公司的DIO驱动板卡,由于它们的驱动端口比较多,便专用来负责产品的环境实验测试。以NI-6509为例,这是一个128的板卡,总共有12个通道,每通道8位I/O控制组成,总计96路I/O控制;NI-2532的矩阵板卡,其能支持的通道数是3216,共512路。这些I/O端口,针对一般的应用是够的,但如果用于引脚数量比较多的产品测试,便会出现I/O口数量不够用的局面。假如环境实验要同时测试12个产品,每个产品有40个引脚,那么对于产品引脚继电器就必须要有480个,再加上一些外围辅助仪器介入的切换,那么在这种情况下,这类DIO板卡的应用就显得很勉强。
针对此类问题,本文提出了一种有效的解决方法,便是应用单片机的I/O口配合市面上一些常见的I/O扩展芯片,进行端口的扩展。单片机有4个端口,每个端口8位,总计32个位控制;扩展芯片,比如I2C芯片,只需要2根线(SDA & SCL)即可,因此可以连16组I2C通信,且每组可同时支持8个芯片(每组最大可容纳8个地址),每个芯片又可控制8个端口。因此,单片机理论上,至少可支持1688个端口。并且,如果芯片头地址可以不一样的话,例如PCA9554和PCA9554A,那么端口数量又可以增加一倍,达到2 048个,这是一般的板卡所远远不能及的。
1系统硬件设计
本文所涉及到的硬件比较简单,上位机与MCU之间通过RS 232串口连接,MCU靠外部电源提供的5 V直流电进行工作。再将MCU的2个端口模拟成SCL,SDA两根线与PCA9554/A进行通信连接。这样就形成了上位机发送指令,单片机接受指令并进行相应解析后再通过SDA、SCL两根线向PCA9554/A发送命令数据,控制它的输入与输出。
1.1 PCA9554/A芯片介绍[1]
PCA9554和PCA9554A是16脚的CMOS器件,它们提供了I2C的应用中的8位通用并行输入/输出口(GPIO),PCA9554/54A包含一个8位配置寄存器(输入或输出选择)、8位输入寄存器、8位输出寄存器和一个极性反转(高电平或低电平操作有效)寄存器。系统主控制器通过写I/O口相应的配置位来激活端口的输入或输出。PCA9554/A有3个硬件管脚(A0,A1,A2)来实现不同的I2C地址,最多允许8个器件共用一个I2C总线上。PCA9554与PCA554A的惟一区别在于I2C地址不同,这样最多允许16个器件(9554和9554A各8个)连接接到同一个I2C总线上。
1.2 基于PCA9554/A的硬件设计[2]
按I2C规约,PCA9554的器件地址为0x40,PCA9554A的器件地址为0x70,当然,由于硬件地址引脚A0~A2可寻址8个器件,所以器件地址并不惟一,例如:A0接GND,A1接VCC,则PCA9554的器件地址为0x4C。下面给出了本文的基本应用原理图如图1所示。在图中使用STC89C52单片机为主控芯片,单片机的P1.3管脚与PCA9554的SDA管脚相连,P1.2管脚与PCA9554的SCL管脚相连。4个LED灯可以受四个按键控制,也可以受上位机进行控制。
2系统软件设计[3]
本文系统软件设计主要分为两部分,一个是上位机的LabVIEW程序设计;另一个则是单片机底层C语言的程序设计。
2.1 LabVIEW的程序设计[4,5]
上位机的程序设计主要是与MCU串口通信,通过向MCU发送控制命令来达到对PCA9554/A的I/O口进行控制的目的。
LabVIEW是一种图形化编程语言,由美国国家仪器(NI)公司研制开发的,类似于C和BASIC开发环境。使用这种语言编程时,基本上不写程序代码,取而代之的是流程图。使用它进行原理研究、设计、测试并实现仪器系统时,可以大大提高工作效率。
本文主要是用LabVIEW语言编写与MCU的串口通信程序,界面友好、操作方便。LabVIEW控制单片机是通过Instrument I/O的Serial控件调用来实现的。主要用到其中的VISA配置串口节点,包括串口配置、读、写、关闭等节点。通过对这些节点的调用就可以方便的对串口进行操作。程序的前面板显示如图2所示。
这里,Command to RS 232栏中的指令必须与单片机事先烧录好的代码相符合。这样,单片机才能将接收到的指令进行正确匹配,并执行相应操作。其程序框图为图3所示。
程序左端调用Configuration模块,进行串口的基本配置,比如BaudRate、数据位等,使这些参数与单片机里面的串口预定义设置相一致;右侧是Close模块,用于程序退出时,释放对串口的控制;中间是程序主体,包含对串口的读、写操作,实现对串口的双向通信。为了程序简洁、形象易懂,此次程序中用到了对LabVIEW子函数的调用,如RS 232-ini,RS 232W-R等模块,这些子函数就是用VISA串口节点来编写的,只是做了封装而已。
2.2 MCU的串口通信[6]
上位机是通过串口将指令字符传递给单片机的。单片机对串口的读操作是通过中断的方式实现的,以字符为单位,每接收到一个8位的字符,MCU产生一个读中断RI=1,此时,单片机将接收到的字符储存起来,然后软件将RI复位置0,继续接收下一个字符。本系统中,所有计算机发送给单片机的指令均以?’结尾。MCU接收到?’后,产生一个终止位,然后与事先定义好的指令进行匹配,如果指令事先有定义,那么就会执行相应的操作。
以下为单片机的配置、写字符、写字符串、读字符串代码程序[7,8]:
2.3 MCU的I2C时序模拟[9]
上位机向单片机发送指令数据,如果这些指令已在单片机中事先定义好,那么单片机将会通过模拟的SDA与SCL两个引脚,根据PCA9554/A的datasheet时序图,将控制命令数据传递给PCA9554/A,从而实现对PCA9554/A 的I/O口进行控制。
2.3.1 PCA9554/A的写操作
根据I2C总线传输协议以及PCA9554/A的总线读写特性,可以看出:对PCA9554/A端口的写操作主要是通过对它的输出端口寄存器进行写操作的。具体过程为:在SCL为高电平期间,SDA由高电平向低电平转换作为起始信号,SDA由低电平向高电平转化则为停止信号。在起始条件后,必须是从机地址,对于PCA9554来说从地址的高4位是0100,而PCA9554A则是0111,A2,A1,A0的高低电平状态可以有8种组合,最后一位是读写选择位。从地址发送完后便是等待从机的应答信号ACK,从机正确应答后便开始由主机发送命令字节,接着又是等待从机应答,应答后便开始发送数据了。以下为PCA9554的写操作部分代码[10]。
2.3.2 PCA9554/A的读操作
对PCA9554/A的读操作稍微麻烦一点,需要在收到第二次从机应答信号后再一次发送总线起始信号及从机地址即可。部分程序如下所示:
由于篇幅原因,以上均只给出了部分重要程序。需要提出的是I2C总线上的起始、停止、以及读写数据的时序模拟均要参考PCA9554/A的datasheet中的时间参数要求,严格对应。
3结语
本文运用单片机与I/O扩展芯片PCA9554/A采用I2C通信进行I/O扩展,提出了具体的软硬件设计,完成了上位机对PCA9554/A端口的直接控制,并进行了I/O的有效扩展。并成功的应用在对多个汽车电子产品同时测量的环境试验中,取得了很好的效果。
参考文献
[1]Anon.PCA9554datasheet[EB/OL].[2008-8-15].http://www.ti.com.
[2]施邦平.采用I2C器件扩展单片机的多机通信[J].自动化与仪器仪表,2006(6):63-64,67.
[3]陈锡辉,张银鸿.LabVIEW8.20程序设计从入门到精通[M].北京:清华大学出版社,2007.
[4]李江全,曹卫彬.计算机典型测控与串口通信开发软件应用实践[M].北京:人民邮电出版社,2008.
[5]刘其和,李云明.LabVIEW虚拟仪器程序设计与应用[M].北京:化学工业出版社,2011.
[6]谢维成,杨家国.单片机原理与应用及C51程序设计[M].2版.北京:清华大学出版社,2009.
[7]陈涛.单片机应用及C51程序设计[M].2版.北京:机械工业出版社,2011.
[8]曹天汉.单片机原理与接口技术[M].3版.北京:电子工业出版社,2009.
[9]何立民.I2C总线应用系统设计[M].北京:北京航空航天大学出版社,1995.
[10]甘辉.模拟I2C总线实现AVR与MCS-51单片机通信[J].宜春学院学报,2008,30(4):54-56.
[11]贾权,郭计云,王晓亮.基于虚拟仪器的浊度测试系统的设计[J].现代电子技术,2009,32(17):143-146.
8086的I/O写周期 第4篇
8086在执行输出指令时进入I/0写周期,在I/O写周期中将指定寄存器的内容输出到指定的I/O端口,图2.1l是I/O写周期的时序图。I/O写周期和存储器写周期基本相同,
区别在于两点,一是M/在I/O写周期内为低电平,表示当前进行的是I/0操作;二是I/0端口的地址只有16位,因此图2.11中T1、T2期间没有出现A19~A16。
串行通信与重叠I/O 第5篇
串行通信在通信领域被广泛应用,标准的RS232接口已成为计算机、计算机外设、交换机和许多通信设备的标准接口。微机与微机、微机与外设、微机与程控交换机等都可以通过RS232接口进行方便的连接,以实现控制外设和传输数据等目的。
在Windows应用程序的开发中,我们常常需要面临与外围数据源设备通信的问题。笔者在实际工作中积累了一些经验,现结合硬件、软件,及需要注意的要点作一番探讨。希望对各位需要编写串口通信程序的朋友有一些帮助。
2 RS232串口标准
EIA-RS-232是美国电子工业协会正式公布的串行总线标准,也是目前最常用的串行接口标准。该标准规定:直接连接的最大物理距离为15m,通信速率低于20kbps。
由于RS232并未定义连接器的物理特性,因此,出现了DB-25、DB-15和DB-9各种类型的连接器,其引脚的定义也各不相同。表1介绍了其中两种连接器(DB-25, DB-9)。
RS232标准接口有25条线,4条数据线、11条控制线、3条定时线、7条备用和未定义线,但常用的只有9根。
目前较为常用9针串口和25针串口,当通信距离较近时,可以用电缆线直接连接,若距离较远,须附加Modem。最为简单且常用的是三线制接法,即地、接收数据和发送数据三脚相连。表2列举了RS232串口通信接线方法。
EIA-RS-232对电气特性、逻辑电平和各种信号线功能都作了规定。
在TxD和RxD上:
(1) 逻辑1 (MARK) =-3V~-15V。
(2) 逻辑0 (SPACE) =+3V~+15V。
在RTS、CTS、DSR、DTR和DCD等控制线上:
(1) 信号有效: (接通, ON状态, 正电压) =+3V~+15V。
(2) 信号无效: (断开, OFF状态, 负电压) =-3V~-15V。
3 Win32串口应用程序
3.1 打开串口
Win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。该函数的声明为:
如果调用成功,那么该函数返回文件的句柄,如果调用失败,则函数返回INVALID_HANDLE_VALUE。
3.2 串口配置和串口属性
在打开通信设备句柄后,常常需要对串口进行一些初始化工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、每个字符的数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时,都要用DCB结构来作为缓冲区。
调用GetCommState函数可以获得串口的配置,该函数把当前配置填充到一个DCB结构中。一般在用CreateFile打开串口后,可以调用GetCommState函数来获取串口的初始配置。要修改串口的配置,应该先修改DCB结构,然后再调用SetCommState函数用指定的DCB结构来设置串口。
除了在DCB中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据,如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串口的输入和输出缓冲区的大小。
3.3 串口读写
在用ReadFile和WriteFile读写串口时,既可以同步执行,也可以重叠(异步)执行。在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而导致效率下降。在重叠执行时,即使操作还未完成,调用的函数也会立即返回。费时的I/O操作在后台进行,这样线程就可以干别的事情。例如,线程可以在不同的句柄上同时执行I/O操作,甚至可以在同一句柄上同时进行读写操作。“重叠”一词的含义就在于此。
ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲中,而且要等这些字符从串口送出去后才算完成操作。
ReadFile和WriteFile函数是否为执行重叠操作是由CreateFile函数决定的。如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志,那么调用ReadFile和WriteFile对该句柄进行的读写操作就是重叠的,如果未指定重叠标志,则读写操作是同步的。
函数ReadFile和WriteFile的参数和返回值很相似。这里仅列出ReadFile函数的声明:
需要注意的是如果该函数因为超时而返回,那么返回值是TRUE。参数lpOverlapped在重叠操作时应该指向一个OVERLAPPED结构,如果该参数为NULL,那么函数将进行同步操作,而不管句柄是否是由FILE_FLAG_OVERLAPPED标志建立的。
当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。
在使用重叠I/O时,线程需要创建OVERLAPPED结构以供读写函数使用。OVERLAPPED结构最重要的成员是hEvent, hEvent是一个事件对象句柄,线程应该用CreateEvent函数为hEvent成员创建一个手工重置事件,hEvent成员将作为线程的同步对象使用。如果读写函数未完成操作就返回,就那么把hEvent成员设置成无信号的。操作完成后(包括超时),hEvent会变成有信号的。
如果GetLastError函数返回ERROR_IO_PENDING,则说明重叠操作还未完成,线程可以等待操作完成。有两种等待办法:一种办法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员,可以规定等待的时间,在等待函数返回后,调用GetOverlappedResult。另一种办法是调用GetOverlappedResult函数等待,如果指定该函数的bWait参数为TRUE,那么该函数将等待OVERLAPPED结构的hEvent事件。GetOverlappedResult可以返回一个OVERLAPPED结构来报告包括实际传输字节在内的重叠操作结果。
如果规定了读/写操作的超时,那么当超过规定时间后,hEvent成员会变成有信号的。因此,在超时发生后,WaitForS-ingleObject和GetOverlappedResult都会结束等待。WaitForSingleObject的dwMilliseconds参数会规定一个等待超时,该函数实际等待的时间是两个超时的最小值。注意GetOverlappedResult不能设置等待的时限,因此如果hEvent成员无信号,则该函数将一直等待下去。
在调用ReadFile和WriteFile之前,线程应该调用ClearCommError函数清除错误标志。该函数负责报告指定的错误和设备的当前状态。
调用PurgeComm函数可以终止正在进行的读写操作,该函数还会清除输入或输出缓冲区中的内容。
3.4 超时设置
在用ReadFile和WriteFile读写串口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会结束。要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。
有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读/写操作的超时,该结构的定义为:
COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是:总超时=时间系数要求读/写的字符数+时间常量
例如, 如果要读入10个字符, 那么读操作的总超时的计算公式为:读总超时=ReadTotalTimeoutMultiplier10+ReadTotalTimeoutConstant
可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。
如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0, 则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。
在用重叠方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。
4 结语
以上给出了用Win32 API设计串行通信的基本思路,这个重叠(异步)I/O操作的串行通信程序,应用于大型任务,表现出良好的性能。在实际应用中,稍加改造,即可设计出满足需要的各种串行通信程序。
摘要:串行通信方便易行, 应用广泛。结合硬件详细介绍在Windows环境下使用Windows API实现异步串行通信的方法。
关键词:串行通信,RS232,重叠,I/O,Win API
参考文献
[1]李现勇.Visual C++串口通信技术与工程实践.人民邮电出版社, 2004.
I/O口 第6篇
1 虚拟地址映射
对外设进行I/O操作即读写外设的寄存器,WinCE系统启动后,无法直接访问其物理地址,因此,我们要理解WindowsCE下的虚拟地址映射。
在WindowsCE中有两种类型的地址:物理地址和映射的虚拟地址[1]。
物理地址是需要被操作系统访问的实际的RAM或设备存储器,它由来自于CPU的物理地址定义,一旦MMU启动,CPU就不能直接访问。内核只能管理512MB的物理内存。
不同架构的CPU硬件上的区别导致虚拟地址映射也不同。SHx和MIPS处理器,不采用MMU,在CPU和内核里定义1G的物理地址,能对其直接进行操作;而ARM和X 86带有MMU单元,在OEMAddressTable中定义物理地址到虚拟地址间的映射关系或者是操作系统启动后调用函数CreateStaticMapping和NKCreateStaticMapping来实现从虚拟地址到物理地址的静态映射。经过静态映射的地址,可以由操作系统内核用于ISR(InterruptServiceRoutine)访问设备。如果我们要在应用程序中访问外设,必须在物理地址和虚拟地址间建立动态映射关系,我们可以使用VirtualAlloc和VirtualCopy(或者直接调用MmmapIoSpace函数)来实现。
静态映射允许OAL,尤其是中断服务历程ISR访问连接到系统的设备。物理的内存块在被映射到虚拟地址空间时,通常被映射两次,分别映射到两个不同的区域,位于512MB使用缓冲(Cached)的区域和512MB不使用缓冲(Uncached)的区域,如图1所示。OEMAddressTable负责创建第一个基于Cached的映射,而操作系统负责自动创建第二个基于Uncached的映射。
如果是操作通过总线挂接的I/O或者存储器,必须先把总线地址转化成CPU上的系统地址,再做物理地址到虚拟地址的映射。这里需要查CPU的Datasheet,找出所要操作的I/O地址。先调用HAL-TranslateBusAddress()把总线地址转化成CPU上的系统地址,再调用MmmapIoSpace函数实现虚实映射;也可以使用TransBusAddrToVirtual()直接把总线上的地址转化成系统的虚拟地址。
在一般的应用程序中访问I/O是访问它的缓存段虚拟地址,而驱动中必须访问无缓存段虚拟地址。缓存段虚拟地址与无缓存段虚拟地址之间的偏移量为0x20000000,即无缓存段虚拟地址=缓存段虚拟地址+0x20000000。
2 关键函数
在动态虚拟地址的映射过程中,需要用到以下3个函数:VirtualAlloc、VirtualCopy、VirtualFree。
VirtualAlloc用于在当前进程的虚拟地址空间中保留或者提交空间,在保留时以64kB为单位,提交时以4kB为单位。其函数原型为
这里需要注意的是fdwProtect参数。如果是驱动程序访问,需要设置为PAGE NOCACHE,以访问无缓存段虚拟地址。如果映射的物理地址范围在0x1FFFFFFF之上,必须使用PAGE PHYSICAL,此时必须把lpvSrc右移八位,实现地址对齐。(这是由内核中VirtualCopy的实现决定的,在那个函数中会判断如果是PAGE PHYSICAL就将PHYSADDR左移8位移回来,源代码位于private/winceos/coreos/nk/kernel目录下的virtmem.c中的DoVirtual-Copy)
3 实例操作
以S3C 2440为例,GPIO的基地址为0x56000000,映射到虚拟地址空间为0xB 1600000,通过对这段虚拟地址空间的操作,就能够完成对GPIO或者其他片内资源的控制、输入输出工作。
(1)首先在BSP中的s2440.h文件,找到虚拟地址映射以及操作GPIO的寄存器结构体(这个在自己制作一些特殊设备的BSP时,会依据需要而发生更改)。
(2)在EVC中建立一个应用程序工程,由于VirtualCopy函数没有在头文件中定义,但是在coredll.lib里面提供了符号连接,所以我们在工程头文件中直接添加一个函数定义就可以了。
v pIOPRegs->rGPFCON=0x5555;
这3个步骤之后,对v pIOPRegs的操作将直接和GPIO的寄存器关联。例如:设置GPF的控制寄存器为全部Output
v pIOPRegs->rGPFDAT=0xFF;
4 结束语
WindowsCE作为实时嵌入式窗口操作系统,是当今应用最多、增长最快的嵌入式操作系统。研究其I/O操作方法,对进一步深入开发具有一定的指导意义。
摘要:介绍Windows CE系统的虚拟地址映射机制,以I/O操作为基础,通用性较强。对关键函数进行了说明,并以S3C2440为例详细介绍了GPIO的操作步骤。
关键词:Windows CE,地址映射,I/O操作
参考文献
[1]张冬泉,谭南林,王雪梅,等.Windows CE实用开发技术.北京:电子工业出版社,2006
I/O口 第7篇
近年来随着海量数据的不断增加, 对象存储成为一种新的管理技术, 基于对象的网络存储文件系统的研究随之也成为新的热点。基于对象的网络存储文件系统由客户端 (Client) 、元数据服务器MDS (Metadata Service) 和对象存储设备OSD (Object Storage Device) 等三部分组成, 是一种高可靠性、高性能以及可扩展的分布式文件系统, 具有良好的综合性能。同时基于对象的网络存储也面临着一个突出的问题:如何高效存储并管理PB (PetaByte) 级容量的数据。目前比较成熟的基于对象的网络存储文件系统中, Lusture、zFS、panFS和Ceph应用比较广泛, 其中Lustre和zFS多采用脚本的方式, 即利用EXT2或EXT3文件系统对OSD中的对象进行管理;panFS很少涉及到OSD中的对象管理。按照OSD的标准, OSD中的存储空间管理器是一个对象管理系统, 它所管理的是对象而不是文件, 而且EXT2和EXT3都是基于块的文件系统, 在文件的大小、目录结构和性能上都有一定的局限性。Ceph网络存储文件系统中采用基于对象的文件系统OBFS (Object-Based File System) , 专门用于 OSD中的对象管理系统, 从而提高OSD设备的效率[1]。本文从基于对象的存储设备, 基于对象的文件系统区域组织形式、文件系统结构、分配策略以及区域清理等角度对基于对象的文件系统进行了研究, 分别对OBFS、EXT2和EXT3的I/O性能进行了比较, 并对测试结果进行了深入的分析。
1基于对象的存储设备
OSD是一种具有智能、能够自我管理、提供对象接口并有较高安全性的存储设备。包括处理器、内存、网络接口、存储介质如磁盘阵列或磁带等以及运行在其中的控制软件。基于对象的存储设备和基于块的存储设备的根本区别在于操作接口, 而不是物理存储媒体[2]。
和NAS和SAN相比较, 基于OSD的存储设备兼有了二者的优点, 其存储的不再是数据块而是对象。对象是字节的逻辑集合, 不但包含数据的属性, 在对象上还可以定义安全策略;对象还结合了文件和块的优点, 可以直接被存取, 具有快速、高效的特点。
在基于对象的存储技术中, 对象是变长的, 可以用来存储整个数据结构, 如文件、数据库表、多媒体映像等, 甚至可以用来存储整个文件系统或数据库。
1.1OSD模型
与传统的基于块存储的文件系统相比较 (如图1所示) , OSD将文件系统的存储管理部分下载到存储设备上, 由存储设备自己负责, 同时将存储设备的操作接口由块接口改成对象接口, 提高了设备的抽象层次。
存储部分下载以后, OSD设备可以向外提供对象接口。用户可以直接请求OSD创建对象、删除对象;还可以获取或修改对象的属性, 读写对象的数据。
由于每个对象存储设备都具备底层文件系统的管理功能, 因此元数据只需要提供逻辑视图, 并不需要为所有节点提供物理视图。这样存储系统的负载被分散到各个存储设备里, 从而可以避免NAS系统中出现在元数据服务器上的性能瓶颈问题。
1.2OSD对象类型
OSD模型提供了以下四类对象:
(1) 根对象 (Root Object)
每个OSD逻辑单元中包含且仅包含一个根对象, 它的属性包括OSD逻辑单元的总容量和分区对象数等。根对象还有一个分区ID列表, 表示包含在逻辑单元中的所有分区。通过LIST命令获得该列表的信息。根对象对应的标识符是<0, 0>。
(2) 分区对象 (Partition)
通过CREATE PARTITION命令创建分区对象, 它包含一组集合对象和用户对象。每个分区对象中都包含一个用户ID (User _Object_ID) 列表和一个集合对象ID (Collection_Object_ ID) 列表, 表示包含在该分区中的所有集合对象和用户对象;用户可以通过LIST命令获得用户对象ID, 通过LIST COLLECTION命令获得集合对象ID列表。分区对象的标识符为< Partition_ID, 0>。
(3) 集合对象 (Collection)
使用CREATE COLLECTION命令创建, 它是对象用户的一个快速索引。一个集合对象只能包含在一个分区对象中, 而一个分区对象可以包含0个或多个集合对象。集合对象的标识符是<Partition_ID, Collection_Object ID>。
(4) 用户对象 (User Object)
用户对象中包含用户最终数据如文件或者数据库数据等。它包括用户数据的逻辑大小、创建时间、存取时间和修改时间等。每个用户对象只能是一个分区的对象成员。用户对象由< Partition_ID, User_Object_ID>标识, 一个User_Object_ID唯一标识一个分区对象中的一个用户对象。
2基于对象的文件系统
目前使用比较广泛的基于对象的网络存储文件系统包括Lustre, panFS和 zFS等, 它们可以管理大量的OSD设备, 但在这三个文件系统的描述中很少涉及OSD设备中的文件系统, 即OSD中的对象管理系统。事实上, 在Lustre和zFS中, OSD中的对象管理系统就是Linux的EXT2或EXT3文件系统。但是由于EXT2或EXT3是基于块的文件系统, 组成文件的数据块通常不是连续地分布在磁盘上, 这样就导致大量的碎片, 从而降低了对文件读写的效率。
OBFS是一种高效的基于对象的文件系统, 它应用于大规模分布式存储系统中的OSD的存储管理部分。其基本思想是根据对负载的分析从而对磁盘布局进行优化。使用区域分块方法改善对象的吞吐量同时保持良好的磁盘利用率。与Linux的Ext2与Ext3相比, OBFS具有更好的数据布局以及高效的管理。
2.1OBFS的块和区域
Ceph用对象存储文件, 它把对象分为大对象 (512KB) 和小对象 (4KB) 。统计表明, 在OSD管理的对象中, 85%是大对象, 只有15%是小对象。
由于OSD管理的大部分是大对象, 因此在OBFS中允许使用任意大小的块尺寸, 按照区域分块, 用来解决存储性能和磁盘碎片之间的矛盾。如图2所示。
OBFS支持4KB的小块和512KB的大块。大对象总是驻留在大块区域中, 每个大对象仅占用一个数据块, 其数据总是连续存放。小对象最多占用124个小块, 不能跨越区域边界。未初始化的区域为空闲区域, 其大小待定。
在OBFS运行的过程中, 大对象占据连续的存储空间始终都不会碎化, 而小对象会有一定程度的碎化, 但碎化程度是有限的。因此, OBFS的性能与磁盘利用率不会随着运行时间而降低。
OBFS区域中包含管理所需要的所有信息, 如onode表、空闲onode位图、空闲块位图等。因此对区域的操作是可以同时进行, 从而提高系统的并行度。
2.2OBFS的元数据和文件系统结构
OBFS的文件控制块onode记录每个对象的元数据, 在小块区域中, 一个区域中所有onode结构构成一个onode表, 而且位置固定。在OBFS的大块区域中, onode结构表是临时创建的, 与对象的数据部分存放在一起, 共同占用大区域的一个数据块。如图3所示。
每个onode结构有唯一的长度为32位的标识符onode ID, 通常被平分成两部分:region ID和onode index。region ID用于定位OSD中的区域, onode index用于定位区域中的onode。在查找过程中, OBFS以region ID为索引先找到该对象的所在区域, 而后根据区域的特性, 以它的onode index为索引找到对象的onode结构。
区域头保存着区域的管理信息, 所有的区域头结构组织成一个链表RHL (RegionHead List) 。OBFS使用对象查找表OLT (Object Lookup Table) 组织其对象。其中OLT使用Hash表的方式组织OBFS中的对象, 每个有效的对象在OLT中都有一个表项 (entry) , 用于记录它的object ID (4字节) 和onode ID (4字节) 。16个表项组成一行, 具有相同Hash值的对象记录在同一行上。由于RHL和OLT都被缓存在内存中, 因此提高了系统的访问和查找的速度。
有了OLT和RHL之后, 解析一个对象ID的过程如下:
(1) 根据对象的object ID计算Hash值, 根据算出的Hash值查得OLT获得对象的onode ID。
(2) 从对象的onode ID中分离出Region ID , 根据Region ID查RHL, 找到对象所属的区域头结构, 获取区域的管理信息, 如区域类型等。
(3) 从对象的onode ID中分离出Onode index。对小块区域, 根据对象的Onode index查区域的Onode table, 找到对象的onode结构;对于大块区域, 直接根据对象的Onode index即可找到对象所在的数据块, 因为它的onode结构就在数据块中。
2.3OBFS的分配策略
在创建新对象时, 客户需要对对象的类型 (大对象或者小对象) 进行选择说明。OBFS负责新对象的数据块和区域的创建。OBFS在收到创建新对象并向其写入数据的请求后, 首先将其缓存起来, 进一步聚合对象的数据, 以便为其分配尽可能连续的存储空间。
在为新对象选择区域时, OBFS首先要根据区域的类型、空闲数据、负载平衡以及最后一次访问区域之间的距离等来搜索区域头链表RHL。并将其划分为三个子链表:大块区域链表、小块区域链表和空闲区域链表。如果找不到合适的候选区域, OBFS会就近选择一个空闲的区域并将其格式化成所需要的类型。在分配空闲区域时, OBFS采取一种保守的分配策略, 即尽可能使用已格式化的区域。
对于数据块的选择, 因为候选区域任意一个空闲数据块都可以存储大对象, 所以大对象选择数据块比较简单。对于小对象数据块的分配, OBFS采取其数据块尽可能连续的原则。具体的方法就是OBFS维护一个空闲域的链表, 分配数据时首先搜索空闲域链表, 找大于或等于小对象尺寸的空闲域。如果区域中有多个满足要求的空闲域, OBFS采用最坏适应 (WorstFit) 算法, 即选择最大的空闲域来存储小对象[3]。
2.4OBFS区域清理
由于OBFS以区域为单位组织和管理数据, 而且每个区域中只有一种类型的数据块, 因此, 可能会出现某种类型的数据块被用完的情况。此时, 虽然OSD中还有空闲, 但却没有所需要的类型的数据块, 在这种情况下, 就需要进行区域清理。其目的就是合并同类区域中的对象, 从而释放出空闲空间。OBFS将一个区域中的所有对象都复制到其他同类的区域中, 从而创造出一个空闲区域。在小对象区域的清理过程中, 由于小对象存储空间被重新组合、分配, 还可以消除小对象的碎化现象, 进一步提高了磁盘的利用率。
3OBFS和EXT2、EXT3性能比较
3.1测试环境
测试使用的是一台PC机器, 配置为双核3.6GHZ主频, 2G内存, 250G硬盘。操作系统为Red Hat Enterprise Linux 5 2.6.18版本。
使用iozone3.0作为测试读写性能的工具。iozone是一个文件系统的benchmark工具, 可以测试不同的操作系统中文件系统的读写性能。可以测试 Read, write, re-read, re-write, read 等不同的模式下的硬盘的性能。
测试对一个固定大小的文件采用不同的 I/O 传输块大小, 对OSD 进行读写操作, 为了避开文件系统的影响, 直接将OSD命令提交给OSD驱动。采用的读写操作原型为[4]:
(1) 读操作
(2) 写操作
在以上读写操作参数里, 分区ID 和用户对象ID 是用来唯一确定一个对象在磁盘上的位置的参数。测试写性能时采用的是不分配写 (non-allocating write) , 即重写, 写之前对象的地址空间已经分配好。
3.2性能分析
EXT2是基于内核级的文件系统。ETX3则是用于Lustre文件系统中的对象存储和磁盘分配策略的内核级的文件系统, 并在EXT2的基础上增加了日志管理用于提高可靠性。为了更大限度地发挥每个系统的I/O性能, 本文采取了避开缓存系统:即所有的文件系统都挂载“-o sync”参数, 使文件系统采用直接写策略。
图4显示了OBFS、EXT2和ETX3文件系统在80%大对象和20%小对象的负载情况下的性能。由图可知, 无论是读性能还是写性能, 在相同负载的情况下OBFS都优于EXT2和ETX3文件系统, 基本上是二者的2到3倍。由于OBFS采用了按照区域分块的方案, 大大地提高了磁盘的利用率。在相同的时间内, EXT2和ETX3文件系统在磁盘分配策略方面没能充分利用相邻块的空间, 为了提高大对象的存储性能, 它们需要增大数据块的尺寸, 但随着尺寸增大其磁盘碎片明显增大, 造成了存储空间的巨大浪费, 使得其性能下降。同样在重写文件方面, OBFS也显示了其巨大的优势, 无论是大对象还是小对象, OBFS文件系统的重写性能都高于EXT2和ETX3文件系统约3倍。
4结论
论文在描述OBFS的结构和关键技术基础上, 在用户级执行OBFS, 并针对性的与EXT2和EXT3文件系统分别进行了性能的比对。从分析的结果来看, 用于管理OSD中对象的OBFS系统有着良好的吞吐量和高效的磁盘利用率, 在基于对象的网络存储领域将会有更加广泛的应用。
摘要:目前基于对象的存储设备中多采用通用的文件系统如EXT2、EXT3等进行管理, 但面临着对海量数据的高效存储、管理问题。基于对象的文件系统实现了对象按大小在磁盘区域中组织管理, 并采用了Hash、最坏适应等机制, 提高磁盘利用率的同时显著提升了磁盘吞吐率。对EXT2、EXT3、基于对象的文件系统进行了I/O性能测试及分析。实验表明, 基于对象的文件系统的I/O性能是EXT2、EXT3文件系统的2到3倍。
关键词:对象存储,对象存储设备,文件系统,性能分析
参考文献
[1] Sage A Weil, Scott A Brandt, Ethan L Miller, et al.Ceph:A Scalable, High-Performance Distributed System.2007.
[2]Object Based Storage Devices Technical Work Group.SNIAOSD[C/OL]. (2006-07-28) .http://www.snia.org/tech_activities/work-groups/osd.
[3] Feng Wang, Scott A Brandt, Ethan L Miller, et al.OBFS:A File System for Object-based Storage Devices[C]//21st IEEE/12th NASA Goddard Conference on MssT2004.2004.
I/O口 第8篇
研华此次将数据采集、智能处理与数据发布三个核心功能有效融合在单个I/O模块中, 以满足如环境监测、设备故障监测及智慧城市等更广泛的行业应用。
研华WISE-4000系列产品是基于以太网的无线I/O模块, 无需通过网关即可采集和传输信息。由于无限定数量的I/O模块可从传感器采集信息并连接到既有网络, 这意味着该配置将更加简单便捷。
WISE-4000系列内部集成HTML5文档, 无需任何接入点, 即可直接通过web浏览器在移动设备上进行访问和配置。由于WISE-4000采用了时下流行的RESTful API, 系统集成商可通过调整程序以满足其特定需求, 并以更少的工作量获得更多数据, 实现用户效率最大化。
通过WISE-4000系列的数据存储功能, 用户可将标记时间的数据发送到Dropbox或私有云平台, 亦可缓冲在模块中。如此, 当发生网络通信故障时, 即便暂未发送数据至管理员也不会丢失, 有效提高了数据的安全性。
I/O口 第9篇
关键词:Linux,I/O复用
0、背景
随着硬件的发展, 单台服务器的能力也逐渐提升, 对服务器程序的性能要求也突破了以往的要求。特别是网络的发展, HTTP应用等高并发低I/O传输的应用越来越普遍。单台服务器同时服务几千甚至上万个客户的情况越来越多。
TCP/IP网络编程的一个重要问题是就是客户端和服务端的通信并不是一直存在的, 由于这个特点, 通常程序会阻塞在诸如accept、send、recv等函数[1], 显然服务端不能只等待一个客户端, 这就需要让服务端具备同时服务的能力。
为了解决这个问题, 通常处理多客户请求时, 可以采用多线程 (进程) 方式或者I/O复用方式。在多线程情况下, 主监听线程接受客户端连接后, 为请求创建一个单独的线程等待客户的请求后响应, 此时每个线程都是阻塞的。当客户请求到达时, 系统唤醒对应的线程, 等待时则让线程阻塞在客户请求上。在并发线程数不多 (小于1000) 时, 这个模型可以较好的提供服务, 且在一定程度上简化服务器程序的设计。但面对远超过1000客户请求时, 如果客户请求为低I/O传输 (例如HTTP请求对象通常都在10KB级别) , 服务器程序消耗的资源 (内存、线程上下文切换) 已经超过网络服务, 从而成为系统的瓶颈。所以在高并发服务器模型中I/O复用[1,2,3]显得尤为重要。
I/O复用模型将阻塞式I/O[1] (图1) 调用转换为非阻塞式[1] (图2) , 不仅是socket的文件描述符, 同样也可以应用在普通文件描述符上。I/O复用函数同时监控所请求的文件描述符, 当监控的描述符有数据或事件时返回。各个请求都统一阻塞在I/O复用函数上而不是各自阻塞在I/O系统调用上, 当I/O复用函数返回时, 根据对应的文件描述符进行处理, 从而在一个循环中完成多个I/O等待。当I/O完成后需要处理复杂逻辑的时候, 再视情况创建线程处理, 从而利用服务器的多CPU (核) 性能。
1、模型介绍
Linux中I/O复用主要有select、poll和epoll。
1.1 Select
函数原型[1,2,4]:
Linux下所有的socket操作都是通过套接字描述符来关联的, 所以select创建了一个fd_set来管理这个我们关心的套接字集合。针对套接字读、写、异常三种不同状态, select提供了三种套接字集合readset、writeset和exceptset, 只需要把套接字加入到不同的集合就可以实现对三种状态的监控。对fd_set类型, 提供了四种操作[4]:
同时select提供了微妙级的超时控制, 通过设置timeout结构体时间为NULL、0、非0三种来实现阻塞select、非阻塞轮询select、超时等待三种方式。其中非阻塞轮询适用于服务器长时间处于高并发状态的情况, 因为此时select测试所有的套接字并立即返回, 会占用大量CPU时间, 当服务端连接数目少且并不活跃时会占用大量的CPU时间。
下面是一个典型select服务端的伪代码:
其中readset是监控的可读套接字集合, monitorset是需要监视的套接字集合, clientfds存储了已经连上服务器的套接字数组, maxi表示当前最大的索引。
服务端是一个死循环, 阻塞在select上, 当有可读套接字, 或发生了异常则返回, nready是变化的套接字描述符数目, 首先测试监听的套接字是否可读, 可读则表明有新的连接。然后顺序扫描clientfd, 查找对应的套接字是否可读。因为发送的包 (自定义消息结构体) 不一定每次都能完整接收, 所以需要一个包缓冲数据, 记录每次读取的大小, 直到读取了完整的一个包, 这样就需要扫描缓冲数组来完成。由于fd_set的实现为一个unsigned long数组, 里面的每一位为一个套接字, 每次select遍历会将没有状态改变的套接字从中清空, 所以需要在循环开始时重置为我们关注的fd_set。在ReadPacket函数中, 当客户端开后 (read的字节为0) , 要从对应的monitorset中使用FD_CLR清除套接字描述符。
POSIX.1[1,4]还定义了select的一个变体pselect。区别在于pselect的超时采用timespec结构体提供纳秒级 (如果系统支持这样的粒度) 的超时控制, 比timeva的微秒级更精确;同时由于select会修改传入的超时值, 这不符合POSIX.1-2001标准, 所以pselect利用局部变量解决了这个问题;pselect还提供了一个可选择的信号屏蔽字。其他用法上两者相似。
1.2 poll
poll[1,2,5]是从UNIX SVR3版本引入针对STREAMS设备的I/O复用函数, 在UNIX SVR4去掉了这个限制, 使得poll可以应用到所有的类型的文件描述符上。poll和select函数实现[1,4,5,7]上基本相同, 除了pol会在处理STREAMS设备上增加额外的信息。
poll通过pollfd来实现对监控文件描述符的监控, 将events属性设置为想要监控的类型, 返回时则通过对revents判断来进行 (与操作, 多个操作通过或连接) 。events和revents可以参考表1[1]。下面是一个典型的poll服务器伪代码:
clientfds是预先分配的最大文件描述符数的数组, maxi是当前最大的数组索引, nready是每次就绪的文件描述符数。服务器的工作流程与select相同, 将需要关注的套接字描述符加入clientfds数组, 同时设置关注的events, 当poll返回时, 根据nready先判断是否是新连接, 通过判断数组第一个pollfd的revents是否发生可读数据 (初始化时设置为监听套接字的文件描述符) 来确定是否有新连接。在ReadPack函数中, 循环遍历clientfd数组, 判断revents是否是对应数据, 并同上面提到的一样, 对数据包进行缓存, 当发生错误或者关闭连接是, 从clientfds中清除对应的数组 (将结构体初始化即可) 。由于poll不会清除clientfds中的内容, 所以无需每次重新赋值。
从上面的流程可以看到, select和poll都存在一个缺陷, 首先需要预先分配与最大请求连接数相匹配的数据空间。由于socket的文件描述符和普通文件描述在Linux下面是一致的, 都受限于系统的预定值 (默认为1024) , 如果要动态改变 (使用sysconf修改_SC_OPEN_MAX) 则当前分配的数据空间也要随之改变。同时每次都要从所关注的集合套接字描述符集合中查找状态改变的文件描述符, 所以当连接数较多时会造成性能下降。由此从Linux内核2.6开始[9], 引入epoll函数[2,6]来改善这个问题, 到Linux内核2.6以后。从Linux下I/O复用模型的变化[3,9]就可以看出这个趋势。
1.3 Epoll
select和poll是传统的Linux系统调用, 属于条件触发 (level-trigger) [3,9], 在条件触发中, 系统会根据当前所监控的条件 (读、写等) 是否满足来回应, 而epoll不仅提供了条件触发, 还提供了沿触发 (edge-trigger) [3,9], 沿触发仅在监控的状态改变时才触发一个事件。条件触发相当于系统帮助程序监视对应的文件描述符是否满足数据要求, 而沿触发则是有程序控制, 当状态改变时, 由程序来处理发生的变化。
Epoll通过epoll_create函数创建epoll上下文并得到的一个文件描述符epfd, 通过epoll_wait监控epfd来实现对文件描述符集合的监控, 当有事件改变时会返回改变集合的大小。下面是一个epoll服务器的伪代码:
epfd是由epoll_create得到的监控描述符, events是struct epoll_event的数组集合, listenfd是套接字创建的一个监听fd, 通过event_ctl对应事件加入events。其他与上面两个服务器类似, 通过监控events的变化, 来获得新连接、新数据、断开连接、异常等信息。判断方法与poll类似。当不再监控某一个文件描述符时, 需要通过event_ctl来删除对应的epoll_events。
struct epoll_events结构体如下, 将需要监控的文件描述符赋给epoll_data_t中的fd, 对events的操作同poll一样, 加入EPOLLIN、EPOLLOUT、EPOLLERR等标志位, 不同的是, 当需要改变默认的条件触发为沿触发时, 我们需要在上述几个事件外或上EPOLLET标志。
当启用沿触发模式时, 系统仅当文件描述符发生状态变化才会触发事件, 因此在使用边缘触发模式时, 在ReadPack中额外要注意每次都要读到socket返回EWOULDBLOCK为止, 否则由于socket依旧可读, 新来的数据不会引起任何事件触发, 从而使得应用程序无法获知相应的事件。其他函数和前面的功能类似, 对描述符集合的操作通过event_ctl实现。
2、结束语
上面介绍了Linux进行I/O复用的三种主要方式。从著名的web服务器apache和ngnix上都可以看到Linux下高性能I/O复用都是通过epoll来实现的, 且由于只关心有变化的文件描述符避免了重复检查, 提高了效率, 且支持条件触发和沿触发两 种方式, 使得epoll成为当前Linux I/O复用的主流方式。
参考文献
[1]尤晋元译, Richard Stevens, Unix环境高级编程 (第二版) , 人民邮电出版社, 2006
[2]W.Richard Stevens, Bill Fenner, Andrew M.Rudoff, NetworkProgramming Volume 1, Third Edition:The Sockets Networking, Addison Wesley, 2003
[3]C10K Problem, http://www.kegel.com/c10k.html, 2010
[4]Linux Manuel Page, Select http://linux.die.net/man/2/select, 2010
[5]Linux Manuel Page, poll http://linux.die.net/man/2/poll, 2010
[6]Linux Manuel Page, epoll http://linux.die.net/man/2/epoll, 2010
[7]select, poll函数源代码http://lxr.linux.no/linux+v2.6.36/fs/se-lect.c, 2010
[8]fd_set类型, http://lxr.linux.no/linux+v2.6.36/include/linux/posix_types.h#L38
I/O口范文
声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。如若本站内容侵犯了原著者的合法权益,可联系本站删除。