笔记—《程序员自我修养》

《程序员自我修养》 笔记

温故而知新

平坦内存模型存在的问题

  • 进程之间地址空间不隔离
  • 多进程时, 内存碎片多(分页), 内存利用效率低(缓存命中率)
  • 程序运行的地址空间不确定

分段

提高缓存命中率
读写权限隔离
提高内存利用效率(多个运行的线程可以共享一份代码段)

分页

提高内存利用率,减少内存碎片
和虚拟内存管理机制一起解决了内存不连续的问题(分配的内存一般是虚拟内存,在第一次使用虚拟内存发生缺页中断中才会真正分配物理内存)

线程 与 进程

Linux 下线程与进程都是使用同一个结构体task_struct来表示的,线程之间共享的内存结构来区分线程和进程之间的并发程度,内核线程 复制阻塞情况下的线程切换, 非阻塞的线程切换在用户态处理。

线程的私有空间

Stack
寄存器
线程局部存储

预处理,编译,汇编,链接

静态链接

目标文件(ELF格式)

.o 可重定位文件 有一个重定位表 rel.text 用于重定位符号
可执行文件
.so 共享目标文件,用于动态链接

文件内容

BSS 未初始化的静态变量
COMMON 未初始化的全局变量(因为暂时不知道需要多少内存)
DATA 初始化的全局变量的静态变量
TEXT
RODATA
符号表(重定位和符号决议)
文件头 版本 大小 偏移量 magic number
重定位表 前置声明的重定位

强符号 与 弱符号

前置声明是弱符号, 定义/初始化是强符号,在副号决议的时候会优先对应强符号,多个强符号会报错冲突。

符号修饰

C++的重载 对符号名的影响
命名空间 对符号名的影响

extern

函数声明规范
函数,变量的 外连接性质

地址和空间分配 + 重定位和符号决议 = 链接

  1. 扫描输入目标文件获得各个段的长度,分配内存并维护一个全局符号表
  2. 根据全局符号表和重定位表 完成副号决议 和 重定位

main 函数之前和之后的内容

初始化进程环境,堆分配初始化,传递main 的参数,全局变量的初始化等。
.init 段:
.fini 段:

可执行文件的装载与进程

32位平台下的4G内存空间,1GB内核,1GB用户态。

加载方式-页映射

页映射是虚拟存储机制的一部分,随着虚拟存储的发明而诞生,数据和指令按页划分完毕后装载,如果程序使用物理地址直接进行操作,那么每次装入页都要进行重定位。将进程需要使用的数据和代码部分以页的形式分配虚拟内存,用内存置换算法来调度。

进程的建立

创建一个独立的虚拟地址空间(分配一个页目录,在发生缺页中断时进行内存调度)
建立空间和可执行文件的映射关系(进程中VMA映射到ELF的.text段)
CPU寄存器设置为可执行文件入口地址(ELF文件头中保存)

页错误

查询页目录,找到缺页中断对应的VMA,计算在可执行文件中的 偏移量,然后在物理内存中分配页面并且建立映射。

进程装载细节

把相同权限的段合并在一起映射

堆栈分配

传入参数 Argc Argv
bash 进程 fork() + execve()
检测ELF文件有效性,设置动态链接路径,映射ELF文件,初始化ELF进程环境。

堆内存分配方式

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk()和mmap()(不考虑共享内存)。

1、brk是将数据段(.data)的最高地址指针_edata往高地址推;

2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

这两种方式分配的都是虚拟内存,没有分配物理内存(不准确,系统调用会执行内核函数,分配内存),在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

  • brk 的内存碎片
  • mmap 的系统调用开销
posted @ 2018-06-11 20:35  joeylee97  阅读(305)  评论(0编辑  收藏  举报