计算机:计算机是怎么运行的

less than 1 minute read

Published:

计算机是怎么运行的,回答这个问题需要的知识可太多了

前言

要从哪里开始回答这个问题,有很多个起点,有高一些的层级,有低一些的层级。思前想后,我决定以数字电路开始解释,本来想以半导体开始的,但觉着这个有点太底层太原始了,不如将数字电路作为起点开始讲这个故事。选择数字电路有几个原因:

  1. 数字电路以下基本使用弱电分析比较多,个人感觉更接近电路的知识
  2. 数字电路以上开始将电平抽象为数字,变为逻辑的0和1,更接近计算机的知识
  3. 实际上数字电路并不是目前软件硬件的分界线,软硬件的分界线应该是指令集

所以这里先从数字电路开始讲述,讲解到和数学相关的算法之后,我们再从数字电路往前看,讲解如何从半导体到数字电路的。


硬件

硬件是计算机的身体,软件是计算机的大脑;这里的东西都是死的,没有动起来

从数字电路说起

(前置知识:三极管的特性可以使得多个三极管经过排列变为满足特性要求的电路,这一层抽象形成了数字电路的基本单元)

门电路(与、或、非)是一个经过抽象的电路单元,能够根据输入的信号(高电平为1,低电平为0),来决定输出的信号,这就是最基本的数字电路的组成单元。

门电路经过特定的组合,可以变为满足要求的其他电路:

  • 半加器,全加器
  • 多路选择器
  • SR触发器,JK触发器等
  • 等等

这些电路,进一步抽象一层,变成了CPU的几个基本组成部分,比如:

  • 运算器ALU
  • 控制器
  • 寄存器
  • 等等

存储部分

有以下这么几个用来存数据的器件:

  • 硬盘:断电后数据不丢失
    • HDD(机械硬盘):使用磁盘存储数据,用读写头读写数据
    • SSD(固态硬盘):闪存芯片存储数据,电子信号读写数据(有点像内存了)
  • ROM:
    • 讲内存之前的一个有趣的东西,因为ROM总和RAM进行对比,所以放在这里讲述
    • ROM断电后数据不丢失,而且只能读不能写
    • 一般用于BIOS和UEFI中,关注度较少
    • 发展到EPROM,EEPROM,闪存后,可以变成SSD
  • 内存(RAM):断电后数据丢失
    • DRAM:1T1C,需要周期性刷新
      • 一个晶体管一个电容,使用电容存储数据,晶体管负责控制
      • 单独做在一个器件上面,通过主板与CPU连接,就是我们经常提到的内存
      • DDR,就是DDRAM,第一个D是双通道的意思
    • SRAM:6T,不需要周期性刷新
      • 组成部分为6个晶体管,这里就基本上是使用数字电路制造的了,一般和芯片做在一起
      • 由于和芯片做在一起了,也就成为了大家口中的CPU缓存(L1, L2, L3)或者GPU缓存
    • 所以CPU取信息的时候,优先从缓存一层一层往下取,都没有命中再去内存寻找
    • 这里的寻找原则就是我们学过的算法,“最近最少使用(Least Recently Used, LRU)”或者“最少频率使用(Least Frequently Used, LFU)”,等等
  • 寄存器:
    • CPU直接使用的东西,基于触发器制作
  • 【参考资料】一个介绍存储器的视频:https://www.bilibili.com/video/BV1H5m6YkE9c

CPU


软件

以上的电路都是死的,一个能够让电路动起来的核心的东西,叫做时钟(Clock)

时钟的来源是晶振,这里也先不细讲(todo),总之先认为时钟信号可以给一个稳定的周期方波信号,其他器件连接到这个信号,或者通过分频器,倍频器等连接到这个信号,拿CPU来讲,这个信号可以通过JK触发器(JK Flip-Flop)变为一个2位二进制数字,然后通过二进制解码器(学过的2-4解码器,3-8解码器等),驱动CPU执行4个阶段之中的一个,并不断循环。

alt text

CPU的执行过程

这个应该是是很多人都熟悉的领域了,CPU的执行过程一般为以下几个阶段:

  • 取指令
  • 解码
  • 执行
  • 指令计数器增加

CPU的指令从哪里来

这部分内容熟悉的人应该更多了,那就是经典的从上层代码到指令的过程,拿C++来说:

  • 预编译
  • 编译
  • 汇编
  • 链接

在进入操作系统之前,有一个基本的引导程序,就是BIOS,现在逐渐进化为UEFI。

操作系统

操作系统就是硬件与上层应用软件之间的桥梁,用软件领域的抽象来理解,对下将不同硬件资源整合,对上提供统一的接口

那么再不厌其烦的说一下操作系统的一些东西:

  • UNIX
    • Linux
    • MacOS
    • IOS
    • Android
  • Windows

