(学习总结30)Linux 历程优先级、历程切换和情况变量

打印 上一主题 下一主题

主题 925|帖子 925|积分 2775

以下代码情况为 Linux Ubuntu 22.04.5 gcc C语言
历程优先级

根本概念

历程优先级:获得 CPU 资源分配的先后次序,也可以说是历程的优先权(priority)。
历程优先级是基于时间片的分时操作体系,每个历程规定使用 CPU 的时间,时间结束后切换到其它历程使用。
历程优先级出现的本质是 CPU 资源相对于历程数目的稀缺,导致多个历程需要通过优先级确认谁先谁后的情况。
优先权高的历程有优先实行权利,配置历程优先权对多任务情况的 Linux 很有用,可以改善体系性能。
还可以把历程运行到指定的 CPU 上,把不紧张的历程安排到某个 CPU,可以大大改善体系团体性能。
当然,历程的优先级可能会变化,但幅度不会太大,否则会导致优先级低的历程长时间得不到 CPU 资源,形成 历程饥饿
检察体系历程

在 Linux 体系中,用 ps ‒l 命令则会类似输出以下几个内容:

我们很容易注意到此中的几个紧张信息:


  • UID : 代表实行者的身份
  • PID : 代表这个历程的代号
  • PPID :代表这个历程是由哪个历程发展衍生而来的,亦即父历程的代号
  • PRI :代表这个历程可被实行的优先级,其值越小越早被实行
  • NI :代表这个历程的 nice 值
另外用户访问文件或资源的时候,都是使用历程访问,历程代表用户,检察当前用户权限其实是检察历程的权限。
PRI 和 NI 解释

PRI(Priority) 即历程的优先级,通俗点说就是程序被 CPU 实行的先后次序,此值越小历程的优先级别越高。
而 NI 意思为 nice 值,其表示历程可被实行的优先级的修正数值。
PRI 值越小越快被实行。加入 nice 值后,历程优先级公式为:PRI(new) = PRI(old) + nice。
Linux 为方便盘算,PRI(old) 默认设置为 80,每次只能调解 nice 值。而 nice 值的取值范围为 [ -20, 19 ],一共 40 个级别。当 nice 值为负值时,该程序优先级值将变小,反之变大。
则可以明白的是,Linux 历程的优先级范围为 [ 60, 99 ]。
所以在 Linux 下调解历程优先级,就是调解历程的 nice 值。
需要强调的是,历程的 nice 值不是历程的优先级,两者不是一个概念,但是历程 nice 值会影响到历程的优先级变化,可以理解 nice 值是历程优先级的修正数据。
历程优先级调解

我们可以从三个方面调解历程的优先级。
命令行调解历程优先级

调解新历程调度优先级命令 nice

语法:nice [选项] [命令]
功能:启动一个历程并设置其 nice 值,从而调解历程的调度优先级。
常用选项:


  • -n :设置 nice 值为 n,如果未指定,默以为 10(只有 root 账号能使用负优先级)。
  • --help :显示帮助信息。
  • --version :显示版本信息。
[命令]:代表实行的具体命令或可实行文件
调解已运行历程调度优先级命令 renice

语法:renice [优先级] [选项] [PID]
功能:调解已经运行的历程 nice 值,从而改变历程的调度优先级。
常用选项:


  • -n :指定历程新的 nice 值 n(只有 root 账号能下降优先级的值)
  • -p [PID]:指定历程的 PID 调解
  • -u [用户名] :指定用户名的所有历程
  • -g [历程组] :指定历程组的所有历程
  • --help :显示帮助信息
  • --version :显示版本信息
使用 top 调解历程优先级

使用 top 命令进入其界面后,按 r -> 输入历程 PID -> 输入 nice 值,即可调解历程优先级。
使用体系调用调解历程优先级

通过 man 手册可以检察历程优先级体系调用:

我们可以用代码测试一下:
  1. #include <stdio.h>
  2. #include <sys/time.h>
  3. #include <sys/resource.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7.     int ret = fork();           // ret 同时接收 fork 返回的两个值
  8.     if (ret < 0)   
  9.     {   
  10.         perror("fork");         // 小于 0 表示调用失败
  11.         return 0;
  12.     }   
  13.     else if (ret == 0)
  14.     {   
  15.         printf("子进程当前优先级 %d\n", getpriority(PRIO_PROCESS, getpid()));
  16.         setpriority(PRIO_PROCESS, getpid(), 15);
  17.         printf("子进程修改之后的优先级 %d\n", getpriority(PRIO_PROCESS, getpid()));
  18.         while(1);
  19.     }   
  20.     else
  21.     {   
  22.         printf("父进程当前优先级 %d\n", getpriority(PRIO_PROCESS, getpid()));  
  23.         setpriority(PRIO_PROCESS, getpid(), 9);
  24.         printf("父进程修改之后的优先级 %d\n", getpriority(PRIO_PROCESS, getpid()));  
  25.         while(1);                                                                                                                                                            
  26.     }   
  27.     return 0;
  28. }
复制代码

历程的竞争、独立、并行、并发概念

