Java EE初阶-计算机是如何工作的


前言

该文章,我们会从软件工程师的角度解释计算机是如何工作的,主要目标既不是期待大家可以造出自己的计算机,也不是介绍如何编程,而是希望让大家了解计算机的核心工作机制后,打破计算机的神秘感,并且有利于理解我们平时编程时的一些行为、动作的历史渊源。


提示:以下是本篇文章正文内容,下面案例可供参考

一、冯诺依曼体系

在这里插入图片描述

(图片来自比特就业课)

CPU 中央处理器: 进行算术运算和逻辑判断.
存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)
输入设备: 用户给计算机发号施令的设备.
输出设备: 计算机个用户汇报结果的设备

ps:存储空间—硬盘 > 内存 >> CPU
数据访问速度—CPU >> 内存 > 硬盘
成本—内存高,外存低(硬盘)
持久化存储—内存断电后消失,外存仍然存在

二、CPU的基本工作流程

比如笔者当前的电脑
在这里插入图片描述
CPU是一个技术含量极高的技术,可以说是我们当前人类科技巅峰(计算速度非常非常快),比如上图的2.59GHz,表示1秒钟执行2.59亿条指令

2.1逻辑门

电子开关 —— 机械继电器(Mechanical Relay)
在这里插入图片描述
我们以前学物理的时候,学过一个东西,叫作电磁继电器/机械继电器。这玩意,你一通电,这开关就吸上了;不通电,开关就打开了。

我们可以借助这个开关的状态来描述1和0这两个数据(比如约定开关闭合是1,开关断开是0)

有一个开关我们可以表示1组0和1,有很多开关,那我们可以表示很多组0和1。那么我们就可以表示2进制数了

而将上面的开关进一步的组合,我们就可以构成门电路:
门电路(Gate Circuit)
在这里插入图片描述
在这里插入图片描述

(图片来自比特就业课)

最基础的门电路,有三种:
非门:可以对一个0/1进行取反
与门:可以针对两个0/1进行与运算
0 0 =>0
1 0 =>0
0 1 =>0
1 1 =>1
(这里是按位与而不是逻辑与)
速记:有零为零

或门:可以针对两个0/1进行或运算
0 0 =>0
1 0 =>1
0 1 =>1
1 1 =>1
速记:有一为一

而再借助与或非门,我们可以构造异或门:
在这里插入图片描述
异或门:可以针对两个0/1进行或运算
0 0 =>0
1 0 =>1
0 1 =>1
1 1 =>0
速记:相同为0,相异为1

2.2算术逻辑单元ALU

基于上述的门电路,还可以搭建出一些运算器

2.2.1算术单元

算数单元,负责计算机里的所有数字操作,比如四则运算,当然它能做的远远不止这些。接下来我们会带着大家实现一个 8 位(bits)的加法器(adder)来,以演示整个过程,其他的运算器就不再详细讲解了。

半加器:针对两个比特位,进行加法运算

在这里插入图片描述
和(sum):通过一个异或门就可以来算和,输入的A和B相同就得到一个0

:
0+0=0,1+1=0,
ps:1+1这个位上是0,然后进1位

进位(carry):两个1遇到进1位
在这里插入图片描述

全加器:针对三个比特位,进行加法运算
在这里插入图片描述
就是两个半加器套在一起,原理就是:先把两个比特位进行相加,把得到的结果再与第三个比特位相加
在这里插入图片描述

基于上述的半加器和全加器,就可以构造出一个针对多比特位的数据进行加法运算的加法器了
8位数加法器
在这里插入图片描述
ps:我们不必理解这里的门电路具体是怎么工作的,你只要知道,这里面的逻辑链:
电子开关=》基础的门电路=》异或门电路=》半加器=》全加器=》8位加法器

有了加法器后,可以计算的不只是加法,还可以计算减法,乘法,除法…

比如C语言中:计算机如果表示一个正数,直接使用原码,如果表示一个负数,就使用补码(原码取反加1)

为什么用补码?——是为了统一加法和减法,相减就等于是加上一个负数

2.2.2逻辑单元

逻辑单元主要用来进行逻辑操作,最基本的操作就是与、或、非操作,但不只是一位(bit)数的比较。
在这里插入图片描述

2.3寄存器

