栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

重学计算机(十六、linux系统优先级)

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

重学计算机(十六、linux系统优先级)

没想到,直到写完才发现,这一篇就写了一个优先级,就可以写这么多,CFS又要往后推。

16.1 分配给进程的时间

linux也是一种抢占式时间片的系统,不过linux系统的时间片分配跟其他的不一样,使用的是一种动态时间片算法,它给每个进程分配了使用CPU的时间比例。

linux为了保证调度算法在异常情况下,有所取舍,定义了两个参数:

    调度延迟,sysctl_sched_latency,记录在/proc/sys/kernel/sched_latency_ns中。

    调度延迟指每一个可运行的进程都至少运行一次的时间间隔。

    调度最小粒度,sysctl_sched_min_granularity,记录在/proc/sys/kerbel/kernel/sched_min_granularity_ns中

    调度最小粒度指任一进程运行的最小时间,除了阻塞或者主动让出CPU。

这两个参数结合起来看就有点意思了,如果就绪进程比较少,是可以满足调度延迟的,如果进程太多,linux调度算法会做一个取舍,尽量去满足调度最小粒度。

那我们来看看这个进程的个数是多少:

root@ubuntu:/proc/sys/kernel# cat sched_latency_ns  (单位ns)
12000000
root@ubuntu:/proc/sys/kernel# cat sched_min_granularity_ns  (单位ns)
1500000

s c h e d _ n r _ l a t e n c y = s y s c t l _ s c h e d _ l a t e n c y s y s c t l _ s c h e d _ m i n _ g r a n u l a r i t y = 12000000 1500000 = 8 sched_nr_latency = frac {sysctl_sched_latency}{sysctl_sched_min_granularity} = frac {12000000}{1500000} = 8 sched_nr_latency=sysctl_sched_min_granularitysysctl_sched_latency​=150000012000000​=8

两个怎么大的数,竟然算出来是8,

也就是说在就绪进程小于等于8的时候,调度周期等于延迟周期。

如果大于8的时候,调度周期等于进程个数*最小粒度。

那我们就可以算出分配给进程的时间了:
分 配 给 进 程 的 时 间 = 调 度 周 期 ∗ 1 就 绪 进 程 个 数 分配给进程的时间 = frac {调度周期 * 1}{就绪进程个数} 分配给进程的时间=就绪进程个数调度周期∗1​
我们上面介绍的都是在同等优先级的情况下,下面我们来看看引入优先级后的情况。

16.2 优先级

linux系统是支持优先级的,不过这个优先级在linux系统下是叫nice,该值的取值范围是[-20, 19],其中nice越高,表示优先级越低。默认优先级是0。

nice越高,优先级越低,我就一直很纳闷,结果看到是怎么翻译的,nice的英文意思是友好,nice值越高,表示越友好,越谦让,即优先级越低。(真的是好人卡啊)

16.2.1 优先级的内核表示

也不知道这种诡异的范围是怎么取出来的,但是在内核中,也有一个优先级的范围,取值0-139:

用户设置的nice值对应的内核中的是100-139,内核中也是值越低,优先级越高。

可以来看看内核代码中的转换:

// include/linux/sched/prio.h
#ifndef _SCHED_PRIO_H
#define _SCHED_PRIO_H

#define MAX_NICE	19
#define MIN_NICE	-20
#define NICE_WIDTH	(MAX_NICE - MIN_NICE + 1)



#define MAX_USER_RT_PRIO	100
#define MAX_RT_PRIO		MAX_USER_RT_PRIO

#define MAX_PRIO		(MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO		(MAX_RT_PRIO + NICE_WIDTH / 2)


#define NICE_TO_PRIO(nice)	((nice) + DEFAULT_PRIO)   // 直接加上偏移,真是简单粗暴
#define PRIO_TO_NICE(prio)	((prio) - DEFAULT_PRIO)


#define USER_PRIO(p)		((p)-MAX_RT_PRIO)
#define TASK_USER_PRIO(p)	USER_PRIO((p)->static_prio)
#define MAX_USER_PRIO		(USER_PRIO(MAX_PRIO))
16.2.2 计算优先级

通过上面的注释,我们是不是也注意到了一个单词:static priority。没错,nice设置的就是进程的静态优先级。在进程的控制块中(进程控制块详细描述再后面吧,这个内容是真的多,先把里面的细节分开介绍),有3个优先级的变量,分别是:

