🎇Linux:进程概念
博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题
博主的能力有限,出现错误希望大家不吝赐教
分享给大家一句我很喜欢的话:看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿🌞🐾。
目录
🎃程序地址空间回顾🎃验证进程地址空间的基本排布🎃什么是进程地址空间🎃理解进程地址空间🎃Linux2.6内核进程调度队列 —— 了解👻进程队列数据结构图👻一个CPU拥有一个runqueue👻优先级👻活动队列👻过期队列👻active指针和expired指针🎃程序地址空间回顾
我们在学C语言的时候,老师应该给大家画过这样的空间布局图
这张图到底有什么含义呢?我们今天来详细学习一下,
C/C++ 进程地址空间是一个很重要的概念,关系到我们能否学好编程语言
🎃验证进程地址空间的基本排布
话不多说,我们直接用一段代码来验证上面那张图是否正确。
#include<stdio.h>#include<stdlib.h>int g_unval;int g_val = 100;int main(int argc, char* argv[], char* env[]){printf("code addr: %p\n", main);const char* p = "hello bit!";printf("read only: %p\n", p);static int a = 5;printf("static global val: %p\n", &a);printf("global val: %p\n", &g_val);printf("global uninit val: %p\n", &g_unval);char* q1 = (char*)malloc(10);char* q2 = (char*)malloc(10);printf("heap addr: %p\n", q1); printf("heap addr: %p\n", q2); printf("p stack addr:%p\n", &p);printf("q1 stack addr:%p\n", &q1);printf("args addr: %p\n", argv[0]);printf("args addr: %p\n", argv[argc - 1]);printf("env addr:%p\n", env[0]);return 0; }
从结果我们得知:
栈区地址是由高到低
增长的堆区地址是由低到高
增长的
细心的小伙伴就会发现,两个args addr的地址为什么是一样的呢?那我们再进一步验证一下
加上选项
-a -b
根本原因是原本只有一行命令行参数,如果加上了选项就不一样了
🎃什么是进程地址空间
直接上代码观察
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int g_val = 0; int main() {printf("begin......%d\n", g_val); pid_t id = fork(); if(id == 0) {int count = 0; while(1) {printf("child pid: %d, ppid: %d, [g_val: %d][&g_val: %p]\n", getpid(), getppid(), g_val, &g_val); sleep(1); count++; if(count == 5) {g_val = 100; } } } else if(id > 0) {while(1){printf("father pid: %d, ppid: %d, [g_val: %d][&g_val: %p]\n", getpid(), getppid(), g_val, &g_val); sleep(1); } } else{//TODO } return 0;}
我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量但地址值是一样的,说明,该地址绝对不是物理地址!在Linux地址下,这种地址叫做虚拟地址
我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理(OS必须负责将虚拟地址转化成物理地址。)
可以知道
&g_val
一定不是物理地址(真正在内存中的地址
),因为同一个物理地址处怎么可能读取到的是不同的值。所以我们断言曾经所看到的任何地址都不是物理地址
,而这种地址本质是虚拟地址,它是由操作系统提供的,那么操作系统一定要有一种方式帮我们把虚拟地址转换为物理地址
,因为数据和代码一定在物理内存上存储,这是由冯 • 诺依曼体系结构
规定的。
🎃理解进程地址空间
地址空间在 Linux 内核中是一个mm_struct结构体,这个结构体没有告诉我们空间大小,但是它告诉我们空间排布情况,比如
[code_start(0x1000), code_end(0x2000)]
,其中就会有若干虚拟地址
,这是因为操作系统为了把物理内存包裹起来
,给每个进程画的一把尺子,这把尺子我们叫进程地址空间。进程地址空间是在进程和物理内存之间的一个软件层,它通过mm_struct这样的结构体来模拟,让操作系统给进程画大饼
,每一个进程可以根据地址空间来划分自己的代码。进程地址空间本质是进程看待物理内存的方式,它是抽象出来的概念,其中 Linux 内核中是用mm_struct数据结构来表示的。这样的话每个进程都认为自己独占系统内存资源(好比每个老婆都认为自己独占10亿);区域划分的本质是将线性地址空间划分成为一个一个的区域[start, end];而所谓的虚拟地址本质是在[start, end]之间的各个地址。
🎃Linux2.6内核进程调度队列 —— 了解
👻进程队列数据结构图
👻一个CPU拥有一个runqueue
如果有多个 CPU 就要考虑进程个数的负载均衡问题。
👻优先级
普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)实时优先级:0~99(不关心)
👻活动队列
时间片还没有结束的所有进程都按照优先级放在该队列nr_active: 总共有多少个运行状态的进程queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
从该结构中,选择一个最合适的进程,过程是怎么的呢?
从0下表开始遍历queue[140]找到第一个非空队列,该队列必定为优先级最高的队列拿到选中队列的第一个进程,开始运行,调度完成!遍历queue[140]时间复杂度是常数!但还是太低效了!
bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!
👻过期队列
过期队列和活动队列结构一模一样过期队列上放置的进程,都是时间片耗尽的进程当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
👻active指针和expired指针
active指针永远指向活动队列expired指针永远指向过期队列可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。**没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!