寄存器是CPU内部用来存储数据的组件

访问速度:寄存器比内存快了3~4个数量级

存储空间:比内存小很多很多,现在64位的cpu,大概有几十个寄存器,每个寄存器8~几百字节

成本:cpu上的寄存器非常贵

持久化:断电后,数据消失

2.4控制单元CU(Control Unit)

在这里插入图片描述

2.5指令

指令其实是我们非常熟悉的一个词——机器语言(通过二进制的数字,来表示不同的操作)

所谓指令,即指导 CPU 进行工作的命令,主要有操作码 + 被操作数组成。其中操作码用来表示要做什么动作,被操作数是本条指令要操作的数据,可能是内存地址,也可能是寄存器编号等。指令本身也是一个数字,用二进制形式保存在内存的某个区域中。

指令是如何执行的呢?比如我们自己现在构造一个简单的指令表
在这里插入图片描述
假设我们现在CPU上有两个寄存器A,B
其中A编号00,B编号01

LOAD_A指令:比如我们给一串数字0010 1010,这个操作的意思就是把1010地址上的数据读取到A寄存器上

LOAD_B指令:比如我们给一串数字0001 1111,这个操作的意思就是把1111地址上的数据读取到B寄存器上

STORE_A指令:比如我们给一串数字0100 1000,这个操作的意思就是把A寄存器的值,存到1000这个内存地址中

ADD指令:比如我们给一串数字1000 0100就是把A寄存器中数据和B寄存器中的数据进行相加,结果放到00寄存器中

2.6小结

cpu的工作流程:
1.从内存中读取指令
2.解析指令
3.执行指令
上面3个操作都是通过CU这个控制单元来实现的

我们编写的程序,最终都会被编译器翻译成CPU能识别的机器语言指令,在运行程序的时候,操作系统可把这样的可执行程序加载到内存中,CPU就一条一条指令的去进行读取,解析,执行。

如果再搭配条件跳转,就可以实现条件语句和循环语句

三、操作系统

关于操作系统,我们大家要先知道,它是一个软件并且它是计算机上最重要,也是最复杂的软件之一,常见的操作系统有windows,linux,mac,ios等等

3.1操作系统的定位

在这里插入图片描述
(图片来自比特就业课)

操作系统是一个搞管理的软件
1.对上,要给各种软件提供稳定的运行环境
2.对下,要管理好各种硬件设备

3.2什么是进程/任务

进程说白了,就是一个跑起来的程序,比如我们一些app的文件夹里面总会有.exe后缀的,比如我们这里的QQ.exe
在这里插入图片描述
我们一双击这个QQ.exe就可以打开QQ了,类似的这种.exe后缀的,都称为可执行文件

这种可执行文件,都是文件,都是静静的躺平在硬盘上,只要你不点它,它也懒得工作。但是你双击这些.exe文件,操作系统就会把这些.exe加载到内存中,并让CPU执行.exe内存的一些指令

这时.exe就已经被执行起来,它就不再是躺平的咸鱼了,而是开始做一些事情了,比如你QQ.exe执行了你就可以聊天了,你的steam.exe执行了,你就可以打游戏了

我们把这种运行起来的可执行文件,称为“进程”

比如我现在打开我的任务管理器
在这里插入图片描述
这些玩意都是我们的进程,还有一些后台进程。这些进程中有一些是我自己创建的,还有一些是系统一启动就创建的,综上所谓的进程就是跑起来的程序

再贴近我们自己来说:我们写代码最终目的都是要run一下嘛,也就是最终都要成为一些进程,对于我们java来说,最终都是通过java进程跑起来的(jvm)

ps:有的同学还经常听说线程,这里简单介绍一下——线程是进程内部的一个部分,如果把进程比作一个工厂,那么线程就是一条生产线。一个工厂可以有多个生产线,也可以只有一条生产线。

3.3进程控制块抽象(PCB Process Control Block)

操作系统是怎么管理进程的呢?

1.先描述一个进程(明确一个进程的相关属性)
在java语言中,我们可以用类/对象来描述

// 以下代码是 Java 代码的伪码形式,重在说明
class PCB {
    // 进程的唯一标识 —— pid;
    // 进程关联的程序信息,例如哪个程序,加载到内存中的区域等
   // 分配给该资源使用的各个资源
   // 进度调度信息(留待下面讲解)
}

