进程地址空间(每个进程都有)
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
int g_val = 100;
int main()
{
pid_t id = fork();
if(!id)
{
int count = 0;
while(1)
{
printf("child, pid:%d ppid:%d g_val:%d &g_val:%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
count++;
if(count == 5)
{
g_val = 200;
printf("chage sccess\n");
}
}
}
else
{
while(1)
{
printf("parent, pud:%d ppid:%d g_val:%d &g_val:%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
return 0;
}
上面代码运行后如下所示:
上面父、子进程同一个变量,相同的地址却有两个值,说明该地址不是物理地址。
字符常量区也在正文代码区
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int g_unval;
int g_val = 100;
int main(int argc, char *argv[], char *env[])
{
printf("code addr: %p\n", main);
printf("init global addr: %p\n", &g_val);
printf("uninit global addr: %p\n", &g_unval);
static int test = 10;
char* heap = (char*)malloc(10);
char* heap1 = (char*)malloc(10);
char* heap2 = (char*)malloc(10);
char* heap3 = (char*)malloc(10);
printf("static stack addr: %p\n", &test);
printf("heap addr: %p\n", heap);
printf("heap1 addr: %p\n", heap1);
printf("heap2 addr: %p\n", heap2);
printf("heap3 addr: %p\n", heap3);
printf("stack addr: %p\n", &heap);
printf("stack1 addr: %p\n", &heap1);
printf("stack2 addr: %p\n", &heap2);
printf("stack3 addr: %p\n", &heap3);
int i = 0;
for(i = 0; i < argc; i++)
{
printf("argv[%d]: %p\n", i, argv[i]);
}
for(i = 0; env[i]; i++)
{
printf("env[%d]: %p\n", i, env[i]);
}
return 0;
}
什么是地址空间
以画饼为例
老板给员工画饼,说给予5%的股份
老板 —— 操作系统
员工们 —— 进程
饼 —— 地址空间
股份 —— 物理内存
同时,画的饼应该和相应的人对应起来,因此需要OS进行管理
所以,地址空间本质是种数据结构,将来要个对应的进程关联起来
地址空间是怎么设计的
是一种专门为进程设计的内核数据结构,里面有各个区域的划分,结合页表控制物理内存的内容和区域
页表有对应关系和读写权限等
以压岁钱为例,去小卖部买东西。
如:想买个3090卡,必定会被拒绝;买点吃的会允许
在页表中无法正确映射就直接拒绝访问
只要保证,每个进程的页表,映射的是物理内存的不同区域,就可以保证其独立性
空间配置器配置的就是虚拟内存空间
进程在编入内存之前就已经有了虚拟地址
语言中的地址都是虚拟地址(线性地址),不是物理地址 ,同时需要编译器的配合
CPU始终访问的是虚拟地址
注:递归的栈大小不大,只有几MB
地址空间和页表 是OS创建和维护的
使用地址空间和页表一定是在OS的监管下进行访问的
为啥要有虚拟地址?
1、 凡是非法的访问或映射,OS都会识别并终止该进程(如是否有读写权限),有效保护了物理内存,也保护物理内存中合法数据的安全和内核相关的有效数据(压岁钱的例子)
2、 因地址空间和页表的存在,在物理内存中,对未来的数据可以进行任意位置的加载。物理内存的分配 和 进程管理模块(PCB)就可以做到没关系了(解耦合)
例如:进行malloc、new操作,当没进行使用访问时,物理内存甚至半个字节都不给,此时仅仅在地址空间中申请。当真正使用访问时(缺页中断)才会给。
以上的方式称为延时分配,提高整机效率
3、 在物理内存上理论上可以在任意位置进行加载,可以认为物理内存中的数据和代码是乱序的。
但是,由于页表的存在,在进程视角的内存分布,可以看作是有序的。
由2可得目前要访问的数据和代码,不一定在物理内存中;同样,可以让不同的进程映射到不同的物理内存。
由上可得,每个进程都认为自己有4GB(假设)的空间,并且各个区域是有序的。每个进程也不知道其他进程的存在。
进程: 对应的数据和代码() + 内核数据结构
下面红方框中的合起来叫进程
什么是挂起(重新看进程状态)
加载本质就是创建进程,那么是不是必须非得立马把所有的程序的代码和数据加载到内存中,并创建内核数据结构建立映射关系?
实际是不会的
在最极端的情况下,甚至只有内核结构被创建出来了!
那么可以称这个进程为新建状态!!
理论上,可以实现进程分批加载/换入。——>也可以分批换出——>甚至短时间内不被执行,阻塞——>进程的代码和数据被换出,叫做挂起。