【Linux】Linux程序地址基础
参考博客:https://blog.csdn.net/sjsjnsjnn/article/details/125533127
一、地址空间的阐述
1.1 程序地址空间
下面的图片展示了程序地址空间的组成结构
我们通过代码来验证一下
int g_unval;
int g_val = 100;int main(int argc, char *argv[]);void test1()
{int a = 10;int b = 20;const char *s = "hello world";printf("code addr:%p\n", main); // 代码区printf("string rdonly addr:%p\n", s); // 字符常量区printf("init addr:%p\n", &g_val); // 已初始化printf("uninit addr:%p\n", &g_unval); // 未初始化char *heap = (char *)malloc(10);printf("heap addr:%p\n", heap); // 堆区printf("stack addr:%p\n", &a);printf("stack addr:%p\n", &b);printf("stack addr:%p\n", &s); // 栈区printf("stack addr:%p\n", &heap);
}
运行结果如下:
- 代码区的地址位于最低处,
0x63e2eb7be37e
- 接着是字符串常量区
0x63e2eb7bf004
- 然后是已初始化区
0x63e2eb7c1010
和未初始化区0x63e2eb7c1154
- 后面是堆区内存地址
0x63e31e4b6ec0
,堆区内存地址不是连续的 - 最后是栈区
0x7fff3e007150~0x7fff3e007160
,栈区地址是连续的
code addr:0x63e2eb7be37e
string rdonly addr:0x63e2eb7bf004
init addr:0x63e2eb7c1010
uninit addr:0x63e2eb7c1154
heap addr:0x63e31e4b6ec0
stack addr:0x7fff3e007150
stack addr:0x7fff3e007154
stack addr:0x7fff3e007158
stack addr:0x7fff3e007160
可以发现,打印的结果符合对应的地址结构
二、进程地址空间
2.1 程序虚拟地址打印
下面的代码演示创建一个子进程后,打印子进程和父进程程序变量的地址
void test2(){int ret = fork();if(ret == 0){std::cout << "I am child, g_val = " << g_val << ", &g_val = " << &g_val << std::endl;for(int i = 0 ; i< 5;++i){g_val--;std::cout << "==========change g_val=========" << std::endl;std::cout << "I am child, g_val = " << g_val << ", &g_val = " << &g_val << std::endl;sleep(1);}}else if(ret > 0){while(1){std::cout << "I am father, g_val = " << g_val << ", &g_val = " << &g_val << std::endl;sleep(1);}}else{std::cout << "error:" << strerror(ret) << std::endl;}}
运行结果如下:
- 在没有修改变量的情况下,父进程和子进程中
g_val
的地址一样,指向同一块内存 - 但是修改了子进程中值的大小后,
g_val
的值不同,打印出来的地址确是同一段 - 这里涉及了虚拟地址,我们打印出来的地址实际上是虚拟机制,而不是物理地址
2.2 进程地址空间结构
- 之前说‘程序的地址空间’是不准确的,准确的应该说成进程虚拟地址空间 ,每个进程都会有自己的地址空间,认为自己独占物理内存。
- 操作系统在描述进程地址空间时,是以结构体的形式描述的,在linux中这种结构体是
struct mm_struct
。它在内核中是一个数据结构类型,具体进程的地址空间变量。
这些变量就是每个空间的起始位置与结束位置。如下图所示
进程地址空间就类似于一把尺子,每个空间都有对应的起始位置和结束位置。通过这个虚拟地址去间接访问内存;
为什么不能直接去访问物理内存?
如果没有进程地址空间的加持,那么程序就会直接访问物理内存,没有区间可言,会存在恶意程序可以>随意修改别的进程的内存数据,以达到破坏的目的。有些非恶意的,但是有bug的程序也可能不小心修改了其它程序的内存数据,就会导致其它程序的运行出现异常。这种情况对用户来说是无法容忍的,因为用户希望使用计算机的时候,其中一个任务失败了,至少不能影响其它的任务。
2.3 如何通过虚拟地址访问物理地址
-
每个进程都是独立的虚拟地址空间,两个独立进程的相同地址互不干扰,但是在物理上对每个进程可能也就分了一部分空间给了某个进程。
-
每个进程被创建时,其对应的进程控制块和进程虚拟地址空间也会随之被创建。而操作系统可以通过进程的控制块找到其进程地址空间,通过页表对将虚拟地址转换为物理地址,达到访问物理地址的目的。
-
这种方式称之为映射,调度某个进程执行时,就要把它的地址空间映射到一个物理空间上。
因此,最终得到的虚拟地址通过下述转换得到物理地址,虽然虚拟地址一致,但是通过不同进程中的映射关系,会得到不同的物理地址,这就是为什么虚拟地址一致,但是得到的值是不同的原因
写时拷贝:就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝
更多资料:https://github.com/0voice