进程线程(四) Deadline调度、实时性和多核负载均衡

该文章参考宋宝华老师的进程视频课程,详细可以去听阅码场宋老师的课程。

  • Linux Deadline调度器(全新内容)

  • Linux 为什么不是硬实时的

  • preempt-rt 对 Linux 实时性的改造

  • 多核下负载均衡

  • 中断负载均衡、RPS软中断负载均衡

1、多核之间的负载均衡

 目前 linux 会自动帮你做 负载均衡,举个例子,你要连续拷贝 一个 1080P 的视频流,每一帧图像都是 1080P 的,你可以开4个线程,分别拷贝不同的区域,linux 会自动的将这4个线程分布到4个核上去跑。

RT进程

比如 4核的CPU , 现在有 6个RT进程,linux 会选择4个优先级最高的进程,分布到这4个核上。

普通进程 :CFS调度的,有nice 值的进程,

周期负载均衡:根据系统的 节拍,系统的核会判断自己是不是闲着,别的核是不是忙,如果闲着 就反之 也可以push任务到别的核,每个核都以劳动为乐。

IDLE时负载均衡 :当一个核进入IDLE状态时,会从别的核上pull 任务自己来做,进行负载均衡

fork exec 时:会有新的进程,task_struct ,这时候会选一个最闲的核来跑,实现负载均衡。

        比如你一下跑两个a.out  CPU会自动帮你分布到两个 核上。

但是 有时候,我们不想在某个CPU上跑,这时候有办法的:

写代码的时候,可以给线程,进程设置 CPU的 affinity 

 taskset -a 进程 所有的线程, -p 第几个CPU ,最后是 pid 

 

 跑一个例子,一个进程 创建两个线程

#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <linux/unistd.h>
#include <sys/syscall.h>

static pid_t gettid( void )
{
	return syscall(__NR_gettid);
}

static void *thread_fun(void *param)
{
	printf("thread pid:%d, tid:%d pthread_self:%lu\n", getpid(), gettid(),pthread_self());
	while(1);
	return NULL;
}

int main(void)
{
	pthread_t tid1, tid2;
	int ret;

	printf("thread pid:%d, tid:%d pthread_self:%lu\n", getpid(), gettid(),pthread_self());

	ret = pthread_create(&tid1, NULL, thread_fun, NULL);
	if (ret == -1) {
		perror("cannot create new thread");
		return -1;
	}

	ret = pthread_create(&tid2, NULL, thread_fun, NULL);
	if (ret == -1) {
		perror("cannot create new thread");
		return -1;
	}

	if (pthread_join(tid1, NULL) != 0) {
		perror("call pthread_join function fail");
		return -1;
	}

	if (pthread_join(tid2, NULL) != 0) {
		perror("call pthread_join function fail");
		return -1;
	}

	return 0;
}

在 有 2个核的 Linux 上跑: 

运行 a.out 之后, top 命令查看,可以看到 稳定后 ,大约在 200% 。这是因为 top 命令是以进程视角看的 CPU利用率,1个核 100%, 两个核 最大 200%, 所以 a.out 被平均分到两个核上跑。

 使用 taskset 可以看出 3=011,是跑在两个核上的。

 此时 可以用 top -H 切换为 线程视角,然后按1显示 CPU信息来确认一下

 执行完 taskset -a -p 1 24276 之后, cpu 利用率就下来了,因为现在就一个核了,最大100%

 top 命令上 有 si  hi 代表的是 软中断  硬中断的时间比例。wa 是等硬盘的时间。id 是空闲时间。

 有时候 负载均衡不单单是 线程的开销,有时候大量的中断处理的开销也是需要做负载均衡的。

 在Linux 里面 中断不支持嵌套,当大量的中断来的时候,需要注意中断的负载均衡。

 

 对于一个多核的CPU来说,都有一个 中断控制器(可配置),中断控制器下可以接很多的中断源,

 关于 网卡的中断到底发给哪个核,其实是可以配置的, Linux 有api的 ,

有可能会遇到一些高端的网卡,一个网卡 有 1个 ip 地址,但是硬件上就有几个队列(multi queue),每个队列都可以产生一个中断,这种就是 网卡硬件上就支持负载均衡的,这样的情况,就可以将中断分配到不同的核上去,可以通过下面的方法来设置。需要先找到中断号。

cat /proc/interrupt  指的都是硬中断

在虚拟机上,用 qmeu 模拟跑一个 cotex-A9 的 4核的板子,其中 将 主机的tcp 1235端口,udp 5001端口 都 映射到ARM板子上,这样在主机的 tcp 1235 udp 5001端口收到数据的时候,ARM板子的 对应端口也可以收到数据。

 在 虚拟机主机上   执行 nc 127.0.0.1 1235 ,多发几次包。

然后在 arm 板子上 观察 cat /proc/interrupt ,发现所有的中断都在 cpu0上。

 现在 要设置 在 CPU1 上收中断,需要执行下面命令,2就是 二进制的10 ,设置的是CPU1

echo 2 > /proc/irq/31/smp_affinity

在虚拟主机上重新发包,然后在arm 板子上 观察

下面的命令是设置CPU3 来收中断 

echo 4 > /proc/irq/31/smp_affinity

如果要想4个CPU 都可以接收中断,可以将掩码设置为

echo f > /proc/irq/31/smp_affinity

一般来说,CPU0接收到的硬中断,抛出来的软中断,也是在CPU0上的。 

CPU1接收到的硬中断,抛出来的软中断,是在CPU1上的

