1 进程的优先级
1.1 如何理解优先级
问题1:优先级和权限的区别
- 权限的意义是这件事我能不能做,而优先级的意义就是对于资源的访问谁先谁后(cpu资源分配的先后顺序,就是指进程的优先权)
问题2:为什么需要有优先级
- 因为资源是有限的,而进程是多个的,所以进程之间存在竞争关系,优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可能会改善系统性能。
问题3:操作系统是如何做的
- 操作系统会根据自己的一套规则来尽可能地保证进程的良性竞争,如果进程长时间得不到CPU资源,那么该进程的代码就长时间无法得到推进(进程的饥饿问题),具体需要去给进程维护运行队列,让进程按顺序去执行,但为了防止某些进程运行时间过长,还会有时间片的限制(调度器在工作)
问题4:人为可以调整优先级吗?
- 优先级是可以被人为调整的,我们可以通过调整优先级让自己的某一个进程可以在同一时间内一直被调度,但是其实Linux并不希望我们有过高的权限,所以他的调整也不是无规则地调整,是带有一定限制的!!
1.2 查看和调整优先级方法
在linux或者unix系统中,用ps –l
命令会类似输出以下几个内容:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
1.3 PRI and NI
- PRI(priotity)即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
- NI(nice)其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice- 当nice值为负值的时候,那么该程序的优先级值变小,即其优先级会变高,则其越快被执行 所以,调整进程优先级,在Linux下,就是调整进程nice值
- nice其取值范围是-20至19,一共40个级别
1.4 其它概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行(少见,大多数配置的电脑都是只有一个cpu)
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发(最常见)
这方面的知识并不需要了解很深,因为大多数场景下我们并不会人为地去修改优先级,应付一下考试或者面试官的口头提问即可(我是这么认为,毕竟很少人为的调整优先级)。
2 Linux切换进程的方法
关于进程切换,就必须提到并发,并发就是进程切换,基于时间片轮转的调度算法。
2.1 CPU知识铺垫
问题1:我们的函数返回值是局部变量,但是为什么可以被外部拿到呢?
- 因为将数据存储在CPU寄存器中带了出去!! 比如return a会转化成move eax 10的指令
问题2:系统如何得知我们的进程执行到了哪句代码?
- 因为有程序计数器pc(在PCB内部)以及eip(栈帧寄存器),他们的作用就是记录当前进程正在执行的下一条指令的地址 (其实我们选择语句、循环……的跳转都跟pc有关)
问题3:寄存器有很多,具体有哪些种类呢?
- 通用寄存器:eax、ebx、ecx、edx……(需要什么就做什么的寄存器)
- 栈帧寄存器:ebp、esp、eip……(ebp和esp是维护栈顶和栈底的,而eip是存储程序计数器的值,表示着进程的下一条指令应该从哪里执行)
- 状态寄存器:status(记录当前CPU的一些状态)
问题4:寄存器扮演什么角色呢?
- 寄存器存储的空间并不大,但是速度快,所以他的作用就是 提高效率,将进程的高频数据放入寄存器中!
问题5:如果返回的是内置类型,寄存器可以做到,但如果返回的是一个特别大的对象呢?
- 因为内置类型很小,所以默认可以被翻译成mov eax ,但如果是一个对象的话,其实本质上来说就是进行了一个拷贝构造,如果是C++11的话可能还会涉及到移动语义,所以本质上来说也是由一个个语句组成的,所以就允许使用多个寄存器来工作。 相当于就是多次mov
寄存器总结
- CPU寄存器里面保存的是进程的临时(高频)数据——>进程的上下文信息
- 由于进程保存的是临时的数据,所以新的数据进入的时候可能会需要覆盖老的数据,因此进程的一些更重要的数据应该被放在离CPU更近的地方!!因为那样更安全更不易丢失!!(比如说重要数据肯定不适合放在通用寄存器上)
2.2 进程切换的本质
下面是抄的两个故事帮助理解:
- 你步入大学,但是你有一个参军梦,于是你报名了并且成功被选上了,你很开心于是你就直接去军营了,但是你走之前床铺没有收拾、也没有告诉学校。 于是当你在军营的时候,其实学校并不知道有你去军营了,所以他会给你安排考试,安排宿舍……结果过了一段时间当你回校后,你发现你的宿舍早就换人了,你床铺的东西也都被丢了,然后你本来应该是大一的,却显示大三,挂了三四十门课,即将被勒令退学…… 这个时候你找到了学校说:“我又不是干坏事,而是去当兵,为什么要让我退学??” 学校:“这不怪我啊,你没有打招呼,我根本就不知道你当兵去了”……
- 还是刚刚的你,但是这次你把自己的床铺收拾好了打包带走,然后你走之前去跟导员报告,将自己的入伍证明交予他查看,然后像学校请求保留学籍,其实就相当于把你的档案给封存了,这个时候学校知道你去当兵了,所以就不会给你安排考试,不会安排宿舍,你的学籍被保留了,你是大二上学期当的兵,你回来时候也是大二上学期……
通过上面两个故事,我们明白了在我们离开之前一定要做好收尾,这样才能更好地回来,所以对于进程来说,进程从CPU离开的时候,也要将自己的上下文数据保存好然后带走,保存的目的是为了更好地回来(恢复)
问题1:进程在被切换的时候要做什么?
- 保存上下文
- 恢复上下文
问题2:pc帮助我们记录下一条指令要运行的位置,那么要保存的数据究竟是存放在哪里呢?
- 存储在PCB中,PCB内部应该有相关的结构体,在寄存器要去执行其他进程之前,要将相关的数据先存在PCB内部,然后寄存器就可以离开了,当后面寄存器回来的时候,PCB就可以帮助进程恢复之前的数据。
3 环境变量
- 环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数。
- 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
- 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
当我们平常执行自己的可执行程序时,必须在前面指定路径来执行,但是执行Linux中的指令时,并不需要指定路径,这是什么原因呢?
其实,在Linux下的各种指令和我们平常自己编写代码生成的可执行程序没有什么区别,它也是个可执行程序,但是因为系统中存在PATH环境变量,这些指令的地址都被存放在环境变量中,当我们调用这些指令时,系统会自定去PATH中寻找这些指令。
3.1 PATH环境变量
PATH是由一堆目录组成的,各目录之间用冒号 “:” 隔开。 当执行某个 Linux 命令时,Linux 会依照 PATH 环境变量中包含的目录依次搜寻该命令的可执行文件,一旦找到,即正常执行;反之,则提示无法找到该命令。
查看环境变量:echo $PATH
添加PATH环境变量:
PATH=$PATH:/home/HYQ/hyq6
,如果不想要这个路径了,最简单的办法就是直接退出XShell,重写登陆。
3.2 查看所有的环境变量:env
指令
3.2.1 获取环境变量 getenv
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("PHTH:%s\n",getenv("PATH"));
return 0;
}
3.2 命令行参数
3.2.1 选项的两个参数
main函数是可以带参数的,因为他其实也是被别人调用的一个函数
- int argc:向量表中命令行参数的个数
- char* argv[ ]: 其实是一个向量表,里面存储着命令行参数 (最后的位置为nullptr)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i = 0;
for(;i < argc;i++)
{
printf("argv[%d]->%s\n",i,argv[i]);
}
return 0;
}
为什么要有命令行参数这个东西----->因为这样能够为我们的一些选项定制特殊化的功能。
3.2.2 系统调用接口模拟实现定制化选项
先假设只能携带一个选项,因为第一个位置必然是./mycode,因此第二个位置就是我们携带的选项,因为选项是字符串的形式,因此我们就可以通过strcmp函数通过条件编译,来根据不同的选项实现不同的功能。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: %s -[a|b|c|d]\n",argv[0]);
return 0;
}
if(strcmp(argv[1],"--help")==0)
{
printf("Usage: %s -[a|b|c|d]\n]",argv[0]);
}
else if(strcmp(argv[1],"-a")==0)
{
printf("功能1\n");
}
else if(strcmp(argv[1],"-b")==0)
{
printf("功能2\n");
}
else if(strcmp(argv[1],"-c")==0)
{
printf("功能3\n");
}
else if(strcmp(argv[1],"-d")==0)
{
printf("功能4\n");
}
else
{
printf("default功能\n");
}
return 0;
}
3.2.3 环境变量相关的参数
其实还有一个参数 char* env[ ]——>存储环境变量向量表
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(;env[i];i++)
{
printf("env[%d]->%s\n",i,env[i]);
}
return 0;
}
两张重要的向量表:(1)命令行参数表 (2)环境变量表
3.2.4 理解环境变量的全局属性
环境变量是系统提供的一组name=value形式的变量,不同的环境变量有不同的用户,通常具有全局属性。
我们所执行的进程都是子进程,bash本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,然后子进程会继承父进程的环境变量!! 因此环境变量表会被继承下去!!就相当于是全局了!
如果是先创建了进程,然后再修改了环境变量,那么新创建的子进程必然会被继承下去(共享数据),但如果创建了子进程然后修改了环境变量并不会影响父进程的环境变量,因为会发生写时拷贝。
3.2.5 本地变量vs环境变量
本地变量只会在bash内部有效,不会被继承,什么情况下需要本地变量呢?
- 只希望在bash里面使用但是不希望被子进程继承下去的,比如说我们的命令行提示符,如果是root用户就是# 如果是普通用户就是$
- set…… ——>查看所有的环境变量的本地变量
- export ……——>将本地变量变成环境变量
- unset……——>清除环境变量
3.2.6 常规命令vs内建命令
常规命令:通过创建子进程完成的
内建命令:bash不创建子进程,而是由自己亲自执行
3.2.7 再次总结环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,通常具有全局属性,并且一般来讲都有其特殊的用途
环境变量相关命令:
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量