计算机系统漫游
1. 信息就是位+上下文
- 一些概念:
源程序(源文件)
位(比特)
字节
上下文
文本文件:只由ASCII字符构成的文件,所有其他文件都称为二进制文件
二进制文件
- 基本思想:系统中所有的信息都是由一串比特表示的,区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串、或者机器指令。
2. 程序被其他程序翻译成不同格式
- 一些概念
编译器驱动程序
翻译
预处理器、源程序、C程序
编译器、汇编语言程序
汇编器、机器语言指令、可重定向目标程序
链接器、可执行目标程序(可执行目标文件)
编译系统
- 在Unix系统上,从源文件到目标文件的转化是由编译器驱动程序完成的,翻译的过程可分为四个阶段完成。执行这个四个阶段的程序(预处理器、编译器、汇编器、链接器)一起构成了编译系统。
- 预处理阶段
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。预处理器读取系统头文件内容并把它直接插入程序文本中,得到另一个C程序,通常是以.i作为文件扩展名。(hello.c-->hello.i)
- 编译阶段
编译器(cll)将文本文件hello.i翻译成hello.s,它包含一个汇编语言程序。(hello.i-->hello.s)
- 汇编阶段
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定向目标程序(relocatable object program),并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,包含的是指令编码。
- 链接阶段
链接器(ld)负责合并,结果得到一个hello文件,它是一个可执行目标文件(简称为可执行文件),可以被加载到内存中,由系统执行。
何为合并:hello程序调用了printf函数,它是每个C编译器都提供的标准C库的一个函数。printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hello.o程序中。
3. 处理器读并解释存储在内存中的指令
- 一些概念
总线、字、字长
I/O设备、磁盘
主存、动态随机存取存储器、地址
处理器、程序计数器、指令集架构、寄存器文件(register file)、算数/逻辑单元(ALU)
加载、存储、操作、跳转
- 系统的硬件组成
1)总线
通常总线被设计成传送定长的字节块,也就是字(word)。字中的字节数(即字长)是一个基本的系统参数,各个系统中都不尽相同。
2)I/O设备
用户输入:键盘、鼠标
用户输出:显示器
长期存储数据和程序:磁盘驱动器(磁盘),最开始,可执行程序hello就存放在磁盘上。
3)主存
临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。
物理上来说:由一组动态随机存取存储器(DRAM)芯片组成
逻辑上来说:存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引),这些地址是从0开始的。
4)处理器
解释(或执行)存储在主存中指令的引擎。
存储器的核心:程序计数器(PC),一个大小为一个字的存储设备(或寄存器)。在任何时候,PC都指向主存中的某条机器语言指令(即包含该指令的地址)。
指令集架构决定指令执行模型,CPU看上去是按照这个模型操作的。在这个模型中,指令按照严格的顺序执行,而执行一条指令包含一系列步骤。指令指示的简单操作不多,它们围绕主存、寄存器文件(register file)和算数/逻辑单元(ALU)进行。
寄存器文件:一个小的存储设备,由一些单个字长的寄存器组成,每个寄存器都有唯一的名字。
ALU:计算新的数据或地址值。
一些简单操作的例子:
- 加载:从主存复制一个字或者一个字节到寄存器,以覆盖寄存器原来的内容。
- 存储:从寄存器复制一个字或者一个字节到主存的某个位置,以覆盖原来这个位置上的内容。
- 操作:把连个寄存器的内容复制到ALU,ALU对这两个字做算数运算,并将结果存放到一个寄存器,以覆盖该寄存器中原来的内容。
- 跳转:从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,以覆盖PC中原来的值。
4. 高速缓存至关重要
- 高速缓存存储器(cache memory):通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在快速的高速缓存中完成。
- L1和L2高速缓存是用一种叫静态随机访问存储器(SRAM)的硬件技术实现的。
5.存储设备形成层次结构
- 主要思想:上一层的存储器作为低一层存储器的高速缓存。
6. 操作系统管理硬件
1)可以把操作系统看成是应用程序和硬件之间插入的一层软件。(中间层)
2)操作系统的两个基本功能:
- 防止硬件被失控的应用程序滥用。
- 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
3)操作系统通过几个基本的抽象概念(进程、虚拟内存、文件)来实现这两个功能:
- 文件:对I/O设备的抽象表示i。
- 虚拟内存:对主存和I/O设备的抽象表示。
- 进程:对处理器、主存和I/O设备的抽象表示。
4)进程
进程是操作系统对正在运行的程序的一种抽象。 一个操作系统可以同时运行多个进程,而每个进程都好像在独占地使用硬件。
并发(concurrency):并发运行是说进程的指令和另一个进程的指令是交错执行的。并发执行的概念和处理核心数没有关系,只要两个逻辑控制流在时间上有交错或重叠的部分都成为并发。
并行(parallelism):并行是并发的一个特例,即并行执行的两个进程一定是并发的。我们称两个进程的逻辑控制流是并行的,是指它们并发的运行在不同的处理器或处理器核上。
上下文:操作系统保持跟踪进程运行所需的所有状态信息。
上下文切换:操作系统实现交错执行的机制成为上下文切换。
内核:内核是操作系统代码常驻内存的部分,是系统管理全部进程所用代码和数据的集合。从一个进程到另一个进程的切换是由操作系统内核管理的。当应用程序需要操作系统的某些操作时,比如读写文件,它就执行一条特殊的系统调用指令,将控制权传递给内核。然后内核执行被请求的操作并返回应用程序。
注意:内核不是一个独立的进程。
补充资料:
内核是内存中的一个或一组进程么?
链接:https://www.zhihu.com/question/269487854/answer/348179125
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以Linux内核为例,的确可以把内核看成内存管理子系统、进程调度子系统、文件子系统、网络子系统等许多子系统的集合,但是这些所谓的子系统是人为地划分,实际上Linux内核的本质就是一个二进制文件,这个文件驻留在内存里。
想理解Linux内核是个什么东西,我们不妨想一下进程在什么时候才能感知到内核的存在。在malloc分配内存的时候,最终会调用内核的sys_mmap
系统调用来申请虚拟内存空间;在fork创建子进程的时候,最终会调用内核的sys_fork
来复制父进程;在open打开文件的时候,最终会调用内核的sys_open
来进行文件系统底层操作。诸如此类的过程都可以感知到内核的存在,那这些过程有什么共同点呢?是的,他们都提到了一个词——“系统调用”。这就是进程与内核交互的本质。内核就是一个二进制文件,就是一块代码加数据,平时驻留在内存里啥也不干,它也没法干,因为它又不是进程。等到有进程向做一些高特权的操作时,就调用一下系统调用,系统调用有个入口程序,从这里开始执行的就是内核代码了,这样的过程我们成为陷入内核。当要执行的高特权操作完成后,系统调用会立即返回,然后进程继续执行。我们可以看到,内核在进程需要时出现,又在操作完成后迅速离开,而内核出现所途径的传送门就叫做系统调用。
所以Linux内核是什么,它什么也不是,既不是特殊的任务,也不是普通的进程,它就是一段代码加数据的二进制文件,驻留在内存里等着系统调用去执行它的部分代码。为了专业起见,我们又给这样的内核起了个名字,叫做宏内核,“宏”的意思是“大”,为什么大呢,因为内核把所有子系统都集成到自身里面去了。看起来有点很抽象,有点笨重,所以有人更喜欢一种叫微内核的东西,比如Minix,内存管理和文件系统以及IO驱动都是单独的高特权进程,非常具象,各个子系统进程间通过消息来交流,内核的作用就削减为一个消息中转站了,所以“微”的意思是“小”,为什么小呢,因为所有子系统都被分离到内核外面去了。
讲到这里应该算是讲明白了。
下面是Linux内核初始化完成后对初始进程的创建操作:
开机后,内核在初始化的最后阶段会创建0
号进程,其进程结构体名字是init_task
,这个进程是完全靠代码一步一步手动创建出来的而不是fork出来的。0
号进程会fork出一个子进程,称作init
进程,此后0
号进程主动进行进程调度,然后1
号进程开始运行,之后1
号进程就开始创建其他进程逐渐启动系统服务。0
号进程在完成它的使命后就会变成idle
进程,如果内核发现没有什么进程可供调度了那就去运行idle
进程。所以当系统启动后从进程的角度来讲, 0
号进程可以认为是内核的代表。系统之所以在不断的保持运行,是因为1
号进程这个祖宗进程创建了其他一切进程,内核的调度器又在CPU时钟中断的触发下不断的切换进程。比较抽象,凑合看吧。