如果有些网卡,不支持这种 硬中断 的负载均衡呢, 谷歌工程师曾经向 linux内核 提了一个非常重要的补丁,可以调整 软中断的负载均衡。让多个核来处理协议栈,收包的程序。

下面设置 让4个核来进行处理软中断

 echo 0xF > /sys/class/net/eth1/queues/rx-0/rps_cpus

测试:

echo 1 > /proc/irq/31/smp_affinity
echo 0xF > /sys/class/net/eth1/queues/rx-0/rps_cpus

发包收包之后,可以看到 软中断已经分布到4个核了

 当我们在做一个网络产品的时候,比如 CPU 有 16个核,但是 有 4个网卡(4个队列),这个时候是需要做 软中断的负载均衡的,rps的补丁的。

2 硬实时

 可以使用 cyclictest 来测试 系统的延迟

注意,应用程序设置的 优先级、top命令显示的优先级、内核的优先级 是不同的,是有一个换算关系的。

应用程序最高的优先级 99,在 top 命令显示 RT 进程

应用程序优先级 98 ,top命令显示 -99 

应用程序优先级 97 ,top命令显示 -98 

普通进程,使用nice 值的,top命令显示 20

linux 是一个抢占的操作系统,但不是一个硬实时的操作系统,因为在linux 系统中,有一些地方是不可以抢占的,比如下图,红色代表不可以抢占的,绿色可以抢占。

 那什么时候不可抢占呢?

中断、软中断、内核中的spin_lock 这三种情况是不可以抢占的。

 

这部分可以参考我写的另外一篇文章:

实时系统的扫盲

如果想在 Linux 上 实现 硬实时,需要打上 RT补丁,RT补丁就是要攻克上面的几种情况。 

把本来不能抢占的,变的可以抢占了,这是因为 中断线程化,软中断线程化,spinlock的改变。

 有时候 优先级并不能解决问题,在嵌入式领域,有一些场景是 周期性任务 ,这时候就可以考虑使用 sche_deadline 的调度策略。

下面的文档是专门介绍 这个调度策略的

https://elinux.org/images/f/fe/Using_SCHED_DEADLINE.pdf

现在的调度策略 有三种

 sche_deadline  涉及三个参数 

period  就是周期

runtime 就是在一个周期内,你要运行的时间

dealine 就是 runtime 要在 deadline 之前跑完, deadline 可以 等于 period,大部分情况是相等。

可参考 Documentation/scheduler/sched-deadline.txt 中 给线程设置 deadline 策略的方法

 #define _GNU_SOURCE
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <linux/unistd.h>
 #include <linux/kernel.h>
 #include <linux/types.h>
 #include <sys/syscall.h>
 #include <pthread.h>

 #define gettid() syscall(__NR_gettid)

 #define SCHED_DEADLINE	6

 /* XXX use the proper syscall numbers */
 #ifdef __x86_64__
 #define __NR_sched_setattr		314
 #define __NR_sched_getattr		315
 #endif

 #ifdef __i386__
 #define __NR_sched_setattr		351
 #define __NR_sched_getattr		352
 #endif

 #ifdef __arm__
 #define __NR_sched_setattr		380
 #define __NR_sched_getattr		381
 #endif

 static volatile int done;

 struct sched_attr {
	__u32 size;

	__u32 sched_policy;
	__u64 sched_flags;

	/* SCHED_NORMAL, SCHED_BATCH */
	__s32 sched_nice;

	/* SCHED_FIFO, SCHED_RR */
	__u32 sched_priority;

	/* SCHED_DEADLINE (nsec) */
	__u64 sched_runtime;
	__u64 sched_deadline;
	__u64 sched_period;
 };

 int sched_setattr(pid_t pid,
		  const struct sched_attr *attr,
		  unsigned int flags)
 {
	return syscall(__NR_sched_setattr, pid, attr, flags);
 }

 int sched_getattr(pid_t pid,
		  struct sched_attr *attr,
		  unsigned int size,
		  unsigned int flags)
 {
	return syscall(__NR_sched_getattr, pid, attr, size, flags);
 }

 void *run_deadline(void *data)
 {
	struct sched_attr attr;
	int x = 0;
	int ret;
	unsigned int flags = 0;

	printf("deadline thread started [%ld]\n", gettid());

	attr.size = sizeof(attr);
	attr.sched_flags = 0;
	attr.sched_nice = 0;
	attr.sched_priority = 0;

	/* This creates a 10ms/30ms reservation */
	attr.sched_policy = SCHED_DEADLINE;
	attr.sched_runtime = 10 * 1000 * 1000;
	attr.sched_period = attr.sched_deadline = 30 * 1000 * 1000;

	ret = sched_setattr(0, &attr, flags);
	if (ret < 0) {
		done = 0;
		perror("sched_setattr");
		exit(-1);
	}

	while (!done) {
		x++;
	}

	printf("deadline thread dies [%ld]\n", gettid());
	return NULL;
 }

 int main (int argc, char **argv)
 {
	pthread_t thread;

	printf("main thread [%ld]\n", gettid());

	pthread_create(&thread, NULL, run_deadline, NULL);

	sleep(10);

	done = 1;
	pthread_join(thread, NULL);

	printf("main dies [%ld]\n", gettid());
	return 0;
 }

 使用  sched_setattr 的时候 要用 root 权限,或者是给普通用户,用 setcap 设置能力才可以运行。

也可以使用  chrt 命令来修改  DEADLINE 策略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值