0%

进程,是指运行中的程序,也称为作业。

早期的计算机,只允许运行 一个 程序,这个程序被分配了计算机所能提供的所有资源。
现代的计算机,允许并行运行 多个 程序,系统资源也就被这些程序瓜分。
每一个程序也就成为了一个 工作单元,将这些 工作单元 称呼为:进程
因此,进程 也是计算机系统分配资源的基本单位。

程序,是静态的代码,而进程是动态的实体。

程序,也就是 代码,在硬盘中存放着,经过 主存储器(内存) 的加载,被封装成 PCB 数据结构,在 内核队列 中排队,等待 CPU调用 执行。相同的一份代码,可以被多次加载,形成多个独立的进程,对于系统来说,不过是队列中多了一个成员,当然,资源也是需要被瓜分的。

操作系统通过进程控制块PCB来表示进程。  

PCB

进程控制块,也就是PCB,是操作系统内部的一种用来表示进程数据结构,记载着和进程相关的一些信息。
包括:内存指针、进程状态、进程号、程序计数器、寄存器、内存限制说明、I/O状态信息等。

内存指针,进程内存数据相关的指针。
进程状态,包含:新建、就绪、运行、阻塞、停止。
进程号,用来标识唯一进程的标识符,也就是PID。
程序计数器,用来标识程序要执行的下一条指令的地址。
寄存器,用于发生CPU中断时临时存储信息。
内存限制说明,包含了内存管理系统的一些信息,比如:页表、段表。
I/O状态信息,包含了进程打开的文件列表及分配给进程的I/O设备。

进程的一生都在队列之间徘徊,一旦被CPU调用,那么就出队列,改变状态,读取PCB中记录的信息,恢复现场(也就是CPU上下文切换),相同状态的PCB会形成链表,从程序计数器记录的地址开始执行,直到时间片用完或者被中断,回到队列之中或者执行结束,释放资源。

如果,所有的进程都是CPU繁忙型,那么等待队列(CPU都在忙着)几乎都是空的。
如果,所有的进程都是I/O繁忙型,那么就绪队列(CPU都在闲着)几乎都是空的。

那么,就需要合理分配进程组合,避免内存需求过多或者设备过度空闲。

一个进程,在运行期间可以创建多个子进程。

创建进程的进程称为父进程,通过fork来实现,而子进程也可以再创建子进程,形成一个进程树
创建进程是需要分配系统资源的,子进程所需要的资源可以直接从系统获取,也可以从父进程获取,父进程所拥有的数据也可以传送给子进程,父子进程也可以共享同样的资源。
子进程也不是无限制的创建的,需要受限于父进程,否则会负载过高。

进程之间,可以通过多种方式相互通信。

进程的通信可以分为:直接通信、间接通信。
具体的方式包含:管道、信号、消息队列、Socket、共享内存。

管道,一种半双工的通信方式,数据只能在父子进程之间单向流动。
信号,一种异步通信方式,通过监听、中断来实现,如:SIGINT。
消息队列,是一种保存在内核中的消息的链表,进程之间通过读写消息队列通信。
Socket,通过TCP、UDP协议进行通信。
共享内存,多个进程读取同一块共享的内存。

最后,进程是系统分配资源的基本单位,是执行中的程序,被封装为PCB数据结构,由CPU来进行调度,每次调度将会加载进程数据,切换上下文,通过此方式切换进程,成本也是过高。

因此,诞生了线程

线程,本质上是轻量级进程,且共享了进程所拥有的数据和资源。

线程,建立在进程的基础上,一个进程可以拥有多个线程,每一个线程共享进程的数据和资源,并且可以拥有自己独享的数据,是CPU执行的基本单位。通过线程这种方式,CPU无需进行复杂的进程上下文切换,只需要切换线程即可,内存数据在同一个进程内是共享的,相比切换进程来说,成本明显降低许多。

thread

一个线程,包含:代码、数据、打开文件列表、寄存区、程序计数器、堆栈。
多个线程之间,处于并行状态。

举例,在我们打开浏览器的时候,一个线程负责拉取数据,一个线程负责加载页面,一个线程负责显示图像等,这几个线程之间互不影响,换做进程的话,需要来回切换。

RPC的场景中,每来一个请求,就单独使用一个线程来处理,比起单线程来说,并行的方式显然提高了处理的效率。当然,线程也不是无限开辟的,可创建的线程数受限于所拥有的资源。虽然创建线程的成本比创建进程的成本低,但也不是没有成本的。为了降低这种成本,基于线程池,可以快速创建进程,充分利用资源。

线程的实现方式有:一对一、多对一、多对多。

线程分为用户线程内核线程,用户线程由内核线程来实现。

一对一模型情况下,一个用户线程由一个内核线程来实现。这种方式使得线程之间并行化,不会因为一个用户线程的阻塞导致其它用户线程的阻塞,但是资源消耗就比较大了。

