进程地址空间
c/c++程序地址空间不是内存
而是进程虚拟地址空间
1 #include<stdio.h>
2 #include<stdlib.h>
3 int g_unval;
4 int g_val=100;
5
6 int main()
7 {
8 const char* s="hello world";//在字符常量区域保存
9 printf("code addr: %p\n",main); //代码区
10 printf("string rdonly addr: %p\n",s);
11 printf("uninit addr: %p\n",&g_unval);
12 char* heap=(char*)malloc(sizeof(char));
13 printf("heap affr :%p \n",heap);//我们要读到堆区的地址,而不是heap,heap是在栈区开辟的地址,
14 printf("stack addr: %p\n",&s);//s也是在栈上开辟的地址,
15 printf("stack addr: %p\n",&heap);//heap也是在栈上开辟的地址,
16 int a=0;
17 int b=9;
18
19 printf("stack addr: %p\n",&a);//a也是在栈上开辟的地址,
20 printf("stack addr: %p\n",&b);//b也是在栈上开辟的地址,
21 return 0;
22 }
~
24 void test2()
25 {
26 if(fork()==0)
27 {
28 //child
29 int cnt=5;
30 while(cnt)
31 {
32 printf("I am child,time %d,g_val %d,&g_val %p\n",cnt,g_val,&g_val);
33 cnt--;
34 sleep(1);
35 if(cnt==3)
36 {
37 g_val=200;
38
39 }
40 }
41 }
42 else
43 {
44 ? //parent
45 while(1)
46 {
47
48 printf("I am father,g_val %d,&g_val %p\n",g_val,&g_val);
49 sleep(1);
50 }
51 }
52 }
53
父子进程中,数据是相互独立的,子进程中变不影响父进程,
- 地址怎么能没有发生变化呢
如果C/C++中打印出来的地址是物理内存的地址,这种现象是绝对不可能存在
所以这里我们使用的地址绝对不是物理地址
而是虚拟地址
进程地址空间概念
进程地址空间本质上也是内核中的一种数据结构
struct mm_struct
struct mm_struct
{
unsigned int code_start;
unsigned int code_end;
unsigned int init_data_start;
unsigned int init_data_end;
……
}
这一个一个的都是区域,
虽然这里只有start和end,但是每个进程都可以认为mm_struct 代表整个内存,且所有的地址为0x00000000到0xffffffff
在start到end内就是其的区域
每个进程都认为地址空间的划分是按照4GB空间划分的,每个进程都认为之际拥有4GB
地址空间进行线性划分时,对应的线性位置可以认为是虚拟地址
而从start到end里就是虚拟地址,
如:start=20,end=30
那么21,22,23就是虚拟地址
页表
:将虚拟地址转化为物理地址(映射表)(哈希表)
char * table[4 * 1024*1024]
可以理解为下标就是对应的是虚拟地址,而下标所对应的值就是物理地址 ,用户使用的都是虚拟地址,而操作系统会根据其虚拟地址找到其对应的物理地址中的数据与代码
为什么要这么干
而地址空间加页表就是操作系统中的管理者角色
而页表和MMU是通过操作系统帮我们转化的(权限管理)
const char* str="hello";
*str='.';
不可以这样做的,在字符常量区
本质是OS 给你的权限只有r权限,
而用str指针的时候用的就是虚拟地址,指向那个位置,访问的时候就要对虚拟地址和物理地址进行转换,而只有读权限,那么就会直接被崩溃掉
- 通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质目的是为了,保护物理内存以及各个进程的数据安全,映射之后会到不同的内存区,绝对不一样
- 将内存申请,和内存使用的概念在时间上划分清楚,通过虚拟地址空间来屏蔽掉申请内存的过程,达到进程读写内存和OS 进行内存管理操作,进行软件上面的分离(写时拷贝)
如我想我爸要8000,我爸答应了,(进程向操作系统要空间,操作系统同意了,在进程地址空间里面开了地址,),到实际要钱的时候,爸发现钱不够,就借钱,各种渠道,把钱给我,(OS通过内存管理算法,把一些没在用的空间,给进程)
-
可以做到站在CPU和应用层的角度,进程统一使用4GB 空间,而且每个空间区域的相对位置,是比较确定的
之后便可以使得程序和代码数据被加载到物理内存的任意位置
因为页表可以映射,在虚拟地址上是连续的,而在物理地址上不是连续的,大大减少内存管理的负担OS 最终这样设计的目的,达到一个目标,每个进程都认为自己是独占资源
void test3()
{
const char*p="hello";
const char*q="hello";
printf("p=%p\n",p);
printf("q=%p\n",q);
}
打印的结果是一样的,都是只读的,这样维护成本最低
int main(int argc,char*argv[],char*env[])
{
int i=0;
for(i=0;argv[i];i++)
{
printf("argv[%d]:%p\n",i,argv[i]);//不需要&,
}
for(i=0;env[i];i++)
{
printf("env[%d]:%p\n",i,env[i]);//不需要&,
}
// test1();
test3();
return 0;
}
命令行的参数地址远高于栈,环境变量更高于命令行