首先我们需要知道的是,当我们在使用标准库的函数去读写文件时,我们所操作的地址并不是这些文件的物理地址(也就是实际上在硬盘上的地址),而是操作系统给进程分配的虚拟内存地址。
为什么会这样呢?
首先,这是操作系统为了防止硬件被某些恶意的程序滥用。其次实际上外存设备的种类十分的复杂繁多,虚拟内存空间也是在为应用程序提供简单统一可操作的空间。
我们的用户界面实际上只是任务管理器中可以看到的一个名为 explorer.exe 的进程,内核才是操作系统的核心。系统上的其他部分都必须依靠内核才能提供服务。对于提供保护机制的现代系统来说,内核独立于普通的应用程序,它一般处于系统态,拥有受保护的内存空间和访问硬件设备的所有权限。这种系统态和被保护起来的内存空间,统称为内核空间。
相应的,我们在使用标准库的函数去读写文件时,实际上是通过标准库去调用内核的方法获取到一个文件的映射内存,然后再通过这个映射的虚拟内存去操作硬件上的文件。
windows操作系统中的每个进程都有分配 4GB 大小的虚拟进程地址空间。32位操作系统将 4GB 的物理内存空间划分为两个部分,进程使用 2G ,一般是被称作用户空间,内核使用 2G ,称为内核空间。一个进程在被创建的时候如果内存不够的话,自然就是靠虚拟内存了,其空间跨度包括三个部分:处理器部分(CPU)<-- 主存储器部分(内存)<-- I/O设备(如硬盘)
如果你的内存只有 2GB 的话,那么对于一个进程而言,它的 4GB 虚拟内存空间多是在IO设备中虚拟的。
好了,话题绕回来。如果你能理解上述的保护机制,那么你也大概理解了内核在操作系统中是怎么样一个角色,那么我们接下来开始谈谈句柄。
句柄是 windows 编程的一个基础概念。如果你有接触过进程ID的话,这个概念上有相近的地方,譬如进程ID就是系统中每一个进程的唯一标识。
而句柄的话则比进程ID更细致一些,它是运行在系统中的程序中的不同对象或者同类对象中的不同的实例。诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。
一个进程可以创建多个窗口,而每一个在 Windows 系统下被创建的窗口都有它的句柄(上一节说过的HWND类型,窗口的句柄)。当然不止是窗口,一个窗口中可能有着更多的实例,或者叫做控件会比较好理解,比如一个QQ的登录窗口有一个HWND(Handle of window),在这个窗口中有可以输入QQ账号和密码的文本框,这些具体的控件也都有着它对应的句柄。OK,介绍到这里,相信大家已经有了一定的概念了。
那么需要注意的是当一个进程被初始化时,系统就会为它分配一个句柄表。大家可以结合上面说的例子理解一下句柄表这个概念。这个句柄表只用于内核对象。作为一个称职的 Windows 程序员,必须懂得这些基本信息。
要在windows 上做开发的话,调用系统的API实际上才是速度最快的办法。我们在C的基础中有学过文件操作,还有一些简单的内存操作。可是如果你看了上面的内核介绍之后,应该会知道实际上操作系统是会提防着每个进程,作为一个程序是无法直接访问到内存和硬件的。
也就是说,如果我要进行文件操作或者内存操作的话,最后都是要通过内核来提供相应的功能来实现的。这也意味着,同样的读写文件的程序,如果一个是标准库的函数写的,一个是用系统API写的,那么系统API写的程序绝对要比用标准库的快。
原因很简单,因为标准库要访问硬件(文件)最后还是要低声下气的去找内核,不然的话就没办法访问到。于是很简单的能看出来两者的优劣,一个是绕着弯子去调用内核的API访问,一个是直接调用内核的API去访问,自然是后者的速度和效率更高。
事实上,我们所熟悉的 malloc() 内存分配函数,到了 windows 系统上最后还是要调用 windows API 中的 HeapAlloc() 函数。人总是追求速度和效率的,所以这也是为什么我们在 windows 上开发需要学习使用 windows API 的原因。