4. 编程语言(Program Language)
本块内容主要是还原下我们已经熟悉的编程语言,即编程语言是如何和 CPU 指令对应起来的。
4.1 程序(Program)
所谓程序,就是一组指令以及这组指令要处理的数据。狭义上来说,程序对我们来说,通常表现为一组文件。
即程序 = 指令 + 指令要处理的数据
4.2 早期编程
最早的电脑,要进行编程,是真的需要用 0、1进行编程的, 下面图给大家展示了 Altair 8800 计算机,是最早的一批微型电脑。用户需要控制开关,一个一个 bit 的 将程序录入该电脑中。
如果要求计算机的用户都必须使用二进制编程,那大家都要疯掉了,这可是一件门槛太高的事情了。所以编程语言应运而生了。
4.3 编程语言发展
为了提升编程效率,最早创造了汇编语言的概念。其实汇编语言和机器语言(也就是指令)直接是完全一一对应的,只是相对于 0、1 这些数字,发明了一些帮助人类记忆和理解的符号将其对应起来,也就是我们上面看到的类似 LOAD_A、LOAD_B 等。程序员完成编程之后,需要使用汇编器(assembler)
将汇编语言翻译成机器语言。虽然汇编降低了程序员的记忆成本,但要求程序还是必须掌握计算机硬件的所有知识,而且随着计算机厂商越来越多,一次编写的程序往往只适用于一类计算机。
这个是远远不够的,所以更为高级的语言诞生了,高级语言屏蔽了硬件细节,让程序员可以站在更高的层面上思考自己的业务。
这里以 C 语言为例,程序员完成程序的编写之后,需要使用编译器(compiler)和连接器(linker)将程序翻译成汇编语言,再借助汇编器变成最终的机器语言。
借助封装的思想,我们学习编程变得越来越容易。不过有利则有弊,高度的抽象,导致很多的程序员把计算机视为一个黑箱,完全无法理解自己的程序是如何工作起来的,希望我们大家不要做这种程序员。
我们使用的 Java 语言相对于 C 语言更高级一点,但基本抽象原理上没有太大的差异,这里就暂时就不展开说明了。且编程语言的历程如下图所示:
注意:高级语言的一条语句(Statement)往往对应很多条指令(Instruction)才能完成
5. 操作系统(Operating System)
需要时刻注意,操作系统的核心概念就是进程;
操作系统是一组做计算机资源管理的软件的统称,即操作系统是一个软件-(由代码构成程序)。
操作系统的主要职责,有两方面:
- 管理各种硬件设备
- 给其他软件提供稳定的运行环境。->抽象封装的作用
如此,程序员写的代码不需要面向硬件,只需要面向操作系统即可;Jvm又是对系统的抽象封装,所以java程序员只用jvm提供的api就可以起到控制各种不同的系统,完成编程的作用。
目前常见的操作系统有:Windows系列、Unix系列、Linux系列、OSX系列、Android系列、iOS系列、鸿蒙等
5.1 操作系统的定位
操作系统由两个基本功能(官方用语):
-
防止硬件被时空的应用程序滥用;(管理各种硬件设备)
-
向应用程序提供简单一致的机制来控制复杂而又通常大相径庭的低级硬件设备。(给其他软件提供稳定的运行环境)
5.2 什么是进程/任务(Process/Task)
进程就是操作系统提供的一种“软件资源”;
咱们现在所使用的操作系统,就属于“多任务操作系统”,->即同一时刻,可以同时运行多个任务(同一时间,使用qq音乐,word,浏览器等),这些正在运行中的程序,就可以称为是“任务”,也叫做进程
与之对应的就是“单任务操作系统”,同一时刻,只能运行一个程序。单任务系统,没有后台执行,想要执行另外一个程序,就需要把之前的那个程序退出。单任务的操作系统十分不便利,多任务操作系统,是当前的主流方式之一。·
如上图所示,我们同一时刻使用多个应用软件,每一个软件执行的当前任务就是一个进程;
如下图所示,上图中的每个任务在执行的过程中,都需要消耗一定的硬件资源。->即可以认为,计算机的每个进程,在运行的过程中,都需要给他分配一定的系统资源(进程是系统分配资源的基本单位),下图是当前多个进程执行的时候所需要耗费的硬件资源;
下面我们需要知道,进程在系统中是如何管理的?
5.3 操作系统的进程管理
1. 先描述(使用类/结构体这样的方式,把实体属性给列出来)
操作系统一般都是c/c++实现的(没有用java写的),因此就可以使用结构体,表示进程信息的结构体,PCB(进程控制块,Process Control Block),是操作系统学科中的通用概念,windows上表示进程的结构,也可以称为PCB,linux也可以称为PCB
2. 在组织(使用一定的数据结构,把这些结构体/对象,串到一起)(数据库->表格)
在linux中,使用链表这样的数据结构把若干个task_struct给串起来;当我们看到任务管理器中的这些进程的时候,意味着系统内部就在遍历链表,并且打印每个节点的关键信息;
如果运行一个新的程序,于是系统中就会多一个进程,多的这个进程就需要构造一个pcb,然后添加到链表上。如果某个运行的程序退出了,就需要把对应进程的pcb从链表中删除,并且销毁对应的pcb资源。有的程序是带有驱动的,这些驱动是在系统“内核”中执行的,这里出现的问题可能会导致系统卡死或蓝屏。
5.3.1Pcb中的核心属性
(pcb这个结构体,是一个非常庞大的结构体)
1. pid:
进程的身份表示(此处通过一个简单不重复的整数来进行区分的),操作系统会保证同一个机器上,同一时刻,每一个进程的pid都是唯一的(后序要针对某个进程进行操作,就可以拿着pid进行区分了)
比如,选中某个进程,并且点击结束任务,此时,就是任务管理器获取到你选中的进程的pid,然后调用一个系统api,把pid作为参数穿进去,从而完成这里杀死进程的操作。
2. 内存指针(一组)
就是用来描述上述所说的这个进程跑起来所使用的那些资源,进程运行过程中,需要消耗一些系统资源的,其中内存就是一种很重要的资源。整个系统,内存这么多(16G),这16G不可以随意使用:
先从系统这里申请,系统分配给你一块,才能使用。每个进程都必须使用自己申请到的内存。故此内存指针,就是用来描述你说的这个进程,都能使用那些内存。
一个进程跑起来的时候,需要有指令,也需要有数据(指令和数据,都是要加载到内存中的去的),进程也需要知道哪边存的是指令哪里存的是数据。
eg:双击一个exe,就会“运行进程”,这个过程中,就是系统先把exe这个文件里面的内容(包含指令和数据),先加载到内存中,然后在创建进程,让进程开始执行。->指令一般不是很大,但是数据一般很大。
3. 文件描述符表:(描述了这个进程,所涉及的硬盘相关的资源)
我们的进程经常要访问硬件。操作系统,对于硬盘这样的硬件设备,进行了封装-------------->文件存储器=内存+外存(硬盘,软盘,光盘)
我们的操作系统,不管哪种盘,都是统一进行的抽象,都是按照“文件”的方式来操作的。 一个进程要想操作文件,需要先“打开文件”(就是让你的进程在文件描述符表中分配一个表项(构造一个结构体),来表示这个文件的相关信息)
番外:
1、进程是系统分配资源的基本单位(内存,硬盘->都会在pcb中有所体现),一个进程,消耗cpu的资源是啥意思?
进程就是执行指令,有的电脑的cpu有16个逻辑核心,但是系统上的进程远远大于16个,所以这就涉及到了分时复用(并发);
2、单个核心的cpu,能否支持“多任务操作系统”运行呢?
可以,虽然cpu核心只有一个,先执行进程1的代码(进程一,登台演出),执行一会后,让进程1下来,进程二上;进程二上执行了一段时间后,进程三上。。。。。。
只要切换速度足够快,认识感知不到这个切换的过程的,在人眼看起来,多个任务/进程,就是“同时执行的”(cpu的频率都是多少ghz,即1s执行几十亿条指令,意味着短时间内,cpu就可以进行很多次的任务切换,只要速度够快,人就感知不到)
后来,随着多核cpu诞生,pcb就开始同时执行进程。称为“并发执行,但是每个核心仍然要时分复用,要快速切换”
当代的计算机执行过程,往往是并行+并发同时存在的。-->故此,往往把并行和并发统称为并发。所以,对应的编程模式,(解决一个问题,同时搞多个任务来执行,共同协作解决)就称为“并发编程”
3、cpu占用率解释?
此时,cpu的百分数,就是你的进程在cpu舞台上消耗的时间的百分比。如果有一个进程,把cpu吃到了100%,意味着其他进程都没有执行的时间了,这个进程就把cpu霸占了,就可能会造成系统卡顿。
进程的调度,分时复用,并发执行。
5.3.2 Pcb中的属性
Pcb中就需要提供一些属性,来支持系统完成对这些进程的调度。
1、状态:某个进程能否去cpu上执行,有的时候,某个进程在某一时间不太方便去cpu上执行,(比如,某个进程需要通过用户scanner输入内容,但是不确定用户啥时候输入,于是这个进程就变得不可控),所以进程的状态分为以下几种:
就绪状态:随时准备好去cpu上执行(操作系统一打招呼,就上了)
阻塞状态:这个进程不方便去cpu上执行,不应该去调度他(比如,进程在等待io,来自控制台的输入输出/硬盘的输入输出/网卡输入输出)
其他的状态不做过多展开
2、优先级:多个进程等待系统的调度,多个进程调度的先后关系,不是很平均。有些系统api是可以设置优先级的
3、记账信息:针对每个进程,占据了多少cpu时间,进行一个统计,会根据这个统计结果来进一步的调整调度的策略。因此在下一个轮次进行调整,确保让每个进程都能被cpu所执行。
4、上下文:(pcb中的数据结构,在内存中)支撑进程调度的重要属性,类似于游戏中的存档和读档。每个进程在运行过程中,就会有很多的中间结果,在cpu的寄存器中。(操作系统的调度进程,过程可以认为是随机的,任何一个进程,代码执行到任何一条指令的时候都可能被调度出cpu,在进程下次调度会cpu的时候,继续之前的进度来执行)
5、存档:因此,就需要在进程调度出cpu之前,把当前寄存器中的这些信息,给单独存到一个地方。
读档:在该进程下次再去cpu上执行的时候,再把这些寄存器的信息给回复回来。
所谓的“保存上下文”就是把cpu的关键寄存器中的数据,保存到内存中(pcb的上下文属性中)
所谓的“恢复上下文”就是把内存中的关键寄存器中的信息,加载到cpu的对应寄存器中。
以上绿色部分其实都是数据结构
5.4 内存分配 —— 内存管理(Memory Manage)
进程如何管理内存其实是一个十分复杂的问题:
核心结论:每个进程的内存,是彼此独立,互不干扰的->(这个情况被称为系统的独立性)
(通常情况下,进程A不能直接访问进程B的内存)-->为了系统的稳定性,如果某个进程代码出bug(比如内存写越界),这样出错影响的范围,只是影响到自己这个进程,不会影响到其他进程。如果系统上一个进程崩溃,从而影响到其他进程,无疑是一个非常糟糕的体验。
5.5 进程间通信(Inter Process Communication)
虽然进程之间的独立性,但是有时候也需要,多个进程相互配合,完成某个工作。
进程间通信和进程的“独立性并不冲突,系统提供的一些公共的空间(多个进程都能访问到的),让两个进程借助这种公共空间来交换数据”
目前,主流操作系统提供的进程通信机制有如下:
管道
共享内存
文件
网络
信号量
信号
一般来说,我们主要使用的进程间通信方式是上述的三和四通信进制,网络,是可以支持同一个主机的不同进程,也能支持不同主机的不同进程。
上述只是对于进程的讨论,实际上再java中,不太会使用“进程”,线程更为重要
线程是线程实际开发中,是十分常用的机制;
多任务操作系统:希望系统能够同时运行多个程序。引入进程本质上来说,就是解决“并发编程”这样的问题的。一般有些场景下,需要频繁的创建和销毁进程的时候,此时使用多进程编程,就会开销非常大。
进程是资源分配的基本单位(一个进程,刚刚启动的时候,首先就是内存资源进程需要把依赖的代码和数据,从磁盘加载到内存中),从系统分配一个内存,并非是一个容易得事(一般来说,申请内存的时候,就需要指定一个大小,系统内部就把各种大小的空闲内存,通过一定的数据结构给组织起来了,实际申请的时候,就需要去这样的空间中进行查找,找到大小合适的空闲内存,分配过来),我们都知道,进程的调度是“分时复用的”,进程消耗的内存,也是在内存空间上“分时复用的;但是当前没在运行的进程,所消耗的内存空间,可以暂时不必正真放到“内存”上,可以暂时放到硬盘的特定区域(swap区域),等到进程真正执行的时候,再把这些内存数据装载进去,这样就可以有效的保证正在运行的进程内存比较充裕。”
虚拟内存:是进程独立性的底层支撑
结论:进程在进行频繁的创建和销毁的时候,开销比较大(主要体现在资源的申请和释放上)
ps:本次的学习就到这里了,如果感兴趣的话就请一键三连哦!!!