本篇文章进行操作系统中进程地址空间的学习!!!
我们曾经在C/C++所学的程序地址空间的分布图是真正的内存吗?
程序地址空间不是真正的内存,它只是一个“虚拟内存”
程序地址空间准确的说是:进程地址空间,这是OS的概念,真正的内存指的是“物理内存”
[lyh_sky@localhost lesson13]$ cat process.c
#include
#include
int un_g_val;
int g_val = 0;
int main(int argc, char* argv[], char* env[])
{
// 代码/只读区
printf("Code area adress: %p\n", main);
const char* p = "abcdef";
printf("Code area adress: %p\n", p);
// 初始化/未初始化数据区
printf("init global area adress: %p\n", &g_val);
printf("uninit global area adress: %p\n", &un_g_val);
// 堆区
char* m1 = (char*)malloc(sizeof(char) * 100 );
printf("heap area adress: %p\n", m1);
// 栈区
printf("stack area adress: %p\n", &m1);
// 命令行环境变量区
for (int i = 0; i < argc; ++i)
{
printf("command line adress: %p\n", argv[i]);
}
for (int i = 0; env[i]; ++i)
{
printf("environ val adress: %p\n", env[i]);
}
return 0;
}
通过验证可以看出程序地址空间是存在的!!!
栈是向下增长的,而堆是向上增长的!!!
堆栈是相对而生的!!!
在定义函数时,会将参数和函数体的变量压入栈中,先定义的变量地址比较高
[lyh_sky@localhost lesson13]$ cat process.c
#include
#include
int un_g_val;
int g_val = 0;
int main(int argc, char* argv[], char* env[])
{
// 代码区/只读区
printf("Code area adress: %p\n", main);
const char* p = "abcdef";
printf("Code area adress: %p\n", p);
// 初始化/未初始化数据区
printf("init global area adress: %p\n", &g_val);
printf("uninit global area adress: %p\n", &un_g_val);
// 堆区
char* m1 = (char*)malloc(100);
char* m2 = (char*)malloc(100);
char* m3 = (char*)malloc(100);
char* m4 = (char*)malloc(100);
printf("heap area adress: %p\n", m1);
printf("heap area adress: %p\n", m2);
printf("heap area adress: %p\n", m3);
printf("heap area adress: %p\n", m4);
// 栈区
printf("stack area adress: %p\n", &m1);
printf("stack area adress: %p\n", &m2);
printf("stack area adress: %p\n", &m3);
printf("stack area adress: %p\n", &m4);
// 命令行环境变量区
for (int i = 0; i < argc; ++i)
{
printf("command line adress: %p\n", argv[i]);
}
for (int i = 0; env[i]; ++i)
{
printf("environ val adress: %p\n", env[i]);
}
return 0;
}
如何理解static变量
从以下测试可以得出:
从全局数据g_val的值和地址都相同,可以看出来
当父子进程中没有人修改数据的时候,父子进程是"共享"该数据的
[lyh_sky@localhost lesson13]$ cat process.c
#include
#include
#include
#include
int g_val = 100;
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("我是子进程: %d, ppid: %d, g_val: %d, g_val adress: %p\n\n", getpid(), getppid(), g_val, &g_val);
sleep(3);
}
}
else
{
while (1)
{
printf("我是父进程: %d, ppid: %d, g_val: %d, g_val adress: %p\n\n", getpid(), getppid(), g_val, &g_val);
sleep(3);
}
}
return 0;
}
如果让父子进程中的一个进行写入,那么g_val的值和地址会发生什么变化呢?
父子进程读取同一个变量(地址相同),但是后续地址没有改变的情况下,父子进程读取到的内容却是不同的!!!
由此可以得出:在C/C++程序中使用的地址,绝对不是物理地址
在C/C++程序中使用的地址是"虚拟/线性/逻辑地址"(后面证明)
我们在用C/C++中所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
OS必须负责将 “虚拟地址” 转化成 “物理地址”
注意:虚拟地址、线性地址和逻辑是完全不同的概念,在Linux下是一样的!
[lyh_sky@localhost lesson13]$ cat process.c
#include
#include
#include
#include
int g_val = 100;
int main()
{
pid_t id = fork();
int count = 1;
if (id == 0)
{
while (1)
{
printf("我是子进程: %d, ppid: %d, g_val: %d, g_val adress: %p\n\n", getpid(), getppid(), g_val, &g_val);
sleep(3);
++count;
if (count == 3)
{
g_val = 200;
printf("我是子进程,全局数据g_val已经被更改!!!\n");
}
}
}
else
{
while (1)
{
printf("我是父进程: %d, ppid: %d, g_val: %d, g_val adress: %p\n\n", getpid(), getppid(), g_val, &g_val);
sleep(3);
}
}
return 0;
}
为什么OS不让用户直接访问物理内存呢?
内存是一个硬件,不能阻拦用户的访问,只能被动的进行读取和写入
如果用户可以直接访问到物理内存,发生越界访问时,可能这个进程会影响到其他的进程
如果越界访问到OS的内存时,对OS内存进行非法修改,会导致OS直接挂掉
概念:
每一个进程在启动的时候,OS都会给这些进程创建一个地址空间,该地址空间就是“进程地址空间”
OS是通过描述再组织来管理进程地址空间的,通过struct描述它,然后用数据结构管理起来
进程地址空间其实是内核中一个数据结构(mm_struct)
进程地址空间需要通过页表将虚拟地址映射到物理地址,后面讲
进程的独立性:多进程运行,需要独享各种资源(包括内存),多进程之间互不干扰
进程相关的数据结构之间是独立的,进程代码数据之间也是独立的
所谓进程地址空间:其实是OS通过软件的方式,给进程提供一个软件视角,认为自己是独占系统的全部资源(内存)!!!
进程地址空间与页表的作用
可执行程序在磁盘中,运行时会被加载到物理内存
进程里面维护一个进程地址空间,进程地址空间会被加载到页表的左边(虚拟地址),然后通过映射转换成物理地址加载到物理内存中
内存分布图中有这么多区域,那么这些空间区域是什么呢???
Linux内核中mm_struct的源代码
程序是如何变成进程的???
回答如何被加载到内存的过程:
在对程序进行编译时,就认为程序是按0x0000 ~ 0xFFFF进行编址的
加载到内存时,会将内存初始地址与程序的地址相加得到虚拟地址
进程读内存拿到虚拟地址后,通过页表映射转化成物理地址,就能找到在内存对应的地址,继续执行
举个例子:
首先说明:父子进程之间具有独立性,二个进程之间互不影响
为什么fork一个子进程,对子进程数据进行修改时,二个进程数据地址一样,值却不一样?
一个进程加载到内存时,需要通过页表将虚拟地址映射转化成物理地址
父子进程的代码是共享的,说明:父子进程在加载到内存时,映射的物理地址是一样的
当子进程数据进行修改时,会影响父子进程有独立性的这一特征
此时,子进程会在物理内存中创建一个新的地址,将数据和代码拷贝到里面(写时拷贝),然后让虚拟地址映射到新的物理内存中,因为只改了物理内存的地址,虚拟地址是不变的!!!
fork有二个返回值,为什么他们的值都不一样呢?
fork内存,return会被执行两次,就是通过寄存器将保存的返回值写到接受返回值的变量中
当pid_t id = fork()的时候,父子进程谁先返回一个值,谁就会发生写时拷贝,所以同一个变量会有不同的值,本质是因为父子进程中的虚拟地址一样,只是物理地址发生了改变!!!
为什么要有虚拟地址呢?
让用户访问内存添加了一层软硬件层,可以对转化过程进行审核,非法的访问,可以直接拦截它
保护内存:用户进行非法访问,可能导致OS直接挂掉或影响其他的进程
进程管理:通过地址空间,进行功能模块的解耦
让进程或者程序可以以一种统一的视角看待内存(独占全部内存资源)
方便以统一的方式来编译和加载所有的可执行程序,简化进程的设计与实现