这里简单介绍一下,计算机是怎样工作的,和实际计算机的工作肯定是有出入的,主要是基本思想
1.计算机的基本构成
现在的计算机所遵循的基本结构是冯诺依曼体系结构,冯诺依曼体系结构由运算器,存储器,控制器,输入设备和输出设备构成。如图,可以看出CPU是计算机中最重要的部分,上面集成了很多运算单元和控制单元。但不仅仅只有这些。
CPU主要分为两类,一类是精简指令集CPU(RISC) ,一类是复杂指令集CPU(CISC)。RISC,主要是arm架构,苹果旗下的产品和高通的芯片就是使用该架构。精简指令集CPU能耗低,相对的性能也比较弱,常用于移动设备。 CISC,主要是x86架构,intel和amd公司主要做这方面,复杂指令集CPU能耗高,性能高,常用于PC设备。
不同架构的CPU,支持的指令集不同。指令集是机器语言,机器语言又可以翻译成汇编语言。
在计算机体系结构中,64位是指整数、内存地址或其他数据单元的宽度是64比特。此外,64位中央处理器 (CPU) 和算术逻辑单元 (ALU) 是基于64位大小的寄存器、地址总线或数据总线的,支持整数的64比特宽度的算术与逻辑运算。使用这种处理器的计算机是64位计算机。
同样的还有32位CPU和16位CPU的存在。但是有的时候,32位CPU的地址总线并不一定等于32,Intel的32位CPU的地址总线宽度最大为36位。
32位的CPU主要是x86架构,代表公司是intel,amd,然后在发展中出现了64位CPU,64位CPU的架构是在x86的基础上扩展的x86_64架构。我们在网上下载可执行文件的时候,一般会看到x86和x86_64给大家选。现在大多数家用计算机是64位CPU,64位的计算机上一般是可以运行32位的可执行文件。32位CPU主要是用于工业。
CPU的基本单元是门电路。不仅CPU,还有其他的内存,硬盘等设备都是由门电路构成的。
CPU中有一个核心,CPU中所有的计算、接受/存储命令、处理数据都由核心执行。因为门电路不能无限小,所以科学家就想让一个CPU上有多个核心,来提高性能。于是有了多核CPU。但是科学家还是感觉性能不够高,于是继续研究,研究出了超线程技术。就是在软件的配合下,将一个核心当成两个核心来用。
2.模拟CPU执行指令的过程
指令主要是由操作码(opcode)和操作数组成,操作码决定要完成的操作,操作数指参加运算的数据及其所在的单元地址,操作数也叫地址码。
举个小例子,当然,和真实的指令肯定不一样,但意思是一样的。
我们做为java程序员可以不用懂CPU的构造但是要懂CPU的指令执行的过程。因为根据计算机系统,最底层是硬件提供服务,了解指令的执行,会对我们写代码有很大帮助。
下面是几个简易版指令
指令 | 功能 | 操作码 | 操作的地址或寄存器 |
load_a | 从内存指定地址,将数据加载到寄存器a中 | 0010 | 4位RAM地址(RAM指内存) |
loab_b | 从内存指定地址,将数据加载到寄存器b中 | 0001 | 4位RAM地址 |
store_a | 将数据从寄存器a中写入到内存里 | 0100 | 4位RAM地址 |
add | 计算两个指定寄存器数据的和,并将结果放入第二个寄存器 | 1000 | 2位寄存器的ID 2位寄存器的ID |
计算机一个操作的指令:
地址(十进制) | 数据 (二进制) |
0 | 00101110 |
1 | 00011111 |
2 | 10000100 |
3 | 01001101 |
4 | 00000000 |
5 | 00000000 |
14 | 00000011 |
16 | 00001110 |
语言描述就是,第一条指令是0号地址的指令,取出0号地址的指令,区分出操作码和地址码部分,操作码代表从指定内存地址取数,通过地址码找到指定位置,并且放到0号寄存器。1号地址同理。2号地址的指令,操作码部分,意思是从地址码的两个寄存器中取数据相加并放入到第二个寄存器,3号地址的操作码意思是,将寄存器更新好的数据保存到地址码指定位置。
通过上面的简单模拟,我们可以简单得出些许结论:
-
CPU要执行的指令是在内存中的,这是冯诺依曼结构体系的设定,让执行单元和存储单元解耦合。
-
CPU执行指令,要先取指令,再解析指令。
-
在图中虽然没有体现,实际上取指令要先从内存中读取指令到寄存器里,取指过程其实比较耗时。因为有很多内存I/O。所以后来有缓存,流水线技术来对此进行优化。
-
CPU对指令进行解析的时候,依靠指令表,指令表写死在硬件上,所以不同架构的CPU的指令表不一样。
-
CPU重要参数主频,表示一个时钟周期内CPU能执行指令的个数,也可以近似看成一秒钟CPU可以执行的指令个数。
3.并行和并发
一个正在运行的程序就是一个进程(Process)
并发就是让两个进程“同时”工作。
但是一个CPU核心同一时刻只能让一个进程运行,另一个就只能等待这个进行运行结束。这样就很不方便,比如说你心上人和你用qq聊天,心上人打字比较慢,你要在手机上守会儿才能收到对方传来的消息,不能干别的,要是你想去玩游戏就得关闭掉qq,就接受不到心上人的消息了,很有可能错过心上人的消息。但是如果能同时运行两个进程,你就可以一边看剧,一边让qq挂在后台,然后心上人的消息一来,qq就会在后台提醒你。
并发(分时复用)操作的实现就解决了这个问题,我们就可以让一个进程运行一段时间,然后挂起,让下一个进程运行一段时间,然后挂起,以此类推来回切换。中间挂起的时间足够短,那么在宏观上,就是几个进程同时在运行。但每个进程的挂起时间不一定是相等的,这要看具体的算法。
然后多核CPU出现了,为了重复利用多核CPU的性能,我们可以使用并行操作,可以让不同的进程分别在不同的核心上运行。
因为有超线程技术,一个物理核心可以当成两个核心用,我们可以打开任务管理器就可以看到CPU的逻辑核心数了,和相关参数了。
现代计算机执行过程。往往是并发+并行同时进行的。
那么对于进程的调度主要是依靠多任务(Task)操作系统来进行调度,不同操作系统的调度算法是不一样的。
对于程序员来说,进行编程来控制硬件设备主要是通过操作系统提供的api,操作系统提供了一些api给程序员来实现并发编程(一般来说并发并行不做特意区分,通常情况下说并发指并行和并发),JVM又在操作系统api上进行了一层封装,所以java程序员只需要学习JVM所提供的api就行了。
操作系统的作用:
管理计算机硬件资源,就是对各种硬件提供的api进行统一封装。
为上层软件的运行提供稳定的环境。
4.进程
进程PCB,即进程控制块,是操作系统中用于描述进程状态及其相关信息的数据结构。 每个正在运行或等待运行的进程都有一个与之对应的进程PCB。 在操作系统中,进程PCB是非常重要的,因为它记录了每个进程所需要的信息,并且是进程管理的核心之一。进程PCB中需要提供一些属性来支持操作系统对进程的调度。
打开任务管理器可以看到最上面的一行,有名称,状态,PID,CPU,内存等等,这些就是PCB的属性,在操作系统的实现里,PCB实际上就是一个结构体。
图上的PID可以理解为每一个进程的身份证,也可以理解为数据库中的主键,不重复的标识每一个进程。
CPU,这里的百分比指进程在CPU上消耗时间的百分比。如果一个进程CPU百分比为100%,那么就代表其他的进程就调不到CPU上了,那么系统就有可能变卡。
4.1 PCB中的一些重要属性:
-
PID 进程的唯一标识符
-
内存指针 进程使用的内存的位置,内存里放数据的位置,内存里放指令的位置
-
文件描述表符 进程使用的硬盘的相关信息
-
状态
描述一个进程能不能调度到CPU上执行。 能调度,就是就绪状态。不能调度,就是阻塞状态。
比如,一个进程需要等待用户的输入,但是用户输入的时间是不确定的,就需要进入阻塞状态等待用户的输入,用户输入了,就改为就绪状态,就可以被调度到CPU上了。 进程进入阻塞状态就要等待(wait)和挂起(hung up)。
5. 优先级
就是确定多个进程都要调度的时候,谁先调度,谁后调度。比如游戏进程和QQ音乐进程,那肯定是游戏优先级更高,游戏延迟一秒,就可能输了,而音乐慢一秒啥事没有。
系统的api可以设置优先级调度。
6.记账信息
统计每一个进程在CPU上调度的时间,根据这个统计,在下一轮次的调度进行调整,确保每一个进程都能在CPU上被调度。
7.上下文
支持进程调度的重要属性。
我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为上文,把正在执行的指令和数据在寄存器和堆栈中的内容称为正文,把待执行的指令和数据在寄存器与堆栈中的内容称为下文。
相当于游戏里的存档和读档。 进程调走之前,先把下文保存到PCB的上下文属性里,进程调度回来的时候,再将PCB的上下文属性的内容读取到对应的寄存器里,然后继续执行。
2到7都是PCB中的数据结构。
4.2进程独立性
通常情况下,a进程是不能直接访问b进程的内存。a进程出bug,不会影响b进程的运行。这种情况就叫做,进程独立性。
4.3.进程间通信
有的工作需要多个进程来互相配合完成,但是进程是独立的。所以操作系统就提供了一些公共的空间,每个进程都可以访问,来实现多进程的交互。
比如说,不同学校的两个同学,因为学校只能刷本校学生的脸进去,所以她们不能去对方的学校找对方,但是可以约一个公共地点,见面。
进程通信的一些方式:
-
管道
-
共享内存
-
文件
-
网络
-
信号量
-
信号
java程序员主要用文件,网络进行进程通信。网络可以支持同一个主机的不同进程,也可以支持不同主机的不同进程,比较灵活。