多对一模型情况下,将多个用户线程映射到一个内核线程。这种方式使得线程管理变得方便了,但一次只能执行一个线程,无法并行化,如果一个用户线程阻塞,将导致所有由此内核线程实现的用户线程阻塞。

多对多模型情况下,多个用户线程映射到多个内核线程上,处于交叉状态,做到多路复用,一个内核线程阻塞的情况下,可以切换到其它的内核线程,避免了上面两种模型的缺点。

操作系统是被设计用来管理计算机硬件和应用程序的系统程序。

试想,一台计算机本身是一堆零件拼凑而成,而这些零件本身也具备了可编程能力,每一个应用程序要运行,必然要去操作调用这些硬件的接口,而多个应用程序之间各自以各自的方式去调用这些硬件,要这些应用程序开发商按照规矩去操作这些硬件,那是很困难的。

为了解决这个问题,操作系统就诞生了,将操作这些硬件的方式封装在操作系统当中,并给这些应用程序开发商提供统一的接口去调用,并管理控制这些应用程序能够有条不紊、无冲突地分配和使用系统资源。

操作系统

简单的说,操作系统相当于一个中介,帮助应用程序去调用硬件资源,同时也管理着应用程序。

操作系统是一个从始至终都运行在计算机中的程序,俗称内核

操作系统要正常运行,也需要底层硬件的支持,而硬件之间也要相互配合。

计算机底层的硬件包含:CPU、内存、磁盘、磁带、打印机等。

这些硬件通过一条公共总线连接到一块,程序指令及任务由CPU来负责调度,由内存和磁盘来存储资源,所有的硬件共同争抢总线资源,为了保证有序使用内存资源,由内存控制器来统一分配。程序只有被加载到内存当中,才能够被CPU执行。

早期计算机一次只能执行一个任务,为了提高处理速度,相似的任务会被分批执行。由于CPU的运行速度远远大于I/O设备的处理速度,为了使得CPU总有任务可以运行,不至于过于空闲,产生了作业系统,也就是多道程序设计,将作业放入底层作业队列中,由CPU空闲时从队列中取出任务然后执行。再后来,为了提高系统吞吐量,一个CPU已经无法满足需求了,一台计算机被植入了多个CPU,大大提高了计算机的处理能力。

系统总线

计算机从开机到启动操作系统需要经过一个初始化过程。

当点击开机按钮的时候,计算机通电,主板BIOS开始进行初始化固件操作,CPU开始运转。

计算机首先会进行一个自检操作,检查硬件是否正常,如果出现了异常,就发出声响或者关机、蓝屏、显示错误信息等。

自检通过以后,读取第一块磁盘的第一个扇区(主引导扇区),开始加载主引导记录MBR,计算机支持多系统的话,通常会有多个引导记录。引导记录是在磁盘格式化的时候写在磁盘上的。系统启动时,自动将它装入内存并用于加载操作系统的其它部分。

接着,启动Boot Loader 引导加载器,通常使用的是GRUB多操作系统启动程序。如果计算机安装了多个系统的话,可以在这个选择要进入的操作系统,同时会在这个阶段进行内存的初始化。

操作系统选择完毕之后,计算机的控制器就转移给了操作系统,操作系统的内核会被装载到系统内存之中,然后执行初始化操作。

初始化的时候,会调用系统底下的一个init方法,如:/sbin/init,执行后续的一些初始化及系统服务的启动,根据传入的参数,给用户展示的界面可以是命令行(通常是服务端),也可以是图形交互界面(通常是客户端)。

执行完以上操作,操作系统就启动了。

系统启动

应用层,建立在传输层的基础上,规定了应用程序的数据格式。

我们所使用的软件都在应用层上工作,每一个应用具有自己的数据格式,也就是要有共同方言。

只有规定好了数据格式,应用程序才可以和服务端正常交互,用户也才能正常使用这些应用程序。

这些数据格式,也被约定俗成为一些通用协议,也可以自定义协议。

比如:Email、HTTP、FTP都属于应用层协议。

应用程序通过实现这些协议,将应用层数据封装成协议规定的格式,然后由TCP或者UDP来进行传输。

七层数据

每一层对应的封装如下:

七层数据

常见的应用层协议有:DNS、HTTP。

DNS域名解析协议

IP地址,即使采用十进制点分法来标识,要记住也是比较困难的,因此产生了域名

域名,相比IP地址来说,会比较容易记住,如:www.baidu.com.

DNS协议是用来解析域名,获取真实IP地址的一种协议。

通过nslookup + 域名,再使用wireshark抓包,可以看到DNS查询的时候,采用的是UDP协议,而DNS服务器之间进行数据推送的时候,会采用TCP协议。

dns

HTTP超文本传输协议

HTTP协议,将数据以明文的方式传输,所有的www文件都采用这种方式。

浏览器为客户端,以TCP为底层传输方式。

通过浏览器向服务端发送请求,服务端向客户端浏览器返回响应。

http