进程基本概念(2)

1.程序地址空间

(1)结构图:进程地址空间不是内存
在这里插入图片描述
(2)代码模拟实现程序地址的变化

//程序地址空间
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<malloc.h>

int g_unval;   //未初始化
int g_val = 100;  //初始化
int main(int argc,char *argv[],char *envp[])
{
  //代码地址
  printf("code addr:%p\n",main);
  //字符常量区
  char *str = "hello world";   //str为指针常量
  printf("read only addr:%p\n",str);
  printf("init addr:%p\n",g_val);
  printf("uninit addr:%p\n",g_unval);

  //堆区地址
  int *p = malloc(10);
  printf("heap addr:%p\n",p);

  //栈区地址
  printf("stack addr:%p\n",&str);

  printf("stack addr:%p\n",&p); 

  //运行参数的地址
  for(int i = 0; i < argc; i++)
  {
    printf("args addr:%p\n",argv[i]);  //ls -a -l
  }
  int i = 0;
  while(envp[i])
  {
    printf("env addr:%p\n",envp[i]);
    i++;
  }

  return 0;
}

地址从下往上变高
在这里插入图片描述

2.虚拟地址,也叫线性地址

以下程序,父子进程的地址一样,但是读出的值不一样,说明所读取的不是物理地址,实际上是不同的虚拟地址

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<malloc.h> 
#include<sys/types.h>

int g_val = 100;  //初始化
int main(int argc,char *argv[],char *envp[])
{
  pid_t id = fork();
  if(id  == 0)
  {
    g_val = 200;
    printf("child:pid %d,ppid:%d,g_val:%d,&g_cal:%p\n",getpid(),getppid(),g_val,&g_val);
  }
  else
  {
    sleep(2);
    printf("father:pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
  }
  	return 0;
  }

在这里插入图片描述
在语言层面上见到的地址都不是物理地址
详细解答:
子进程的地址空间是拷贝的父进程的地址空间,所以他们映射的虚拟地址是一样的,但是子进程又写入了其它数据,则会在物理内存上重新开辟一块空间存放数据,不影响父进程的任何地址(每个进程都是独立的),这也是写时拷贝的一种应用。
在这里插入图片描述

虚拟地址的理解

举例理解
在这里插入图片描述
(1)结构区域化

struct ruler{
	unsigned long start_left;
	unsigned long end_left;
	
	unsigned long start_right;
	unsigned long end_right;
}

(2) 区域数据化
ruler r = {0, 10, 10, 20};

(3) 0~10之间的所有刻度,都可以看作是一个地址,即虚拟地址
在这里插入图片描述
在左边放一支笔,笔的长度,start:6,end:8,
即**基地址+偏移量**

另外需要注意:
(1)扩大区域
可把划分的线重新划分,一边占小一点,一边占大一点,即:若堆区想扩大区域,则栈区可以缩小内存区域

(2)磁盘上的可执行程序(.exe文件)与物理内存的对应
exe文件是被划分好的(可执行程序是分段的),若要执行代码,则页表会自动找到物理内存代码段的地址
Linux可执行的格式是:ELF

结论
a. 进程地址空间本质是内存中的一种内核数据结构(mm_struct

b. 在划分区域之前,必须得有“大的区域”,32位,4GB,2^32 字节,2^10 * 2^10 * 2*10 * 2^2(1KB→1MB→1GB→4GB)

c. 2^32空间大小与物理内存本身有直接关系吗?
没有
232空间大小:相当于直尺的长度为20,这是人为抽象定义的(这也类似于进程地址空间,计算机能访问的最大内存空间232)
物理内存本身:相当于直尺,几乎看不到物理内存
进程地址空间具体划分:
在这里插入图片描述
d.(1)扩大访问的范围:stack的end减小,heap的end增大
(2)可执行程序已经被划分好了区域(code、readonly、uninit、init等),编译器按照区域内存进行加载进程
e. 创建进程:肯定会有struct task_struct,struct mm_struct,页表的创建

e.为什么要有地址空间?
① 不会有任何系统级别的越界问题存在,错误地访问物理内存(若存在野指针,在页表内不会有它的映射,就不会映射到物理内存),虚拟地址+页表:主要是保护内存
②每个进程都认为看到的是相同的空间范围,包括构成和顺序(同一种方式处理内存空间)
③每个进程都认为自己在独占内存,更好的完成进程独立性以及合理使用空间(例如:给每个人画大饼,相当于自己都认为自己有很多财富) 将进程调度与内存管理进行解耦或者分离(动态地调用,需要哪就调用哪)

3.调度队列(runqueue)

80 nice [-20,19]
nice修正值:[0]-[40] (也叫优先级数组)
nice修正值相同的进程要链接成一个链表队列,优先级均相同,但是都是从序号0开始,直到0号进程的队列没有进程后,再选择1号的队列,依次选择下去……
在这里插入图片描述

4.活动队列

结构图:
在这里插入图片描述

nr_active:总共有多少个运行状态的进程
时间片还没有结束的所有进程都按照优先级放在该队列
queue[40]:一个元素就是一个进程队列,相同优先级的进程按照FIFO规则排队调度,数组下标就是优先级,从0开始遍历queue[140](虽然复杂度是常数,但是效率比较低)

改进:
利用bitmap[5]:一共140个优先级,一共140个进程队列,可以用5*32个比特位表示队列是否为空,提高查找非空队列的效率

5.过期队列

结构图:
在这里插入图片描述
过期队列上放的进程都是时间片耗尽的进程
当活跃队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

6.active指针和expired指针

active指针永远指向活动队列
expired指针永远指向过期队列
活动队列上的进程会越来越少,过期队列上的进程会越来越多(原因:进程时间片到期时一直都存在),在合适的时期,交换active指针和expired指针的内容,就相当于有了一批新的活动进程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值