竞争性:体系历程数目浩繁,而 CPU 资源只有少量,甚至 1 个。则历程之间出现竞争属性是不可避免的。为了高效完成任务,更合理竞争相关资源,便出现优先级概念。
独立性:多个历程运行,需要独享各种资源,多个历程运行期间互不干扰。
并行:多个历程在多个 CPU 下分别同时运行。如:两个历程在自己对应 CPU 下运行,此时两者为并行。
并发:多个历程在一个 CPU 下采用历程切换的方式,在一段时间之内,让多个历程都得以推进,称之为并发。
历程切换

CPU 上下文切换,其实际含义是任务(任务 == 历程)切换,或者 CPU 寄存器切换。
当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是 CPU 寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入 CPU 寄存器,并开始下一个任务的运行,这一过程就是 context switch

当代盘算机都是分时操作体系,每个历程都有它符合的时间片(其实就是一个计数器)。时间片到达,历程就被操作体系从 CPU 中剥离下来。
我们可以参考 Linux 内核 0.11 版本:


历程切换最焦点的是保存和恢复当前历程的硬件上下文的数据,即 CPU 内寄存器的内容。
Linux 内核历程调度队列

以下是 Linux 2.6 内核中历程队列的数据结构简化图:

Linux 对每个 CPU 分配一个 runqueue ,如果有多个 CPU 就会考虑历程个数的负载平衡问题。当一个 CPU 的 cpu_load 过高,会将部分历程分担给其它 CPU 来优化服从。
Linux 不但是一个分时操作体系,内里也集成了实时操作(实时优先级),其功能通过条件编译裁剪。实时操作具体在应用范畴,这里省略。
优先级处理

此中的 queue[140] 的范例为指针数组,140 个元素范例都是 task_struct*,用于指向 task_struct。
实时优先级:0〜99(这里不讲解)
普通优先级:100〜139。
我们使用的都是普通的优先级,nice 值的取值范围刚好与之对应。
运动历程队列

时间片还没有结束的所有历程都按照优先级放在运动历程队列中
nr_active 表示总共有多少个运行状态的历程。
queue 访问方式:
queue[140] 中一个元素就是一个历程队列,相同优先级的历程按照 FIFO 规则进行列队调度,所以数组下标就是优先级。访问 queue 对应的下标只需通过盘算 下标 = 优先级 - 60 + (140 - 40) 即可。
queue 查询:
从该结构中选择一个最符合的历程,并不会遍历 queue,而是使用 bitmap[5]。
140 个优先级,则有 140 个历程队列,为了提高查找非空队列的服从,Linux 采用 5 * 32 个比特位表示队列是否为空,对 queue 的遍历转为对位图 bitmap[5] 的查询,便可以大大提高查找服从。
新历程加入
当一个新历程分配完数据后,Linux 会根据此历程的优先级分配到运动队列对应下标的 task_struct* 队列,从而实现了历程优先级高的先使用 CPU 的资源。
历程时间片结束
当历程时间片适用完,Linux 会将此历程转移到逾期历程队列。这样的处理方式更易维护。
逾期历程队列

逾期历程队列和运动历程队列结构一模一样,但 CPU 是不会调度此中的历程的。
而逾期队列上放置的历程,时间片都耗尽了,直到运动队列上的历程都被处理完毕,才会对逾期队列的历程进行时间片重新盘算。
active 指针和 expired 指针

active 指针永远指向运动队列,expired 指针永远指向逾期队列
随着 CPU 调度 active 指向的运动队列历程,运动队列上的历程会越来越少,逾期队列上的历程也会越来越多。
当运动队列没有历程了,只需要交换 active 指针和 expired 指针的内容,就可以快速具有一批新的运动历程!
总结

Linux 的历程优先级调度方案本质是从数据结构的哈希桶中获取,添加、移除和调用都是 O(1) 时间。则可以说,在 Linux 中查找一个最符合调度的历程的时间复杂度是一个常数,不随着历程增多而导致时间成本增加,称之为历程调度 O(1) 算法
情况变量

根本概念

情况变量(environment variables) 一般是指在操作体系中用来指定操作体系运行情况的一些参数
如:我们编写 C/C++ 代码链接时,编译器不知道我们的所链接的动态静态库在那里,但是照样可以链接乐成,天生可实行程序,原因就是有相关情况变量帮助编译器进行查找。
情况变量通常具有某些特殊用途,在体系当中一般具有全局特性。
检察或设置情况变量相关操作

显示当前情况变量或在指定情况运行程序命令 env

语法:env [选项] [名称=值] ... [命令 [参数]...]
功能:显示当前的情况变量,或在指定的情况中运行程序。
常用选项:


  • -i :启动一个扫除所有情况变量的新情况
  • -u :删除指定的情况变量
其它操作:


  • env :表示只打印当前 shell 情况变量
删除情况变量、shell 变量 或 函数命令 unset

语法:unset [选项] [变量名或函数名]
功能:删除情况变量、shell 变量 或 函数。
常用选项:


  • -v :删除变量(默认举动)
  • -f :删除函数
其它命令

