惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
V
V2EX
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
小众软件
小众软件
Vercel News
Vercel News
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
J
Java Code Geeks
P
Proofpoint News Feed
MongoDB | Blog
MongoDB | Blog
B
Blog
美团技术团队
量子位

博客园 - coderlee

SysListView32 的使用资料 使用ThinkPHP的一些小经验 记下来 关于InitCommonControls() windows内核对象 笔记 vc开发windows程序时的字符串问题的一个归纳(原创) ie6的一个css的BUG MSSQL 注入资料总结 sql 高级注入【转载 国外某牛人写的】 MYSQL 注入资料总结 Access SQL 注入指南 Ver0.2.1 上传文件与文本编辑器的那点破事:冗余与管理 xss,csrf入侵方式小结 站内的GET和POST 与 站外的GET与POST 记一次艰难的sql提权。 CSS需要注意的事项 PHP5的__get与__set 如何在web服务器或ftp服务器上开设VPN服务 - coderlee 有关javascript 有关xhtml和css 有关字符,字节以及编码
Windows操作系统中的I/O(读/写 输入/输出)
coderlee · 2009-10-10 · via 博客园 - coderlee

导言

写一个Windows平台下的应用程序大多时候都是离不开读写文件,网络通信的。
比如一个服务应用程序来说,它可能从网络适配器接受用户的请求,对请求进行处理计算,最终将用户端所需的数据返回,中间可能还涉及到对磁盘的读写,这些都是I/O操作,所以,要设计一个稳健的,高效的,伸缩性好的应用程序,就必须将Windows的I/O机制搞清楚。

一、 两种 读/写 机制

输入Input / 输出Output,有两种机制,他们是:

1 同步I/O: 线程执行一个输入输出函数时,输入输出工作执行完毕后,函数返回继续执行以后的代码。
2 异步I/O: 线程执行一个输入输出函数时,函数不等待读/写操作完成便立即返回,线程可先去执行下文,执行下文时可查询刚刚发起的读/写操作是否完成。

解读:

可以看出,异步I/O是更加高效的,同步I/O将会阻塞线程的执行,不应该被提倡。

有时候我们会认为,我们为每次执行I/O操作的时候,新开一个线程,让他去执行同步I/O函数,然后我们的原线程去执行工作,这样做是可以的,但是其实这样就是我们自己模拟实现了异步I/O,其实我们调用异步I/O函数的时候,操作系统内部也是维护了一个新线程去为我们进行I/O操作的。

但是,新建线程是一件很耗费CPU资源的事情,而且I/O操作时,CPU需要计算,需要从磁盘或网络适配器的缓冲区,将数据读取到内存中,或者将内存中的数据写到磁盘或网络适配器的缓冲区里,所以,I/O操作的线程不是睡眠的,而是忙碌的,所以我们得出了一个结论,如果我们的计算机上只有两个CPU,同时执行的I/O线程超过两个以上时,CPU会

因为I/O线程不是睡眠的而分配给他们时间片,于是线程间的频繁切换,也会降低我们的应用程序的性能。

那么,我们得出结论:同时执行的I/O线程的数量不应该大于CPU数量,客户发起的I/O请求需要一个队列,每次并发处理的I/O操作应该等于CPU数量,如果并发处理的I/O请求数量太多,CPU切换过于频繁,会将CPU资源浪费在线程切换上,从而严重降低程序性能。

二、读/写 前后的工作:创建与释放要读/写的设备的内核对象

要对设备进行读/写,必须先知道创建一个用于该设备的读写的内核对象,在读/写之后,释放该内核对象。

1. 创建I/O内核对象的函数: CreateFile(...)  与其他内核对象一样,我们获取的仅仅是一个句柄。
2. 关闭I/O内核对象的函数: CloseHandle(...)

解读:

HANDLE CreateFile(
LPCTSTR lpFileName,    
//要读写的设备
DWORD dwDesiredAccess,  //访问模式:0(不读写,如改变设备配置)  GENERIC_READ,GENERIC_WRITE(可异或)
DWORD dwShareMode,      //共享模式:FILE_SHARE_DELETE FILE_SHARE_READ FILE_SHARE_WRITE
LPSECURITY_ATTRIBUTES lpSecurityAttributes,  //安全属性  里面可设置内核对象的继承性
DWORD dwCreationDistribution,  //创建方式  CREATE_NEW  CREATE_ALWAYS  OPEN_EXISTING  OPEN_ALWAYS  TRUNCATE_EXISTING
DWORD dwFlagsAndAttributes,    //文件属性和标志
HANDLE hTemplateFile    //文件属性模板,如指定了这个参数,则忽略上个参数中的文件属性
);/*
参数 dwFlagsAndAttributes
1)文件属性:
  (FILE_ATTRIBUTE_)ARCHIVE COMPRESSED HIDDEN NORMAL OFFLINE READONLY SYSTEM TEMPORARY
2)标志:
  (FILE_FLAG_)WRITE_THROUGH OVERLAPPED NO_BUFFERING RANDOM_ACCESS SEQUENTIAL_SCAN DELETE_ON_CLOSE BACKUP_SEMANTICS POSIX_SEMANTICS
*/

这个CreateFile函数返回了一个文件内核对象句柄,这里说的文件不是狭义上的磁盘文件,也包括一些设备,比如串口,并口,邮件槽,命名管道和匿名管道,另外,有些设备句柄不是通过CreateFile创建的,下面是一个创建设备内核对象的函数的说明表。

设备            创建设备内核对象的函数

文件            CreateFile(pszName为路径名或UNC路径名)。

逻辑磁盘驱动器       CreateFile(pszName为\\.\x:)。x是盘符,打开驱动器可以格式化或者检测该驱动器的大小。

物理磁盘驱动器       CreateFile(pszName为\\.\PHYSICALDRIVEx)。x是物理驱动器序号,比如PHYSICALDRIVE0

串口            CreateFile(pszName为"COMx")

并口            CreateFIle(pszName为"LPTx")

邮件槽服务器        CreateMailslot(pszName为\\.\mailslot\mailslotname)

邮件槽客户端        CreateFile(pszName为\\servername\mailslot\mailslotname)

命名管道服务器       CreateNamedPipe(pszName为\\.\pipe\pipename)

命名管道客户端       CreateFile(pszName为\\servername\pipe\pipename)

匿名管道          CreatePipe

套接字           Socket,accept或AcceptEx

控制台           CreateConsoleScreenBuffer或GetStdHandle

以上这些函数,会创建一个I/O内核对象,并取得该对象的句柄值,然后我们可以调用与具体设备相关的函数,并传入得到的设备内核对象句柄,来和设备进行通信。

和其他内核对象一样,可以调用函数CloseHandle来关闭CreateFile创建的I/O内核对象。

BOOL CloseHandle(HANDLE hObject);

但是如果设备是套接字,就必须调用closesocket。

int closesocket(SOCKET s);

如果有一个设备句柄,可以通过GetFileType来查处具体的设备类型。

DWORD GetFileType(HANDLE hDevice);

返回值为:

描述
FILE_TYPE_UNKNOWN 未知类型
FILE_TYPE_DISK 磁盘文件
FILE_TYPE_CHAR 字符文件,一般是并口设备或者控制台设备
FIEL_TYPE_PIPE 指定的文件时一个命名管道或匿名管道