01 进程的虚拟地址空间
一、操作系统让进程访问的是虚拟地址空间,而不是物理地址
1.任何程序在编译时都会产生指令和数据,进行地址编号,但是如果地址不连续,就会程序运行不起来,编译器的地址管理比较麻烦(无法动态的获知物理空间的使用情况,也就无法为数据进行编号)
2.进程直接访问物理地址,如果此时有一个野指针,那么在进行操作野指针的时候可能会改变其他空间的数据,造成不安全的事件发生(无法进行内存访问控制)
3.程序运行空间通常需要一块连续的空间,空间利用率低,通过虚拟地址空间映射到物理内存上进行数据存储,可以实现数据在物理空间上离散式存储,提高内存的利用率,并且每个进程都有一份属于自己的连续空间使用
二、进程的虚拟地址空间
指令存放在.text段
int a = 12;
int b = 0;
int c;
// 编译后成为汇编指令
// mov dword ptr[a],0ch
//存放.text段
//局部变量 指令
数据存放在
static int e = 13;
static int f = 0;
static int g;
//局部变量静态变量e存放在.data段 不为0 f、g存放在.bss段 为0/未初始化
可打印g的值为0,无法打印c的值(栈上的无效值)
#include <iostream>
using namespace std;
int main(){
int c;
static int g = 0;
cout << c << g << endl;
return 0;
}
//无法打印c的值 可以打印g的值为0
1.包括了用户空间、内核空间
2.全局变量均存放在data段(不为0)、bss段(为0 或者 未初始化)
3.局部变量 产生指令
局部变量中的静态变量 产生数据(data段、bss段)
三、进程和线程的区别
进程是系统进行资源分配和调度的一个独立单位
线程是进程的一个实体,是CPU调度和分配基本单位
简而言之
一个程序至少要有一个进程,一个进程至少要有一个线程
四、多进程
1.有各自的用户空间(隔离),共享内核空间
2.进程之间通信方式 : 匿名管道通信(内核空间内存数据可共享)
参考链接
1.https://blog.csdn.net/weixin_45975835/article/details/115010310
2.《深入理解计算机系统》 第七章
3.https://blog.csdn.net/yaosiming2011/article/details/44280797
02 函数调用堆栈
#include <iostream>
using namespace std;
int sum(int a, int b) {
int temp;
temp = a + b;
return temp;
}
/*
push ebp
mov ebp,esp//sum函数栈帧
sub esp,4Ch//给esp开辟栈帧空间
mov dword ptr[ebp-4]//int temp = 0;
mov eax,dword ptr[ebp+0Ch]
add eax,dword ptr[ebp+8]
mov dword ptr[ebp-4],eax
mov eax,dword ptr[ebp-4]//return temp
mov esp,ebp
pop ebp
ret
*/
int main() {
int a = 10;//局部变量不产生符号 mov dword ptr[ebp-4], 0Ah
int b = 20;// mov dword ptr[ebp-8], 14h
int ret = sum(a,b);
/*
mov eax,dword ptr[ebp-8] //把b传给sum的形参int b
push eax //压栈
mov eax,dword ptr[ebp-4]
push eax
call sum
add esp,8
mov dword ptr[ptr-0ch],eax
*/
cout << "ret is " << ret << endl;
system("pause");
return 0;
}
Q1:main函数调用sum,sum执行完以后,怎么知道回到哪个函数中?
通过汇编语言中的call指令存储了下一条指令的地址。
Q2:sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行的?
在sum函数调用结束以后,ret指令在CPU的PC寄存器中读取刚刚存储的地址,回到原来的函数。
03 C++的编译与链接
编译过程
main.cpp sum.cpp
extern int gdata;// gdata *UND* 符号引用
int sum(int, int);// sum *UND*
int data = 20; //data .data段
int main() { //main .text段
int a = gdata;
int b = data;
int ret = sum(a, b);
return 0;
}
int gdata = 10;//gdata .data段
int sum(int a, int b) { //sum_int_int .text段
return a + b;
}
预编译:
’#‘开头的命令在预编译完成
#pragma lib
#pragma link
//在链接阶段处理
编译:
gcc
g++ -o
汇编:
主要是符号表的输出
二进制可重定位的目标文件(*.obj) main.o sum.o
其中,.o文件的格式组成:
elf文件头
.text
.data
.bss
.symbal
.section table
链接过程
链接:编译完成的所有.o文件和静态库文件
步骤一:
所有.o文件段的合并==(.text、.bss、.data等)==
符号表合并后,进行符号解析(UND所有对符号的引用,都要找到该符号的定义的地方,可能会报错,符号未定义、重定义等)
给所有的符号分配虚拟地址(在此时给符号分配虚拟地址)
步骤二:
符号的重定位(重定向)
在代码段将符号的地址填成正确的地址
查询符号段、地址指令
objdump -s sum.o
objdump -t a.out
readelf -h a.out
输出
a.out
读取a.out的入口地址,入口地址为main函数的地址
都是各种段组成,.out相比于.obj多了progrma headers段,有两个load,告诉系统运行到这个程序的时候,把哪些内容加载到内存中(.text、.data)
-o 可执行程序的名称
a.exe
可执行文件加载过程: