进程的虚拟地址空间内存划分和布局
- (在x86体系 32位linux环境下学习)
前言
-
任何编程语言产生两种东西: 指令 + 数据
-
可执行文件在磁盘上,加载到内存当中,不是直接加载到物理内存
-
IBM解释的虚拟的定义
- 进程的虚拟地址空间: Linux系统会给当前进程分配一个 2^32大小的空间(4G)
- 虚拟的定义: 不存在看得见
- 透明的定义: 存在看不见
- 物理的定义: 存在看的见, 物理的
- 删除的定义: 不存在看不见
两种语言的字符串指针简单看下内存问题
- C 字符串和指向字符串的指针
- 当你在定义一个字符数组时,如果在定义的同时给它一个初始值,那么就需要使用[]来保存这些字符。
- 而当你定义一个字符指针时,如果需要给它一个初始值,就可以直接赋值为一个字符串常量的地址。
// 当你在定义一个字符数组时,如果在定义的同时给它一个初始值,那么就需要使用[]来保存这些字符。
void strlen_demo(){
char str[] = "Hello World";
cout << "Lenth of: " << str << "is: " << strlen(str) << endl;
}
// 而当你定义一个字符指针时,如果需要给它一个初始值,就可以直接赋值为一个字符串常量的地址。
void strlen_ptr_demo(){
char* str = "Hello World";
cout << "Lenth of: " << str << "is: " << strlen(str) << endl;
}
- C++ 字符串和指向字符串的指针
// C++ 的字符串
void cppstring_demo(){
string str = "Hello World";
cout << "Lenth of: " << str << "is: " << str.length() << endl;
}
void cpp_string_ptr() {
string* str = new string("Hello World");
int* asd = new int(8);
cout << "Lenth of: " << *str << "is: " << str->length() << endl;
delete str;
delete asd;
}
-
new是C++中的一个关键字,用来在堆上分配内存。它会在堆上为你分配足够的空间,并返回一个指向该内存的指针。
-
例如,在上面的代码中, new string(“Hello World”) 会在堆上分配一块内存,并使用string的构造函数来创建一个string对象,并将指针str指向这个对象。
-
因为在堆上分配的内存在程序结束之前不会被释放,所以如果不手动释放这块内存,会造成内存泄漏。所以需要使用delete来手动释放内存
-
其他变量,如局部变量、全局变量等,都是在栈上分配的。栈是一种先进后出的数据结构,变量在函数调用时进栈,在函数返回时出栈。因此,当函数返回时,所有在该函数中定义的局部变量都会被销毁,其所占用的内存也会被释放,所以不需要手动删除
-
栈是一种先进后出的数据结构,它可以用来存储函数的调用过程、局部变量、参数等。
-
在函数调用时,系统会在栈上为该函数分配一块内存,用来存储该函数的局部变量、参数等。在函数返回时,系统会销毁该函数在栈上分配的内存,释放所占用的空间。
进程线程
(https://zhuanlan.zhihu.com/p/101230252#😃
- 运行一次代码可能会产生一个进程,也可能不会。当你在操作系统中运行一个程序时,操作系统会为该程序创建一个新的进程。这个进程会拥有它自己的内存空间和系统资源,并且可以独立于其它进程运行。但是如果你在运行一个已经在运行中的进程(重新启动这个进程),那么不会产生新的进程,而是会使用已经存在的进程。
- 进程: 进程是指由计算机中央处理器执行的程序的一个实例。它包括程序代码及其当前活动,以及程序使用的数据。进程具有自己的内存空间和系统资源,并且可以与其他进程和操作系统交互。进程也可以有多个线程,这是进程的较小单元,可以独立和同时运行,允许并行处理。
- 总之,运行一次代码是不能确定是否会产生一个进程的。
- 多线程: 多线程指的是一个进程中有多个独立的执行流。线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。每一个线程都有自己的执行流程,可以被操作系统单独调度。多线程可以让一个进程在执行多个任务时有更高的效率,可以提升程序的运行速度。
进程在内存可用的主要的三个布局
- .data 数据段,主要存放的全局变量,存在于整个程序的生命周期
- heap 堆内存,通过new delete自己开辟的空间,自己释放的
- stack 栈上面,出去后就自动销毁了
虚拟地址空间
每一个进程都有一个默认4G的虚拟空间(可以更改的) 被默认划分成两部分
0x00000000 | |
---|---|
零地址不可访问 | 访问会报错的,例如: 传空指针会出现: Segmentation fault的程序异常 |
0x08048000 | 以上的空间不能放东西也不能访问 |
代码段: 主要放 .txt .rodata | 只读数据段,不能更改 |
.data | 专门存放初始化数据的符号表 |
.bass | 存放没有初始化的数据的符号表 |
.heap | 堆内存 |
*.dll(Linux) *.so(Windows) | 动态链接库/加载共享库 |
stack栈空间 | asd |
命令行参数和环境变量 | ./a.out 192.168.1.100 |
0xC0000000 | 3G (user space 用户空间: 3G ) |
ZONE DMA, Zone NORMAL, Zone HIGHMEM | 内核空间被分成了三个 |
0xFFFFFFFF | 4G (内核空间 1G ) ZONE DMA NORMAL HIGHMEM |
内核空间被划分成三块
Zone DMA: 约16MB
Zone NORMAL: 800MB 存放: 进程控制快,内核空间的线程 栈空间
Zone HIGHMEM: 高级内核 映射高地址的物理内存
用一个案例看零地址访问
访问的时候会报错,因为零地址是不可以访问的,但是按照道理strlen§;也不能够访问,不知道为什么这里可以访问。这也是为什么在使用指针之前,要先检查它是否为nullptr,防止出现错。
void ex1() {
char *p = nullptr;
strlen(p);
char *src = nullptr;
char dest[100];
strcpy(dest, src);
}
问题: 指令(.txt)运行的时候放在哪里?
- 代码段,0地址
问题: 为什么未初始化的数据是0?
- 因为放在了.bass上面
用全局变量的内存地址
- 全局变量
- 全局变量都叫做数据 在符号表中都会产生符号
- gdata1, 4有初始化都会放在符号表.data上面
- gdata2, 3, 5, 6 没有初始化或者初始化为0都放在符号表.bass
- 局部变量
- 没有被初始化的,用到才会初始化
- 放在数据段的
- a, b, c 是不产生符号的,产生的是三个move指令
- a: ,把12(0CH),移到a的内存里面
- 静态局部变量是放在符号表的,用到的时候才会初始化的
- 总结: 全局变量和局部静态变量放在符号表.bass .data里面,根据是否初始化划分,非静态局部变量产生指令放在.txt数据段上,运行的时候cpu在栈上开辟空间放入内存
// ex2
#if 1
int gdata1 = 10;
int gdata2 = 0;
int gdata3;
static int gdata4 = 11;
static int gdata5 = 0;
static int gdata6;
#endif
int main() {
// ex2
#if 1
int a = 12;
int b = 0;
int c;
static int e = 13;
static int f = 0;
static int g;
cout << "c: " << c << endl;
#endif
return 0;
}
每一个进程的空间是私有的,但是内核空间是共享的!!!
- 进程之间的通信方式?
- 匿名管道通信: 在内核空间划分了一块空间
- 我pornhub开了一个进程,xvedio也开了一个进程,pornhub的内核写了一个数据,其他进程也看得见
总结:
- 不要再说什么全局变量区,局部变量区,说.bass .txt .data 这样的话稍微专业点