使用 set 命令不但可以打印当前 shell 情况变量,还可以打印当地变量等。
使用 echo $[情况变量名] 可以检察单独一个情况变量。
使用 export [变量名]=[值] 可以设置一个情况变量。
使用 set [变量名]=[值] 可以设置一个当地变量。
使用 export [变量名] 可以将当地变量提升为情况变量,供子历程使用。
常见情况变量

PATH :指定命令的搜索路径。
HOME :指定用户的主工作目次(即用户登岸到 Linux 体系中时,默认的目次)。
SHELL :当前使用的 shell,它的值通常是 /bin/bash。
PWD :当前工作目次。
OLDPWD :上一次所处目次。
LOGNAME :登录时的用户名。
LANG :体系的默认语言和区域设置。
LS_COLORS :定义 ls 命令输出文件和目次的颜色。
SSH_CONNECTION :当前 SSH 毗连的源和对应的网络信息。
SSH_TTY :表示当前 SSH 会话关联的终端装备。
USER :表示当前用户。
HISTSIZE :当前 shell 会话中历史命令列表的最大数目。
HOSTNAME :当前体系的主机名。
PATH 作用

我们注意到,体系命令可以直接实行不需要带路径,而我们的二进制程序需要带路径才能实行。
这是因为实行命令会在 PATH 标明的路径进行查找,找到实行,反之报错。
一种方法是将我们的实行文件放入 /bin 文件中,但这个可实行文件不确定有没有 bug,会污染体系实行文件。
保险的方法是将对应实行文件的路径加入到 PATH 中,使用 PATH=$PATH:[当前文件所在绝对路径] 可以在内存级别记录路径。
如果想在用户级别存储路径,在当前用户家目次的 .bashrc 文件末尾加入 export PATH=$PATH:[绝对路径] 即可。
HOME 作用

每个用户的 HOME 都不一样,其记录的是对应用户的家目次,实行 cd ~ 或 cd 命令会返回到 HOME 记录的目次。
代码层面操作情况变量

main 函数命令行与情况参数(非标准扩展)

在 C语言 的 main 函数中其实是有参数的,分别为 int argc、char *argv[]、char *env[],分别表示 参数个数参数字符指针数组情况变量字符指针数组
  1. #include <stdio.h>
  2. int main(int argc, char* argv[], char* env[])        // argc 和 argv 是一起使用的,env 单独使用
  3. {
  4.     for (int i = 0; i < argc; ++i)                                // argc 代表命令行参数个数
  5.     {   
  6.         printf("命令行参数 argv[%d] == "%s"\n", i, argv[i]);  // 打印命令行参数
  7.     }   
  8.     printf("\n");
  9.     for (int i = 0; env[i]; ++i)                                // env 和 argv 一样是个指针数组,都规定最后一个元素后面为 NULL
  10.     {   
  11.         printf("环境变量 env[%d] == "%s"\n", i, env[i]);      // 打印环境变量                                                                                             
  12.     }   
  13.     return 0;
  14. }
复制代码

使用 environ 全局变量

通过第三方变量 environ 获取,在 libc 中定义的全局变量 environ 指向情况变量表,environ 没有包含在任何头文件中,所以在使用时要用 extern 声明:

  1. #include <stdio.h>
  2. extern char **environ;                        // 使用指向环境变量的 environ 全局变量                                                                                                                                            
  3. int main()
  4. {
  5.     for (int i = 0; environ[i]; ++i)
  6.     {   
  7.         printf("env[%d]-> %s\n", i, environ[i]);
  8.     }   
  9.     return 0;
  10. }
复制代码

使用 getenv 函数

可以在 man 手册中查找 getenv 函数的使用方法:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5.     char* PATH = getenv("PATH");
  6.     if (PATH == NULL)        // 获取失败则退出
  7.     {   
  8.         return 1;
  9.     }   
  10.     else
  11.     {   
  12.         printf("当前 PATH 为 %s\n", PATH);
  13.     }                                                                                                                                                                        
  14.     return 0;
  15. }
复制代码

三种方式中,使用 getenv 函数比较方便,并且我们可使用情况变量来制作一个只有特定用户才能使用的程序。
putenv 函数也可以访问情况变量。
情况变量特性与其它

情况变量通常具有全局属性,可以被子历程继续下去。在 main 函数中使用的 env 就 " 继续 " 自 bash 历程。
情况变量存储在 bash 中,不同用户 bash 历程对用户有关情况变量设置是不同的。
另外在 bash 中会记录两张表,一个是情况变量表,另一个是当地变量表。
对于当地变量,直接在命令行中使用 [变量名]=[值] 即可创建当地变量,当地变量不会传给子历程。
一般情况下,子历程创建的情况变量父历程是接收不到的,而 export 命令是一个内建命令(built-in command),会让 bash 自己亲自实行或让体系来调用。
如何做到 C语言 main 函数参数可变?main 函数被实行之前会先实行一个函数 _start ,其会统计 main 函数中的参数,条件编译选择对应参数的 main 函数即可。但注意, gcc 支持第三个参数 env 获取情况变量,但这黑白标准扩展,在其它编译器中可能会报错。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

徐锦洪

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表