LWN:在kernel中实现完整的task-isolation!

关注了就能看到更多这么棒的文章哦~

A full task-isolation mode for the kernel

April 6, 2020

This article was contributed by Marta Rybczyńska

原文来自:https://lwn.net/Articles/816298/

主译:DeepL

有些应用需要能一直拥有对CPU的访问,甚至不允许一点中断存在。实时系统,还有那些使用了用户空间驱动(user-space driver)的高带宽网络应用程序都属于这种情况。虽然Linux现在已经提供了CPU isolation(将除了最核心工作以外的其他task都从这个或这些CPU上移走),但这是一个不完美的解决方案,仍然会受到中断影响。社区中一直在继续改进内核的CPU-isolation能力,主要是对nohz(tickless)模式进行改进,但尚未完成。最近,Alex Belits提交了一个patch set(基于Chris Metcalf 2015年的一些工作),为Linux应用引入了一个完全可预测的环境,前提是只要它们不需要任何内核服务就可以。

Nohz and task isolation

目前,Linux中的nohz mode已经在一定程度上支持了task isolation。它减少了CPU接收到的中断数量,具体来说是几乎所有的CPU都会禁用clock tick interrupt。然而,nohz并不能保证不会有中断;运行中的任务仍然可能会被page fault(精心设计的应用程序其实可以避免这种情况)或delayed workqueue所打断。这种模式的好处在于,这个task仍可以执行常规代码,包括系统调用。除此以外,任何额外的时间开销基本都是在系统调用的进、出的路径上。

对于某些应用来说,由于nohz无法绝对保证它不被中断,所以可能会导致他们执行时出现一些问题。例如,那些高性能用户空间的网络驱动,处理每个数据包的CPU周期都很少,对于这些应用,interrupt以及interrupt handling可能会导致其响应被显著延迟,把所有可以用来执行的时间全耗光。实时操作系统(RTOS)可以更好地满足实时性需求,但它们只支持有限的硬件平台。patch set作者认为,比起移植、维护一个RTOS来说,在Linux中开发和维护interrupt-free application要更加容易。Belits原话如下:

The alternative, running RTOS instead of Linux, is becoming more and more labor-consuming because modern CPUs and SoCs have very complex device/resource configuration and management procedures, and at this point for some hardware it is clearly in the realm of impractical to maintain an RTOS with hardware support on par with Linux kernel, reliable and secure at the same time.

在这个时代,哪怕嵌入式系统也往往包含多个CPU core,系统设计者也会针对那些需要确保CPU资源的task而在系统中设计更多的CPU core。Belits对此做了进一步的解释:

Therefore OS ability to switch a CPU core into RTOS-ish mode [...] is an important feature for modern embedded systems development. Probably more important than even real-time interrupts latency and preemption, now that people, when they don't like how their interrupts are handled, can just add CPU cores.

内核目前有几个功能,都是用来使应用程序在没有中断的情况下运行的:nohz(上文描述的)和CPU isolation(或称 "isolcpus")。后者的的实现是把一个或多个CPU隔离出来——调度器无法用到这些CPU,只有通过明确设置affinity擦可以使用这些CPU。这样一来,在这几个CPU上运行的进程就不需要与其他workload争夺CPU时间了。这样,被isolate的CPU上的中断数量就大大减少了,但是并没有完全消失。而我们要讲的task isolation,就是希望通过彻底消除所有中断来达到目的。进入隔离模式的进程能够正常在用户空间中运行,不会受到内核或其他进程的干扰。

Configuring and activating task isolation

作者提出了一个假设条件,就是在内核态执行的代码或者task的初始化代码并不需要进行隔离。这个任务会在某个位置开始进入隔离模式,并保持在隔离中,直到它自己决定离开隔离,或者执行一些导致隔离被打破的动作,再或者就是收到一个发给此进程的signal(信号)。

内核需要在编译时打开CONFIG_TASK_ISOLATION,然后采用与打开CPU isolation的nohz模式相同的选项启动:

  isolcpus=nohz,domain,CPULIST

其中nohz是用来禁用指定的CPU上的timer tick,domain用来将CPU从调度算法中删除,CPULIST是要对哪些CPU进行隔离的一个CPU列表。还可以选择是否利用task_isolation_debug这个内核命令行选项来在task失去隔离条件时打印出它的stack backtrace。

当某个task完成初始化后,可以使用prctl()系统调用提供的PR_TASK_ISOLATION操作来激活隔离。这个操作可能会失败,既有一些永久错误,也可能是由于一些临时错误而失败。永久错误的一个例子就是在把task给设置到没有隔离的CPU上时,在这种情况下,无法进入隔离模式。暂时性错误会得到EAGAIN错误代码,比如delayed workqueue暂时无法停止的情况下。在这种情况下,task如果想进入隔离模式,可以稍后重试,下次就可能会成功。

