在复习socket通信时遇到了I/O复用技术这个问题,由这个问题引申出了select模型,I/O通信模型的问题,在Windows下对其进行了实现。但是很多概念性的东西不是很明白,于是查看了《Windows核心编程》这本书,不得不说这本书对Windows的很多东西讲的很透彻,因为时间的原因,只专门看了其中的几章,下面是一些笔记,不得不说操作系统的很多东西都需要记忆,不然今天看懂的东西明天就忘记了。千万不要觉得记忆是可耻的!!!同时也发现碰到问题了再看这本书时会好理解很多,下次再看这本书时一定要写一些demo。下面是看这本书之前写的一些demo。
socket通信之一:TCP/IP模型与socket
socket通信之二:实现一个基本的客户/服务器模型
socke通信之三:阻塞版本的客户/服务器模型
socket通信之四:多线程版本的客户/服务器模型
socket通信之五:select多路复用的客户/服务器模型
socket通信之六:Overlapped I/O 事件通知模型实现的客户/服务器模型
socket通信之七:Overlapped I/O 完成例程模型实现的客户/服务器模型
socket通信之八:完成端口模型实现的客户/服务器模型
socket通信之九:使用完成端口实现的一个聊天室
系统会创建和处理几种类型的内核对象,比如说访问令牌对象,事件对象,文件对象,文件映射对象,I/O完成端口对象,作业对象,邮件槽对象,互斥量对象,管道对象,进程对象,信号量对象,线程对象,可等待的计时器对象,线程池工厂对象。
每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。
调用一个会创建内核对象的函数后,函数会返回一个句柄(handle),它标示了所创建的对象。
句柄值是与进程相关的(为了增强系统的可靠性)。
内核对象的所有者是操作系统内核,而非进程,通过引用计数来实现。
进程内核对象句柄表:
一个进程在初始化时,系统将为它分配一个句柄表(handle table)。这个句柄表仅供内核对象使用,不适用于用户对象或GDI对象。
进程的内核对象句柄表结构:
索引 | 指向内核对象内存块的指针 | 访问掩码 | 标志 |
---|---|---|---|
1 | 0x??? | 0x??? | 0x?? |
2 | 0x??? | 0x??? | 0x?? |
3 | 0x??? | 0x??? | 0x?? |
4 | 0x??? | 0x??? | 0x?? |
进程终止时,系统自动扫描该进程的句柄表,如果这个表中有任何有效的记录项,操作系统会为我们关闭这些对象句柄。只要这些对象中有一个的使用计数为0,内核就会销毁该对象。
跨进程边界共享内核对象:
1. 使用对象句柄继承
2. 为对象命名
3. 复制对象句柄
内核对象的内容被保存在内核地址空间—-系统上运行的所有进程都共享这个空间。
虚拟地址空间的分区:
- 空指针赋值分区 0x00000000~0x0000FFFF
- 用户模式分区 0x00010000~0x7FFEFFFF
- 64kb禁入分区
- 内核模式分区
用户模式分区:
这一分区是进程地址空间的驻地,所有exe和动态链接库都载入到这一分区中。
内核模式分区:
系统需要这一空间来存放内核代码,设备驱动代码,设备输入/输出高速缓存,非分页缓冲池分配表,进程页面表。
这一分区是操作系统代码的驻地,与线程调度,内存管理,文件系统支持,网络支持以及设备驱动相关的代码都载入到该分区,驻留在这一分区内的任何东西为所有进程共有,虽然这一分区就在每个进程中用户模式分区的上方,但该分区中所有代码和数据都被完全保护起来。
当今操作系统能让磁盘空间看起来像内存一样,磁盘上的文件一般被称为页交换文件,其中包含虚拟内存。
事件的触发表示一个操作已经完成,有两种不同类型的事件对象,手动重置事件和自动重置事件,当一个手动重置事件被触发时,正在等待该事件的所有线程都将变成可调度状态,而当一个自动重置事件被触发的时候,只有一个正在等待该事件的线程会变成可调度状态。
MS为自动重置事件定义了一个等待成功所引起的副作用:当线程成功等到自动重置事件对象的时候,对象会自动重置为未触发状态,这也正是自动重置对象名字的由来。
当线程正在等待的对象处于未触发状态的时候,它们是不可调度的,一旦对象被触发,线程就会变成可调度状态。
异步设备I/O(asynchronous device I/O)允许线程开始读取操作或写入操作,但不必等待读取操作或写入操作完成。
设备对象是可同步的内核对象,这意味着我们可以调用WaitForSingleObject,并传入文件句柄,套接字,通信端口等等。当系统执行异步I/O的时候,设备对象处于未触发的状态,一旦操作完成,系统会将对象变成触发状态。这样线程就已知操作已经完成了。
当用户在用鼠标和键盘进行输入的时候,窗口消息 会被添加到相应的消息队列中去,这个消息隶属于创建窗口的线程。如果线程由于正在等待CreateFile返回而被阻塞,那么窗口消息将无法得到处理,该线程创建的所有窗口都会停滞在哪里。应用程序停止响应的最常见原因,就是因为要等待同步I/O操作完成而被阻塞住。
用来接收I/O完成通知的方法:
1. 触发设备内核对象:当向一个设备同时发出多个I/O请求的时候,这个方法没什么用,它允许一个线程发出I/O请求,另一个线程对结构进行处理。
2. 触发事件内核对象:这种方法允许我们向一个设备同时发出多个I/O请求,它允许一个线程发出I/O请求,另一个线程对结果进行处理。
3. 使用可提醒I/O:这种方法允许我们向一个设备同时发出多个I/O请求,发出I/O请求的线程必须对结果进行处理。
4. 使用I/O完成端口:这种方法允许我们向一个设备同时发出多个I/O请求,它允许一个线程发出I/O请求,另一个线程对结果进行处理,这项技术具有高度的伸缩性和最佳的灵活性。
关于I/O操作的例子可以参考下面一些文章:
socket通信之八:完成端口模型实现的客户/服务器模型
socket通信之七:Overlapped I/O 完成例程模型实现的客户/服务器模型
socket通信之六:Overlapped I/O 事件通知模型实现的客户/服务器模型
当系统创建一个线程的时候,会同时创建一个与线程相关联的队列。这个队列被称为异步过程调用(APC)队列。当发出一个I/O请求的时候,我们可以告诉设备驱动程序在调用线程的APC队列中添加一项。
当设备驱动程序完成I/O请求的时候,会在发出I/O请求的线程的APC队列中添加一项,该项包含完成函数的地址,以及在发出I/O请求时所使用的OverLapped结构的地址。
SleepEx:可提醒的状态。
可提醒I/O的优劣:
1. 回调函数。
2. 线程问题:发出I/O请求的线程必须同时完成对通知进行处理。
I/O完成端口关联的数据结构:
- 设备列表
- I/O完成队列(先进先出)
- 等待线程队列(后进先出)
- 已释放线程队列
- 已暂停线程队列