目录
- 1. 进程的优先级
- 1.1 什么是进程的优先级
- 1.2 优先级的具体表示与查看方式
- 2. 进程的切换与调度
- 2.1 切换
- 2.2 调度
- 3. 环境变量
- 3.1 main参数/命令行参数
- 3.2 什么是环境变量
- 3.3 环境变量的使用与特性
- 3.5 本地变量与环境变量的脚本配置文件
1. 进程的优先级
- 在计算机运行的过程中,有着许许多多的需要被执行的进程,而就绪状态的进程被加载到内存中的运行队列上。那么,它们之间的先后是如何区分的,它们是照申请先后的顺序排列而后执行吗?接下来,让我们对进程的新属性,进程的优先级来进行学习与了解。
1.1 什么是进程的优先级
- 在现实生活中,各种事物的优先级代表着这些事物所要被执行的先后顺序。而在计算机中对于进程来说,也是如此,进程的优先级决定了其被CPU执行的先后顺序。
- 当我们去谈论优先级的概念时,就代表有了需要我们去讨论优先级的前提与场景,进程的优先级决定着进程被CPU的执行顺序。
- 而当我们提及到进程的优先级时,就一定代表着CPU不能够第一时间内处理所有需要被进行的进程,或者可以说CPU的资源无法满足支持可以第一时间处理每个进程,所以,此时就需要将各个进程进行排队等待。
- 内存中需要被执行的进程各种各样,我们应该怎么将它们进行排队,并且需要保证排队的方式足够合理与高效,可以支持操作系统与各个进程的正常运行。
1.2 优先级的具体表示与查看方式
- 优先级也属于进程PCB属性中的一个,我们使用指令
ps -la [进程号]
,可以查看到指定进程的优先级等相关信息。(PRI:priority)
- 进程的优先级表示,PCB中以int类型的变量来表示进程的优先级,其可被修改的数据范围为
[60,99]
,一共40个数,数值越小优先级越高,数值越大优先级越低。- Linux下我们创建的每个进程的优先级默认为80,每个进程的优先级是可以被我们手动更改的,进程优先级计算判定由两部分组成
old PRI + NI(nice)
。- 我们无法直接修改PRI,需要通过修改NI从而达到间接修改PRI的效果,修改指令为
renice [n] [进程号]
,n的取值范围为[-20, 19]
,每次计算的old PRI都是从默认PRI即80开始计算的,而每次对NI的写入都是覆盖式写入。
- 当使用renice指令调整NI的范围超过[-20, 19]时,renice只会取极值不会超过NI的限制范围。
补充:为什么要对能够修改的优先级划定范围
- 当进程优先级的优先级可以被随意修改时,可能会因为认为的操作出现大量的高优先级的进程,这些进程会大量占用CPU的资源,甚至可能会导致原本的进程因此无法被CPU执行,导致这些进程及其卡顿甚至不能够被运行。
- 这些因为高优先级泛滥问题而不能够被正常执行的进程,我们称之为饥饿进程。
2. 进程的切换与调度
2.1 切换
- CPU具体是如何处理一个个进程的,每一个进程都是在CPU上一直被执行,直到彻底执行完毕吗?
- 事实并给如此,Linux操作系统中进程的执行方式是以时间片进行切换,每个进程都以时间片(1ms)为单位进行轮转执行。
- 进程相关概念补充:
<1> 竞争性:进程会因为优先级的不同区分先后顺序
<2> 独立性:各个进程之间不会相互干扰,独享各种资源
<3> 并行:计算机中有多个CPU,各个进程被分散在不同CPU上同时执行
<4> 并发:多个进程被一个CPU通过高频进程切换得方式执行,同时得以推进- 多核:CPU有一个控制器,同时拥有多个运算器
- CPU存在着大量的寄存器,不同种类的寄存器担任着不同的职能:
<1> eax/ebx/ecx/edx,通用寄存器:临时性的数据保存
<2> eds/ecs/fg/gs,段寄存器:衡量区域调用数据的区域
<3> eip/cr0-cr4,PC指针:记录标识程序执行到的进度与位置
<4> 程序状态字
<5> 浮点数存储器
<6> ebp/esp(小盒子),函数栈帧开辟相关- 根据前面的了解,我们直到进程是以时间片为单位在CPU上执行的,而进程被执行时会携带着大量的自身数据,这些数据是需要交给CPU进行处理计算的,CPU获取与存储这些数据的方式就是将它们拷贝至自身的寄存器中。(寄存器不等于寄存器内容,一套寄存器可以有多套寄存器内容)
- 进程被切换走,中断执行的步骤:
<1> 剥离寄存器中的核心数据数据
<2> 将核心数据全部带走,保存至进程的PCB中,记录执行进行执行到的位置,即,保护进程的硬件上下文数据(CPU内部所有的临时数据)- 进程二次切换回来,继续执行的步骤:
<1> 恢复上下文,覆盖是写入上下文数据
<2> 从上次执行到的位置继续执行
2.2 调度
- 操作系统按照一定的方式去执行运行队列上的各种进程,这就被称为进程的调度,不同的操作系统对于进程有着不同的调度算法。
- 根据操作系统应用场景的不同,按照进程调度算法的偏向不同可以分为两类:
<1> 分时优先级:操作系统会公平的执行每个进程
<2> 时时优先级:操作系统对于用户的请求,发送的进程会进行优先处理,高响应(车载系统)
- Linux操作系统O(1)调度算法:
- O(1)调度算法操作步骤 与 变量解释:
<1> struct task_struct* queue[140]:PCB指针数组,进程的可修改优先级范围为40,此数组开放[100, 139]范围的40个下标用来存储不同优先级队列的指针
<2> int bitmap[5]:int类型数组,其容量大小为5,有40个bit位,每一位都标识着一个优先级运行队列中是否有进程,二进制位为1代表有,0代表没有。通过这种方式,可以位运算的方法快速检测并找到需要被执行有进程的优先级队列
<3> int nr_active:一个整形变量,标识bitmap中8组优先级队列是否仍有进程剩余,若没有值为0,若有值为非0
<4> 运行队列中存在着两个相同的结构,分别用来标识活跃队列与过期队列,CPU只会执行活跃队列中的进程,而新进的进程只会添加至过期队列中。
<5> 通过活跃队列与过期队列的方式,O(1)调度算法很好的规避的优先级队列的进程饥饿问题
<6> struct q* active:执行活跃队列的指针
<7> struct q* expried:指向过期队列的指针
<8> 当活跃队列中的进程执行完毕,active与expried指针就会进行值交换,这样就达到了运行队列切换的效果,又因为只是指针的交换,消耗极小,效率很高
3. 环境变量
3.1 main参数/命令行参数
- 从初始接触编程开始我们就识得了main函数,在使用时从来没有给main函数传递过参数,可是,main函数真的没有参数吗
- main函数其实存在着三个可以添加的隐藏参数,接下来就让我们来进行对它们的学习
int main(int argc, char* argv[], char* env[])
{
return 0;
}
- 命令行参数argv 与 argv
<1> argv:字符串类型的数组,执行可执行程序时,从命令行中以空格为间隔获取字符串
<2> argv:argv字符串数组的元素个数
int main(int argc, char* argv[])
{
int i = 0;
for(i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
- 我们在前面的学习中已经了解到,操作系统中的一个个指令其实都是可执行程序,而各种指令附带不同的参数选项就有不同的效果,其底层的实现就是利用了命令行参数实现的。类似操作,如下
int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("waring Usage:\n\t./process [a~c]\n\n");
}
else if(strcmp(argv[1], "-a") == 0)
{
printf("-a usage\n");
}
else if(strcmp(argv[1], "-b")== 0 )
{
printf("-b usage\n");
}
else if(strcmp(argv[1], "-c") == 0)
{
printf("-cx usage\n");
}
else
{
printf("no usage\n");
}
return 0;
}
3.2 什么是环境变量
- 在编程语言中的变量是我们于内存中开辟的一块空间,我们赋予这块空间名称并以此调用这块空间进行各种数据的存储与运算,变量在运行期间也可以开辟。
- 环境变量也是变量,同样与编程语言中变量的属性相同,不同的只是环境变量是操作系统从内存中申请,用来存放一些必要的信息资源,系统内置具有特殊用途的变量。
- 系统中的环境变量有许多,环境变量的命名方式通常都为大写英文字母前加特殊符号:
$环境变量名
,我们可以通过指令:echo [环境变量]
来查看对应环境内的具体内容。
- 下面罗列几个常见的环境变量:
<1>$PATH
:存放常用资源路径的环境变量
<2>$USER
:存放当前用户名的环境变量
<3>$PWD
:存放当前所处目录路径的环境变量
<4>$HOME
:存放当前用户家目录的环境变量
- 我们在前面的学习中了解到,可执行程序要想运行必须要给出可执行程序的绝对或相对路径,让操作系统可以找到对应可执行程序并执行。
- 可是,系统中的一些自带指令为什么调用时可以不加路径声明呢,这是因为系统每次启动时bash都会在$PATH环境变量中会默认添加入,系统自带指令的所在路径,这样我们在使用这些指令时系统可以直接从环境变量中拿到自己需要的资源信息。
- 我们若想让自己编写的可执行程序像系统自带指令一样使用,有两种方法:
<1> 像操作系统登录时每次都会检索并以以此生成环境变量的路径里添加我们的可执行程序(不推荐,开发不完备可能会污染其他文件)
<2> 向环境变量$PATH中临时添加我们可执行程序所在的路径,退出销毁,指令如下:
PAHT=[需添加路径]:$PATH
//:$PATH不可省,否则会将原有的内容全部覆盖
3.3 环境变量的使用与特性
- main函数的第三个命令行参数char* env[],是一个字符串数组以NULL结尾,其中存储内容为父进程传递给main函数所在可执行程序的环境变量。
int main(int argc, char* argv[], char* env[])
{
int i = 0;
for(i = 0; env; i++)
{
printf("%s\n", env[i]);
}
return 0;
}
- 环境变量为全局属性,子进程可以继承父进程的环境变量,bash命令行解释器是我们所创建进程的父进程。
- 定义环境变量的方式:
export [环境变量名]=[赋值]
- 子进程除开通过传递env环境变量参数列表的方式一次性获取父进程所有环境变量,我们还可以通过C语言库中函数
getenv
获得指定的环境变量。(stdlib.h
头文件中)
//身份识别
int main()
{
if(strcmp(getenv("USER"), "zyc") == 0)
{
printf("this is a limited core process\n");
}
return 0;
}
- C语言库中存在着一个第三方变量
environ
,此变量的是一个二级指针数组,其中以字符串指针的形式存放着bash进程中所有的环境变量。
int main()
{
//做符号声明
extern char** environ;
int i = 0;
for( i = 0; environ[i]; i++)
{
printf("%s\n", environ[i]);
}
return 0;
}
3.5 本地变量与环境变量的脚本配置文件
- 环境变量为bash定义,适用于不同应用环境具有特殊用途的信息。而系统中拥有的变量种类不止一种,还有很多其他种类。
- 本地变量:一种在当前bash进程内定义,不能被子进程继承只在bash内部有效的变量
- 定义本地变量的方式:
[变量名]=[赋值]
,查看本地变量:echo [$本地变量]
- <1> 指令:
set
(查看所有变量,包括环境变量与本地变量)
<2> 指令:unset
(从上下文信息中移除变量)
- 经过前面的学习我们已经知道,环境变量是通过bash进程定义出来,我们此退出后重新登录Xshell后,之前定义的环境变量就会消失。
- 可是为什么bash自带的环境变量不会消失呢,其实这些环境变量并不是不会消失,而是存储在服务器磁盘中脚本与配置文件中,每次我们登录Xshell重新启动bash进程,都会导入脚本。
- 我们若想要让自己定义的环境变量等也可以登录加载,就可以通过在配置文件中添加定义的方式来实现。
- 每个用户的配置文件都不同,它们都处于当前用户的家目录下,文件名为
.bash_profile
,此文件又从bashrc中导入内容。- 在配置文件.bash_profile中添加提示语,可以登陆时提示。
示例: