Windows内存体系(3)--内存映射文件
为什么需要内存映射
“内存映射文件”可以将硬盘上的文件映射到虚拟地址空间,这样就不需要将所有东西都放入到页交换文件中,比如系统有许多程序同时运行时,如果将这些程序文件都加载到页交换文件中,页交换文件将会变得非常大。事实上,Windows 也并没有将硬盘上的程序文件复制到页交换文件中,因为这样不仅会让页交换文件将会变得非常大,也会浪费很多时间,特别是可执行程序非常大的时候。
当用户要求执行一个应用程序时,系统会打开该应用程序的.exe文件,并计算出应用程序的代码和数据的大小,然后系统会在进程的虚拟地址空间预定一块地址空间,并注明与该区域相关联的物理存储器就是.exe文件本身。
当把一个位于硬盘上的文件(可以是.exe,.dll也可以是普通文件)映像用作地址空间区域对应的物理存储器时,我们称这个文件映像为“内存映射文件”。
Windows内存体系(2)--虚拟内存
Windows内存体系(1)--虚拟地址空间
实模式下内存分配机制
在 8086 或者 80186 以前,程序运行时,操作系统会把程序全都装入内存,程序都是直接运行在物理内存上的。也就是说程序中访问的内存地址都是实际的物理内存地址。当计算机同时运行多个程序时,必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。
例如某台计算机总的内存大小是128M ,现在同时运行两个程序 A 和 B ,A 需占用内存10M , B 需占用内存110M 。计算机在给程序分配内存时会采取这样的方法:先将内存中的前10M分配给程序 A ,接着再从内存中剩余的118M中划分出 110M分配给程序 B 。这种分配方法虽然可以保证程序 A 和程序 B 都能运行,但是这种简单的内存分配策略会导致很多问题:
初识WebRTC
WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。
它并不是单一的协议,包含了媒体、加密、传输层等在内的多个协议标准以及一套基于 JavaScript 的 API。通过简单易用的 JavaScript API ,在不安装任何插件的情况下,让浏览器拥有了 P2P音视频和数据分享的能力。同时WebRTC 并不是一个孤立的协议,它拥有灵活的信令,可以便捷的对接现有的SIP 和电话网络的系统。
不同技术领域的人都可以从WebRTC中获取到需要的东西。
SDP格式解析
会话描述协议(Session Description Protocol 或简写 SDP)描述的是流媒体的初始化参数。此协议由 IETF 发表为 RFC 2327。
SDP 完全是一种会话描述格式,它不属于传输协议。
SDP 用于描述多媒体通信会话,包括会话建立、会话请求和参数协商。SDP 不用于传输媒体数据,只能用于两个通信终端的参数协商,包括媒体类型、格式以及所有其他和会话相关的属性。SDP 以字符串的形式描述上述初始化参数。
网络协议(7)--HTTP与HTTPS协议
HTTP 是Hyper Text Transfer Protocol(超文本传输协议)的缩写。HTTP 协议位于 TCP/IP 协议栈的应用层。
网络协议(6)--TCP协议
Windows的消息机制
消息队列
首先我们要明确一个观点:窗口是和线程相关联的,消息队列也是和线程相关联的,这个线程无论是主线程还是子线程。
当一个线程被创建时,系统假定该线程不会被用于任何与用户界面相关的任务,所以不会为它分配相应的资源(如消息队列等),因为这样可以减少线程对系统资源的占用。
但是,一旦这个线程调用一个与图形用户界面有关的函数(例如检查它的消息队列或建立一个窗口),系统就会为该线程分配一些额外的资源,以便它能够执行与用户界面有关的任务。特别是,系统会分配一个THREADINFO结构,并将这个数据结构与线程关联起来。
THREADINFO结构是微软内部的、没有被公开的数据结构,我们无法找到这个结构体的准确的定义。但从其他文档中可以得知,THREADINFO结构包含:
- 一组成员变量,利用这组成员,线程可以认为它是在自己独占的环境中运行。
- 登记消息队列(posted-message queue)
- 发送消息队列( send-message queue)
- 应答消息队列( reply -message queue)
- 虚拟输入队列(virtualized-input queue)
- 唤醒标志(wake flag)
- 用来描述线程局部输入状态的若干变量。

上图描述了THREADINFO结构中的各个成员。线程拥有了THREADINFO结构也就有了各种消息队列。
窗口消息处理函数
下面是一个完整的、简单的创建 Windows 窗体的 C++代码:
1 | #include <windows.h> |
上面代码中的WndProc函数就是窗口消息处理函数。消息循环中的DispatchMessage函数派发消息时,系统就会调用这个函数对消息进行处理。
消息循环
消息循环是程序员自己编写的从线程消息队列中循环获取(Get 或 Peek)消息的循环体,代码大致如下:
1 | while(GetMessage(&Msg, NULL, 0, 0)) |
循环何时结束?
GetMessage函数的返回值如下:
1 | 收到WM_QUIT消息,返回0 |
利用GetMessage函数返回值的特性,在收到WM_QUIT消息之后,消息循环就会结束。
TranslateMessage
TranslateMessage函数的作用就是将虚拟键值信息转换为字符信息。这一步并不是必须的。
DispatchMessage
将消息派发到窗口的消息处理函数(如第 2 节中的WndProc函数)。
PostMessage
PostMessage 原理
1 | BOOL PostMessage( |
当一个线程调用这个函数时,系统要确定是哪一个线程建立了用hwnd参数标识的窗口。然后系统分配一块内存,将这个消息参数存储在这块内存中,并将这块内存增加到相应线程的登记消息队列中。并且,这个函数还设置QS_POSTMESSAGE唤醒位。PostMessage函数在登记了消息之后立即返回,调用该函数的线程不知道登记的消息是否被指定窗口的窗口过程所处理。实际上,有可能这个指定的窗口永远不会收到登记的消息。
PostThreadMessage
还可以通过调用PostThreadMessage将消息放置在线程的登记消息队列中。
1 | BOOL PostThreadMessage( |
PostQuitMessage
为了终止线程的消息循环,可以调用PostQuitMessage函数。PostQuitMessage函数类似于:PostThreadMessage(GetCurrentThreadId(), WM_QUIT, nExitCode, 0);
但是,PostQuitMessage并不实际登记一个消息到任何一个THREADINFO结构的消息队列。只是在内部,PostQuitMessage会设定QS_QUIT唤醒标志,并对THREADINFO结构的nExitCode成员进行设置。因为这些操作永远不会失败,所以PostQuitMessage的原型被定义成VOID返回类型。
SendMessage
SendMessage的实现分为 2 种情况:向本线程的窗口发送消息、向其他线程的窗口发送消息。
向本线程的窗口发送消息
如果调用SendMessage的线程向本线程所建立的一个窗口发送一个消息,SendMessage的做法很简单:直接调用指定窗口的“窗口消息处理函数”,将其作为一个子例程。当“窗口消息处理函数”完成对消息的处理后,该函数会返回一个值给SendMessage,SendMessage再将这个值返回给调用线程。
向其他线程的窗口发送消息
但是,当调用SendMessage的线程向其他线程所建立的窗口发送消息时,SendMessage的内部工作就复杂得多(即使两个线程在同一进程中也是如此)。
Windows 要求建立窗口的线程来处理该窗口的消息。所以当一个线程调用SendMessage向一个由其他进程所建立的窗口发送一个消息,也就是向其他的线程发送消息,发送线程不可能处理窗口消息,因为发送线程不是运行在接收进程的地址空间中,因此不能访问相应窗口过程的代码和数据。实际上,发送线程要挂起,而由另外的线程处理消息。所以为了向其他线程建立的窗口发送一个窗口消息,系统必须执行下面的动作:
首先,发送的消息要追加到接收线程的发送消息队列,同时还为这个线程设定QS_SENDMESSAGE标志(后面将讨论)。
其次,如果接收线程已经在执行代码并且没有等待消息(等待消息是指:如调用GetMessage、PeekMessage或WaitMessage等),发送的消息不会被处理,系统不能中断线程来立即处理消息。当接收进程在等待消息时,系统首先检查线程的QS_SENDMESSAGE唤醒标志是否被设定,如果是,系统扫描发送消息队列中消息的列表,并找到第一个发送的消息。有可能在这个队列中有几个发送的消息。例如,几个线程可以同时向一个窗口分别发送消息。当发生这样的事时,系统只是将这些消息追加到接收线程的发送消息队列中。
当接收线程等待消息时,系统从发送消息队列中取出第一个消息并调用适当的窗口过程来处理消息。如果在发送消息队列中再没有消息了,则QS_SENDMESSAGE唤醒标志被关闭。当接收线程处理消息的时候,调用SendMessage的线程被设置成空闲状态(idle),等待一个消息出现在它的应答消息队列中。在发送的消息处理之后,窗口过程的返回值被登记到发送线程的应答消息队列中。发送线程现在被唤醒,取出包含在应答消息队列中的返回值。这个返回值就是SendMessage的返回值。这时,发送线程继续正常执行。
当一个线程等待SendMessage返回时,它基本上是处于空闲状态。但它可以执行一个任务:如果系统中另外一个线程向一个窗口发送消息,这个窗口是由这个等待SendMessage返回的线程所建立的,则系统要立即处理发送的消息。在这种情况下,系统不必等待线程去调用GetMessage、PeekMessage或WaitMessage。
由于 Windows 使用上述方法处理线程之间发送的消息,所以有可能造成线程挂起。例如,当处理发送消息的线程含有错误时,会导致进入死循环。那么对于调用SendMessage的线程会发生什么事呢?它会恢复执行吗?这是否意味着一个程序中的 bug 会导致另一个程序挂起?答案是确实有这种可能。
利用 4 个函数—— SendMessageTimeOut、SendMessageCallback、SendNotifyMessage和ReplayMessage,可以编写保护性代码防止出现这种情况。
