windows消息机制是怎么一回事?谢谢!

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了windows消息机制是怎么一回事?谢谢!相关的知识,希望对你有一定的参考价值。

参考技术A Windows的消息系统是由3个部分组成的:
· 消息队列。Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取
消息,然后分派给某个窗口。
· 消息循环。通过这个循环机制应用程序从消息队列中检索消息,再把它分派给适当的窗口,然
后继续从消息队列中检索下一条消息,再分派给适当的窗口,依次进行。
· 窗口过程。每个窗口都有一个窗口过程来接收传递给窗口的消息,它的任务就是获取消息然后
响应它。窗口过程是一个回调函数;处理了一个消息后,它通常要返回一个值给Windows。
注意回调函数是程序中的一种函数,它是由Windows或外部模块调用的。
一个消息从产生到被一个窗口响应,其中有5个步骤:
1) 系统中发生了某个事件。
2) Windows把这个事件翻译为消息,然后把它放到消息队列中。
3) 应用程序从消息队列中接收到这个消息,把它存放在TMsg记录中。
4) 应用程序把消息传递给一个适当的窗口的窗口过程。
5) 窗口过程响应这个消息并进行处理。
步骤3和4构成了应用程序的消息循环。消息循环往往是Windows应用程序的核心,因为消息循环
使一个应用程序能够响应外部的事件。消息循环的任务就是从消息队列中检索消息,然后把消息传递给适当的窗口。如果消息队列中没有消息,Windows就允许其他应用程序处理它们的消息。
Windows操作系统最大的特点就是其图形化的操作界面,其图形化界面是建立在其消息处理机制这个基础之上的。如果不理解Windows消息处理机制,肯定无法深入的理解Windows编程。可惜很多程序员对Windows消息只是略有所闻,对其使用知之甚少,更不了解其内部实现原理,本文试着一步一步向大家披露我理解的Windows消息机制。可以说,掌握了这一部分知识,就是掌握了Windows编程中的神兵利器,灵活运用它,将会极大的提高我们的编程能力。

select/poll/epoll到底是什么一回事

面试题:说说select/poll/epoll的区别。
这是面试后台开发时的高频面试题,属于网络编程和IO那一块的知识。Android里面的Handler消息处理机制的底层实现就用到了epoll。
为此,我在Google上看了很多相关文章,才大概搞懂是怎么一回事。

背景知识

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。在Linux系统中,流在内核中可以表示成文件的形式。

IO模型

IO可以理解成对流的操作。

一般对于一个read操作发生时,它会经历两个阶段。

  • 第一个阶段是等待数据准备。
  • 第二个阶段是真正读取的过程,将数据从内核缓冲区拷贝到用户进程缓冲区中,

而五种常见的IO模型也是围绕这两个阶段来区分的。

  • 同步模型(synchronous IO)
    • 阻塞IO(bloking IO)
    • 非阻塞IO(non-blocking IO)
    • 多路复用IO(multiplexing IO)
    • 信号驱动式IO(signal-driven IO)
  • 异步IO(asynchronous IO)

其中,IO多路复用就是一种机制,实现一个进程可以监视多个描述符,一旦某个描述符就绪,就能够通知程序进行相应的读写操作。IO多路复用相比于多线程的优势在于系统的开销小,系统不必创建和维护进程或线程,免去了线程或进程的切换带来的开销。而操作系统支持IO多路复用的系统调用有select,poll和epoll。

select

先来看看select的函数声明:

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

fd_set是表示文件描述符集合的数据结构。readfds,writefds和exceptfds分别对应三类文件描述符集。当select被调用时,内部逻辑如下:

  1. 将3个fd集copy到内核,这里限制了fd最大数量为1024
  2. 线程阻塞,直到超时或内核检测到有fd可读或可写,内核会通知监控者select,select返回可读或可写的fd总数
  3. 那么用户进程如何找到可读可写的fd呢?select会将之前传递给内核的fd集从内核copy到用户进程。用户进程通过遍历的方式找到可读可写的fd。

缺点:

  1. copy次数过多,而且每次调用select方法都要进行fd集的copy操作
  2. select监控fd数量有限
  3. 用户进程通过遍历的方式找到可读写的fd,时间复杂度为o(n),IO效率随着fd数量增多而线性下降

poll

先来看看poll的函数声明:

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

pollfd是表示文件描述符集合的数据结构。

struct pollfd 
    int fd; //文件描述符
    short events; //监视的请求事件
    short revents; //已发生的事件
;

poll与select差不多,但poll的pollfd没有最大数量的限制,可是IO效率依旧没有提升orz。

epoll

select/poll都只有一个方法,而epoll的操作过程有3个方法,分别是epoll_create()epoll_ctl()epoll_wait()

epoll_create()

int epoll_create(int size);//用于创建一个epoll的句柄,size是指监听的描述符个数。

该方法会在内核创建专属于epoll的高速cache区,并在该缓冲区建立红黑树和就绪链表,用户态传入的文件句柄将被放到红黑树中。

epoll_ctl()

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

该方法对epoll_create()所创建的内核cache区进行操作的,操作对象是需要监听的fd。

比如,把要监听的fd注册到cache内,那么epoll_ctl()会将fd插入到红黑树中,并向内核注册了该fd的回调函数。内核在检测到某fd可读可写时则调用该回调函数,而回调函数的工作是将fd放到就绪链表。

epoll_wait()

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);  

epoll_wait只需监控就绪链表,如果就绪链表有fd,则表示该fd可读可写,并返回给用户态(少量的copy);

该函数返回需要处理的事件数目,如返回0表示已超时。

小结

执行epoll_create时,在创建了红黑树和就绪链表。执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树上,然后向内核注册回调函数,用于当中断事件到来时向准备就绪链表中插入数据。执行epoll_wait时返回就绪链表里的数据即可。

因此,epoll比select和poll高效的原因是:

  1. 减少了用户态和内核态之间文件句柄的copy
  2. 降低了在文件句柄集中查找的时间复杂度。用红黑树维护fd集,可以将查找fd的时间复杂度降为o(logn)。

参考

以上是关于windows消息机制是怎么一回事?谢谢!的主要内容,如果未能解决你的问题,请参考以下文章

Android 异步消息处理机制前篇:深入理解Message消息池

windows消息处理机制的消息内容

windows下的MFC消息映射机制

windows下的MFC消息映射机制

windows消息机制

windows程序消息循环机制