test.c
进行预处理成test.i
,例如将头文件、宏命令补全等等test.s
包含了一个汇编语言程序test.o
总线
I/O 设备
输入/输出
控制器、适配器
主存
存放程序指令和数据
一个字节数组,每字节具有唯一地址
处理器
解释(执行)指令
程序计数器(PC)指向内存中某条指令
执行操作:加载、存储、操作(运算)、跳转
hello.c
程序运行样例:hello
命令上一层的存储器作为下一层储存器的高速缓存
当我们对系统某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。
加速比计算: S = 1 ( 1 − α ) + α / k S={1\over(1-α)+α/k} S=(1−α)+α/k1
α : 该部分占比时间 ; k : 性能提升比例 \alpha : 该部分占比时间; k : 性能提升比例 α:该部分占比时间;k:性能提升比例
0
屏蔽, F
取值。x << k
: 右端补 k k k个 0 0 0x >> k
unsigned
):左端补 k k k个 0 0 0signed
):左端补 k k k个原最高位值Binary to Unsigned int:
Binary to Two’s-complement:
除去最高位取负号运算以外,其他位按权运算
最高位决定了符号(可以以符号位进行理解), 1 1 1为负
非负数时 B 2 U w B2U_w B2Uw 和 B 2 T w B2T_w B2Tw 值相同
范围: [ − 2 w − 1 , 2 w − 1 − 1 ] [-2^{w-1},2^{w-1}-1] [−2w−1,2w−1−1]
000...000 = 0 000...000=0 000...000=0; 111...111 = − 1 111...111=-1 111...111=−1
当表达式中的unsigned
和 signed
混用时,signed
将被强制转换为unsigned
注意:sizeof()
本身就是 unsigned long
下面这个例子将会发生内存错误:
unsigned i;
for (i = n - 1; i >= 0; --i) // i 总是大于零,无法跳出循环
f(a[i]);
零扩展
符号扩展,假如是负数,扩展 1 1 1;假如是正数,扩展 0 0 0
删除最高位。本质上其实是取模运算。 补码运算来说,如果最高位有很多个重复的1
,则可以删除前面的进行运算。
定义: w w w 位无符号整数 x x x, y y y 之和:将 x + y x+y x+y 截断成 w w w 位后的无符号数。
溢出:发生了截断的操作。
运算符: + w u +_w^u +wu
无符号数求反: − w u -^u_w −wu
/* 检测是否会溢出的函数 */
int uadd_ok(unsigned int x, unsigned int y)
{
unsigned int sum = x + y;
return sum >= x;
}
定义: w w w 位补码数 x x x, y y y 之和:将 x + y x+y x+y 截断成 w w w 位后的补码数。
运算符: + w t +_w^t +wt
/* 检测是否会溢出的函数 */
int taad_ok(int x, int y)
{
int sum = x + y;
int neg_over = x < 0 && y < 0 && sum >= 0;
int pos_over = x >= 0 && y >= 0 && sum < 0;
return !neg_over && !pos_over;
}
运算符: − w t -_w^t −wt
− x = -x= −x= ~ x + 1 x+1 x+1
运算符: ∗ w u *_w^u ∗wu
做法:按位乘法运算之后,截取最低的 w w w 位
运算符: ∗ w t *_w^t ∗wt
做法:按位乘法运算之后,截取最低的 w w w 位
低位等价性:补码与无符号的乘法产生的位模式相同
/* 检查是否溢出的函数 */
int tmult_ok(int x, int y)
{
int p = x * y;
return !x || p / x == y;
}
乘以二的幂: x 2 k x2^k x2k = x << k
乘以不是二的幂 –> 乘法分配律拆成二的幂相加/减的形式
对于无符号数来说,x >> k
逻辑右移,可以产生数值 ⌊ x / 2 k ⌋ \lfloor x/2^k \rfloor ⌊x/2k⌋;这个操作是向零舍入的。
对于补码数来说,x >> k
算术右移,来产生数值 ⌊ x / 2 k ⌋ \lfloor x/2^k \rfloor ⌊x/2k⌋;这个操作对负数是向下舍入的,不好。因此使用偏置(偏量为 y − 1 y-1 y−1 )来解决:(x + (1 << k) - 1) >> k
;这个操作对负数是向零舍入的,好!总结就是:x < 0 ? (x + (1 << k) - 1) >> k) : (x >> k)
。
( − 1 ) s M 2 E (-1)^s\ M\ 2^E (−1)s M 2E
其中, s s s为符号位, E E E为阶码(exp), M M M为尾数(frac)
对于不同精度,有不同的长度格式:
当 e x p ≠ 000 … 0 exp≠000…0 exp=000…0 和 e x p ≠ 111 … 1 exp≠111…1 exp=111…1
E 的值: E x p − B i a s Exp-Bias Exp−Bias (阶码值 = 阶码字段 - 偏置值)
其中, B i a s = 2 k − 1 − 1 Bias=2^{k-1}-1 Bias=2k−1−1, k k k 为阶码字段的位数。单精度为 127 127 127,双精度为 1023 1023 1023
M 的值: 1. f r a c 1.frac 1.frac (小数值 = 小数字段 + 1)
默认方式,向偶数舍入。在二进制中,最低有效位 0 0 0是偶数, 1 1 1是奇数。 < 1 / 2 <1/2 <1/2 - 舍; > 1 / 2 >1/2 >1/2 - 入; = 1 / 2 =1/2 =1/2 - 偶数舍入(要么丢掉,要么看成1,不能显式修改前面的数字位,只能通过进位完成)
( − 1 ) s 1 M 1 2 E 1 ∗ ( − 1 ) s 2 M 2 2 E 2 = ( − 1 ) s M 2 E (-1)^{s_1}\ M_1\ 2^{E_1} * (-1)^{s_2}\ M_2\ 2^{E_2} = (-1)^{s}\ M\ 2^{E} (−1)s1 M1 2E1∗(−1)s2 M2 2E2=(−1)s M 2E
M = M 1 ∗ M 2 M=M_1*M_2 M=M1∗M2
E = E 1 + E 2 E=E_1+E_2 E=E1+E2
( − 1 ) s 1 M 1 2 E 1 + ( − 1 ) s 2 M 2 2 E 2 = ( − 1 ) s M 2 E (-1)^{s_1}\ M_1\ 2^{E_1} + (-1)^{s_2}\ M_2\ 2^{E_2} = (-1)^{s}\ M\ 2^{E} (−1)s1 M1 2E1+(−1)s2 M2 2E2=(−1)s M 2E
加法、乘法运算具有交换性,但是没有结合性
单调性
不可分配性
float
< int
< double
gcc -Og -S
objdump -d " using object-dump
gdb " using GDB
disassemble
通用目的寄存器/整数寄存器
%rsp
只可用于栈指针的存储
movq
其中,movq
会先将32位扩展成64位的值,而 moveabsq
可以直接以任意64位立即数作为源操作数
void swap(long *xp, long *yp)
{
long t0 = *xp;
long t1 = *yp;
*xp = t1;
*yp = t0;
}
swap:
movq (%rdi), %rax
movq (%rsi), %rdx
movq %rdx, (%rdi)
movq %rax, (%rsi)
ret
decode1:
movq (%rdi), %r8
movq (%rsi), %rcx
movq (%rdx), %rax
movq %r8, (%rsi)
movq %rcx, (%rdx)
movq %rax, (%rdi)
ret
void decode1(long *xp, long *yp, long *zp)
{
long x = *xp;
long y = *yp;
long z = *zp;
*yp = x;
*zp = y;
*xp = z;
}
注意,x86-64
中,栈向低地址方向发展。
每一个指令,都对应了 b
, w
, l
, q
四种长度相关的变种
leaq
lea
时,传输的是整个地址,而不是地址中的值leaq (%rdi), %rax <-> a = &(*p);
一元
incq
decq
negq
notq
二元
addq
subq
imulq
xorq
orq
andq
salq
shlq
sarq
- arithmaticshrq
- logical这种操作支持全128位结果,具体是使用了 %rdx
和 %rax
,用于储存「乘法:高64位|低64位」/「除法:余数|商」
long arith(long x, long y, long z)
{
long t1 = x + y;
long t2 = z + t1;
long t3 = x + 4;
long t4 = y * 48;
long t5 = t3 + t4;
long rval = t2 * t5;
return rval;
}
arith:
leaq (%rdi, %rsi), %rax # t1
addq %rdx, %rax # t2
leaq (%rsi, %rsi, 2), %rdx
salq $4, %rdx # t4
leaq 4(%rdi, %rdx), %rcx # t5
imulq %rcx, %rax # rval
ret
CF
:进位标志(无符号数)- (unsigned) t < (unsigned) a
ZF
:零标志 - t == 0
SF
:符号标志 - t < 0
OF
:溢出标志 - (a < 0 == b < 0) && (t < 0 != a < 0)
a == b
(a-b) < 0
(as signed)(a>0 && b<0 && (a-b)<0) || (a<0 && b>0 && (a-b)>0)
a&b == 0
a&b < 0
SET
将寄存器/内存地址的低位字节设置成 0
or 1
Example:
int comp (data_t a, data_t b)
{
return a < b;
}
/*
comp:
cmpq %rsi, %rdi 比较 a:b
setl %al 将 %eax 低位设置成 0 或 1
movzbl %al, %eax 清除 %eax 多余部分
ret
*/
JUMP
条件跳转到程序其他部分.L3
jmp *%rax / jmp *(%rax)
if-else
if ()
else
在汇编语言中这样处理
t =
if (!t)
goto flase;
goto done;
false:
done:
Example:
void cond(long a, long *p)
{
if (p && a > *p)
*p = a;
}
/*
cond:
testq %rsi, %rsi
je .L1
cmpq %rdi, (%rsi)
jge .L1
movq %rdi, (%rsi)
.L1:
rep; ret
*/
void goto_cond(long a, long *p)
{
if (p == 0)
goto done;
if (*p >= a)
goto done;
*p = a;
done:
return;
}
cmov
有条件地传送数据(BAD!!!)v = test-expr ? then-expr : else-expr;
v = then-expr;
ve = else-expr;
t = test-expr;
if (!t) v = ve;
将条件操作的两种结果计算出来,最后根据条件是否满足选取一个。
Example:
long absdiff(long x, long y)
{
long result;
if (x < y)
result = y - x;
else
result = x - y;
return result;
}
/*
absdiff:
movq %rsi, %rax
subq %rdi, %rax rval = y-x
movq %rdi, %rdx
subq %rsi, %rdx eval = x-y
cmpq %rdx, %rax
ret
*/
long cmovdiff(long x, long y)
{
long rval = y-x;
long eval = x-y;
long ntest = x >= y;
if (ntest) rval = eval;
return rval;
}
do-while
do
Body
while (Test);
loop:
Body
if (Test)
goto loop
Example:
long fact_do(long n)
{
long result = 1;
do
{
result *= n;
n = n - 1;
} while (n > 1);
return result;
}
/*
fact_do:
movl $1, %eax
.L2:
imulq %rdi, %rax
subq $1, %rdi
cmpq $1, %rdi
jg .L2
rep; ret
*/
long fact_do_goto(long n)
{
long result = 1;
loop:
result *= n;
n = n - 1;
if (n > 1)
goto loop;
return result;
}
while
while (Test)
Body
// jump to the middle
goto test;
loop:
Body
test:
if (Test)
goto loop;
done:
// guarded-do
if (!Test)
goto done;
loop:
Body
if (Test)
goto loop;
done:
Example:
long fact_while(long n)
{
long result = 1;
while (n > 1)
{
result *= n;
n = n - 1;
}
return result;
}
/*
fact_while:
movl $1, %eax
jmp .L5
.L6:
imulq %rdi, %rax
subq $1, $rdi
.L5:
cmpq $1, %rdi
jg .L6
rep; ret
*/
// jump to the middle
long fact_while_jm_goto(long n)
{
long result = 1;
goto test;
loop:
result *= n;
n = n - 1;
test:
if (n > 1)
goto loop;
return result;
}
// guarded do
long fact_while_gd_goto(long n)
{
long result = 1;
if (n <= 1)
goto done;
loop:
result *= n;
n = n - 1;
if (n != 1)
goto loop;
done:
return result;
}
for
switch
语句call label
%rip
rep; ret
对于数据的存储
每次调用一个新的嵌套函数,只需要往栈中不断压入即可。FILO
%rbx
rax
rdi, …, %r9
%r10, %r11
%rbx, %r12, %r13, %r14, %r15
%rbp
%rsp
i
在内存中的地址为:p + i * sizeof(T)
(%rdx, %rcx, 4)
-> a[4]
C语言 | 汇编代码 |
---|---|
p |
move %rdx, %rax |
p[0] |
move (%rdx), %rax |
p[i] |
movl (%rdx, %rcx, 4), %eax |
&p[i] |
leaq (%rdx, %rcx, 4), %rax |
p + i - 1 |
leaq -4(%rdx, %rcx, 4), %rax |
二维数组的元素行首地址在内存中的表示:A + i * (j * sizeof(t))
Example:
int get_univ_digit(size_t index, size_t digit)
{
return univ[index][digit];
}
/*
salq $2, %rsi # 4*digit
addq univ(,%rdi,8), %rsi # p = univ[index] + 4*digit
movl (%rsi), %eax # return *p
ret
*/
struct
在内存中顺序存储,记录下每个元素的位置并通过偏置进行访问。
struct rec
{
int a[4];
int i;
struct rec *next
};
/* 汇编语言
.L11: # loop:
movslq 16(%rdi), %rax # i = M[r+16]
movl %esi, (%rdi,%rax,4) # M[r+4*i] = val
movq 24(%rdi), %rdi # r = M[r+24]
testq %rdi, %rdi # Test r
jne .L11 # if !=0 goto loop
*/
/* 优化后的等价C代码 */
void set_val(struct rec *r, int val)
{
while (r)
{
int i = r->i;
r->a[i] = val;
r = r->next;
}
}
需要进行数据对齐:
最大的类型的整数倍
运用多个DRAM芯片,对应不同地址单元。例如DRAM0对应第一个低位字节,DRAMx对应第x个低位字节
总线结构(抽象)
mov A, %eax
)
mov %rax, A
盘片——表面——磁道——扇区+间隙
影响因素
磁盘容量计算公式:每个扇区的字节数 * 每个磁道的扇区数 * 每个面的磁道数 * 每个盘面的表面数 * 总共盘片数
此处, 1 G B = 1 0 9 b y t e s , 1 T B = 1 0 12 b y t e s 1GB=10^9\ bytes, 1TB=10^{12}\ bytes 1GB=109 bytes,1TB=1012 bytes
顺序读写和随机读写的差距:
顺序读写只需要第一次 T a v g s e e k + T a v g r o t a t e T_{avg\ seek} + T_{avg\ rotate} Tavg seek+Tavg rotate 加上 圈数 × T m a x r o t a t e 圈数 \times T_{max\ rotate} 圈数×Tmax rotate,而随机读写每次都需要 T a v g s e e k + T a v g r o t a t e T_{avg\ seek} + T_{avg\ rotate} Tavg seek+Tavg rotate
磁盘控制器完成映射;固件完成快速表查找(盘面,磁道,扇区)
块-页的存储:写入页必须要移除整个块
顺序快,随机满;读取快,写入慢
对比机械硬盘:
时间局部性:【使用同一个变量进行操作】被引用过一次的内存位置很可能在不远的将来再被多次引用
空间局部性:【使用相邻位置的内存】被引用过的内存位置,它附近的内存位置很可能在不远的将来被引用
int i, sum = 0;
for (i = 0; i < n; ++i)
sum += v[i];
return sum;
sum
:时间局部性++i
:空间局部性k k k 层更小更快的存储设备作为$ k+1 $层更大更慢存储设备的缓存
每组只有一行地址构成的叫做直接映射高速缓存
假如不命中,将会移除旧的行并且用新的数据替代
步长 - 空间局部性
大小 - 时间局部性
一个例子:计算矩阵的内积
a-row, b- col, c-fixed
for (i=0; i<n; i++)
{
for (j=0; j<n; j++)
{
sum = 0.0;
for (k=0; k<n; k++)
sum += a[i][k] * b[k][j];
c[i][j] = sum;
}
}
Miss Rate: A-0.25, B-1.0, C-0.0
a-fixed, b-row, c-row
for (k=0; k<n; k++)
{
for (i=0; i<n; i++)
{
r = a[i][k];
for (j=0; j<n; j++)
c[i][j] += r * b[k][j];
}
}
Miss Rate: A-0.0, B-0.25, C-0.25
a-col, b-fixed, c-col
for (j=0; j<n; j++)
{
for (k=0; k<n; k++)
{
r = b[k][j];
for (i=0; i<n; i++)
c[i][j] += a[i][k] * r;
}
}
Miss Rate: A-1.0, B-0.0, C-1.0
写入比读取更加灵活
分块矩阵的运算
为什么要使用链接?
链接器操作步骤:
.o
.out
.so
.dll
上面所有的可重定位目标文件都可称为——可执行可链接格式(ELF)
ELF头
.text
.rodata
.data
.bss (block started by symbol)
.systab
-g
调试才会生成这张表.rel.text
.rel.data
.debug
-g
调试才会生成这张表.line
.text
中的机器指令进行映射-g
调试才会生成这张表.strtab
.systab
和 .debug
节中的符号表)static
属性的C函数、全局变量.systab
节条目?
type
)
OBJECT
)FUNC
)Ndx
)
.data
:已初始化的全局和静态C变量.text
:机器代码(和模块名相同).bss
:未初始化的静态变量,以及初始化为0的全局/静态变量COMMOM
:未初始化的全局变量UNDEF
:未定义的符号(引用了外部定义的模块)ABS
:无需重定位假如多个模块定义了同名的全局符号:
规则:
因此对于全局变量来说:
static
extern
基于已有的链接框架的选择:
.o
文件中
.o
文件
解决方案:静态库链接(老式).a
archive file
利用上面的方案二,将所有的库函数全部生成 .o
文件,但是多了一个表格,指明每个 .o
文件的偏移量以便直接调用。所以只需要将arch file传递过去即可。
unix> ar rs libc.a atoi.o printf.o … random.o
然后用什么拿什么。
.o
& .a
文件.o
& .a
文件,尝试去解析那些没有定义的列表项目流程简述就是:先运行前面的➡️碰到能解析前面的就解析
但是库有先后顺序!!!
又要注意!目标文件是不需要重复引用的!
编译器不知道链接器会选择什么地址,只好创建临时条目来储存链接器指令。
a : R_X86_64_32 array
typedef struct {
long offset; // 重定位的偏移位置 a
long type:32; // 如何修改新的引用 R_X86_64_32
long symbol:32; // 指向的符号 array
long append; // 符号参数(偏移调整) -
} Elf64_Rela;
foreach section s {
foreach section entry r {
refptr = s + r.offset; /* ptr to reference to be relocated */
/* Relocate a PC-relative reference */
if (r.type == R_X86_64_PC32) {
refaddr = ADDR(s) + r.offset; // 1
*refptr = (unsigned) (ADDR(r.symbol) + r.append - refaddr); // 2
}
/* Relocate an absolute reference */
if (r.type == R_X86_64_32)
*refptr = (unsigned) (ADDR(r.symbol) + r.append);
}
}
程序头部表
包含了:
off
:目标文件的偏移vader/paddr
:开始于内存地址align
:对齐filesz
:目标文件中的段大小memsz
:总的内存大小flags
:访问权限vaddr
mod align
= off
mod align
可执行程序在Linux系统之下的内存结构抽象图
和静态库不同,动态链接共享库并不在一开始就链接,而是在加载时链接(动态链接器)/运行时链接(dlopen
,用于分发/服务器/库打桩)/多进程共享。通常为 .dll
, .so
Example:
/* dll.c */
#include
#include
#include
int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];
int main()
{
void *handle;
void (*addvec)(int *, int *, int *, int);
char *error;
/* Dynamically load the shared library that contains addvec() */
handle = dlopen("./libvector.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
/* Get a pointer to the addvec() function we just loaded */
addvec = dlsym(handle, "addvec");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(1);
}
/* Now we can call addvec() just like any other function */
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
/* Unload the shared library */
if (dlclose(handle) < 0) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
return 0;
}
-fpic
malloc
/free
get macro-expanded into calls to mymalloc
/myfree
-I.
让预处理器现在当前目录下查找文件malloc
➡️ __wrap_malloc
__real_malloc
➡️ malloc
-Wl,--wrap,file
LD_PRELOAD
,解析未定义的引用时,先查看 LD_PRELOAD
库Example: Divide by 0, arithmetic overflow, page fault, I/O request completes, typing Ctrl-C
运用异常表,不同的异常对应不同的处理结果
两个假象:
程序计数器(PC)
一个运行到一半被暂时挂起,执行另一个
客户端视角来观察:
上下文切换中的两个模式:
#include
#include
pid_t getpid(void); /* get current PID */
pid_t getppid(void); /* get parent PID */
进程永远是这三种状态:
终止原因:1. 收到了终止信号;2. 从main()
返回;3. 显式调用exit()
。
exit()
:会有一个结束状态void exit(int status)
;被调用一次,但是永远不会返回值
#include
#include
pid_t fork(void);
当进程终止的时候,并不会被抛弃,而是会继续存在,占用系统资源,成为「僵尸进程」
回收进程
wait
/ waitpid
终止子进程假如父进程没有回收
pid=1
的init
系统进程进行回收#include
#include
pid_t wait(int* statusp); // <=> waitpid(-1, &status, 0);
pid_t waitpid(pid_t pid, int &status, int options);
pid
pid>0
单独子进程pid=-1
所有子进程options
WNOHANG
等待子进程终止的同时还想做其他事情WUNTRACED
检查已终止的、被停止的子进程WCONTINUED
等statusp
没有子进程,返回-1
,errno = ECHILD
;中断,返回-1
,errno = EINTR
#include
unsigned int sleep(unsigned int secs); /* 返回剩余秒数 */
int pause(void); /* 总是返回-1 */
#include
int execve(const char *filename, const char argv[], const char *envp[]);
filename
,带有参数列表argv
和环境变量envp
-1
fork
和execve
运行程序只能回收前台进程,解决方法——ECF!
kill
函数,显式要求发送信号pid_t getpgrp(void); /* 返回进程组ID */
int setpgid(pid_t pid, pid_t pgid); /* 成功返回0,错误返回-1, pid=0 代表当前进程 */
/bin/kill
程序发送信号linux> /bin/kill [-x the operation code] [x pid] [-x process group id]
例如 ctrl-c = SIGINT
/ ctrl-z = SIGTSTP
kill
函数发送信号#include
int kill(pid_t pid, int sig); /* 成功返回0,错误返回-1 */
对子进程的kill并不会回收
alarm
函数发送信号unsigned int alarm(unsigned int secs);
处理未被阻塞的待处理信号的集合。如果非空,那么就会强制接收信号。
预定义的默认行为:
SIGSTOP SIGKILL 不能修改
#include
typedef void (*sighandler_t)(int);
sighander_t signal(int signum, sighandler_t handler);
如何改变?
handler == SIG_IGN
忽略信号handler == SIG_DFL
恢复默认sigprocmask
#include
int setjmp(jmp_buf env); /* 调用一次,返回多次,返回值为0 */
void longjmp(jmp_buf env, int retval); /* 调用一次,从不返回 */
假如不命中,就会前往虚拟内存中找到该页,将它放入物理内存中并且修改PTE表。同时,被替换掉的物理内存被称为“牺牲页”。
目前都是采用按需页面调度
使用malloc()
的时候,会发生这个过程。
现在虚拟内存中开辟一个新的空间并储存数据,然后再修改PTE表。
保证了程序总在一个较小的页面集合上活动,成为工作集/常驻集合
带许可位的页表
虚拟地址、物理地址的格式转换
VPO-虚拟页内部偏移量 = PPO-物理页内部偏移量
VPN-虚拟页号 → page table \stackrel{\text{page table}}{\rightarrow} →page table PPN-物理页号
操作过程
页面命中
PTE即使放在L1中仍然不好,所以就在MMU中增加了一个后备缓冲器
TLB HIT:
TLB MISS:
task_struct
mm_struct
:虚拟内存的当前状态
pdg
指向第一级页表的基址mmap
指向 vm_area_struct
链表(描述一系列区域)定义:讲一个内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容
虚拟内存区域可以映射到两种类型的对象中的一种:
fork
函数execve
函数加载并运行一个程序需要的步骤:
mmap
函数的用户级内存映射#include
#include
void *mmap(void *start, /* 起始地址(可由内核决定,通常为NULL)*/
size_t length, /* 字节空间 */
int prot, /* 访问权限 */
int flags, /* 映射对象类型 */
int fd, /* 文件指针 */
off_t offset); /* 文件中的偏移量 */
void munmap(void *start, size_t length);
动态内存分配器:维护一个进程的虚拟内存区域,称为堆
分配器将堆视为一组不同大小的块,每个块是连续的虚拟内存片。
块的状态分为 已分配 和 空闲
malloc
free
new
delete
malloc
和 free
函数会有对齐:32 - 8;64 - 16
显式分配器的要求:
目标
吞吐率 与 内存利用率 二选一
已分配的可以不需要脚部
保守的垃圾收集器
mark
& sweep
[Facepalm]