int prio , static_prio, normal_prio;
unsigned int rt_priority ;

这么多个优先级,那之间是怎么联系的呢?我们来看看代码

// 文件:kernel/sched/core.c


static int effective_prio(struct task_struct *p)
{
	p->normal_prio = normal_prio(p);
	
    
	if (!rt_prio(p->prio))
		return p->normal_prio;
	return p->prio;
}

我们现在先不管实时进程,所以只要了解normal_prio()函数里面是做啥的,就知道普通优先级是啥了。


static inline int normal_prio(struct task_struct *p)
{
	int prio;
	
	if (task_has_dl_policy(p))    
		prio = MAX_DL_PRIO-1;
    
	else if (task_has_rt_policy(p))
		prio = MAX_RT_PRIO-1 - p->rt_priority;
	else  
		prio = __normal_prio(p);
	return prio;
}

接下来看看普通进程是怎么赋值优先级的:

static inline int __normal_prio(struct task_struct *p)
{
	return p->static_prio;
}

是不是惊呆了所有小伙伴,直接把静态优先级赋值。

综上所述:普通进程的优先级其实还是等于静态优先级,也就是nice的值。

吹了这么多水,才得出这么个结论,哎。

注意:在进程分出子进程时,子进程的静态优先级继承自父进程。子进程的动态优先级会设置为父进程的普通优先级。

16.2.3 计算负荷权重

我们接下来看一个nice是怎么影响进程的负荷权重的,先来看看进程负荷权重保存在哪?

// task_struct->se.load  task_struct是进程的控制块
struct load_weight {
	unsigned long weight;
	u32 inv_weight;
};

内核中定义了一个数组,来表达每个不同nice值对应的权重:

// kernel/sched/sched.h

static const int prio_to_weight[40] = {
      88761,     71755,     56483,     46273,     36291,
      29154,     23254,     18705,     14949,     11916,
       9548,      7620,      6100,      4904,      3906,
       3121,      2501,      1991,      1586,      1277,
       1024,       820,       655,       526,       423,
        335,       272,       215,       172,       137,
        110,        87,        70,        56,        45,
         36,        29,        23,        18,        15,
};

英语厉害的,直接看上面的介绍也能明白了。下面我在用中文介绍一波:

一般的概念是这样的,进程每降低一个nice值,将多获得10%的CPU时间。我们来举一个例子,如果运行队列中有两个进程,一个nice值为0的进程A,一个nice值为1的进程B,那么按约定,进程B获取45%的CPU,进程A获取55%的CPU,是怎么算的:
进 程 A = 1024 1024 + 820 = 0.55 进程A = frac {1024}{1024+820} = 0.55 进程A=1024+8201024​=0.55

进 程 B = 820 1024 + 820 = 0.45 进程B = frac {820}{1024+820} = 0.45 进程B=1024+820820​=0.45

就是这么算出来的,上面的数组之间的乘数因子是1.25。

接下来看看代码是怎么给他赋值的:

// kernel/sched/core.c
static void set_load_weight(struct task_struct *p)
{
	int prio = p->static_prio - MAX_RT_PRIO;     // 映射到数组坐标中
	struct load_weight *load = &p->se.load;		// 获取指针,后面用来保存

    // 这里没有实时进程了,难道内核改版了?
    
	
	if (idle_policy(p->policy)) {		// 空闲进程SCHED_IDLE,填入很少的值
		load->weight = scale_load(WEIGHT_IDLEPRIO);
		load->inv_weight = WMULT_IDLEPRIO;
		return;
	}

    // 这两个就是把值填入负载中,啥时候用呢?下面再分析
	load->weight = scale_load(prio_to_weight[prio]);
	load->inv_weight = prio_to_wmult[prio];
}
16.2.4 应用设置nice值

都深入内核,还回到应用,这种玩法确实有点不太习惯啊。

linux提供了如下函数来获取和修改进程的nice值:

#include 
#include 

int getpriority(int which, int who);
int setpriority(int which, int who, int prio);

which和who是有关系的,我们一起来看看这两个参数:

whichwho
PRIO_PROCESS操作进程ID
PRIO_PGRP操作进程组ID的所有成员
PRIO_USER操作所有真实用户ID的进程

getpriority返回nice值,如果有多个进程符合条件,那么返回优先级最高的那个nice。(也是nice值最小的)

nice的范围是[-20,19],所以不能直接判断返回值是否等-1,需要结合errno来判断。

