回到本真,梦回计算机发展史

2022-07-10

前言


关于之前「Go语言的内存管理实现」这部分,本来接下来是要给大家继续讲解「Go语言堆内存、栈内存分配」的,以及这部分之前图都画完了。但是呢,写文章的时候,写着写着发现写不下去了,为什么?

我发现想要彻底理解「Go语言堆内存、栈内存分配」必须需要很清楚的理解:什么是堆内存和栈内存,看到这里肯定很多朋友笑了「堆内存/栈内存」那不简单么:

  • 栈内存:栈内存的分配和释放都是由程序自己操作的,无需我们程序员关心。
  • 堆内存:一种是编程语言要求由我们程序员分配、释放堆内存,不小心的话就会带来内存泄漏;另一种由于编程语言自己实现了垃圾回收器,堆内存的分配、释放也不需要由我们程序员关心了,垃圾回收器会自动回收堆内存。

确实,上面的说法没什么问题。但是呢,本着刨根问底的态度,我再来引申几个问题:

  • 栈内存到底存在于哪?栈内存具体是被谁分配和释放的?栈内存分配和释放的具体时机是什么时候?
  • 有了栈内存为什么还需要堆内存?堆内存到底存在于哪?为什么只有堆内存需要垃圾回收机制回收?

为了彻底帮助大家简单明了的吃透「堆内存、栈内存」,又不断引申出了新的问题:

我们写的代码、以及运行起来的程序到底是什么?

在解决这个问题的时候,却又引申出了新的问题:

刨根问底,计算机到底是怎么运行起来的?

最后这样一步步引申到了「计算机原理」和「计算机的发展史」,为了更好理解这些问题,于是这两个月我又读了三本书,具体如下:

  • 《程序是怎么跑起来的》
  • 《计算机是怎样跑起来的》
  • 《计算机:一部历史》

我把读完这三本书学习到的知识点进行整理,以及结合自己的理解,抽丝拔茧帮助大家去理解:

  • 我们写的代码到底是什么?
  • 运行的程序到底是什么?
  • 计算机到底是什么?

搞定了这些问题,我们再去学习「Go语言堆内存、栈内存」或者别的语言的「堆内存、栈内存」都会得心应手,容易很多。

正文


本篇文章是自己对这两个月学习内容的总结,同时也帮助大家对计算机发展历史有一个基本的认知,本文主要目录如下:

  • 计算机启蒙于数学
  • 计算机发展于电子学+数学
  • 具有语义的编程语言
  • 程序集合:操作系统

计算机启蒙于数学


理论计算机:图灵机


上个世纪伟大的数学家们发起了一个挑战大概意思是:“制造一台机器可以自动计算数学问题”。当时伟大的数学家艾伦·图灵提出了自己的理论,大概思路是通过输入之后机器可以自动计算并输出结果,这个理论就是大名鼎鼎的“图灵机”,艾伦·图灵就是理论计算机“图灵机”的创造者。

算术问题也是逻辑问题


不难理解,数学问题都可以转化为两类问题:

  • 算术问题:四则运算等
  • 逻辑问题:与、或、非、异或等

算术问题可以通过逻辑运算解决,所以所有数学问题都可以看作是逻辑问题。所以,只要找到可以自动判定真假的某种机器即可实现自动计算。

这里就有人疑惑🤔了,逻辑问题怎么解决算术问题的,想要理解这个问题我们先回到小学加法运算,比如16+36的计算过程:

16
36
+
------
2
4 1(进位)
------
52

这个运算过程我们只需要重点关注以下几点:

  • 从左到右按位进行10以内的加法运算(算术问题)
  • 保存当前位的计算结果(存储)
  • 当前位计算结果是否需要进位(逻辑问题是否需要进位)

目前进位的问题已经可以转化为逻辑问题了。以上是十进制的加法运算过程,我们换成更简单二进制加法运算再来看看,如下:

最简单的二进制


二进制相对于十进制更加简单,只有0和1两个状态,简单回顾下二进制的加法运算过程:

为了简化理解这里以十进制 3 + 6 的二进制运算过程为例:

二进制分别为:
0011
0110

A:加数
B:被加数
C:进位,当前位运算后的进位结果,有进位则进位值为1,无进位则进位值为0
D:结果,当前位运算结果
C1:进位,上一位运算后的进位

