对于进程有两个幻觉:一认为自己独享内存;二以为自己独享处理器。我们对于一台机器上的多个进程的幻觉是感觉他们是同时运行。
我们来依次解释下上面的三个幻觉:
关于独享内存不是我们的重点,简单说说。独享内存是指我们每个进程都独享虚拟内存。而虚拟内存地址最终是通过MMU翻译成实际的物理地址。这样做只是为了提供一种逻辑上的连续性,屏蔽内存碎片或是规避因内存有限而扩展到硬盘的各种问题,这样不用考虑实际的的限制从而使应用程序开发变得容易。还有一个值得注意的问题是在这个虚拟内存中如果这个进程是多线程的,那么将共享改空间,除了各自的堆栈、寄存器和所谓的虚拟处理器。这样会导致一个问题就是多个线程的stacksize对进程栈空间的要求呈线性增长,与复杂的多层级递归运算类似,导致stackoverflow。这也是好多语言比如Java的线程模型要求线程创建时指定好stacksize大小的原因。
以前是单处理器的机器,后来因为通过单纯的提高处理器主频,已经无法明显提升系统整体性能。实在没办法了,科学家们就想啊想啊,就相出了多核处理器。这样的话处理线程级的多任务,就可以实现真正的并行了。但问题是处理器的核数远小于需要并行的任务数量。有许多因素都客观限制处理器核数。那要完成多个进程同时执行的幻觉就只能通过来回的轮番执行,快速切换。这就到上下文切换的话题了。
关于上下文切换我仅仅参考linux内核的实现从技术角度来解释:
在linux中一个叫做task_struct结构体代表一个线程,linux调度器会对一个结构体:sched_entity结构体感兴趣并对其进行调度,而它正好嵌入到task_struct中。因此对可以看出linux调度是线程级的。那具体怎么调度呢?
Linux用红黑树存所有可运行的进程(注意是可运行),使用等待队列wait_queue记录休眠(被阻塞)线程。用一个例子来介绍调度和上下文切换的细节,例如网卡产生一个中断通知有网络数据,执行中的线程阻塞(从执行状态剥离并放入等待队列),然后再到红黑树里面选一个来执行。这个过程的详细过程是:虚拟内存映射和处理器状态均要切换到新线程,前一个线程寄存器、栈信息还有其他状态信息被保存。而新线程的栈信息和寄存器信息被恢复,刚好是反操作。我们把上述过程叫做上下文切换。等到网络数据读取就绪,在等待队列中的线程又被唤醒,接着放入红黑树中,成为可执行态,等待被执行。
多处理器就是一台机器具有多个处理器。他的主流架构叫做对称多处理器(SMP),这些处理器共享内存,共用一个系统,程序可以并行执行在每一个处理器上。拿多核处理器来说,通过一个核心执行一个线程,操作系统通过指令分派让一个核心负责一个程序执行,达到真正意义上的并行。目前的手机尤其是android手机通过添加核心数来提升运行速度。这确实可以得到提高。但是在软件角度还受到几方面限制:一是调度算法针对核心数优化,以充分利用多核优势;二是程序的并行性,如果程序是单线程再多核同时也只能跑在一个上面,其他的却白白浪费;还有就是,增加核心数和处理能力并非成线性关系。