大家好,我是小昭,一路在debug调试,代码缓慢地优化中,希望生活也能优化起来……

前言
为了应对自身专业能力的提升和工作的要求,开始学习linux,过程中遇到一些问题,比如像mmu,为什么单片机上不了Linux?mmu的出现是为了解决什么问题?如果这些问题可以得到解决,我相信对入门linux学习会有一定的帮助。学习的过程我参考了很多前辈的总结分享,在这分享我整理总结的内容。

目录
虚拟地址的出现
mmu解决什么问题
fork父子进程资源拷贝
C小测试
话不多说直接上代码例程
/*ubuntu下环境,gcc编译*/#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(int argc,char* argv[]){pid_t pid;int a_value=0;/*创建子进程*/pid = fork();if(pid == -1){perror("fork_error!\r\n");}if(pid > 0){/*父进程执行*/a_value=1;while(1){printf("father_process---a_value:%d,adress:%p\r\n",a_value,&a_value);sleep(1);//引起进程间调度a_value++;}}else if(pid == 0){/*子进程执行*/while(1){printf(" son_process---a_value:%d,adress:%p\r\n",a_value,&a_value);sleep(1);//引起进程间调度}}return 0;}
这个程序运行两个进程,分别打印a_value的数值和地址,先不看后面的运行结果,自己独立思考下,都用着同一个变量地址也一样,按C语言的规则,既然地址一样,数值肯定也一样,但是实际运行出来的效果,数值是不一样的。凸显出linux的进程间资源的独立性。
运行结果:

a_value变量地址相同,父进程的中变量一直自增,子进程中的变量却还是初始值。这样也就为什么说linux中进程间的资源是互相独立,井水不犯河水。但是但是,有一点我们需要思考,与我们的认识的C语言相矛盾,按C语言的解释既然地址一样,为什么数值却不同?
因为在Linux下,用户看到的地址是虚拟地址,这个地址不是变量在内存中实际的真实地址(物理地址),是需要转换的,确切来说,他们的物理地址是不同的。补充:内核一般会先调度子进程(后文有解释)。


什么是虚拟地址和物理地址?
以stm32等MCU为例,在代码中,实际操作变量的地址全是物理地址,CPU是直接通过地址总线,访问内存(RAM),读取该地址的变量数值,这个地址就是物理地址(Physical Address),简写PA。

而运行linux的芯片(像cortex-a、arm9和x86等)就比较牛,CPU手底下有MMU(内存管理单元,只有CPU能识别)来帮忙,当要读取变量时,它是不直接访问内存,直接通过访问MMU(虚拟地址Virtual Address 简写VA),MMU再访问内存(物理地址),得到变量的数值。将虚拟地址转换访问内存的不同物理地址这一过程,称地址重定位。所以在linux平台上,用户看到的地址都是虚拟地址。



mmu解决什么问题
还是以stm32为例子,flash的程序是bootloader+APP1+APP2(固件升级),要让这三个程序独立运行,访问内存地址就不能重叠。那么会出现一个问题,在编译前,就要给每一个程序划分好内存空间,也就物理地址分配使用。但是像linux、window这种系统装那么多软件,显然是做不了,会访问到相同的地址,资源不能独立,访问的数据出错,系统崩溃。虚拟地址就能很好解决这个问题,即使出现相同的地址,当通过MMU地址映射,因为他们的页表不同,就是映射的规则不一样,可以理解为一个函数 PA = f(VA),页表看成函数f(),f()不一样,对应的物理地址肯定就不一样(可以这样简单的理解),这也就解释虚拟地址相同,映射出物理地址不同。像多用户、权限不同、多进程的系统,更需要mmu,可以做到对系统空间保护和安全。(mmu对用户是隐藏)
补充:像stm32单片机没有进程的说法,在RTOS这种多任务系统,程序执行的最小单位:任务(线程)。


fork父子进程资源拷贝
当fork后,会创建一个与父进程完全相同的子进程,但进程多数是调用exec,打开其他进程。所以出于效率考虑,linux引进了“写时复制”技术,当父进程中的各段发生变化时,父进程才会将对应的段复制给子进程。
当执行fork后,子进程没有使用exec,子进程指向父进程的代码段、数据段和堆栈的物理空间(VA和PA相同,权限"只读"),当父进程的内容发生改变时,父进程才会复制对应的段分配物理空间给子进程;如果使用exec,两者的代码不用,将分配独立的物理空间,父进程的各段物理空间被子进程占用,父进程不复存在。
OK,按照这个逻辑,多数情况是使用exec启动子进程,如果先执行父进程,还做“写时复制”是不是会显得有点多余!?如果先执行子进程,就不用多做“写时复制”的骚操作,这也就解释为什么前面说内核会先调度子进程。
不得不说,单单进程这一块就要掌握很多知识,在linux面前只是冰山一角,需要学习的地方特别多。


1、C小测试
#include<stdio.h>void swap_int(int a,int b){int temp = a;a = b;b = temp;}void swap_str(char* a,char* b){char* temp = a;a = b;b = temp;}int main(int argc,char* argv[]){int a = 10;int b = 5;char* str_a = "hello world";char* str_b = "world hello";swap_int(a,b);swap_str(str_a,str_b);printf("%d %d %s %s\n",a,b,str_a,str_b);return 0;}
思考下输出结果?
正确结果
10 5 hello world world hello
如果没有疑问就不用往下看了,要是感到诧异,请继续往下了解,一定对你有所帮助。
swap_int(a,b);这个应该没什么问题。疑问大部分会出现在swap_str(),其实代码是这样swap_str("hello world","world hello"),传入的是字符,并不是str变量地址,所以值并没发生变化。交换两个字符串,原变量数据类型是char*,要进行值交换,就要传char**二级指针,或者用C++的引用可以解决。具体交换代码示例可以到公众号回复 C测试 查看。
我是小昭debug,如有问题,请麻烦联系我,同时,也很乐意和你做技术交流。




