内存、地址与十六进制

内核代码里到处是 0x80000000 这样的数字。读懂它们只需要三件事:内存怎么编号、十六进制怎么读、一个变量占几个字节。

1. 内存是一排带编号的格子

把内存想成一条很长的街,每个门牌住一个字节(byte,8 个二进制位,能存 0–255 的数)。门牌号就是地址,从 0 开始往上数。

你写 int x = 5;,编译器会给 x 分几个连续的格子,把 5 存进去。程序里“读 x”,CPU 实际做的是“去某个地址取几个字节回来”。变量名只存在于源代码里,CPU 只认地址。

2. 十六进制:地址的写法

地址是很大的整数,用十进制写又长又看不出规律,所以习惯写成十六进制,前面加 0x 表示“这是十六进制”。

十六进制逢十六进一,用 0–9 再加 a–f 表示 10–15:

十进制091015162554096
十六进制0x00x90xa0xf0x100xff0x1000

它好用的原因是:一个十六进制位正好对应 4 个二进制位,两个十六进制位正好是一个字节。0xff 就是二进制 1111 1111,一个字节能存的最大值。换算二进制时,每一位单独换成 4 位就行,不用做除法(见位运算)。

几个会反复见到的数:

3. 一个变量占几个字节

sizeof(类型) 告诉你某种类型占几个字节。在 64 位 RISC-V 和你的 x86-64 电脑上:

类型字节课程框架里的别名
char1int8、uint8
short2int16、uint16
int4int32、uint32
long long、指针8int64、uint64

前缀 u 表示 unsigned(无符号):不存负数,全部位都用来表示大小。地址、寄存器的值都用无符号类型存。框架在 src/kernel/arch/type.h 里用 typedef 起了这些短名字,typedef unsigned long long uint64; 的意思就是“以后 uint64 就代表 unsigned long long”。

一个 int 占 4 个格子,它的地址指的是第一个格子的门牌号。数组的元素挨着放,int a[3] 的三个元素地址依次相差 4。

4. 有些地址不是内存

硬件可以把某些地址“接”到设备上。往普通地址写字节,是存进内存;往 0x10000000 写一个字节,其实是交给了串口芯片,它会把这个字符发到终端上。这种做法叫 MMIO(内存映射 I/O):用读写内存的指令来操作设备。Lab1 的串口驱动就是这么工作的。

动手:看变量住在哪

cd prereq/examples
make memory

观察:name[0] 到 name[3] 的地址每次加 1,因为 char 占 1 字节;内容 0x61 是字符 'a' 的编码。g(全局变量)和 x(局部变量)的地址差得很远,它们放在内存的不同区域,Lab1 讲栈时会看到局部变量放在哪。

每次运行地址都可能不同,这是 Linux 的安全机制(随机化布局)。我们的内核没有这个机制,地址是固定的。