注意:以下是个人看很多文章,加上自己的理解,如有不对请指正
我们所熟知的window
,linux
,macOs
被称为操作系统,我们所熟知的微信
,支付宝
等软件叫做程序
,运行在操作系统上。一个操作系统上可能运行着上千个程序
。但是我们的操作系统只有一个,那么操作系统是怎么管理这些程序
呢?
我们先想明白一件事情:
我们写的代码到底是怎么在计算机上跑起来的?
我们都知道,计算机只能识别0
或者1
这种二进制数据,代码执行的过程也就是计算机读取二进制指令
并分析指令以及执行的的过程,那么我们编写的代码中有for
,while
,if
这些语句,对于计算机来说他们根本是不可能识别出来这些关键字的。所以我们目前所使用的语言被称为人类语言
,人类语言到机器语言需要经过一系列的翻译操作。
- 有的编程语言要求必须提前将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序(Windows 下的 .exe),比如C语言、C++、Golang、Pascal(Delphi)、汇编等,这种编程语言称为编译型语言,使用的转换工具称为编译器。
- 有的编程语言可以一边执行一边转换,需要哪些源代码就转换哪些源代码,不会生成可执行程序,比如 Python、JavaScript、PHP、Shell、MATLAB 等,这种编程语言称为解释型语言,使用的转换工具称为解释器。
在网上盗了一张图:
引入进程的原因
从上面我们可看出:经过一波操作后,就会将人类语言
转化为机器语言
。那么这个时候就有问题了,比如我开发微信
的人,定义了一系列变量,我开发支付宝
的人,也定义了一系列变量。那么如果我们不对这些变量作区分限制的话,就有可能会存在命名冲突的问题。在一些高级语言里,为了避免命名冲突的问题引入了namespace
字段。那么在操作系统中该如何区分呢?
这是引入进程的原因之一。这也就是经常被大家所说的:进程是资源分配到最小单位。可以给每个进程分配不同的内存空间,作用就与高级语言的namespace
功能类似。
在上文中,我们简单提到了指令
这个东西。程序运行的过程就是读取指令的过程。但是在读取到某些指令的时候,比如:IO
操作,在读取到指令后解析是个IO操作,然后计算机执行这个操作,可能这个操作写入磁盘的时间有一个小时之久,那么在这个时间之内,cpu已经不读取指令了。那么这个时候cpu完全是处于空闲状态,要是在没有进程的时候,只能等他执行完毕之后,在继续读取指令。没有进程,我所有的指令都是在一个大锅饭里。如果跳过的话,我就不知道到底是哪一个指令的时候,我跳过了它,执行到后面去了(就算是用临时变量存起来,你也不知道在他之后的是不是已经执行过了)
。
但是有了进程之后,当我遇到某个进程进行IO操作或者其他已经不再读取指令操作的时候,我就可以将他挂起,等到他IO操作完成或者其他已经不再读取指令操作的时候,等待下一次cpu空闲时间,在切回到他的上下文,继续从挂起的那个状态之后继续读取。就可以保证不浪费cpu的空闲时间。
因此经常会说:进程是资源分配的最小单位
引入线程的原因
线程是什么?要理解这个概念,需要先了解一下操作系统的一些相关概念。大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”
,这也就是我们所说的并发(别觉得并发有多高深,它的实现很复杂,但它的概念很简单,就是一句话:多个任务同时执行)。多任务运行过程的示意图如下
程序之所以能运行,就是因为cpu在不断的从指令寄存器IR中取指令取执行。如果直接用进程操作的话:在当前进程还未进入到cpu上执行的时候,也就是处于就绪状态的时候。这个时候,他的指令是需要存储到一个存储在自身的私有堆栈上。当上一个进程被挂起,执行当前进程的时候,会将上一个进程的指令存储到他自身的私有堆栈上,然后将我当前进程的指令写入到指令寄存器IR中,然后将控制器PC指向上次暂停的地方,继续读取指令。
一个程序要想运行起来,就必须经过解释或者编译转为机器语言,假如一个程序的代码量非常大,都直接从进程中写入指令寄存器IR的话,或者说挂起的时候,将指令寄存器IR上的指令暂存到进程的私有堆栈上,是非常费时间,并且处理了一些比不必要的操作。我完全可以只保留一个我当前这个代码块的执行的函数的指令
。
举个例子:
function a() {
console.log('123');
}
function b() {
console.log('456');
};
a();
可以看出,如果生成机器语言的话,肯定是有函数a和函数b的指令信息的,但是呢,我只执行了函数a,因此我在切换的时候,只需要将a的指令写入到指令寄存器中去。这样的话,在进行上下文切换的时候,需要暂存或者写入的操作少了很多。
图片引自:[以操作系统的角度述说线程与进程]
因此就出现了线程,线程是CPU调度的最小单位。