不同的操作系统会提供不同的硬件接口,比如分配内存,Windows或者Linux首先实现了对DDR的封装,然后再由C的标准库进行封装,但不同的标准库之间可能也有区别。而且在你写的C++代码中,有时候使用了标准库的接口,有时候使用了操作系统的接口(系统调用),一般来讲,系统调用更底层,效率更高,标准库本质上是封装了系统调用,使用更方便安全。

在一个计算机运行的状态下,CPU执行的大部分指令也就是操作系统运行时的指令,这里面可能有一些加载的内核,系统服务等,当有应用启动时,操作系统也会触发系统调用开始执行应用。

并发和并行,线程和进程

并发是想办法一次处理多个任务,并行是一次处理多个任务

之所以这么说,是因为并行一定要多个物理的硬件才行,比如多核CPU,GPU等。

最早的计算机(大型机)需要先用程序(纸带)去编译,然后拿到汇编语言(纸带)继续去汇编,最后再执行,这是最原始的执行;后来人们为了让计算机在读取和输出的时候可以同时计算,有了让多个用户同时连接的终端。

再后来,人们不希望计算机顺序执行 ABCD 4个人的事情,因为如果A去找IO资源了,那么后面的BCD也只能等着,CPU就又空闲了,于是有了时间片什么的概念,让CPU不断地分别执行ABCD的指令。

到这里为止,计算机还都是单核的,所以概念也只是并发,没有牵扯到并行,后面有了多核CPU才真正的实现了并行。

但这引入了一个问题,任务切换时当前任务的数据没有被保存,也不安全,可能被下一个任务读取,于是操作系统最成功的概念,进程,就诞生了。进程对每个任务都保存切换时的状态,有各自独立的内存空间,保障了进程间的安全。

有必要说一下,线程的概念比多核CPU出现的早,也就是说,单核CPU也会有多线程。

还是回到上面的例子,当一个进程内部有多个任务ABCD,任务间不互相依赖时,如果A任务去找IO资源了,那么B任务就要等进程队列走一圈后,才能执行一次,对于网络请求之类的任务,很影响实际体验。于是人们想了办法,可以由A创建多个进程,这样ABCD任务地位就提高了,但这样会造成进程的创建开销以及内存的浪费。于是有了线程的概念,线程们属于同一个进程,可以复用进程的一些资源,但是有自己独立的数据,所以线程也被叫做轻量级进程。

后来有了多核CPU,但是待执行的任务还是一样的,区别就是不再让一个CPU不停切换了,而是真的可以同时执行多个任务,实现了并行。

到这里可以你会发现,如果CPU只是在线程之间切换,进程只是一个特殊的线程(主线程),那么为什么还要区分进程和线程呢?没错,Linux内核确实将进程和线程统称为“任务”了,源码路径https://github.com/torvalds/linux/blob/master/include/linux/sched.h

应用软件

讲到这里就变得越来越有意思了,因为在操作系统的基础上,开发者不需要考虑底层的东西,可以专心于功能本身,出现了好玩的游戏,方便的应用等,这也是普通人接触计算机的入口。

单机软件

我感觉计算机刚走进千家万户时,单机软件应该是很多的,还有很多单机游戏;但随着计算机网络的迅速发展,网络软件越来越多,几乎找不到纯粹的单机软件了。

网络软件

当应用软件开始与其他软件或者其他计算机的软件通信时,它就变成了一个网络软件。据我所知,操作系统内核就提供了最基本的通信功能,常考的TCP/IP就来自于此,操作系统内核比较底层,再往下走就是硬件了,就像OSI的7层协议,物理层和数据链路层其实考虑的不多,并且我感觉数据链路层还是和数字电路关系更大,所以也没在操作系统内核中。虽然内核的接口比较底层,效率更高,但使用也不方便,所以操作系统做了封装,变成了<socket.h>,我猜网络软件大多数也就是用的这个接口。

这里也是一大堆就业岗位的来源:

  • 前端
  • 后端
  • 移动端

这一层有很多知名的框架,方便开发者开发,本质上就是将常用的接口功能实现了封装,提供了更简单的接口。

算法

算法其实已经是计算机和数学的交叉点了,算法的底层原理都是数学知识,研究出来的算法,其实还是要投入实际的应用场景的,而目前最直接的算法落地,应该就是通过计算机了。

算法其实有很多种,我并没有一个很好的分类:

  • 信号处理:
    • 语音信号处理:傅里叶变换,滤波器
    • 图像处理:高斯滤波,图像压缩,视频编解码
  • 人工智能:机器学习,深度学习
    • 计算机视觉
    • 自然语言处理
  • 机器人:
    • 路径规划

我印象中就业的岗位中算法工程师大概有这些。