这样,每一个 PCB 对象,就代表着一个实实在在运行着的程序,也就是进程

2.再组织若干进程(使用一些数据结构,把很多描述进程的信息放到一起,方便增删改查)

典型的表现就是使用双向链表来把每个进程的PCB给串起来

所谓的“创建进程”,就是先创建出PCB,然后把PCB加载到双向链表中

所谓的“销毁进程”,就是找到链表上的PCB,并且从链表上删除

所谓的“查看任务管理器”,就是遍历链表

下面是PCB的一些属性:

属性1.pid(进程id)——进程的身份标识,类似我们的身份证号码
在这里插入图片描述

属性2.内存指针——指明了这个进程要执行的代码/指令的内存的哪里,以及这个进程执行中依赖的数据都在哪里

属性3.文件描述符表——程序运行过程中经常要和文件打交道(文件在硬盘上)
进程每打开一个文件,就会在文件描述符表上多增一项。(文件描述表就可以视为一个数组,里面的每个元素又是一个类/c语言中的结构体这种,就对应一个文件的相关信息)

一个进程只要已启动,不管你代码中是否打开/操作文件的代码,都会默认的打开三个文件——标准输入(System.in)、标准输出(System.out)、标准错误(System.err)

而要想让一个进程正常工作,就要给这个进程分配一些系统资源:内存、硬盘、CPU

3.4CPU分配-进程调度

进程调度,这个是理解进程管理的重要话题,现在的操作系统,一般多是“多任务操作系统”:一个系统同一时间,执行很多任务

ps:单任务操作系统不考虑调度

笔者现在的是windows系统,就是多任务的,同一时刻有很多进程在运行:QQ,微信,网易云。。。

我系统上的任务数量(进程的数量)有几十上百个,但我的CPU核数是6核的,那如何让这么多的任务进行呢?这就是进程调度:

两个解决办法:
1.并行:微观上,两个CPU核心,同时执行两个任务的代码
2.并发:微观上,一个CPU核心,先执行任务1,再执行任务2,再任务3…(只要切换的足够快,宏观上看就是很多任务同时执行,比如100个任务,1个CPU几毫秒就完成了,在你看来,好像他们是一起完成的,其实是有先后顺序)

ps:并行和并发都是微观上的,我们宏观是无法感知的,所以,我们在写代码的时候也不必刻意去区分这两个词,除了研究操作系统进程调度我们稍作区分,其他情况下,统一用“并发”

下面我们再举一个生动的例子来介绍实现进程调度的4个属性:
假设笔者现在是一个人美声甜的妹子,热情又大方…
然后这就导致我非常受欢迎,然后我就有了很多追求者。我在这众多追求者中,选了3个作为我的男朋友
A:长相一般,但是有钱
B:长的很帅,但是没钱
C:长的一般也没钱,但是个舔狗,舔的很舒服

那我现在找了3个男朋友,就需要合理的时间管理了:我为此安排了一个时间表,避免同一时刻他们见面:
周一:陪舔狗C
周二—周五:陪帅哥B
周末:和有钱的财主A去逛街

那么宏观上看:我谈了3个男朋友
但是微观上看:同一时刻,我只是一个人的女朋友,我是个好女孩/doge

这就是我们所说的并发(1个人不同时间线进行陪不同的男朋友),而规划时间表的过程,也就是调度过程

接下来我来介绍实现进程调度的4个属性:
1.状态:
状态描述了当前进程接下来怎么调度:
第一种状态:就绪状态——还是上面的例子,正常情况下,因为舔狗C非常喜欢我,那么我找他办事那都是随叫随到的。
第二种状态:阻塞状态——比如A要出差1个月,那么我想榨干他的钱包就没有办法了

2.优先级:
这就是优先给谁分配时间,谁给的时间多,谁给的时间少,比如提款机A总是给我买名牌包包,那么我很喜欢他,我给他的时间肯定是要最多的;而对于舔狗C,虽然他总是舔我,但是对我生活其实没什么实质帮助,而且只要应付他一下就可以了,所以我分配给舔狗C的时间是最少的

