前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Linux】从零开始认识进程 — 终篇

【Linux】从零开始认识进程 — 终篇

作者头像
叫我龙翔
发布2024-03-28 10:04:57
1070
发布2024-03-28 10:04:57
举报
文章被收录于专栏:就业 C++ 综合学习

前言

经过前三篇的认识,现在应该已经大致认识到了进程到底是什么,也认识了进程的状态,进程的优先级,环境变量等知识。今天我们继续学习,来一起认识地址空间!!!

1 环境变量的组织方式

上一篇文章我们介绍了什么是环境变量,今天我们来看看如何创建获取环境变量

代码语言:javascript
复制
补充一下和环境变量相关的命令
1. echo: 显示某个环境变量值
2. export: 设置一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除环境变量
5. set: 显示本地定义的shell变量和环境变量

我们先在当前路径创建一个变量看看

我们可以通过echo 命令查到,说明HELLO在Bash中是存在的,只是没有把它当做环境变量。我们也可以通过下面一段代码来验证一下:

代码语言:javascript
复制
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 
  5
  6 
  7 int main()
  8 {
  9 
 10   char *path = getenv("HELLO");                                                    
 11   if(path == NULL) return 1;
 12   printf("hello:%s\n",path);
 13   return 0;
 14 }

这样执行程序后会发现无法打印出来(因为子进程会继承父进程的环境变量表,所以证明不是环境变量,也就是本地变量) 可以使用export可以加入bash的内存中。

2 程序地址空间

2.1 实验

我们先来做一个小实验,来看一个神奇的现象:

代码语言:javascript
复制
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 
  5 int g_val = 10000;
  6 
  7 int main()
  8 {
  9 
 10   printf("father is running ,pid:%d,ppid:%d\n",getpid(),getppid());
 11 
 12   pid_t id = fork();
 13   if(id == 0)
 14   {
 15     //child
 16     int cnt = 0;
 17    while(1)
 18    {
 19       cnt++;
 20       printf("I am child process,pid:%d,ppid:%d,g_val:%d, &g_val:%p\n",getpid(),getppid(),g_val,&g_val);
 21       sleep(1); 
 22       if(cnt == 5)
 23       {
 24         g_val = 9999;
 25         printf("I am child process,change %d -> %d\n",100,g_val);                                                                                                             
 26       }                                                                                                                                     
 27    }                                                                                                                                        
 28                                                                                                                                             
 29   }                                                                                                                                         
 30   else                                                                                                                                      
 31   {                                                                                                                                                                
 32     //parent                                                                                                                                                       
 33     while(1)                                                                                                                                                       
 34    {                                                                                                                                                               
 35       printf("I am parent process,pid:%d,ppid:%d,g_val:%d, &g_val:%p\n",getpid(),getppid(),g_val,&g_val);                                                           
 36       sleep(1);                                                                                                                                                    
 37    }                                                                                                                                                               
 38                                                                                                                                             
 39   }                                                                                                                                         
 40                                                                                                                                             
 41   return 0;                                                                                                                                 
 42 }  

来看执行的效果:

同一个变量,甚至地址都一样,但是为什么是不一样的值???

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变
  • 但地址值是一样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做 虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理

2.2 概念认识

其实我们常说的地址并不是在磁盘或内存中的真正的地址,程序地址空间是在操作系统中来说的。

每一个进程都会有对应的地址空间,储存对应数据和代码。那么如何保证每个进程都正确的读取数据和代码,而不会与其他进程搞混呢?这就与其本质有关了: 程序地址空间的本质是结构体对象,通过这个结构体操作系统可以管理进程。子进程的页表会拷贝自父进程,所以子进程会继承父进程的数据。 当子进程想要修改g_val时,如果父进程也被修改,那么就破坏了进程的独立性,可能导致程序崩溃,那么操作系统是如何解决这个问题的呢??? 操作系统会检查该变量是不是子进程独有的,如果不是,那么就会重新开辟一个物理空间来储存新值,对应的页表映射也发生改变,注意页表的虚拟地址不变,改变的是映射的物理空间,就能够修改变量值了,而且打印的虚拟空间一致(物理空间不同)。 来注意一些细节:

  1. 如果父进程和子进程都不修改变量,那默认是父子进程共享的,代码也是共享的(只读)
  2. 只有修改时才会开辟新空间(写时拷贝)
  3. 为什么这么做?保证进程的独立性,按需申请(做到节省空间)