二进制计算过程:

<------从右到左最低位开始逐位运算------

A 0 0 1 1
^ 按位异或运算
B 0 1 1 0
---------------------计算第1位
D 1
C 0


C1 0
^
A 1
^
B 1
---------------------计算第2位
D 0
C 1


C1 1
^
A 0
^
B 1
---------------------计算第3位
D 0
C 1


C1 1
^
A 0
^
B 0
---------------------计算第4位
D 1 0 0 1

  • A:加数
  • B:被加数
  • C:进位,当前位运算后的进位结果,有进位则进位值为1,无进位则进位值为0
  • D:结果,当前位运算结果
  • C1:进位,上一位运算后的进位
A B C1 D C
- - - A异或B异或C1 (A 与 B) 或者 (C1 与 (A 或 B))
1 0 0 1 0
1 1 0 0 1
0 1 1 0 1
0 0 0 1 0
  • 结果D表达式: D = A 异或 B 异或 C1
  • 进位C表达式:​ C = (A 与 B) 或者 (C1 与 (A 或 B))

以上过程把十进制 3 + 6 的算术运算完全转化为了逻辑问题,所以只要找到可以自动判定真假的某种机器即可实现自动计算。

计算机发展于电子学和数学


上个世纪电子学开始广泛发展,晶体管的诞生为理论上的「自动的机器」指明了新的方向🧭。晶体管有两个状态,分别是:

  • 导通:即可以代表二进制的1
  • 截止:即可以代表二进制的0

接着,数学家&电子学家们用多个晶体管构成门电路:

  • 与门
  • 非门
  • 或门
  • 异或门

这样就实现了可以「自动判定逻辑问题的真假的设备」,同时数学家和电子学家们又通过多个门电路实现了半加器、全加器、全减器、乘法器等实现算术运算:

  • 半加器
  • 全加器
  • 全减器
  • 乘法器

这样就诞生了世界上第一台真正意义的计算机。

具有语义的编程语言


上面诞生了硬件,也就是真正意义的计算机。问题是如何编写程序?我们的程序其实就是门电路中的晶体管不断的运行导通1和截止0两个状态之间,对应的文本代码其实就是数字0和1,所以早期的代码就是直接编写0和1的代码。但是对于人类阅读友好的永远是具备描述性的具备可读性的文本,于是诞生了更适合人们阅读和编写的汇编代码。

汇编代码


人们把中央处理器CPU可以运行的一系列指令集合分别命名了具备可读性的文本,这样就诞生了由助记符和操作数等组成的汇编语言。通常来看CPU一般具备四类指令,分别为:

  • 数据传输指令
  • 运算指令
  • jmp指令
  • call/return指令

简单来看就这些类别的指令,分别给它们用更适合人类阅读文本替代0和1,比如MOV指令代表传输数据、jmp指令代表跳转到代码任意位置。

同时人们发明了编译器自动把汇编代码转换为0和1组成的机器代码。

子函数和函数库


同时人们发现编写程序过程中,发现经常会出现重复性的逻辑编写,比如算术平方根。为了提高效率和复用,就诞生了子函数和函数库的概念。

编程语言和集成开发环境


随着计算机技术和编程技术的快速发展,逐步诞生了更高效的编程语言、以及集成开发环境等等。

程序集合:操作系统


先来看看计算机的组成:

  • 输入设备
  • 输出设备
  • 中央处理器CPU:运行指令
  • 存储器:内存、磁盘等

最早工程师编写的程序是可以直接操作这些硬件设备的:

存在问题:每个编写程序的工程师都要实现对这些硬件的操作,存在大量重复的工作,以及安全性等等问题。

于是对硬件设备的操作进行统一的封装,比如对输入/输出设备的操作,对磁盘的操作等等 这样就形成了一系列统一的API以及应用程序,提升了开发效率也保证了安全等等。

这就是操作系统:封装了一系列对计算机硬件设备操作的API和应用程序的集合。

结语


计算机简易的关键发展点:

编程语言的关键发展点:

本文从一些关键点回顾了计算机的发展史,为后面理解程序的运行打好基础。下篇文章我们就来看看:

我们写的代码到底是什么?

TIGERB