单片机过渡到,对linux的初识(线程到进程)

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

前言

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

目录

  • 虚拟地址的出现

  • mmu解决什么问题

  • fork父子进程资源拷贝

  • C小测试

话不多说直接上代码例程

/*ubuntu下环境,gcc编译*/
#include 
#include 
#include 
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的数值和地址,先不看后面的运行结果,自己独立思考下,用着同一个变量地址可能一样,既然地址一样,数值肯定也一样。但是实际运行出来的效果,数值却不一样的,linux的进程特点也凸显出来。

运行结果:

单片机过渡到,对linux的初识(线程到进程)_第1张图片

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

什么是虚拟地址和物理地址?

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

单片机过渡到,对linux的初识(线程到进程)_第2张图片

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

单片机过渡到,对linux的初识(线程到进程)_第3张图片

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启动子进程,如果还先执行父进程,还做“写时复制”是不是会显得有点多余!?如果先执行子进程,就不用多做“写时复制”的骚操作,这也就解释为什么前面说内核会先调度子进程

1、C小测试

#include
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++的引用可以解决。具体交换代码示例可以到 公众号 小昭debug的自救历程 回复 C测试 查看。
我是小昭debug,如有问题,请麻烦联系我,同时,很乐意和你做技术交流。。
单片机过渡到,对linux的初识(线程到进程)_第4张图片

你可能感兴趣的:(linux,单片机,linux,单片机,运维,c语言)