前言

最近在b站看到了马士兵老师的 netty 课程,虽然放出来的大概约等于试看版,但是干货还是挺多的,一直深入到了计算机的组成原理,从软件到硬件把原理给你讲明白了,结合《深入理解计算机系统》这本书来学习效果更佳。

这是这个课程的第一部分:讲了计算机底层发生中断时的过程,以及为什么 IO 的效率比较低(这个只给了结论)


计算机组成:

硬件:

  • CPU
  • 内存
  • 硬盘
  • 网卡
  • ...

软件

  • 操作系统
  • 其他应用程序

软件是计算机的灵魂,而在 Linux 操作系统中,你编写的程序被看做一个「文件」。

【开机之后计算机中的第一个程序是什么?】

开机之后会将 kernel 加载到内存中 —— 第一个程序是 Linux 内核。

内核的作用:

  • 暴露系统调用 system call , 让其他应用程序调用硬件功能。

调用计算机底层硬件,其他应用程序如果需要使用硬件,跟「内核」进行交互就可以,不需要程序自己编写操控硬件的代码。

但是由于「安全」的需求,并没有上面说的这么简单。

  • 如果放任应用程序直接调用底层内核,那么可能导致某块内存区域被恶意软件修改,引起安全隐患。

于是有了「对策」:当 CPU 将 内核加载到内存中之后,会从「实模式」转换为 「保护模式」。

概念:

  • 实模式 :实际物理内存地址模式。

  • 保护模式 :将 内存地址 进行了一次转换,隐藏真实内存地址,用户无法访问真实内存地址。

这里画了个图,也就是将内存一分为二,内核可以访问真实的内存地址,其他应用程序无法访问,只能访问被转换后的内存地址。

前后矛盾

之前说 内核的意义就是分层解耦,让程序可以调用操作系统,从而操作底层硬件。

但是由于安全的原因,保护模式程序又无法获取真实的内存地址,这就产生了「矛盾」 —— 「内核既要保证 安全 ,又要让应用程序能够调用系统功能」。

怎样解决?

引入另外一个引申概念:「中断」,这里的中断其实就是 「CPU时间片」。

下面这张图包含了:

什么是中断 :

  • 物理层面: 发生晶振,每次晶振都是给 CPU 一个电信号,让 CPU 知道要中断了
  • 逻辑层面: 中断发生之后应用程序如何进行切换 —— 操作系统内核中包含了切换的逻辑

中断发生之后 CPU 怎么知道要做什么?

内核中维护了一个「中断向量表」,以键值对的形式存在,发生中断之后 CPU 根据中断号找到对应的回调函数,然后执行对应的操作。

谁可以发起中断?

【能发起中断的软硬件很多】

  • IO设备:键盘,鼠标
  • 网卡
  • 硬盘
  • 应用程序

【中断概念示意图:】

image-20201130152622763

Java 中调用 system.out.println("hello world") 底层发生了什么

系统调用示意图:

【系统调用文字描述】

  • Java 程序想要输出一条语句 "hello world" ,但是要把这条语句打印到显示器上,需要 「内核」 来完成,于是, Java API 调用了 系统 API
  • 内核接收到了这个调用,然后将一个函数写入 「CPU寄存器」 , 这个函数就是 write("hello world") ,但是仅仅有一个函数是无法完成切换的,还要有相对应的 「CPU指令」,于是内核紧跟着给了 CPU 一个指令 「int0x80」,这里的 int 代表的就是 「CPU指令
  • CPU 去 「内核」 中的 「中断向量表」中寻找 0x80 代表的 「回调函数」,并且执行该函数
  • 这个例子中的 「回调函数」 的内容就是进行 「切换」,暂存当前进程的信息,从「用户态」 切换到 「内核态」,切换完成之后,由 kernel 内核完成 执行 write("hello world") 这个函数
  • 调用完成, hello world 被打印到了 控制台上。

结论

IO」 一定涉及系统调用,而「系统调用」的成本相对较高。

问题

  1. 为什么成本高?

  2. 是否存在与系统调用相关联的概念?

答案

  1. 因为 IO 涉及到了硬件调用,而硬件调用就涉及到了 「内核」,涉及到内核之后就存在着从 「用户态」 转为 「内核态」 的转换与切换,所以成本较高。
  2. 与 「系统调用」 相对应的是 「函数调用」 ,函数调用指的是在 同一个应用程序中,给到 CPU 一个指令,因为是同一块内存区域,所以不会涉及到内核,可以直接执行。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

最是人间留不住,曾是惊鸿照影来。