【本节重点】
- 了解进程调度,Linux进程优先级,理解进程竞争性与独立性,理解并行与并发
- Linux的调度与切换,了解Linux2.6内核中进程队列的数据结构
- 理解环境变量,熟悉常见环境变量及相关指令, getenv/setenv函数
- 理解C内存空间分配规律,了解进程内存映像和应用程序区别, 认识地址空间。
1.进程优先级
前提:进程要访问某种资源,进程进行通过一定的方式(排队),确认享受资源的先后顺序。
1.1.基本概念
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
提问:优先级 vs 权限
权限绝对的是能不能的问题,而优先级决定的是你先还是我先的问题,说白了,只要到了优先级,大概率一定是你拥有享受这个资源的权限了,只不过需要通过优先级确定先后的问题。
为什么要有优先级呢?
我们可以想一想,如果我们在食堂打饭的时候,打饭的人比学生多,那么学生就不用排队,可是现在打饭的人明显少于学习,所以学生就要排队,进程也是如此,因为系统的资源过少!但是这是一个相对概念,只有在中午去食堂买饭就需要排队,而在下午三四点去买饭就不用排队!这也就解释了CPU为什么有运行队列,因为CPU只有一个,资源过少,每个进程想获取CPU就必须要排对,进程需要通过队列确认优先级去获得CPU的访问,如果我们电脑的CPU很多,那就不需要队列了,直接运行进程就行啦。
1.2.查看系统进程
我们先写一份上面的代码,然后执行一下,同时输入在右侧输入:ps -al
ps -al
是一个用于显示进程信息的命令,常用于Linux。下面是对该命令的解释:
- ps: 这是进程状态的缩写,是一个用于报告当前进程状态的命令。
- -a: 显示所有终端上的进程,而不仅仅是与当前用户相关的进程。
- -l: 以长格式显示输出。长格式包括更多的列,提供更详细的信息。
我们很容易注意到其中的几个重要信息,有下:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
这里我就要插几句话啦!不要把优先级想象的特别复杂,它在sturck task_struct里面就是一个整型变量,而且上面我们观察到Linux下默认优先级是80,但是Linux的优先级是可以被修改的,但是Linux优先级是有区间的,其范围是[60,99],一共40个。Linux优先级本质就是整型变量数字,数字越小,优先级越高!
1.3.PRI and NI
- PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高
- 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 所以,调整进程优先级,在Linux下,就是调整进程nice值
- nice其取值范围是-20至19,一共40个级别。
1.4.PRI vs NI
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
- 可以理解nice值是进程优先级的修正修正数据
1.5.用top命令更改已存在进程的nice:
Linux系统允许用户调整优先级,但是不能直接让你修改PRI,而是通过修改nice值,它不是优先级,而是进程优先级的修正数据!现在我们来演示一下优先级的修改。
结果展示:
根据这个公式:PRI(new)=PRI(old)+nice,我们再恢复出原来的优先级
咦!这是什么情况!!!
nice值是通过覆盖式的写入的,但是我们的PRI不应该是80 = 90 + (-10)嘛,为什么是70呢?其实这是正常的,你想想如果我们的PRI是80,那我们的nice就是-10了,而其他进程nice值是0,这样系统的数据就不一致了,所以PRI在每一次设置的时候,PRI(old)都是80。
我们再来测试一下nice其取值范围
首先我们将nice值干到100。
然后我们将nice值干到-100。
总结:nice其取值范围是-20至19,一共40个级别,Linux优先级是有区间的,其范围是[60,99],一共40个,两个相互对应。
提问:Linux为什么调整优先级是受收限制的?
如果允许任意进程调整优先级,用户可能会将自己的进程提升到高优先级,自己的进程抢占系统资源,使其他进程无法正常运行,无法享受到CPU等其他资源,此时其他进程很难得到资源,此时就被称为进程饥饿问题。任何得分时操作系统,调度上,都要具有较为公平的进行调度,所以就要求优先级是受收限制的。
2.Linux的调度与切换
概念准备:
- 进程在运行的时候,放在CPU上,必须直接把代码跑完,才行吗???不行,这很明显,如果我们写一个死循环的代码,那么CPU永远都跑不完,其他进程也就永远得不到调度,那么此时系统就卡死了。所以现代操作系统都是基于时间片轮转进行轮转执行的!!!
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为 并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发
2.1.进程切换
我们可以在struct task_struct查看到这些硬件上下文。
所以进程切换的过程就是:进程在CPU上运行 -> 时间片用完,保存硬件上下文 -> 运行队列进程阻塞 ->时间片轮转到该进程,恢复硬件上下文。这也就是一个进程在CPU一整个周期的情况。
2.2.进程调度
前提:Linux实现进程调度算法需要考虑优先级,考虑饥饿,还要考虑效率。
上图是Linux2.6内核中进程队列的数据结构,我们先不看,后面我会基于上面的画一个。
2.2.1.一个CPU拥有一个runqueue
- 如果有多个CPU就要考虑进程个数的负载均衡问题
2.2.2.优先级
- 普通优先级适用于一般用户进程,它们不需要对实时性有极高的要求。这些进程的调度是基于时间片轮转的,并且由操作系统决定它们在CPU上运行的时间。普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)
- 实时优先级是为具有严格时间要求的任务设计的,这些任务需要在确定的时间内完成。实时优先级:0~99(不关心)
2.2.3.活动队列
- 时间片还没有结束的所有进程都按照优先级放在该队列
- nr_active: 总共有多少个运行状态的进程
- queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下 标就是优先级!
- 从该结构中,选择一个最合适的进程,过程是怎么的呢?
1. 从0下表开始遍历queue[140]
2. 找到第一个非空队列,该队列必定为优先级最高的队列
3. 拿到选中队列的第一个进程,开始运行,调度完成!
4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
- bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个 比特位表示队列是否为空,这样,便可以大大提高查找效率!
2.2.4.过期队列
- 过期队列和活动队列结构一模一样
- 过期队列上放置的进程,都是时间片耗尽的进程
- 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
- 如果我们的一个进程的时间片已经轮转完,它就应该从活跃队列中剥离出来,而成为新进程进入来过期队列中。
2.2.5.active指针和expired指针
- active指针永远指向活动队列
- expired指针永远指向过期队列
- 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。
- 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!
上图是Linux2.6内核中进程队列的数据结构,之间关系也已经给大家画出来,方便大家理解。
2.2.6.总结
在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增 加,我们称之为进程调度O(1)算法!
补充:参考文档
3.环境变量
3.1.mian参数 --- 命令行参数 --- 掌握
我们知道main函数是有形参的,只不过我们一直没有使用过,今天我们来看一下main函数的参数,看看main函数的参数是什么意思?它和密命令行参数有什么关系?
首先我们知道argv是一个指针数组,数组元素有argc个,它里面的元素类型都是char*的,而我们知道char*指向的是字符串的首地址,我们来看一下argv里面的每个char*都指向了那个字符串?
我们发现随之我们的程序执行,当我们以空格作为分隔符传入-a,-b,-c,程序按照空格为分隔符,依次传入一个一个子串到argv这个指针数组里面,同时每传入一个字串,argc的个数就会加一。现在我再来解释一下输出内容。
所以我们在命令行上输入以空格为分隔符输入的字符串,会被bash解析一个一个子串,维护成指针数组argv,同时还维护了一个计数器argc,只要是函数它就会被调用,main函数也不例外,main函数由系统调用,bash会把参数传入给main函数,所以我们的main函数就拿到了命令行上输入以空格为分隔符输入的字符串和计数器argc,然后就输出了上述的结果。整个传入main函数的参数我们可以称之为一张表,我们称之为命令行参数表,把这张表就传给当前进程,而且整张表必须以NULL为结尾。那么我们的程序还可以这样写:
【解锁创意之门:环境变量引领你的编程奇思妙想】(中):https://developer.aliyun.com/article/1425753