02 September 2013

不容被忽视的第一章 温故而知新

很惭愧,在学习这一章的时候,我并没有温故的感觉,所有的仅仅只是知新。

第一次在翻阅这本书的时候,选择性的将第一章跳过了。后来回头看看,还是要捡起来。因为基础有的时候并不想我们想想的那么简单,后面往往隐含很多的容易忽略掉的点。

先说说开始的九个问题,对于那九个问题的解释我准备在另一篇POST中详细的记录我在学习过程中对这几个问题的不断变化的认知。这里我不想再啰嗦。总之,这九个问题应该是围绕这本书的主线,如果学习的时候能够围绕这几个问题有目的的学,可以更容易的记忆一些知识。

我要说的有几件事情,在学习第一章的过程中是我印象特别的深刻的。

首先是那一句的名言 :

“计算机科学领域的任何问题都可以通过增加一个简介的中间层来解决。”

“Any problem in computer science can be solved by another layer of indirection.”

第一次看到这句话就是在这本书看到的。有一种惊醒的感觉,没错!作为程序员的我好像一直不停的在造各种各样的中间层用来解决问题。我们也是学习各种各样的中间层来解决问题。比如,我们学习STL来解决各种各样复杂数据结构和算法的问题,STL不就是你和复杂数据结构之间的中间层? 当你需要隔离你代码的实现和接口的时候,通常需要一个接口类充当中间层。当处理多种类似的抽象类型的时候,你通常需要建立一个公用的基类来充当这些不同种派生类和调用者之间的中间层。 封装出来的函数是调用代码与实现代码之间的中间层。C库是程序代码和操作系统API之间的中间层。甚至语言和代码就是程序员和计算机之间的中间层。多么熟悉的中间层啊

这个话可没单单指的是计算机领域的软件问题哟~ 硬件也是一样的。CPU为了能够更好,更高效的调度外设,因此计算机硬件的设计者在IO设备与告诉的CPU之间加入了中间层(控制器和总线)。为了不让程序直接与硬件时序打交道,设计了操作系统和驱动程序来充当中间层。 为了更好的隔离和管理内存,在程序和物理内存之间增加虚拟的内存控件作为中间层。为了隔离磁盘复杂的寻址算法,增加了LBA逻辑扇区地址作为中间层。你会发现,当人们在计算机领域为了解决一个复杂的问题,往往通过增加一个中间层。而且第一章关于计算机基础知识的描述也是围绕这这个问题来的。

关于内存地址的隔离

以前我老是闹不明白,为什么VC调试的时候,同一个程序在不同次的调试过程中,函数地址和变量地址大多数情况都一样呢?比如一个变量A(不是堆上的),这次它是0X12345678, 下次它的地址还是这个值。这回可以解释了吧?因为程序的地址空间是独立的。在程序编译生成了以后,装载进内存以后我们假设他的布局是一样的。也就是每条指令和数据的位置和顺序都是一样的。 因此他在虚拟内存空间中的地址也就是一样的。但是它在实际的物理内存中的地址可不一定是一样的。

VC调试窗口中的WATCH和memory给大家的印象深刻吧?当你在那个窗口中,对一个变量取地址,或者观察一个指针的值的时候,你得到的32位无符号整数诸如0X12345678,这是一个虚拟地址空间的值,它代表你当前的这个变量的地址位于程序0x0后面虚拟地址空间的偏移量。而这个值在每个进程的虚拟内存空间里都有一份,而且是唯一的。

为什么一个指针在不同的namespace ,不同的函数,不同的obj都可以指向同一个值?原因就是因为指针的值标识的就是这个偏移,而这个值对应的内存在进程空间内是unique的。

那么如果我有两个helloworld程序,同时访问一个同样的内存地址,会出现什么情况?

答,各改各的。因为是不同的虚拟地址空间。至于这个值在寻址的时候到底对应哪一个内存空间,实际又映射的是那块物理内存,这复杂的工作交给内核来完成了。

关于更详细的内存装载和页切换的过程将在第六章详细的学习。

关于多线程的TASK

这里说两句题外话,学习过程中从thread的调度讨论到我们经常使用的TASK。谈论darwin的TASK机制的好处,总结一下同事给我们做的培训。

使用TASK有什么好处么?或者说TASK存在的意义是是什么?可以用一个问题来引出,就是M个任务需要用N个thread来玩儿,怎么玩儿?

假设你只有N个线程,不能创建你的线程和任务之间的一对一的关系。对于服务器程序来说,通常要处理成百上千个并发任务,但是却又不能创建成百上千个对应的线程,怎么办呢?于是想出了TASK来封装thread的功能。

对于执行TASK的实体thread来说,在TASK的代码中被封装成了taskthread,一个task可以运行于多个taskthread,也可以绑定一个taskthread。然后以队列承载even,以堆来承载循环事件。一个taskthread在执行了一个TASK的任务以后,不停的从队列和堆中把新的需要执行的任务取出来,然后执行。做到TASK和thread之间的隔离。

对于Signal,是TASK机制的另一个重要部分。他只有两个任务:

  • 1 修改TASK的位与标志,改变TASK的状态。
  • 2 将TASK添加到队列中,等待taskthread的轮训。

疑惑 关于内核线程和用户线程

看了1.6.3以后,虽然认识了线程的模型,但是对什么是内核线程,什么是用户线程?WIN上和linux上如何创建一个内核线程都有很大的疑惑。但是我知道去哪里寻找答案。龙书。现在并不准备看,以后有机会一定会学。