generated from cotes2020/chirpy-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
140 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
--- | ||
layout: post | ||
title: CS:APP读书笔记-1-计算机系统漫游 | ||
date: 2024-03-07 21:55 +0800 | ||
categories: [CS:APP] | ||
tags: [读书笔记] | ||
--- | ||
# 第一章:计算机系统漫游 | ||
|
||
计算机是一个很庞大的东西,在初次面对的时候难免束手无策,不知从何下手。我们常见的操作系统是Windows,除了它其实还有Linux、Unix、MAC、RTOS这些分类。后续的讲解,将会以Linux[^linux是什么]作为目标,但是请放心,虽然名字各不相同,但是它们的基本组成都还是保持着一致,不必担心知识迁移的问题。 | ||
|
||
|
||
|
||
## 文件的表示 | ||
|
||
我们常说的源代码,其实就是一个文本文件。比如,下面这个最经典的例子。 | ||
|
||
```c | ||
#include <stdio.h> | ||
|
||
int main(int argc, char *argv[]) | ||
{ | ||
printf("Hello,world!\n"); | ||
return 0; | ||
} | ||
``` | ||
我们可以把这个内容保存在任意位置,例如`/home/csapp/code/hello.c`这个路径。 | ||
使用`hexdump -C hello.c`我们可以看到出现如下的一些字符,你可能好奇这都是什么,为什么上面我们可读的字符变成了下面这一串难以辨认的数字和字母。 | ||
```bash | ||
00000000 23 69 6e 63 6c 75 64 65 20 3c 73 74 64 69 6f 2e |#include <stdio.| | ||
00000010 68 3e 0a 0a 69 6e 74 20 6d 61 69 6e 28 69 6e 74 |h>..int main(int| | ||
00000020 20 61 72 67 63 2c 20 63 68 61 72 20 2a 61 72 67 | argc, char *arg| | ||
00000030 76 5b 5d 29 0a 7b 0a 09 70 72 69 6e 74 66 28 22 |v[]).{..printf("| | ||
00000040 48 65 6c 6c 6f 2c 77 6f 72 6c 64 21 5c 6e 22 29 |Hello,world!\n")| | ||
00000050 3b 0a 09 72 65 74 75 72 6e 20 30 3b 0a 7d 0a |;..return 0;.}.| | ||
0000005f | ||
``` | ||
|
||
接下来请使用`man ascii`,应该能看到这样一张表。注意到了吗,他们之前存在映射关系,23、69正对应着`#`与`i`。 | ||
|
||
```bash | ||
2 3 4 5 6 7 | ||
------------- | ||
0: 0 @ P ` p | ||
1: ! 1 A Q a q | ||
2: " 2 B R b r | ||
3: # 3 C S c s | ||
4: $ 4 D T d t | ||
5: % 5 E U e u | ||
6: & 6 F V f v | ||
7: ' 7 G W g w | ||
8: ( 8 H X h x | ||
9: ) 9 I Y i y | ||
A: * : J Z j z | ||
B: + ; K [ k { | ||
C: , < L \ l | | ||
D: - = M ] m } | ||
E: . > N ^ n ~ | ||
F: / ? O _ o DEL | ||
``` | ||
接下来,我们一个个的说明这里出现的命令所代表的含义: | ||
1. hexdump是一个命令行[^命令行是什么]工具,用来按照用户需要的格式来展示文件内容。 | ||
2. man ascii是一个用来显示关于ascii的帮助手册。你还可以使用`man hexdump`来查看关于这个命令的更多帮助。 | ||
除此外,你还可以使用hexdump去查看任意你有权限访问的文件,比如这个命令:hexdump -C \`which ls\` -n 64。请注意观察它们的区别。在右侧你不再能观察到大量的可读字符,相反,计算机同一用`.`来展示这些不可见字符。这就是我们用来区分文件的一种方式,类似`hello.c`的,我们称为文本文件。相对应的,类似`ls`这种文件的,我们称为二进制文件。 | ||
在计算机系统中,`23`代表着一个字节(byte),而每一个字节,由8个比特(bit)组成。这就是计算机内部存储数据的最基本单元。实际上,无论是磁盘文件、内存数据、可执行代码、可读文件、网络数据等等,它们本质上都只是一串比特,只不过由于我们的上下文(context)不同,我们对这些比特流/字节流产生了不同的解读方式。一个同样的字节序列,可以被解释成一个指令、一个无符号整数或者一个浮点数。 | ||
> 为什么是8bit,Google搜了一下,据说是和IBM的360系统有关。它们采用8bit,然后约定俗成。同时,我们也能从ascii表中注意到,它的大小恰好是0x80,由于计算机内部采用二进制表示,这刚好能占用8bit的空间。由于计算机的发展原因,一开始很多表示都是英文字符,所以也不需要很大的表示空间,8bit的空间就足够容纳。到现在,为了兼容多国语言表示,ascii已经不够使用,所以Unicode应运而生,不过这就是另一个故事了。有兴趣的朋友请自行搜索—UTF8。 | ||
同时,由于计算机内部是采用了二进制的表达方式,所以注定会存在一个问题:精度。 | ||
> 计算机一定是二进制吗?这也是一个历史问题,最开始的计算机采用晶体管,我们常说二极管,其实代表它只有两个状态:开和关,这两种状态很容易区分。其实历史上苏联也有过三进制计算机。我觉得还是由于接受度的问题,二进制计算机流传至今。 | ||
## 程序的翻译 | ||
当我们拥有了一份源代码,它还不能马上被我们执行。在Windows中我们常通过双击一个exe文件来试图执行,而在Linux我们通常使用`./FILE_NAME`的形式来执行一个可执行文件。需要从源代码转化到可执行文件,这中间我们还有几步路需要走一下,简化一下各自的功能描述,它们分别是: | ||
1. 预处理(cpp):将源代码展开,引入其他代码文件 | ||
2. 编译(ccl):将高级语言翻译成汇编 | ||
3. 汇编(as):将汇编语言翻译成机器指令 | ||
4. 链接、重定向(ld):解决代码中指向的问题,链接外部库 | ||
也许你曾经听到过**逆向**这个说法,至少在C语言这个层面,它代表着上述四步中倒着处理的第三步和第二步—反汇编(disassemble)和反编译(decompile)。 | ||
通过下面这个命令,我们可以编译这个`hello.c`并尝试运行它: | ||
```bash | ||
gcc -o hello hello.c | ||
./hello | ||
``` | ||
gcc是我们后面会经常用到的一个工具,它的全名是`GNU Compiler Collection`,支持多种语言的编译过程,隐藏了上述的四步,让我们能通过一个命令就获得可执行文件。当我们决定执行`hello`的时候,我们实际上把这个可执行文件放在内存上,并开始按照顺序执行这里面包含的指令。 | ||
## 程序是怎么被执行的 | ||
在我们输入`./hello`的时候,我们实际上是按压键盘,然后电信号被翻译,触发中断,传送到CPU进行处理。随后将对应的字符再输出到屏幕。当我们按下`回车`时,`shell`知道我们结束了输入,并开始对之前的输入进行解析。寻找`hello`,将其拷贝到内存中,跳转到`main`函数,CPU开始读取指令并执行。这样说还是比较抽象,我们需要对计算机的硬件设备做一个非常简单的概括: | ||
1. 总线(bus):负责携带信心并传送到各个部件 | ||
2. I/O设备:负责Input/Output的设备,通常代表信息的输入和输出 | ||
3. 内存(memory):通常这是一块DRAM(动态随机存储设备),断电后信息不保存。用来保存执行的程序和所需数据。 | ||
4. 处理器(CPU):用来解释指令并执行的单元。 | ||
现在,我们可以再来描述下`hello`的执行过程了,通过I/O设备,CPU知道我们将会执行的文件,然后将其加载到内存上,并开始执行,在这中间,总线担任了信息传送的功能。 | ||
## 操作系统扮演的角色 | ||
在我们上述的描述过程中,我们只需要敲动键盘,就能完成这一复杂的过程。我们不需要亲自去过问该怎么调用cpu、该怎么去寻找文件又该怎么控制输出。这一切都是因为操作系统提供的便利。 | ||
操作系统管理着硬件,同时按照层级关系,提供可用的抽象接口。它屏蔽了复杂的接口、协议。我们不再需要为那些底层的适配苦恼,相反,我们只需要使用操作系统给我们提供的抽象接口就可以完成对硬件的操作。 | ||
抽象是一个很有力的概念,我们可以把它理解成一个封装,用户不需要关心内部的实现,只需要知道,这个封装对外提供了什么接口以供使用,并且这个使用效果是什么即可。比如OS中的文件系统(file system),每一个文件系统的细节(squash fs\ext4\ext3\ramfs\sysfs\procfs……)都不同,但是我们可以通过同样的`read`,`write`来操作。 | ||
> 如果你想查看一下自己目前使用了多少文件系统,mount命令可以让你一窥。 | ||
在操作系统中,我们有以下比较高级的抽象概念: | ||
1. 进程/线程:这是os对运行中的程序一种抽象。其中线程是对进程的进一步划分,一种更小的执行单元。 | ||
2. 虚拟内存:对进程系统的一种抽象,看起来拥有完整的内存可用权。 | ||
3. 文件:最重要的一种抽象,我们常说Linux中的一切都是文件,或许就来源于此。不仅仅是前面的文件文件,甚至I/O设备、磁盘、进程状态信息(procfs)、系统本身信息(sysfs)、网络接口都可以被看成文件。 | ||
在此之上,系统之间通过网络进行通信,它们互相交换数据,才发展到我们现在的互联网。 | ||
本书第一章内容非常丰富,概括性的快速带我们走过了计算机,并介绍了其中的关键性质和定义。这里仅仅是我阅读后的小部分理解,有兴趣的朋友可以自己阅读第一章,加深了解。 | ||
[^linux是什么]: 请参考 https://linux.do/t/topic/17577/130,并持续催更。 | ||
[^命令行是什么]: 参考 https://zh.wikipedia.org/wiki/%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%95%8C%E9%9D%A2 | ||