进程:链接: link.
1. 程序地址空间回顾
在学习C语言的时候,就经常看到这样的空间布局图,但又不是完全一样的。因为这个更准确的说是操作系统那一部分的图。其实这个叫做进程地址空间。共享区里存放的是:动态库和共享内存
通过代码来解释
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int g_val = 100;
5 int g_unval;
6
7 int main(int argc,char *argv[],char *env[])
8 {
9 printf("code addr : %p\n",main);
10 printf("g init addr : %p\n",&g_val);
11 printf("g uninit addr: %p\n",&g_unval);
12
13 char *mem = (char*)malloc(10);
14
15 printf("heap addr: %p\n",mem);
16
17 printf("stack addr: %p\n",&mem); // 因为你的char*是一个局部变量所以存放在栈里
18
19 printf("opt addr: %p\n",argv[0]);
20 printf("opt addr: %p\n",argv[argc-1]);
21
22 printf("env addr : %p\n",env[0]);
23 return 0;
24 }
代码区,初始化的数据,未初始化的数据,堆(且地址从低向高增长),栈(地址高到低不断减小),命令行参数,环境变量,内核空间,他们的地址都是逐渐增加的。
2. 地址空间深入理解
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int g_val = 100;
5
6 int main()
7 {
8 pid_t id = fork();
9 if(id == 0){
10 //child
11 while(1){
12 printf("g_val : %d , g_val addr : %p, child\n", g_val, &g_val);
13 sleep(1);
14 }
15 }
16 else if(id >0){
17 //parent
18 while(1){
19 printf("g_val : %d ,g_val addr : %p,father\n", g_val, &g_val);
20 sleep(1);
21 }
22 }
23 else{
24 printf("output error");
25 }
26 return 0;
27 }
我们发现,输出出来的变量值和地址都是一模一样的,很好理解呀,因为子进程按照父进程为模板,父子并没有对变量进行任何修改。但是此时稍微修改一下,让子进程先跑起来,并且走它的过程中会把g_val 的值修改为1000,再看效果?
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int g_val = 100;
5
6 int main()
7 {
8 pid_t id = fork();
9 if(id == 0){
10 //child
11 g_val = 1000;
12 while(1){
13 printf("g_val : %d , g_val addr : %p, child\n", g_val, &g_val);
14 sleep(1);
15 }
16 }
17 else if(id >0){
18 //parent
19 sleep(3);
20 while(1){
21 printf("g_val : %d ,g_val addr : %p,father\n", g_val, &g_val);
22 sleep(1);
23 }
24 }
25 else{
26 printf("output error");
27 }
28 return 0;
29 }
我们发现,父子进程输出的地址是一样的,但是变量的内容却不一样。
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
- 但地址值是一样的,说明,该地址绝对不是物理地址!
- 在Linux地址下,这种地址叫做虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理OS必须负责将虚拟地址转化成物理地址。
- 地址空间仅仅只是一种区域的划分,只有物理内存才具有保存数据的能力
这里也反映出了进程在运行的时候,具有独立性。
进程的地址空间是虚拟地址是不发生变化的,变化的只是虚拟地址到物理内存上的映射关系。
2.1 什么是地址空间?
地址空间:本质是用来描述进程所占有资源的一张表,在OS内核中的本质就是一个数据结构struct mm_struct{};
将内存划分为了多个区域。
系统中可能存在多个进程,所以系统中一定存在多个地址空间,既然多了,就需要被管理起来,如何管理呢?
先描述:地址空间本质就是一个数据结构struct
在组织:struct mm_struct {};
而这个结构体内的成员是通过对区域的划分来区别开的
struct mm_struct
{
unsigned long code_start;
unsigned long code_end;
unsigned long init_date_start;
unsigned long init_date_end;
unsigned long uninit_date_start;
unsigned long uninit_date_end;
unsigned long heap_start;
unsigned long heap_end;
....
};
struct mm_struct mm = {0x011010,0x012032,.....};
申请空间的本质:向内存所要空间,得到物理地址,然后在特定的区域申请没有被使用的虚拟地址,然后建立映射关系,最终返回虚拟地址。
2.2 为什么要有地址空间?
我直接使用物理内存的地址不行吗?
如果没有地址空间,那么进程直接访问的就是物理地址,就会出现很多错误比如野指针问题无法在避免,还有就是进程的数据存放在物理内存中是不连续的,一旦空间不连续,就会造成访问不方便,并且增加了异常越界的概率。为了解决上面的问题就引入了地址空间。加入了地址空间以后你的任何非法的操作都由OS去帮你检查。简单点说就是起到保护内存和将空间连续化处理
2.3 地址空间是怎么工作的?
通过地址空间所提供的地址,然后在通过页表转化为物理内存,拿到数据和内容。
MMU是内存管理单元,虚拟转物理通过软件效率可能会很低,所以采用软硬件结合来转换,并且还比较方便,相对简单很多。
便于理解的一个图表,其中学生都是可以在教室里面乱坐的,也有可能某个学生不在教室,是在家里上课的。
tast_struct 中是包含了很多的进程链接信息的
运行队列:是把进程PCB从等待队列中拿来运行的过程
等待队列:本质是把进程PCB排队的过程
task_struct中一些需要被解释的:
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。