大家注意,在这个部分比较抽象,但是,这个部分是非常重要的一个,对于以后我们接触更好更新的技术会打下一个非常好的基础。
好了,为什么说进程或着说调度进程是那么的重要呢。以下内容参考了《Linux内核设计与实现》这本书,特此感谢本书作者。
进程 (Process)
所谓进程,就是处于执行状态下的程序。但是,大家可不要以为进程当中加载只有程序的代码段(text),编写过程序的朋友一定知道,所谓程序是由指令加数据组成的。依据我学习开发时的经验,我认为一般的程序,相对于内存占有率来说,一个程序的数据往往是指令的数个数量级。不知道这样描述是不是非常精确,如果有错误,请尽快联系我,不要让我误人子弟。
比如说一个当我编写一个程序,想要打开一个文件,然后拿一行数据出来,然后保存到另外一个文件中。指令段(text session),通俗点讲,就是你程序中,包含逻辑关系的代码,我们抛弃不说是不是系统调用那个函数或者需要加载系统库等等情况的前提下,代码段只有区区的几行。但是从内容方面看,我需要加载一个文件到内存中,然后创建一个文件到内存中。一个文本文件可能不足以说明这个问题,万一你要调用的是一个长达两个多小时的视频文件呢,哈哈,恭喜你中奖了,内存需要加载这么一个文件然后听你的指令进行下一步的动作。其中差距可想而知。
直观一点,还是视频吧。你在的桌面电脑上打开一个电影,在一些配置较低的PC上,为什么会时间那么长才能打开呢。就是这个道理,难道是你打开的播放器让他运行的这么慢么(要不就是”打开方式不对”),不是的。
说点别的,只是听人说的有这么个技术(应该是09年在青岛上大学的时候听说的,当时还是一个电脑白痴,虽然现在也不怎么精神),有一种东西叫做迅盘,这个东西相当于你把你的内存空间抽取出一块然后模拟成硬盘一样块设备,让你可以想其他分区一样,可以完成一样的新建编辑修改文件。有人就用这种方法存放一些需要加速处理的文件,让这一段内存专属于你所存放的内容,然后当我们使用它时就会流畅很多。这样就减少了操作系统对于这个文件的页面调度,就不用让资源,一遍一遍在磁盘和内存中来回转移。好吧,又扯远了。我们继续回到正题中。
我们现在知道了,进程有很多资源,这些资源都要加载到我们的内存当中。所以我们可以这么说,进程是处于执行其的程序以及相关的资源的一个总称。而我们操作系统不就是用来运行用户程序的吗?所以说,进程管理就是几乎所有OS的心脏所在。
线程(Tread)
线程总的来说,就是一个进程的子单元,也就是说一个进程可能有若干个线程。线程可以认为是进程中活动的对象。
其实,内核调度的对象是线程而不是进程。这句话说完,我发现又给自己挖了一个坑。我需要说一下,我们从OS运行机制中,体会出来的一点思想了。当我们去完成一个大任务的时候,我们总是倾向于将这个大任务分割成很多的小任务。不管是在计算机领域中,还是在现实社会中都有很多的实例让我们来比较。
其实说到思想,我推荐大家去看一本书。如果说,大家学习了很多计算机相关的知识,或者说对于哲学方面有着很高的兴趣的话。大家可以去看一下《Linux设计哲学》(应该是这个名字吧),在这本书中,我们可以学到很多的关于Linux设计者在设计OS的时候所遵循的哲学思想。这些思想,可以说是我们在解决一个问题时的一种思维方式的展示。
好的我们回到我们的线程上来,为什么我们在设计solution的时候,我们更加倾向于分割呢,因为当我们,分割出来,变成更小的对象的时候,可以极大的简便或者说减轻调度的复杂度。在计算机技术中,这个非常常见,比如说我们可以将一个门户网站的不同类别进行拆分。比方说有一个门户网站,我们可以将其中新闻专栏的请求分配给server news,将论坛部分分配给server forum。这样既可以减少服务的压力,而且还可以减少整个系统的耦合度。也就是论坛服务器挂了,我们新闻服务器是可以正常接受请求的。不至于一锅端,鸡蛋不能放到同一个篮子里边不是么?另外的好处也说了,便于调度,据一个例子,宜家大家都知道吧。为什么这种可拆卸的家具会这么受欢迎,道理可想而知。
说一个在法国的经历。知道法国年轻人为什么那么喜欢可拆卸的家具么?因为搬家方便呀,法国年轻人一般都是租房子住的,即便是结婚的情侣。这一点,呵呵,你懂得,岳母大人,这段好好看吧。随着工作地址的变换,他们会经常搬家,这个时候就造成的了一个问题,就是他们租住的房子,一般都是其年龄的三倍以上,像是我在ANGERS住的时候,我租的房子是1940年盖的,电梯你说有没有呢,我笑了。最糟糕的还不是这个,电梯没有也就算了,楼梯呀,还是半双工。(希望你还没有我IO复用说的)所以,你说搬个沙发上楼,我又笑了。可以可拆卸的家具就非常的灵活,拆了之后搬上去,爽。
现在大家明白为什么内核设计者,在设计内核的时候为什么要这么复杂的层层分级,分拆任务,尽可能的让内核调度一个更加轻量的东西了吧。同样,在内存调度中,我们也是采用了这种思想来调度内存资源的。这个以后用到的时候,我会再写一博客,同大家一起学习。
在这本书上(《内核设计与实现》)中还提到了,内核的虚拟机制,既然说了,我觉得我有必要复述一下。内核的虚拟机制有两个,一个是虚拟CPU,一个是虚拟内存。现实中,一个CPU简单来讲,在某一段时间内只能运行一个线程,也就是我们说的分时复用。但是虚拟CPU机制会给进程(因为Unix上边,早期是没有线程的概念的,也就是说,一个进程就是一个线程。就算是现在引入可线程这个对象,我们也可以认为它只是一个特殊的进程而已。所以我不加区分的使用这两个词。)一种假象,让这些进程觉得自己是在独享处理器。同理,虚拟内存则是让进程在分配和管理内存时觉得拥有整体系统的所有内存。这个可能有人会反驳我,好吧,我说一下,并不是所有的内存。一个基于32bit开发的软件中,系统最大内存为4g,这个软件能够使用的内存峰值是3g,因为有1g要预留给kernel使用。其实内核的虚拟化还不止如此,内核也是一个应用程序,只不过他是最靠近硬件的应用程序。但是内核和其他工作在用户空间中的不同的是他拥有系统的最高控制权。我对于内核的理解是,他是将计算机中电子元件虚拟化成人类可以识别的或者说便于调用的方式,另外还提供了能让用户可以轻松调用硬件的接口程序,这样有效的提高程序员的开发效率,程序员可以利用这些接口就可以轻松调用想要是用的硬件。好了,差不多了,最后说一句问一下大家,你说这种虚拟机制又是在什么哲学思想上解决的呢?
拓展一下知识,下面是我从《内核设计与实现》3.2.3中摘录的关于进程状态的内容:
每一个进程都必须在下面五种进程转带中的一种状态下存在
1 TASK_RUNNING
运行状态,也就是说进程是可执行的,他或者中在进行,或者是在运行队列中等待执行。这是进程在用户空间中执行的唯一可能的状态,这种状态也可以应用带内核空间中正在执行的进程。
2 TASK_INTERRUPTIBLE
可中断状态,进程正在休眠(也可以说他是在被阻塞中),等待某些条件的达成。一旦这些条件达成,内核就会吧进程状态设置为运行。处于此状态的进程也会因为接受到信号而提前被唤醒并随时准备投入运行。
3 TASK_UNINTERRUPTIBLE
不可中断状态,除了就算是接受到信号也不会被唤醒或者准备投入运行外,这个状态与可终端状态相同。这个状态通常是进程必须在等待是不受干扰或这个等待事件很快就会发生时出现。由于处于这个状态的任务对信号不做响应,所以较之可中断状态,使用的很少。
4 __TASK_TRACED
被其他进程追踪状态,例如通过ptrace对调试程序进行跟踪。
5 __TASK_STOPPED
停止状态,进程停止执行,进程没有投入运行也不能投入运行。通常这种状态发生在接受到SIGSTOP, SIGTSTP, SIGTTIN,SIGTTOU等信号的时候。此外,在调试期间接受到任何信号,都会是进程进入这种状态。
了解了进程我们开始正式讲一讲内核调用进程的机制吧。
说到内核调度进程,我们必须先清楚,内核调度进程实际上是依赖内核中的一个内核调度程序来运行。
他的基本过程是,如果说有一个进程A,在某一个时间内,内核的调度程序认为现在需要将A运行。首先,kernel会首先唤醒A,然后将CPU上的运行指针指向A的内存加载位置,(也就是让内核找到A的位置),然后调度程序退出,转变为睡眠状态。之后,当A执行若干时间(还记得CPU时间复用吗?这个就是kernel对其指定的允许运行时间),然后内核醒来,将A转入睡眠状态。最后,进程调度程序使用相应的算法,算出下一个需要在CPU上执行的进程,然后循环往复。说这个目的就是让大家知道,进程切换会消耗CPU的时钟周期,这些时钟周期浪费在了进程调度程序的执行上面。
之后的博客中,你们会在I/O复用中,使用到上述的知识。