3.上下文:
上下文表示了上次进程被调度出CPU的时候,当时程序的执行状态,下次进程上CPU的时候,就可以恢复之前的状态,然后继续向下执行(类似存档+读档)

进程被调度处CPU之前,要先把CPU中所有寄存器中的数据都给保存到内存中(PCB的上下文字段中)(相当于存档)

下次进程再被调度上CPU的时候,就可以从刚才的内存中,恢复这些数据到寄存器中(相当于读档)

存档存储的信息,就称为上下文
举例说明:
帅哥B和我说:下周他过生日
提款机A和我说:带我买大钻石

然后到了第二周,我和A说:“生日礼物准备好了”
A:“什么生日礼物?”

这种情况对于文中女主这种海王肯定是大失误了
那么为了避免这种状况的发生,就需要记录每次和不同人约会的重要信息,这就是上下文属性

4.记账信息:
统计了每个进程,分别被执行了多久,分别执行了哪些指令,分别排队等了多久…然后给进程调度提供指导依据

以上故事纯属虚构,和笔者本人没有任何关系/doge

3.5内存分配-内存管理

我们内存资源分配是依赖于虚拟地址空间

由于操作系统上,同时运行着多个进程,如果某个进程出现了bug,进程崩溃了,是否会影响到其他进程呢?我们现代的操作系统(windows,linux,mac…)

而一个进程崩溃避免影响其他进程就是我们“进程独立性”来保证的,依仗了虚拟地址空间

下面我们再举一个生动的例子来介绍虚拟地址空间:

现在有疫情了,然后我们住在某个小区的某个居民楼:
在这里插入图片描述
然后假设我们小区某个居民核酸阳性了(害怕.jpg)
那么其他人也可能会被他感染

这个情况就类似早期的操作系统。早起的操作系统里面的进程都是访问同一个内存的地址空间,如果某个进程出现bug,把某个内存的数据给写错了,就可能引起其他进程的崩溃

那么避免上面情况的发生,我们设计了多条道路从居民楼到大门,每个人只能走他规定的道路,保证每个路上只有一个人,那么就可以避免接触,从而避免感染
在这里插入图片描述
但这个时候会有同学问:“你要是像这种解决办法,把进程按照虚拟地址空间的方式划分出很多份,这个时候不是每份只剩一点了吗?”

我的回答是:虽然你的系统有百八十个进程,但是实际上从微观上看,同时执行的进程只有6个(笔者是6核CPU),也就是只会分成6份,而不是百八十份。另一方面,也不是所有进程都需要那么多内存(一个3A游戏可能吃几个G,大多数只占几M)

那么到这里,进程之间通过虚拟地址空间已经各自隔离开了,但是实际工作中,进程之间还是需要一些交互的
那么我们可以开辟一块公共空间给它们彼此交互,仍以刚才的例子说明:
比如我们要做核酸,那么可以开辟一个核酸站点给大家使用,这个站点是每个人都可以进来的
在这里插入图片描述
类似的,虽然我们各个进程之间,是隔离开的,也是不能直接交互的,但是操作系统也会提供类似的公共空间,比如进程A先把数据放到公共空间,然后B再取走

3.6进程间通信

操作系统中,提供的“公共空间”有很多中,并且各有特点,有的存储空间大,有的小,有的快,有的慢…

操作系统中提供了多种这样的进程间的通信机制(有些机制是历史遗留的,已不适合于现代的程序开发)

现在最主要使用进程间的通信方式有两种:
1.文件操作
2.网络操作

如上所述,进程是操作系统进行资源分配的最小单位,这意味着各个进程互相之间是无法感受到对方存在的,这就是操作系统抽象出进程这一概念的初衷,这样便带来了进程之间互相具备”隔离性(Isolation)“。
但现代的应用,要完成一个复杂的业务需求,往往无法通过一个进程独立完成,总是需要进程和进程进行配合地达到应用的目的,如此,进程之间就需要有进行“信息交换“的需求。进程间通信的需求就应运
而生。目前,主流操作系统提供的进程通信机制有如下:

  1. 管道
  2. 共享内存
  3. 文件
  4. 网络
  5. 信号量
  6. 信号

其中,网络是一种相对特殊的 IPC 机制,它除了支持同主机两个进程间通信,还支持同一网络内部非同一主机上的进程间进行通信。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

劲夫学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值