在prctl()调用中,开发者还可以配置在task失去隔离时要向task发送的信号。只要调用这个宏PR_TASK_ISOLATION_SET_SIG(),将要用的信号作为参数传递给它。这样,该命令就变成了类似于示例代码中的命令。

    prctl(PR_SET_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE
          | PR_TASK_ISOLATION_SET_SIG(SIGUSR1), 0, 0, 0);

这里,进程请求使用SIGUSR1信号,而不是默认的SIGKILL。也就是在它失去了隔离状态时,要求收到一个SIGUSR1信号。

Losing isolation

如果任务由于系统调用、page fault、exception或者中断而进入内核空间,那么任务将失去隔离(losing isolation)。这种情况发生时,系统会发送(默认是fatal)信号,但有几个例外:使用了prctl()来关闭隔离,或者调用了exit()和exit_group()这些会导致任务退出的系统调用,这样isolation模式也就会在这时结束。

当任务通过上述系统调用以外的任何方式失去isolation状态时,它将收到一个信号,默认情况下是SIGKILL,从而导致任务终止。这个信号是可以修改的,应用程序愿意的话也可以捕捉截获这个信号。例如,如果应用程序希望在退出之前记录isolation lost的信息,或者尝试在没有isolation保证的情况下也继续运行代码,都可以截获这个信号。

task可以随心所欲地主动进入和退出isolation。如果希望在不触发signal的情况下离开isolation状态,它可以进行如下调用:

    prctl(PR_SET_TASK_ISOLATION, 0, 0, 0, 0, 0)

The internals

当某个进程调用 prtcl() 来启用task isolation时,内核会用 TIF_TASK_ISOLATION 标志来标记此进程。不过,设置task isolation的主要工作都是在从prctl()返回时完成的。当内核返回到用户空间并看到TIF_TASK_ISOLATION标志被设置时,它就会做好安排确保task将来不会被中断。也就是说interrupt会被禁用,kernel也会禁用任何可能打断这些isolated CPU的event。在当前的patch

 set中,禁用了scheduler的clock tick,以及vmstat delayed work,并从per-CPU的pagevec中把这些page给drain出去,以避免使用inter-processor interrupts(即IPI)来进行cache flush。未来可能会增加更多的isolation准备工作。

与2015年的补丁集相比,当前版本中的这种isolation工作更加简单直接。在2015年版本中,Linux已经可以把timer tick

从被隔离的CPU上迁移到housekeeping CPUs(就是不在isolcpus的列表之内的CPU)上去了。这样一来,这些CPU上就不需要先处理pending timer事件,而可以直接进行isolation。

该补丁集还在非隔离的CPU上增加了diagnostics(诊断)功能。如果内核发现自己即将打断一个isolated CPU,它将通过在发起打断动作的CPU上生成diagnostics信息(默认情况下在kernel log中发出warning,但也可以打印stack dump)。这种情况的例子包括IPI发送,或者TLB flush。如果某个中断不是由Linux处理的,例如是个hypervisor中断,最终会向isolated CPU发送一个reschedule IPI,这样这个isolated task就会收到一个signal。针对这个问题,Frédéric Weisbecker提问说对hypervisor的支持是否有必要,但目前还没有得出结论。

task-isolation mode需要对architecture代码进行改动。patch set里面已经包括x86、arm和arm64的实现。每个architecture需要定义HAVE_ARCH_TASK_ISOLATION和新的TIF_TASK_ISOLATION任务标志。还需要改变它的interrupt和page-fault entry routines(处理代码),加上对task_isolation_interrupt()的调用,这样所有isolated task的都会退出隔离。出于同样的目的,rescheduler IPI也需要调用task_isolation_remote()。最后,系统调用代码则应该调用task_isolation_syscall()来检查是否允许进行这次系统调用。当退出到user space时,则应该调用task_isolation_check_run_cleanup()来完成清理动作,如果当前任务的isolation flag被设置上了,就需要调用task_isolation_start()。

除了体系结构特定代码的变化以外,添加了isolation功能之后导致其他内核子系统中也产生了一些变化。例如,在网络代码中,flush_all_backlogs()将对non-isolated CPU的队列中enqueue work。tracer的ring buffer在isolated CPU上的行为很像是CPU offline了一样,也就是只有在task退出isolation的时候才会更新trace buffer。isolation mode中的另一个变化是,kernel job只会在housekeeping CPU上进行调度执行。这也包括那些PCIe device的probe等动作。最后,kick_all_cpus_sync()也被修改了,以避免在有isolated task的CPU上触发scheduling interrupt。Weisbecker不同意这种做法,列举了一些这个函数和task进入isolation状态这个节点之间可能产生的race condition。。他建议直接修改caller这一方。

Summary

这组patch set已经得到了不少正面评价,看来这个功能是许多开发者们感兴趣的。但仍有一些未解决的意见有待继续处理,其中有些patch还没有收到评论。这组patch set略微修改了kernel中一些基本函数,所以肯定会有人询问这个功能的测试细节。当然,除此之外,还有可能出现的regression。当所有这些问题得到解决后,很大概率会被很可能会合入后续的kernel release中。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值