常见的errno介绍:

errno说明
EACCESS尝试获取更高的优先级(更低的prio值),但是没有CAP_SYS_NICE权限
因为之前的系统是不能自己调整优先级的
EINVALwhich的值不对的时候
ESRCHwhich和who指定的进程不存在
EPERM指定进程的有效用户ID和调用进程的有效用户ID不一致,且调用进程没有CAP_SYS_NICE权限

看着上面这么复杂就知道是新办法。

下面看一下老方法,nice函数也可以更改优先级。

#include 
int nice(int incr);

incr参数被增加到调用进程的nice值上。如果incr太大,系统直接把它降到最大合法值,不给出提示。

类似地,如果incr太小,系统也会无声息地把它提高到最小合法值。由于-1是合法的成功返回值,在调用nice函数之前需要清除errno,在nice函数返回-1时,需要检查它的值。nice的范围是[-20,19],所以只有返回值=-1并且error不为0时,nice调用失败。

写了一个测试程序:

// 编译命令:gcc test_nice.c -o test_nice -lm
#define _GNU_SOURCE

#include 
#include 
#include 
#include 
#include 

int heavy_work()
{
    double sum = 0.0;
    unsigned long long i =0;
    while(1)
    {
        sum = sum + sin(i++);
    }
    return 0;
}

int main()
{
    // 设置CPU亲和度
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(0, &set);

    sched_setaffinity(0, sizeof(cpu_set_t), &set);  // 设置亲和度


    int m_nice = nice(0);
    printf("nice = %dn", m_nice);


    int prio = getpriority(PRIO_PROCESS, getpid());
    printf("prio = %dn", prio);

    // 设置优先级
    setpriority(PRIO_PROCESS, getpid(), 5);

    prio = getpriority(PRIO_PROCESS, getpid());
    printf("prio = %dn", prio);

    m_nice = nice(0);
    printf("nice = %dn", m_nice);

    m_nice = nice(-4);                  // nice的值是在原优先级上增加或减少的值
    printf("nice = %dn", m_nice);

    int pid = fork();
    if(pid < 0)
    {
        return -1;
    }

    if(pid == 0)
    {
        prio = getpriority(PRIO_PROCESS, getpid());
        printf("son prio = %dn", prio);

        setpriority(PRIO_PROCESS, getpid(), 0);

        prio = getpriority(PRIO_PROCESS, getpid());
        printf("son prio = %dn", prio);
    }

    heavy_work();

    return 0;
}

因为现在的计算机都是多核的,所以需要设置CPU的亲和度,这个等到多核的时候,会详细讲,我们来编译执行看看:

root@ubuntu:~/c_test/16# ./test_nice
nice = 0
prio = 0
prio = 5
nice = 5
nice = 1
son prio = 1
son prio = 0

几个api测试的结果也符合预期,nice的参数是incr在原优先级上增加或减少incr的值。

最后父进程优先级设为1, 子进程的优先级设为2。

我们可以用ps来查看一下:

root@ubuntu:~# ps -C test_nice -o pid,ppid,cmd,etime,nice,pri,psr
   PID   PPID CMD                             ELAPSED  NI PRI PSR
 21700   1670 ./test_nice                       00:04   1  18   0
 21701  21700 ./test_nice                       00:04   0  19   0

NI表示的是优先级,PRI是折算后内核的优先级-100的表示,要还原就要加上100的基数。

这样是不是还看不出,两个进程运行时间的比例,我们可以在/proc/PID/sched中查看se_sum_exec_runtime,这个值的意义是累计运行的物理时间:

21700 :se.sum_exec_runtime : 8023.416966

21701:se.sum_exec_runtime : 10010.900838
10010 8023 ≈ 1.25 frac {10010}{8023} approx 1.25 802310010​≈1.25
刚刚好,是1.25,这个调度算法设计的还是很不错的。

注意:绝对的nice值并不影响调度决策,而是nice的相对值,影响了CPU时间的分配。如果进程A的nice是5,进程B的nice是6,这两个进程的运行时间比例也是1.25。

总结:引入优先级后,分配给进程的时间的公式:
分 配 给 进 程 的 时 间 = 调 度 周 期 ∗ 进 程 权 重 就 绪 进 程 权 重 之 和 分配给进程的时间 = frac {调度周期 * 进程权重}{就绪进程权重之和} 分配给进程的时间=就绪进程权重之和调度周期∗进程权重​

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/705747.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号