2.3 如何理解地址空间

什么是划分区域?

可以打个比方,小学生的桌子都有一个三八线,这个划分区域就类似这样,保证每个人(进程)有独属于自己 的空间

代码语言:javascript
复制
struct area
{
	int start;
	int end;
}
struct destop
{
	struct area left;
	struct area right;
}

就类似这样,做到控制空间大小区域。 我们,来看源码里是如何调节的:

这里面进行了区域的划分。

为什么要有地址空间

如果直接使用物理地址,可以想象是很混乱的,很容易发生越界等危险操作。 而通过页表来进行映射,就明确了储存地址的范围,保证了数据读取的安全:

  1. 将无序变有序,让进程以统一 的视角来看待物理内存,以及自己运行的各个区域!
  2. 拦截非法请求,保护物理内存空间!
如何理解页表(和MMU)和写时拷贝

CPU可以储存页表,并且有一个简单的寄存器MMU,可以快速的将虚拟地址转化为物理地址。页表中有对应的位置会存入rwx权限,做到保护物理地址,例如:

代码语言:javascript
复制
char* str = "Hello world";
*str = 'J'; 

这肯定会报错的,这个报错就是页表检查权限进而报错了,只读的权限不能进行写入操作。 操作系统可以进行一下检查:

  1. 检查是不是在物理内存中(缺页中断,页表中不存在物理内存,会重新开辟空间)
  2. 检查是不是数据需要写时拷贝(发生写时拷贝)
  3. 如果都不是就进行异常处理。
如何理解虚拟地址

可执行程序进行运行时,会将页表对应的物理内存的数据直接读取出来。等…

3 如何调度进程

Linux是一个分时操作系统(与之对应的是实时操作系统,例如车机操作系统可以实时反应)。

优先级

普通优先级: 100~ 139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!) 实时优先级: 0~ 99(不需要关心这个)

活动队列

  • 时间片还没有结束的所有进程都按照优先级放在该队列
  • nr_active: 总共有多少个运行状态的进程
  • queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
  • 从该结构中,选择一个最合适的进程,过程是怎么的呢?
    1. 从0下表开始遍历queue[140]
    2. 找到第一个非空队列,该队列必定为优先级最高的队列
    3. 拿到选中队列的第一个进程,开始运行,调度完成!
    4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
  • long bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32 (160)个比特位表示队列是否为空,这样,便可以大大提高查找效率! 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000.......这个位图就可以快速查询(以32个比特位进行查找)。

过期队列

  • 过期队列和活动队列结构一模一样
  • 过期队列上放置的进程,都是时间片耗尽的进程
  • 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

active指针和expired指针

  • active指针永远指向活动队列
  • expired指针永远指向过期队列
  • 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。
  • 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!

总结

在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法!

由图所示。

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1 环境变量的组织方式
  • 2 程序地址空间
    • 2.1 实验
      • 2.2 概念认识
        • 2.3 如何理解地址空间
          • 什么是划分区域?
          • 为什么要有地址空间
          • 如何理解页表(和MMU)和写时拷贝
          • 如何理解虚拟地址
      • 3 如何调度进程
        • 优先级
          • 活动队列
            • 过期队列
              • active指针和expired指针
                • 总结
                • Thanks♪(・ω・)ノ谢谢阅读!!!
                • 下一篇文章见!!!
                相关产品与服务
                轻量应用服务器
                轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、Web应用、小程序/小游戏、游戏服、电商应用、云盘/图床和开发测试环境,相比普通云服务器更加简单易用且更贴近应用,以套餐形式整体售卖云资源并提供高带宽流量包,将热门软件打包实现一键构建应用,提供极简上云体验。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档