🐶博主主页: @ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列: 线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux
🔥座右铭:"不要等到什么都没有了,才下定决心去做"
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀
目录
[struct mm_struct](#struct mm_struct)
程序地址空间
区间的地址分布
cpp[BCH@hcss-ecs-6176 10_21]$ cat test.c #include<stdio.h> #include<stdlib.h> int un_gval; int init_gval=100; int main(int argc,char* argv[],char* env[]) { printf("code addr:%p\n",main);//代表代码区的地址 const char *str="hello linux"; printf("read only char addr:%p\n",str);//代表字符常量取得地址 printf("init global value addr:%p\n",&init_gval);//代表初始化全局数据区 printf("uninit global value addr:%p\n",&un_gval);//代表未初始化全局数据区 char* heap1=(char*)malloc(100); printf("heap addr:%p\n",heap1);//代表堆区的地址 printf("stack addr:%p\n",&str);//代表栈区的地址 int i=0; for(;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]); } return 0; } 结果: [BCH@hcss-ecs-6176 10_21]$ ./mytest code addr:0x40057d read only char addr:0x40077e init global value addr:0x60103c uninit global value addr:0x601044 heap addr:0x1c9d010 stack addr:0x7ffdc6730228 argv[0]:0x7ffdc67307fd env[0]:0x7ffdc6730806 env[1]:0x7ffdc6730819 env[2]:0x7ffdc6730830 env[3]:0x7ffdc673083b env[4]:0x7ffdc673084b env[5]:0x7ffdc673085a env[6]:0x7ffdc673087e env[7]:0x7ffdc673088f env[8]:0x7ffdc673089c env[9]:0x7ffdc67308af env[10]:0x7ffdc67308b8 env[11]:0x7ffdc6730e54 env[12]:0x7ffdc6730e82 env[13]:0x7ffdc6730e9b env[14]:0x7ffdc6730ef5 env[15]:0x7ffdc6730f09 env[16]:0x7ffdc6730f1a env[17]:0x7ffdc6730f31 env[18]:0x7ffdc6730f39 env[19]:0x7ffdc6730f48 env[20]:0x7ffdc6730f54 env[21]:0x7ffdc6730f88 env[22]:0x7ffdc6730fab env[23]:0x7ffdc6730fca env[24]:0x7ffdc6730fe4
真正理解同一个地址进行读取不同内容
cpp[BCH@hcss-ecs-6176 10_21]$ cat test.c #include<stdio.h> #include<unistd.h> #include<stdlib.h> int g_val=100; int main() { pid_t id =fork();//创建子进程 if(id==0) { //child int cnt=5; while(1) { printf("pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); sleep(1); if(cnt==0) { g_val=200; printf("child change g_val:100->200\n"); } cnt--; } } else { //parent while(1) { printf("pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); sleep(1); } } return 0; } [BCH@hcss-ecs-6176 10_21]$ ./mytest pid:29919,ppid:7584,g_val:100,&g_val:0x60105c pid:29920,ppid:29919,g_val:100,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c pid:29920,ppid:29919,g_val:100,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c pid:29920,ppid:29919,g_val:100,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c pid:29920,ppid:29919,g_val:100,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c pid:29920,ppid:29919,g_val:100,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c pid:29920,ppid:29919,g_val:100,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c child change g_val:100->200 pid:29920,ppid:29919,g_val:200,&g_val:0x60105c//子进程的值是200,地址0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c//父进程的是100,地址0x60105c pid:29920,ppid:29919,g_val:200,&g_val:0x60105c pid:29919,ppid:7584,g_val:100,&g_val:0x60105c
得出的结论是:我们C/C++看到的地址,绝对不是物理地址!!!
我们平时用的地址都是虚拟地址/线性地址!!
地址空间
每一个进程运行之后,都会有一个进程地址空间存在!!
就可以解释fork之后形成了父进程和子进程
返回给父进程的id是子进程的pid,返回给子进程的是0
因为,父进程有自己的虚拟地址空间,给id在栈区开辟一个空间,然后通过页表实现虚拟地址映射物理地址,id的存储在内存中
子进程继承父进程,拷贝父进程的地址空间和页表,但是页表上虚拟地址映射物理地址不同,id在子进程虚拟地址和id在父进程虚拟地址是相同的,而映射的物理地址是不同的,所以内存中又会开辟空间去存储在子进程中的id
虚拟地址和物理地址
在 Linux 中,虚拟地址和物理地址是计算机系统中重要的概念,特别是在操作系统和硬件之间的交互中。
虚拟地址:
虚拟地址是由 CPU 生成的地址空间中的地址,它是进程所见到的地址。每个进程都有自己的虚拟地址空间,通常是连续的地址空间,从 0 开始。
虚拟地址提供了一种抽象机制,使得每个进程都认为它在独占系统资源,而不必担心与其他进程的冲突。
虚拟地址的转换是由硬件中的内存管理单元(MMU)来完成的。MMU 根据页面表(Page Table)将虚拟地址映射到物理地址。
物理地址:
物理地址是实际存在于计算机系统中的内存单元的地址,它是 RAM 中存储数据的位置。
物理地址是实际用于访问计算机系统中内存模块的地址。
虚拟地址通过地址映射机制转换为物理地址,然后在物理地址上进行访问和操作。
在 Linux 中,虚拟地址和物理地址的管理是由操作系统负责的。操作系统通过分页机制将进程的虚拟地址映射到物理地址,从而实现了内存管理和进程间的隔离。
struct mm_struct
虚拟地址空间也要被OS管理起来!!每一个进程都要有地址空间,系统中,一定要对地址空间做管理!!,地址空间最终一定是一个内核的数据结构对象!
在linux中,这个进程/虚拟地址空间的东西,叫做:struct mm_struct
cppStruct mm_struct { Long code_start; Long code_end; Long data_start; Long data_end; Long heap_start; Long heap_end; Long stack_start; Long stack_end; ...... }
页表
总结:
1)每一个进程都是有页表的
2)如何理解切换:只要把上下文数据保存好,pcb、地址空间、页表就保存好了
3)让进程以统一的视角看待内存,所以一个进程,可以通过地址空间+页表可以将乱序的内存数据,变成有序,分门别类的规划好!无序变有序
4)页表还有访问权限字段这就是为什么常量字符串,不可被修改,就是由于对应的内存访问权限是只读的
5)存在虚拟地址空间,可以有效的进行进程内存的安全检查!
6)将进程管理和内存管理进行结耦,通过页表,让进程映射到不同的物理内存处,从而实现进程独立性
CPU中的CR3寄存器
存储当前进程的页目录表物理地址。当CPU从虚拟地址空间中访问内存时,会首先将虚拟地址通过分页机制翻译为物理地址,而这个翻译过程需要使用到页表。
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