文章目录
- 一、进程优先级
- 1.1 补充知识
- 1.2 查看进程优先级的命令
- 1.3 优先级的极值问题
- 1.4 补充
- 1.5 进程切换
- 1.6 进程调度
- 二、环境变量
- 2.1 概念介绍
- 2.2 命令行参数
- 2.3 环境变量
- 2.4 其他环境变量
- 2.5 获取环境变量的方法
- 2.6 环境变量的特性
- 三、 程序地址空间
- 3.1 虚拟地址
- 3.2 地址空间原理
- 文章结语
一、进程优先级
1.1 补充知识
进程优先级是进程得到CPU资源的先后顺序,因为目标资源稀缺,导致要通过优先级确认谁先谁后的问题,实际优先级也是一种数字,int,存在task struct,值越低,优先级越高,反之,优先级越低。关于操作系统,是基于时间片的分时操作系统,考虑公平性,优先级可能变化,但是变化幅度不能太大
可以通过ps -al查看详细信息ps -al |head -1&& ps -al |grep myprocess
其中:UID: user id,操作系统内用的数字,可以用ps -ln来查看
系统怎么知道我访问文件的时候,是拥有者,所属组,还是other?
Linux系统中,访问任何资源,都是进程访问,进程就代表用户,进程里的uid与文件的uid进行比较就可以得知是哪一个关系
进程真实的优先级= PRI(默认)+ NI,Linux系统里进程的优先级默认是80(PRI)。
1.2 查看进程优先级的命令
用top命令更改已存在进程的nice,进入top后按“r”->输入进程PID->输入nice值
PRI是最终计算的优先级,而这个ni严格意义上讲不能叫做优先级,它应该叫做进程优先级的修正数据,在改的时候可能操作系统不然你频繁改,不安全,进程真实的优先级=PRI(默认)+NI,Linux在做调整的时候,默认都是从八十开始,然后让你再做调整(因为如果我们每一次都是基于上次的优先级让我们去调整的话,就还得查一次上次的优先级)
还有其他方法调整优先级nice和renice
1.3 优先级的极值问题
用renice -n 100 -p 904336去试探边界
nice的取值范围为[-20,19],Linux进程的优先级范围为[60,99],为什么范围是这个,因为我们进程调度它的调度器在调度的时候要考虑公平性。虽然优先级是可以就是去调整我先谁后的问题,但这个幅度不能太大。优先级设立不合理,会导致优先级低的进程,长时间得不到CPU资源,进而导致:进程饥饿
每个cpu都有调度队列,所以优先级和调度队列之间,队列不就排队吗?为啥还要有优先级
1.4 补充
竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
可以查看改操作系统的cpu指令,图中就有两个CPU,就可能真的在并行跑cat /proc/cpuinfo
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称
之为并发(同时在跑是错觉,只要我切换的速度足够快,用户就感觉不到)
1.5 进程切换
死循环进程如何运行
一旦个进程占有CPU,会把自己的代码跑完吗?不会!!会存在时间片的东西,死循环进程,不会打死系统,不会一直都占有CPU!
关于寄存器
寄存器在我们对应的cpu内部,它都起着是临时保存当前进程所对应的临时数据,当我们在进行运行时,我们对应的进程在跑的时候,它的所有的这些寄存器都会被被填上对应的临时值,寄存器很多,有的是描述你这个进程,比如说这个计算结果有没有溢出,有的就是看浮点数计算有没有错误,有的寄存器是用来描述你的代码是从哪开始的,数据从哪开始的,有的寄存器是用来直接帮我们可以指向我们对应的pcb,有的未来还可以指向其他数据结构的,有的寄存器是用来就是帮助我们去进行确定我们的代码数据,那么代码段数据段它的起始地址各是什么,
结论:寄存器就是CPU内部的临时空间!寄存器不等于寄存器里面的数据,是一个存储空间
如何切换进程
CPU上下文切换:其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下一个任务的运行,这一过程就是进程切换,原来是进程要把自己的进程硬件上下文数据,保存起来,保存到进程的task_struct 里面,通过task_struct可以找到TSS:任务状态段
在早期内核源码可以查看得到
在调度的时候,全新的进程应该不用恢复上下文,在我们的task_struct里再新增一个标记位就行了。如果这个进程刚开始正准备第一次运行时,如果没有运行i,标识符就为零,一开始第一次调度,我们直接标识符置一,在调度CPU调度的时候,调度器它就会直接检测到你是只有全新的还是已经调度过了,这就很容易就能区分出来。
注意:
时间片:当代计算机都是分时操作系统,没有进程都有它合适的时间片(其实就是一个计数器)。时间片到达,进程就被操作系统从CPU中剥离下来。
1.6 进程调度
下面是一个CPU和一个运行队列
queue[140]完整名字为struct task_struct *queue[140],一共140项,之前说过优先级范围是[60,99] ,才40个,前面这100个优先级我们称之为实时优先级
分时操作系统:分时操作系统很明显它追求的就是基于时间片进行公平调度。
实时操作系统:来了一个进程,那么这个进程如果你要处理,就必须把它处理完,一般在工业领域,还有一些制造业领域应用比较多
为啥互联网只用到分时操作系统,却还会有实时操作系统,因为操作系统想让更多人用,由于我们用到的这些操作系统呢,全 全都是基于时间片调度的,所以不考虑0到99了,同学们一旦不考虑了,还剩40个优先级,根据公式×-60 + (140-40)算出下标
所以整体上我们未来在挑一个进程时,我们只需要从上往下遍历,从上往下查,这个数组,这个数组当中第一个下指针为空查下一个,为空查下一个,为空查下一个,不为空,把第一个挑出来,这个就是我们要调度的进程(可以宏观上按照从前往后遍历就完成了我们对应的按照不同的优先级去调度,局部上我们采用先进先出),这叫做FIFO调度算法,这个也相当于一个哈希表
但是时间复杂度为O(n),调度器,如何快速地挑选一个进程呢?
运行队列存在bitmap[5](位图),比特位的位置:queue[140],哪一个slot(0000 0000),比特位的内容:1/0,是否为空,为啥是5,因为32x5为160,正好覆盖了140
1. 挑队列 2. 挑进程
不需要遍历位图,只需要查看位图,时间复杂度变为O(1)
以前我们是一个一个一个需要遍历,现在只要检测每个比特位,整体把32比特位整体来查,把32比特位整体来查,然后查到了比如说对应的三号下标,它不为零,然后再根据32比特位内再去检测它对应的哪一个是哪个队列,这样就可以在一定程度上缓解我们遍历这个整个140个队列的时间复杂度,把它由遍历转化成位图的查找操作。
进程内还定义了结构体
存在两个其他的结构体指针
如果一个60号进程,也有一个90号进程,60号进程是个死循环,60号进程优先级时间片到了,它运行完毕了,重新再放到这个队列里,再放到队列结尾处,这就发现一个很尴尬的情况,CPU调度永远都是要把前面这批进程全跑完才能到第二个,这不就造成了进程的饥饿问题吗?
实际上有两个队列,可以不要让已经调度过的进程插回到active队列,插入到expired队列,之后再交换swap(&active, &expired);,现在很多操作系统都支持内核优先级抢占,新进来的进程也会到active队列中
操作系统,它首先要根据我们的调度算法,然后在cpu上可能有两个队列或者四个队列,它要在这几个队列里查负载最低的,把进程放在负载最低的那个cpu里。这就叫做系统内多cpu并行运行时,保证cpu的负载均衡
补充:nr active代表的是整个运行队列有多少个进程
为什么在我们对应的Linux内核当中,优先级有一个单独的变量(Ni)?
答案是同学们因为更改一个进程的优先级是随时都能更改的,万一你更改这个进程优先级,它如果在我们对应的活跃队列里呢,因为是老进程,不能直接改,要运行完时间片才能改,如果改了之后不变,那就特别奇怪,所以要有单独的变量
二、环境变量
2.1 概念介绍
环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
2.2 命令行参数
#include <stdio.h> int main(int argc, char * argv[]) { return 0; }argc是数组个数,argv是一个char*类型的,叫做指针数组(它第二个参数其实是一个指向一个一个字符串所对应的一个数组)
进一步查看底层细节
code.c
#include <stdio.h> int main(int argc, char* argv[]) { for(int i=0;i<argc;i++) { printf("argv[%d]=%s\n",i,argv[i]); } return 0; }makefile
code:code.c gcc -o $@ $^ .PHONY:clean clean: rm -rf code运行得到
进一步探索
我们发现argv它就是一个变长的一个数组。当我们输入命令行,或者以空格作为分隔符,其实我们输入的就是一个长字符串,我们在命令行上输入的这东西,其实是被当做一个整体,是个大字符串。然后当我们在执行程序的时候,就有一种人,有一种角色,或者有一个人帮我们把命令行当中我输入的字符串打散成这种以空格作为分隔符打散成这样,放到argv数组中,这个东西叫做命令行参数。
我们平时用到命令,本质上就是可执行程序
可以自己模拟实现,main的命令行参数,是实现程序不同子功能的方法!(指令选项的实现原理!)
#include <stdio.h> #include <string.h> int main(int argc, char* argv[]) { if(argc!=2) { printf("Usge: %s [-a|-b|-c]\n",argv[0]); return 1; } const char *arg=argv[1]; if(strcmp(arg,"-a")==0) { printf("功能1\n"); } else if(strcmp(arg,"-b")==0) { printf("功能2\n"); } else if(strcmp(arg,"-c")==0) { printf("功能3\n"); } else { printf("Usge: %s [-a|-b|-c]\n",argv[0]); } return 0; }
所以进程拥有一张表,argv表,用来支持实现选项功能!
为啥我们写的可执行程序和系统自带的,一个可以直接运行,一个需要带
./
因为要执行一个程序,必须先找到它!!系统中存在环境变量,来帮助系统找到目标二进制文件!,系统文件放在指定目录下,可以直接运行,自己的可执行文件不能放到/usr/bin目录下,有权限问题
不建议你把你自己写的二进制文件拷贝到系统当中
2.3 环境变量
系统存在PATH环境变量,系统当中存在的,用来标识一串路径。他这个PATH里面表明的是告诉系统去哪些路径下找二进制文件。
可以用env查看环境变量
查具体的环境变量可用echo $某某,比如PATH
当你今天要执行一个ls命令时,操作系统它不是默认在user/bin路径下,它默认是去查你这个PATH环境变量。而PATH环境变量找的时候,它会以冒号作为分隔符,先在第一个路径下找,如果没有找到s,那么就在第二个路径下找,如果还是没有找到,就在第三个路径下找,依此类推。如果把所有路径全遍历一次,找不到,就会给我们报command not found
除了把自己写的可执行文件添加到指定目录,还可以将路径加到环境变量里
我们把PATH内容覆盖成我们当前路径,它找不到系统对应的命令了,进而证明我们系统找命令时就是根据PATH来找的
如何恢复PATH,一种方法是可以直接赋值,另一种是关掉,重新登录,如果你把环境变量PATH不小心改了不影响,因为这个PATH这个变量它是一个内存级的变量,其实是在你当前bash的进程上下文里的,就相当于是你的bash内部malloc了一段内存空间,环境变量名和内容字符串保存起来了 ,如果不想覆盖,可以PATH=$PATH:某某路径
如何理解环境变量呢?存储的角度
在我们登录的时候,系统就会给我们创建一个bash进程。bash从系统当中读取我们所有的环境变量信息,然后在bash进程启动时,要在内部形成一张表(环境变量表),而环境变量表呢非常简单,它就是一个指针数组(跟命令参数表差不多,也是一个一个字符串,内容不一样)
在我们输入指令(比如ls -l),首先这个命令行字符串并不是被子进程拿到的,是先被bash先拿到,他就要给我们先构建第一张表叫做命令行参数表,经过拆解,我们对应的bash它就要拿着你的程序的名字,然后拿着Is命令,再在环境变量表当中找到PATH,把每个路径拼上你的程序名,由bash在系统当中找对应的要执行的命令是否存在。如果在就创建子进程执行它,不存在就报command not found。最终的结论是bash那么它会默认在bash内部有两张表。
所以环境变量究竟是什么呢?
环境变量就是key-value的长字符串,我们在进行我们的sh或者bash在启动的时候,他会想办法去帮我们在自己进程内部。它new/malloc出一段空间,然后给每个环境变量再new/malloc空间形成个二维数组,然后把环境变量字符串依次拷到这个表里面,所以bash就在内部维护了这张表,所以当我们用env查的时候,就是打印这张表的内容
环境变量,最开始从哪里来的呢?
环境变量最开始是从配置文件来的。我们Linux系统存在相关的系统配置文件。这个配置文件当中包含所有的环境变量的直接或间接环境变量的内容。当我们的bash在进行启动时,它会从配置文件当中读取所有的环境变量值,并在自己的内部创建这一张表。
我们的系统在启动的时候,登录时,bash进程就会被创建,bash进程创建时它就会读取这个bashrc和bashprofile文件。这个配置文件里的内容给我们构建出环境变量。配置文件包括。.bashrc和.bash_profile,可以将自己的目录给添加进来,这样下次登陆也可以用自己的命令
环境变量是从系统的配置文件中来的,指令的查找工作是由bash自己完成的,执行一个程序,必须先找到它,左手有你的要执行的命令参数表,右手有环境变量表有一个PATH,通过PATH来找
2.4 其他环境变量
HOME
为什么
cd ~会到家目录,~只是个字符
存在环境变量HOME
SHELL
它代表的就是你当前你自己在你这个用户登录时,你用的是哪一个版本的shell。所以他把对应的bash的路径给我们保存起来
USER和LOGNAME$USER代表的是当前用户,$LOGNAME是当前登录的用户,直接用su,两个不会改,用su -会用root重新登陆,两个环境变量都会改,你其实变成su一下变成root,其实就是跟正常的su -是其实是一样,只不过它就不更新我们对应的环境变量
按键盘上下翻、ctrl+r(查找)、!v这些查找历史命令怎么实现的?
bash会把历史上输过的命令全部给我们记录下来,可以想象它会存在一个队列命令字符串,也可以用history查看命令
**HISTSIZE:**只会记录最近1000条命令
也可以查看当前记录了多少历史记录
HOSTNAME
查看当前主机的主机名
PWD
记录当前工作目录,PWD它会记录下来当前所在的路径。
OLDPWD
解释了为什么能够cd -就能让最近的两个路径来回切换,直接切换到old pwd
总结
环境变量它是操作系统中用来指明操作系统运行环境的一些参数。大家可以发现我们当前所对应的环境变量,每一种环境变量它的功能都不太一样。每一个环境变量都有它特定的功能,所以这个环境变量表本质其实是操作系统在自己的配置文件里提供的,所以它就叫做系统当中用来指定系统运行环境的一些参数。 这些参数未来被bash用了。
2.5 获取环境变量的方法
导入一个环境变量
export加上自己定义的环境变量
取消环境变量
unset取消
env
查看所有的环境变量
echo $环境变量
查看单个环境变量的内容
通过代码获得环境变量
- 方法一:main函数参数获得环境变量
code.c
#include <stdio.h> #include<string.h> int main(int argc,char* argv[],char *env[]) { for(int i=0;env[i];i++) { printf("env[%d]->%s\n",i,env[i]); } return 0; }运行后得到
main函数可以不带参,可以带argc,“也可以带argv,然后也可以带那个env。这个main函数它是提前写好的还是怎么样的?为什么它会出现这样神奇的效果呢?
最开始main函数会被一个叫crt的函数调用,CRT函数里面它里面会有很多的判断,它会判断你将来,在你就相当于你不是做过词法分析做过语法分析吗?里面有个类似这样的
带几个参数编译器会自己识别的,参数不就是字符,所以对他来讲,它可以做词法语法分析,然后进行判断来决定我们调哪个调哪一个。
自己导入的环境变量(export)可以被自己的子进程拿到的,结论是环境变量可以被子进程继承。子进程的子进程也能继承,环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
- 方法二:getenv获得环境变量
先通过man指令来查看,getenv它会根据环境变量的名字获取环境变量,如果获取成功了,就会返回我们环境变量字符串的起始地址,否则就是NULL。利用这个可以获取已经存在的对象
具体实现
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[], char *env[]) { (void)argc; (void)argv; (void)env; char *value = getenv("PATH"); if (value == NULL) { return 1; } else { printf("%s", value); } return 0; }为啥在变量前加void,因为我们对应这些变量把它定义出来,我们不用它的话,在我们的GCC加加当中或者在VS当中,编译器编译有可能报错,有可能有告警。
可以设计一种程序,只能自己运行
而我在我们的整个系统里面其实只有一个人知道我当前这个程序当前的登录用户是谁,就是bash,bash会把环境变量交给子进程,就可以传递下来,就可以让我的这个程序做身份识别,也可以通过环境变量来做一些进程级的数据传递,进而可以控制我们子进程的一些控制逻辑。这就是它环境变量的意义。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char* argv[], char *env[]) { (void)argc; (void)argv; (void)env; const char *value = getenv("USER"); if (strcmp(value, "hlf") == 0) { printf("正常运行\n"); } else { printf("ERROR"); } return 0; }- 方法二:environ获得环境变量
存在一个全局的指针environ,指向环境变量表
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> extern char **environ; int main(int argc, char* argv[]) { (void)argc; (void)argv; for(int i=0; environ[i]; i++) { printf("environ[%d]=%s\n",i,environ[i]); } return 0; }结果如下
2.6 环境变量的特性
环境变量具有全局特性,子进程也可以打印环境变量
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> extern char **environ; int main(int argc, char* argv[]) { (void)argc; (void)argv; pid_t a = fork(); if(a == 0) { for(int i = 0; environ[i]; i++) { printf("environ[%d]=%s\n", i, environ[i]); } } else { sleep(1); } return 0; }bash会记录两套变量:1.环境2.本地,本地变量不会被子进程继承,只在bash内部被使用!!
为什么要有本地变量?
它为了让自己这个bash变成一门语言,定义变量就可以用了,bash内部它也要做词法语法分析,有些本地变量是具有功能性的
导入环境变量,既可以export环境变量的定义,也可以export直接跟上本地变量
export这样的命令,是由这个bash创建的子进程去执行这个export,而子进程导环境变量是把数据放在了父进程的环境变量表里面,之前我们了解到我们对应的进程之间是具有独立性的,父进程可以把环境变量交给子进程,因为它没有继承关系,所以export这种命令它一旦被执行,但子进程是没办法把数据交给父进程的,如何做到的呢?
这个export命令它和我们一般的命令不一样,这个命令在执行时不需要创建子进程,这个export命令我们一般把这种命令叫做内建命令。bash是自己调系统调用完成的,一般都是通过bash自己调函数
补充:shell脚本(配置文件是用shell脚本写的)
code.sh
#!/bin/bash touch hello.c i=100 echo $i可以让命令批量化一次性执行
#include <stdio.h> #include <unistd.h> int gval = 100; int main() { pid_t id = fork(); if(id == 0) { while(1) { printf("子: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid()); sleep(1); gval++; } } else { while(1) { printf("父: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid()); sleep(1); } } return 0; }三、 程序地址空间
3.1 虚拟地址
先执行一段代码
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int g_unval; int g_val = 100; int main(int argc, char *argv[], char *env[]) { const char *str = "helloworld"; 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_mem = (char*)malloc(10); char *heap_mem1 = (char*)malloc(10); char *heap_mem2 = (char*)malloc(10); char *heap_mem3 = (char*)malloc(10); printf("heap addr: %p\n", heap_mem); printf("heap addr: %p\n", heap_mem1); printf("heap addr: %p\n", heap_mem2); printf("heap addr: %p\n", heap_mem3); printf("test static addr: %p\n", &test); printf("stack addr: %p\n", &heap_mem); printf("stack addr: %p\n", &heap_mem1); printf("stack addr: %p\n", &heap_mem2); printf("stack addr: %p\n", &heap_mem3); printf("read only string addr: %p\n", str); for(int i = 0 ;i < argc; i++) { printf("argv[%d]: %p\n", i, argv[i]); } for(int i = 0; env[i]; i++) { printf("env[%d]: %p\n", i, env[i]); } return 0; }结果
空间布局
解释下为啥局部变量前加static后生命周期变成全局了,因为地址从栈转移到初始化数据那里了
C/C++指针用到的地址,全部都是虚拟地址,用一段代码验证
#include <stdio.h> #include <unistd.h> int gval = 100; int main() { pid_t id = fork(); if(id == 0) { while(1) { printf("子: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid()); sleep(1); gval++; } } else { while(1) { printf("父: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid()); sleep(1); } } return 0; }结果
值不一样,地址一样,进而说明地址不是内存地址,是虚拟地址,虚拟地址与task_struct有关,32位机器-2^32个地址(4G),64位机器-2^64个地址分为用户和内核空间
一个变量它会有一个叫做虚拟地址和物理地址的东西,操作系统内为每一个进程,进程在创建的时候要为进程构建一张叫做页表的东西,这个页表当中它的左侧填充的是我们当前进程某一个叫做虚拟地址,右侧填充的是我们对应的叫做变量所对应的,物理地址。访问虚拟地址时,操作系统会自动把这个虚拟地址查表转化成我们对应的物理内存对应的地址,一个进程一个虚拟地址空间,一个进程也有一套页表,父子进程都有自己的虚拟地址空间,子进程的地址空间,也是拷贝父进程的,子进程的pcb地址空间页表全部都要从父进程那里拷贝
写实拷贝的原理如下,该过程是由操作系统自动做的。
画大饼,让每一个进程都认为自己有4GB的物理内存,或者,每一个进程都认为自己在独占物理内,为了防止弄混,需要把“大饼”也要管理起来,地址空间的本质就是一个结构体对象。
3.2 地址空间原理
它划分了很多区域。但是整个区域它其实从全0到全F所以编址,每一个地址代表一个字节的空间。
进行区域的划分,只需要定一个叫做开始和结束就可以了,可以详细成桌子,每一个地址就是刻度
虚拟地址它的结构体里是每一个区域的开始虚拟地址开始虚拟地址和结束虚拟,它就把对应的值往里面一填,就把地址空间划分出来了。调整区域就对其加减即可
内容大致是这样
要把一个程序加载到内存,要做两件事情。
1.在虚拟地址空间中申请指定大小的空间(通过start、end来调整范围)
2. 加载程序,申请物理空间
这样就可以把物理地址转化成为虚拟地址!!
描述完后也用链表链接起来
为什么要有虚拟地址空间
- 将地址从无序,变有序
- 地址转换的过程中,也可以对你的地址和操作进行合法性判定,进而保护物理内存!(操作系统不给你这个进程访问,甚至把你这个进程直接杀掉)
对于野指针,对一个已释放的内存区域进行访问时,页表当中根本就不存在这组虚拟到物理的映射关系,查页表会失败,操作系统就会知道,操作系统会把你干掉了。所以你的野指针之后,你的进程会崩溃,你指针就是地址,所以查页表查不过去,查不过去
其他情况也是
有时候正文部分有两个G(太大了),就只把前面的500兆的放在物理地址中,其中还有剩下的1.5GB没有加载进来。这个时候我们操作系统就可以实现继续动态加载,操作系统发现虚拟地址有,但物理内存并不在内存里。这个时候我们操作系统就可以实现继续动态加载, 这种机制我们称之为缺页中断。
**让进程管理和内存管理,进行一定程度的解耦合!!**我们可以不加载代码和数据,只有task struct,mm struct,页表,创建进程,先有task_struct,mm_struct等,后加载代码和数据?进程挂起就能解释的通(左边进程管理留下,页表和代码数据清空)
关于堆区
比如栈区,调函数时形成栈帧,栈有着自己的栈底和栈顶。栈只要往下迁移,往下进行扩展,函数调完了再把栈区直接往上重新移动,所以栈区只需要这个指针来回就这个指针来回上下移动就行,但是栈区是零散化的,一直malloc咋管理的
mm_struct里面会维护一张链表
mm_struct和vm_area_struct这两个里面的字段的开始地址和结束地址可能会有重复, 它的mm_struct是表示的是整个地址空间整体的一个排布。那么它表示的堆区的开始和堆区的结束地址整体的。但是我们对应的这个你其实每一个子区域它都会对应的vm_area,所以实际上你的进程在查你对应的代码的时候,就只查vm_area就行
因为页表的映射的存在,程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的虚拟地址和物理地址进行映射,在进程视角所有的内存分布都可以是有序的。
进一步解释了进程具有独立性:
1.内核数据结构独立
2.加载进入内存的代码和数据独立!
文章结语
感谢你读到这里~我是「键盘敲碎了雾霭」,愿这篇文字帮你敲开了技术里的小迷雾 💻
如果内容对你有一点点帮助,不妨给个暖心三连吧👇
👍点赞| ❤️收藏| ⭐关注
(听说三连的小伙伴,代码一次编译过,bug绕着走~)
你的支持,就是我继续敲碎技术雾霭的最大动力 🚀
🐶 小彩蛋:
/^ ^\ / 0 0 \ V\ Y /V / - \ / | V__) ||摸一摸毛茸茸的小狗,赶走所有疲惫和bug~我们下篇见 ✨