目录
一、进程优先级
理解优先级
排队的本质就是在确定优先级,而排队的本质是因为资源不足,软硬件资源再多也没有进程多,所以进程需要通过排队来确认优先级, 进而确定哪个进程先被调用
优先级 vs 权限
权限是能还是不能得到某种资源,而优先级的潜台词是保证能够得到申请的资源,只是需要等一等!
Linux中的优先级本质是进程pcb中的1个字段,数字越小,优先级越高【类比考试排名,名次越小,成绩越高】
查看优先级
Linux中进程优先级范围是60-99,默认的进程优先级是80
之所以把进程优先级限制在一定的范围内,是因为优先级越高,在一定时间内被cpu调度的时间就越长,响应速度越快,如果用户可以随意调整优先级就会导致一些进程的优先级过高,有些优先级较低的进程长时间内得不到cpu调度,而调度是有自己的调度算法的,能较为均衡的保证所有进程调度的公平性,优先级低的进程长时间得不到cpu调度就会导致"进程饥饿"
修改优先级
Linux是支持修改优先级的, 但不能直接修改PRI,而是要通过修改NI(nice值)来动态调整优先级
ps:新的优先级 = 旧的优先级 + nice值, 旧的优先级每次调整时都重新从80开始
Linux进程默认优先级是80, 优先级范围是60~99,所以NI值的范围是-20~19, 当NI小于-20,会被当成-20, 大于19,会被当成19
调整步骤: top --> r -> 要调整的进程id -> 调整的nice值
ps:普通用户通常可以提高优先级,但要降低优先级,则需要使用root用户权限
四个概念
竞争性: 硬件资源有限,进程数量众多,进程之间必然存在竞争性,优先级本质是竞争之后的结果
独立性: 进程=代码/数据+内核数据结构, 无论是代码/数据还是内核数据结构, 都要保证进程之间互不影响,相互独立
并行: 多个cpu同时运行多个进程,叫做并行性
并发: 1个cpu, 在很短的时间内来回切换运行多个进程,在用户能感知的时间内,多个任务同时得以推进,逻辑上可以认为是多个cpu同时运行多个进程, 这就叫做进程并发
进程切换过程:
---预备知识:
1.cpu内存在很多寄存器, eax, ebx, ecx, edx, ss, ds, cs, gs, fs, bs, ebp, esp, eip, status_reg, cr0~cr4, ....
2.C语言中,函数内部创建的局部变量是如何返回到外部的? --- 寄存器(充当返回值的寄存器一般是eax寄存器, 充当代码的临时空间) 变量 != 变量的内容
3.我们的进程/程序,它怎么知道我们当前运行到哪里呢??如何做到函数间跳转?
cpu内有一个寄存器, eip(extern, i是指令意思, point, 也可以叫pc指针) 程序计数器, 保存当前正在执行的指令的下一条指令的地址,当前指令执行完之后,从下一个地址处继续读指令,读完之后eip自动++, 想要在函数之间跳转,只需要让eip保存要跳转函数的入口地址即可!
代码在运行的时候是会使用cpu内的寄存器的, 我们的进程会产生各种数据,在寄存器中临时保存!!
如果有多个进程, 各个进程在cpu寄存器中形成的临时数据,都应该是不一样的, cpu寄存器内的各种临时数据叫做进程的硬件上下文
4.进程运行了一半就切走了,把cpu内寄存器的数据保存到进程的pcb中,这种行为叫做硬件上下文的保护,而进程重新切回来到cpu上运行,首先将曾经保存在进程pcb中的数据恢复到cpu寄存器的过程叫做硬件上下文的恢复
4.cpu寄存器硬件只有1套,各个进程的上下文数据有几套有多套
5.之所以cpu要来回切换进程,是因为cpu要较为公平地调度所有进程
---过程演示:
二、Linux2.6内核的调度队列与调度原理
queue[140]数组
之前的博客已经提到过,每个cpu都会维护一个自己的运行队列(runqueue结构体),而运行队列里面有一个重要的成员变量,就是queue[140]这个数组,数组元素类型是task_struct *,显然queue[140]这个数组的每个元素都指向了(维护着)的是task_struct结构体变量(进程pcb)
queue[0] 到 queue[99] 的元素下标对应的是实时优先级,用于实时操作系统做实时响应用的,比如车载系统就是实时操作系统,实时操作系统要求优先级高的进程必须先被快速执行完,再去执行优先级低的进程(听音乐的同时遇到紧急情况刹车,必须先相应刹车,先把刹车进程执行完!)
实时优先级我们暂时不用关心, 普通优先级对应的下标是100~139,一共40个元素,对应的是上文提到的处于60-99范围的这40个优先级
所以按优先级去调度进程其实就是将queue[140]数组扫描一遍,如果遇到指针不为空,就先调度该进程
active与expired
上图可以看到,runqueue中维护了两份queue[140]数组,意义何在? active与expired指针又是干什么的??
操作系统既要保证优先级较高的进程先被调度,又不能让一些进程长时间占据cpu导致其他进程饥饿,所以采用了如下策略:
active与expired是两个指针,分别指向两个queue[140]队列,开始cpu调度进程的时候进程pcb就先链入到active指向的queue[i]里面,过一会就如下所示:
然后如果还有进程要等待运行,就不要再插入到active指向的队列来了,因为如果来了优先级稍微高一点的进程就会插到已经等待了一会的进程前面,可能会导致进程饥饿;所以将新来的进程插入到expired指向的队列中, 随着cpu的调度,此时active指向的队列进程越来越少,而expired指向的队列进程越来越多,当active指向的进程都运行完之后,交换active和expired内容,于是指针指向就交换了!
交换完之后,active指针指向的队列依旧是要调度进程的队列, 所以继续重复上述过程,就可以实现既按照优先级排队又能较为均衡调度所有进程!
所以进程插队或者抢占指的就是不要将新来的进程放入到expired队列中,而是直接按照优先级放入到active队列中进行插队!
bitmap[5]
bitmap是位图的意思,bitmap[5]的每个元素都是整形,占32个比特位, 共160个比特位
因为swap(active, expired)前提是系统已经检测到active队列中没有进程了,而检测必须把所有queue[140]扫描一遍,时间复杂度有点高,因此可以将queue[140]中每个位置是否有元素(也就是是否有进程需要被调度)的信息记录在位图中,二进制为1表示有进程,二进制为0表示没有进程,这样系统就可以以8个比特位/16个比特位为整体做检测,提高了效率!
三、命令行参数和环境变量
命令行参数
命令行输入内容会被当成一个大的字符串,以空格为分隔符,被分割成了5个子串(shell完成)
argc表示字符串个数
argv是一个指针数组,下标从0开始,各个数组元素依次保存了各个字符串的起始地址, 而数组元素会比命令行上的字符串多一个,数组最后一个元素必须以NULL结尾
为啥要这样做??
命令行版的计算器
借助main函数的参数我们就可以实现在命令行中控制输入的参数来达到计算加减乘除的目的
现在我们就可以理解各种指令搭配的选项了,指令本质是系统特定目录下的可执行程序,"可执行程序"参数就是传递给argv[0]的, 而指令带的各个选项本质就是传递给argv数组其余元素的,这就是为啥指令带不同的选项能够实现不同功能,就是因为程序内部根据参数进行了选择!
掌握了原理,我们也可以自己实现指令,比如实现新建文件指令-touch
环境变量
PATH
系统指令也是可执行程序,但是运行系统指令不用带 "./", 而运行自己的可执行程序要带 "./", 之所以要要带 "./", 是因为在执行程序之前先要找到程序,"./"就是在告诉系统要执行的可执行程序就在当前目录下,不用去其他地方找了!而系统指令不带"./"说明系统指令一定有自己的默认搜索路径,系统中存在一个环境变量,记录了系统可执行程序的默认搜索路径,这个变量叫做PATH
PATH中以:为分割符,提供了一些默认搜索路径,供整个系统搜索, 所以在这些路径下的可执行程序都可以直接运行,不用带路径!
如果想让我们自己的可执行程序直接运行:
法1. 把要运行的可执行程序添加到PATH中
关闭xshell,重新登录,环境变量就会恢复到默认的状态!
法二:将可执行程序拷贝到系统指令所在目录下
而简易程序安装的本质就是将要安装的可执行程序拷贝到系统的默认路径下!
PWD
PWD是给当前的bash,内置的专门用来记录当前所处目录的一个环境变量,所以pwd指令怎么知道当前所处的路径呢?他只需要去读取PWD环境变量,把它打出来就可以
HOME
每一个用户登录时,都会处于自己的工作目录下,普通用户是/home/xxx, root用户是/root, 如何做到的呢??
当要登录时HOME环境变量会识别到登录用户的身份,然后决定HOME的内容,当系统登录成功的最后一刻,cd $HOME, 就会进入到工作目录
1.登录用户名和密码
2.认证
3.形成环境变量(不止一个,PATH, HOME, PWD)
3.1 根据用户名,初始化HOME=/root, HOME=/home/xxx
4.cd $HOME
查看环境变量
env指令
系统会存在大量的环境变量,每一个环境变量都有他自己的特殊用途,完成特定的系统功能
su与su-区别: su切换成root, USER还是dck, 但是su -是以root身份重新登录,USER变成root
获取环境变量
法1:getenv(环境变量名字)
环境变量有何用处??
假如只想让root用户执行mycode程序,其他用户执行不了,就可以这样用环境变量:
所以可以通过环境变量来进行指令级别的拦截,当然也可以进行部分拦截,也就是有些功能可以做,有些功能不能做!
法2:main函数第三个参数
shell在登录的时候,系统会自动为shell创建一张环境变量表, 这个表里面保存了各个环境变量(字符串)的首字符地址
问题: 系统会提供一张环境变量表,这个系统指的是谁?
命令行启动的进程都是shell/bash的子进程, 子进程的命令行参数和环境变量是父进程bash给我们传递的! 父进程要传递给子进程,父进程肯定要拿到命令行参数和环境变量,那父进程的环境变量信息又从哪里来的??
上文提到了,如果环境变量PATH没设置好(比如置空了), 只需要关闭xshell重新登录即可!这时因为我们直接更改的是bash进程内部的环境变量信息!每一次重新登录,都会形成新的bash解释器并且新的bash解释器会自动从某个地方形成自己的环境变量表信息!
结论:环境变量是以脚本配置文件的形式存在的!
每次系统启动的时候都会从读取.bash_profile配置文件中的内容,为bash进程形成一张环境变量表信息!
如何证明上述结论???
在bash中,除了系统环境变量, 我们也可以手动添加属于自己的环境变量!
除了上述写法之外,我们也可以在一行中定义自己的本地变量同时导出环境变量
当时当xhsell关闭后,刚才定义的两个环境变量又不在了,因为刚才环境变量是导入到了父进程的进程上下文中,依然是在内存中操作的!所以想让自定义的环境变量在每次登录时都生效,就需要将环境变量添加到.bash_profile文件中!
bash通过命令行参数将环境变量传递给mycode(子进程), 未来子进程也可以继续创建子进程, 也可以以某种方式将环境变量传递给mycode的子进程,所以系统中所有的子进程都可以继承bash的环境变量,所以我们通常说系统环境变量具有全局属性!
上面说的mycode的环境变量也会以某种方式传递给它的子进程,这种方式是啥?下面我们介绍获取环境变量的第三种方式!
法3:全局变量environ
environ是一个全局的二级指针变量,指向了bash进程中的环境变量表!而bash创建子进程是通过fork创建的,之前文章提到过,子进程可以通过写时拷贝方式与父进程共享数据,所以不一定要通过传参将环境变量传递给子进程!
验证一下全局变量具有全局属性!
本地变量与 vs 环境变量
本地变量只在bash内部有效, 不会被子进程继承下去!环境变量通过让所有子进程继承的方式实现自身的全局性!
问题:echo也是命令,运行起来之后也是bash的子进程,没有将本地变量导出为环境变量,所以echo并没有继承环境变量,为啥echo能够输出本地变量的值呢??
Linux中的命令分类:
1.常规命令, shell通过fork创建子进程执行的!
2.内建命令,shell命令行的一个函数, 可以直接读取shell内部定义的本地变量了!
和环境变量相关的几个命令
1. echo: 显示某个环境变量值2. export: 设置一个新的环境变量3. env: 显示所有环境变量4. unset: 清除环境变量5. set: 显示所有本地 变量和环境变量
![](https://i-blog.csdnimg.cn/blog_migrate/5712807a9d9f580a9941de65de465977.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6036480a90526c653a495a48213e8a62.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4fb34902130d16d2fe55293364fc8ef0.png)