索引
- 一.进程优先级
- 二.环境变量
- 1.通过代码如何获取环境
- 1.通过第三个命令行参数获得
- 2.根据第三方变量environ获取
- 3.通过系统调用获取环境变量
- 2.验证环境变量可以被子进程继承下去
- 三.验证地址空间
- 1.验证程序地址空间
- 2.证明地址空间不是物理地址
- 四.虚拟地址空间
- 虚拟地址空间存在的好处
一.进程优先级
什么是进程的优先级?
优先级是进程获取资源的先后顺序
为什么会存在优先级?
因为系统永远都进程占大多数,此时资源对应就很少,操作系统就需要根据进程的优先级来决定哪个进程先被执行,哪个进程后执行。
优先级如何表示?
PRI:表示这个进程可被执行的优先级,其值越小越早被执行
如何修改进程的优先级?
通过nice值修改,nice取值范围是-20 至 19
PRI(new) = PRI(old) + nice
因此在Linux下调整进程的优先级就是调整nice的值
二.环境变量
为什么系统的指令不用带路径,但我们自身的程序却每次都要带./表示运行当前程序的路径?
因为在Linux中,可以直接执行的指令通常是系统的内置命令,或者是已经在$PATH环境变量中定义的可执行文件,而这些命令或文件的路径已经被系统定义,运行这些进程的时候,可以直接在路径中个搜索,所以不用带./,但是我们编译后生成的二进制可执行文件默认不是在 $PATH中的,所以需要带路径。
所以什么是环境变量?
环境变量是一般是指在操作系统中用来指定操作系统运行环境的一些参数。
eg:我们在编写C/C++代码的时候,链接的时候,编译器会自动通过环境变量去帮我寻找动静态库的位置然后链接。
常见环境变量
PATH:定义可执行文件的搜索路径
HOME:指定主目录的路径
LANG:指定系统的语言
SHELL:指定用户的默认shell
PS1:定义shell提示符的格式
查看环境变量:env
显示PATH的内容:echo $PATH
我们所写的环境变量都是具有临时性的,系统重启之后又会重新恢复,为什么?涉及到环境变量的性质
在Linux系统中,环境变量是由shell进程管理的,当一个进程启动时,他会继承其父进程的环境变量,并可以在自己的环境变量中添加,删除或修改键值对。而系统每次重启,都会重新加载配置文件,bash会根据上下文导出环境变量,然后每次env就可以查看环境变量了,如果需要永久性修改,就修改配置文件,但不建议
1.通过代码如何获取环境
先了解什么是命令行参数?
指命令行中给的那个的参数
理解选项的含义
1.通过第三个命令行参数获得
argc:表示命令行参数的个数
argv:指向命令行参数的指针
env:指向环境变量(字符串)的字符指针
#include<stdio.h>
W> 2 int main(int argc,char*argv[],char*env[])
3 {
4
5 int i = 0;
6 for(i = 0;env[i];i++){
7 printf("%s\n",env[i]);
8 }
9 return 0;
10 }
2.根据第三方变量environ获取
#include<stdio.h>
W> 2 int main(int argc,char*argv[])
3 {
4
5 extern char**environ;
6 int i = 0;
7 for(i = 0;environ[i];i++){
8 printf("%s\n",environ[i]);
9 }
10 return 0;
11 }
~
3.通过系统调用获取环境变量
#include<stdio.h>
2 #include<stdlib.h>
3 int main()
4 {
5 printf("%s\n",getenv("PATH"));
6 return 0;
7 }
2.验证环境变量可以被子进程继承下去
1 #include<stdio.h>
2 #include<stdlib.h>
3 int main()
4 {
5
6 char *env = getenv("MYENV");
7 if(env){
8 printf("MYENV = %s\n",env);
9 printf("PATH = %s\n",getenv("PATH"));
10 }
11 // printf("%s\n",getenv("PATH"));
12 return 0;
13 }
~
在bash导出MYENV后才可以打印出环境变量,而我们创建的进程自然就是bash的子进程,验证完毕
三.验证地址空间
先回答一个问题:程序地址空间是内存吗?不是,是进程的虚拟地址空间
之前了解过每一个程序其地址空间如图所示
1.验证程序地址空间
7 printf("code addr : %p\n",main);
8 printf("init global addr : %p\n",&g_val);
9 printf("uninit global addr : %p\n",&un_g_val);
10 char*m1 = (char*)malloc(100);
11 char*m2 = (char*)malloc(100);
12 char*m3 = (char*)malloc(100);
13 char*m4 = (char*)malloc(100);
14 char*m5 = (char*)malloc(100);
15 char*m6 = (char*)malloc(100);
16 printf("heap addr : %p\n",m1);
17 printf("heap addr : %p\n",m2);
18 printf("heap addr : %p\n",m3);
19 printf("heap addr : %p\n",m4);
20 printf("heap addr : %p\n",m5);
21 printf("heap addr : %p\n",m6);
22 printf("statck addr : %p\n",&m1);
23 printf("statck addr : %p\n",&m2);
24 printf("statck addr : %p\n",&m3);
25 printf("statck addr : %p\n",&m4);
26 printf("statck addr : %p\n",&m5);
27 printf("statck addr : %p\n",&m6);
28 for(int i = 0;i < argc; i++)
29 {
30 printf("argc addr : %p\n",argv[i]);
31 } for(int i = 0;env[i];i++)
33 {
34 printf("env addr : %p\n",env[i]);
35 }
2.证明地址空间不是物理地址
#include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int g_val = 100;
5 int main()
6 {
7 pid_t id = fork();
8 if(id == 0)
9 {
10 //child
11 int flag = 0;
12 while(1)
13 {
14 printf("我是子进程:%d,ppid:%d,g_val:%d,&g_val:%p\n\n",getpid(),getppid(),g_val,&g_val);
15 sleep(1);
16 flag++;
17 if(flag == 5)
18 {
19 g_val = 200;
20 printf("我是子进程,全局数据已经修改了,请注意查看!\n");
21 }
22 }
23
24 }
25 else
26 {
27 //parent
28 while(1)
29 {
30
31 printf("我是父进程:%d,ppid:%d,g_val:%d,&g_val:%p\n\n",getpid(),getppid(),g_val,&g_val);
32 sleep(2);
33 }
34 }
35
36 return 0;
37 }
由运行结果可知
当父子进程没有人修改数据的时候,父子是共享数据的,数据的值和地址显示也都是一样的,但是当子进程修改值后,发现打印出父子进程的地址显示还是一样的,得出一个结论:我们在C/C++中使用的地址,绝对不是物理地址,如果是物理地址,这种现象是不可能产生的。
既然地址空间不是物理地址,那是什么地址?是虚拟地址,是线性地址
四.虚拟地址空间
什么是虚拟地址空间?
虚拟地址空间是指每个进程独立的内存地址空间,是OS系统给每个进程分配给进程的,是虚拟的,相当于是OS给进程花了一个大饼,告诉每个进程我都有4g的空间可以让你使用,然后这个4g空间上有特定的位置存储特定类型的数据。
总结:每个进程都认为自己是独占系统中所有资源的,实际上每个进程根本用不了那么多资源,在虚拟地址空间和物理地址中间加一层映射就可以了,如何加映射?下面讲
每一个进程都会有自己的task_struct(进程控制块)控制调度进程,而在task_struct内部还有一个
指针struct mm_struct *
指向进程的地址,其在源码中的部分结构在下面展示
每个虚拟地址空间都有自己对应的区域,区域的大小无非就是改变分界线的值罢了,不放猜测每个区域的范围,都是有对应的区域编号的,就好比是这样的
struct mm_struct{
long code_satrt;
long code_end;
long init_start;
long init_end;
long uninit_start;
long uninit_start;
long heap_start;
long heap_end;
两个问题:
- 程序被编译出来,没有被加载的时候,程序内部,有地址吗?
程序在被编译完成的时候,代码其实就有一套逻辑地址。
- 程序被编译出来,没有被加载的时候,程序内部,有区域吗?
磁盘中是有区域划分的,那么可执行程序在编译完成之后也是有区域划分的。
理解虚拟地址到物理地址的转化
- 编译程序的时候,虚拟地址就认为程序是按照0000~FFFF进行编译的
- 磁盘中是有区域划分的,那么可执行程序在编译完成之后也是有区域划分的,加载之后将内存中的代码和数据地址全部转化为我们认为的从0开始的地址,加载到内存后,操作系统会给每一个进程创建PCB,每一个PCB中都有一个指针指向进程地址空间,地址空间再经过映射到物理内存,映射到物理内存之后程序就开始读取你代码中的数据,但他读取你代码的时候,因为你的代码已经转化为虚拟地址,所以你的CPU读到全部的都是虚拟地址,然后再进过寻址转化的时候也一定会经过页表的转化转化成物理地址。
所以为什么此时地址一样,但是指向的内容却不一样?
子进程的代码和数据基本上是继承自父进程的,所以最初父子进程的虚拟进场以及页表的映射都是一样的,但此时子进程修改了数据,此举可能会影响到父进程逻辑的运行以及判断,所以此时内存会重新开辟一段空间给子进程,并将原来的数据拷贝给子进程,子进程的虚拟地址是不变的,但是经过页表的映射所转化的物理地址发生了改变,因此会出现上述现象。
关于上述的现象称为写实拷贝:
如果父子进程中但凡有一个进程可能涉及到数据的修改,那么内存便会重新开辟一份内存并将数据拷贝,但此时修改的只是页表的映射关系,对应的虚拟地址是不会发生改变的。
虚拟地址空间存在的好处
- 保护内存,避免程序员直接接触物理内存,造成不可控的伤害
- 允许多个进程共享同一份代码和数据,节省资源,提高OS效率性能
- 让进程或者程序可以以一种统一的视角看待内存,方便以统一的视角看待内存,以统一的方式加载所有的可执行性程序