目录
在讲解进程之前,我们需要对操作系统有一个初步的认识,因为操作系统本质上就是一个管理软件,比如内存管理,文件管理和我们今天要讲的进程管理。
首先我们需要对计算机系统的分层有一个基本认识:
由上层到下层我们可以看到逐渐向硬件靠拢,而我们的软件也就是通过这样一层层的调用通过操作系统最终实现与硬件的交互。由此我们也可以总结出操作系统的主要作用:
1.对下管理各种硬件设备
2.对上给各种软件提供稳定的运行环境
每个应用程序运行于现代操作系统之上时,操作系统会提供一种抽象,好像系统上只有这个程序在运 行,所有的硬件资源都被这个程序在使用。这种假象是通过抽象了一个进程的概念来完成的,进程可以 说是计算机科学中最重要和最成功的概念之一。
进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程; 同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
最简单的一个例子就是当我们双击运行QQ.exe这个可执行文件时QQ的进程就已经在我们后台运行了。
操作系统对进程的管理我们可以简单理解为以下两个部分
1.先描述:使用一个类/结构体把这个东西的所有特征表示出来(因为操作系统是c语言编写的所以会提到结构体这个概念)
2.再组织:使用一个数据结构将很多个这样的对象/结构体整合在一起
注意:我们在1中提到的结构体/类其实就是进程控制块抽象(PCB Process Control Block),以下我们简称PCB。
计算机内部要管理任何现实事物,都需要将其抽象成一组有关联的、互为一体的数据。在 Java 语言中, 我们可以通过类/对象来描述这一特征。
- // 以下代码是 Java 代码的伪码形式,重在说明,无法直接运行
- class PCB {
- // 进程的唯一标识 —— pid;
- // 进程关联的程序信息,例如哪个程序,加载到内存中的区域等
- // 分配给该资源使用的各个资源
- // 进度调度信息
- }
这样,每一个 PCB 对象,就代表着一个实实在在运行着的程序,也就是进程。
操作系统再通过这种数据结构,例如线性表、搜索树等将 PCB 对象组织起来,方便管理时进行增删查改 的操作。
当然这里我们还是要再提一下PCB里面包含了什么,以免有些同学没注意:
1: pid——进程的唯一标识 ;
2: 内存指针——进程关联的程序信息,例如哪个程序,加载到内存中的区域等
3 :文件描述符表——分配给该资源使用的各个资源4 :进度调度信息(下面详细讲)
目前民用cpu大多已经达到6核12线程或者8核16线程,但是我们一个操作系统中往往有几十甚至上百个进程,很直观的感受就是僧多粥少。所以此时就需要操作系统来进行进程调度。这里可以类比我们高铁的调度。
在讲进程调度之前我们先了解一下cpu的时钟周期,所谓1时钟周期就可粗略代表cpu可在1s内执行一条指令,而如今家用cpu的时钟周期(主频)已经达到了GHz级别,也就是说他能在1s内执行几十亿条指令,虽然在我们人的直观感受上看好像这些进程是在同时执行的,但实际上它是通过操作系统的调度实现的,它可以在极短时间内让cpu的核心去执行一个进程,而在下一刻又去执行另一个进程,由于这个时间极短,所以我们宏观上认为它是同时执行的,而这种执行方式也称作并发式执行。
进程调度主要有以下几个部分构成:
1.进程的优先级:字面意思,就是我们优先安排哪个进程运行
2.进程状态:进程最典型的有两种状态,一种是就绪状态,此时进程随时可以上cpu执行,另一种是阻塞状态,此时进程在等待某个任务完成,只有等待任务完成后才可以上cpu执行
3.进程的记账信息:操作系统在安排进程的时候,也会记录每个进程在cpu上的运行时间,如果发现某个进程被安排太少就会适当调整策略
4.进程的上下文:这里的上下文具体指的是cpu里面一堆寄存器的值,当进程从cpu切出时就会把寄存器中的数据保存到PCB(内存),等到进程再次被调用时cpu就把PCB里的上下文重新读取入寄存器内(这里可以想象成游戏内的存档与读档)
进程需要使用到系统的内存资源,每个进程都有一个指针指向自己的内存地址,既然又指针就会存在解引用操作,就有可能会出现空指针异常或者出现指针指向了别的进程的内存里。所以为了各个进程之间不会互相干扰,操作系统就引入了“进程虚拟空间地址 ”的概念,保证每个进程只能访问自己的地址空间,就算指针出现问题也会被限制在进程内部。可以看到下图:
这大概就是进程向操作系统申请内存资源的过程,这里我们假设进程3的指针指向发生了错误,而此时要先经过MMU进行地址映射才能拿到内存资源,但是此时MMU发现访问的内存地址有问题,便会告知操作系统,而此时操作系统便会发出”信号“告诉进程你的内存访问出错。而这个”信号“通常为终止该进程,即会导致程序崩溃。
虽然每一个进程都有一个进程虚拟空间地址,并且一个系统有非常多的进程,但是我们不需要担心虚拟空间地址内存占用过大,因为同一时刻执行的进程不多,即使某一时刻有多个进程同时运行,但这些进程也不是同时把所有虚拟地址的空间都用上了。只有极少数可能会导致物理内存不足。
一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行 着多份代码。一个进程内可以同时拥有多个线程,同时线程也可以看作是一个轻量级的进程。
其实理论上线程能完成的操作进程也能完成,并发编程进程也能够做到。但是我们想象一个场景,一台服务器需要同时为多个客户端同时提供服务,而这期间就存在大量的创建/销毁进程的操作,而这一过程又涉及到分配系统资源这一比较耗时的操作,所以线程的出现解决了这一问题。
虽然多进程也能实现并发编程, 但是线程比进程更轻量.
1.创建线程比创建进程更快.
2.销毁线程比销毁进程更快.
3.调度线程比调度进程更快.
线程包含在进程中,每个线程也有着自己独立的PCB,但是同一个进程多个线程间他们的系统资源是共享的,所以才有了线程的轻量级优势。
我们用一个例子简单让大家理解一下进程与线程的区别:
比如你的面前有100只鸡,而你一个人吃不完。
planA:你选择在隔壁房子加了一张桌子,分了50只鸡过去,又加了一个人来吃。(见不到双方,有隔离性)
planB:你直接在这间房子里又邀请了好几个小伙伴来帮你吃鸡(见得到双方,无隔离性)
很明显我们看到planB只是新增了几个人,而planA则是又多了一栋房子和一张桌子,消耗资源多,而planA就是多进程方案,planB是多线程方案,这个例子可以帮助我们快速理解他们的关系。
当然多线程技术确实能够提高效率,但前提是多核资源是充分的,否则再多的线程也无济于事,因为cpu处理不过来。