Linux下自动调整进程优先级
Posted
技术标签:
【中文标题】Linux下自动调整进程优先级【英文标题】:Automatically adjusting process priorities under Linux 【发布时间】:2010-09-25 21:36:24 【问题描述】:我正在尝试编写一个基于配置文件(基本上是路径 - 优先级对)自动设置进程优先级的程序。
我认为最好的解决方案是替换 execve() 系统调用的内核模块。太糟糕了,系统调用表没有在内核版本 > 2.6.0 中导出,所以如果没有真的丑陋的 hack,就不可能替换系统调用。
我确实不想做以下事情:
-用 shell 脚本替换二进制文件,这些脚本启动和修改二进制文件。 - 修补/重新编译我的 Ubuntu 内核 - 做一些丑陋的黑客,比如读取内核可执行内存和猜测系统调用表的位置 -轮询正在运行的进程
我真的想成为:
-能够根据进程的可执行路径和配置文件控制任何进程的优先级。规则适用于任何用户。
你们中有人对如何完成这项任务有任何想法吗?
【问题讨论】:
【参考方案1】:如果您已选择轮询解决方案,那么您想要实现的大部分功能已经存在于Automatic Nice Daemon 中。您可以根据进程名称、用户和组为进程配置 nice 级别。甚至可以根据目前使用的 CPU 时间动态调整进程优先级。
【讨论】:
【参考方案2】:有时轮询是必要的,甚至更多最终是最佳的——信不信由你。这取决于很多变量。
如果轮询开销足够低,它会远远超过开发自己的风格内核挂钩以获取所需更改的通知所增加的复杂性、成本和风险。也就是说,当钩子或通知事件可用或可以轻松注入时,如果情况需要,当然应该使用它们。
这是经典的程序员“完美”思维。作为工程师,我们力求完美。这是真实的世界,有时必须做出妥协。具有讽刺意味的是,在某些情况下,更完美的解决方案可能效率更低。
我为 Windows 开发了一个类似的“流程和流程优先级优化自动化”工具,称为 Process Lasso(不是广告,它是免费的)。我有一个类似的选择,并且有一个混合解决方案。内核模式挂钩可用于 Windows 中的某些与进程相关的事件(创建和销毁),但它们不仅不会在用户模式下公开,而且对监视其他进程指标也没有帮助。我认为任何操作系统都不会本机地通知您任何进程指标的任何更改。这么多不同的钩子的开销可能比简单的轮询要大得多。
最后,考虑到进程更改的高频率,一次处理所有更改(间隔轮询)可能比通知事件/挂钩更好,因为通知事件/挂钩可能需要每秒处理更多次。
远离脚本是正确的。为什么?因为它们很慢(呃)。当然,Linux 调度程序在处理 CPU 绑定线程方面做得相当好,方法是降低它们的优先级并奖励(升级)I/O 绑定线程的优先级——所以即使在高负载下脚本应该我猜是响应式的。
【讨论】:
我承认投票是必要的,有时。但在我看来,在这种情况下,它会很慢(呃)并且非常竞争条件。我有数百或数千个进程,每秒轮询它们肯定比在进程启动时触发挂钩要慢。 是的,我当然同意,只要您只关注进程创建和销毁。例如,如果您正在跟踪虚拟内存使用情况的变化,那么显然您必须进行轮询,因为获取每个更改的通知将导致该单一指标每秒发出大量通知。你明白我的意思;)。 “这是经典的程序员‘完美’思维。作为工程师,我们力求完美。但这就是现实世界,有时必须做出妥协。”, :D 是的,你是对的。这个“现实世界”的东西涉及有缺陷和不完整的内核,所以我必须继续轮询。 “太好了”。 @netom:我没有说创建废话。我应该把那条评论去掉。我只是说,有时理论上最好的解决方案并不是最实用的。显然,一个垃圾的解决方案对任何人来说都不是好的解决方案。你可以走到任何一个极端——就像所有事情一样,都涉及到平衡。当时我评论说,我参与了一个项目,他们不断重写所有内容以实现更好的代码,但永远延迟任何发布——所以这是一个极端。另一个极端就是扔一些废话,这是任何有自尊的程序员都不应该做的事情。【参考方案3】:您可能会考虑另一个攻击点:将系统的dynamic linker 替换为应用您的逻辑的修改后的。 (请参阅paper 了解一些很好的例子,这些例子说明了在很大程度上被忽视的链接器黑客技术的可能性)。
这种方法会出现问题的地方是纯静态链接的二进制文件。我怀疑现代系统中有很多东西实际上并没有动态链接(busybox-static 之类的东西是明显的例外,尽管当一切都变得可怕时,您可能会将在控件之外获得最小外壳的能力视为一项功能错误),所以这可能没什么大不了的。另一方面,如果优先级策略旨在为过载的共享多用户系统带来一些秩序,那么您可能会看到聪明的用户准备静态链接版本的应用程序以避免链接器强加的优先级。
【讨论】:
我一定会检查这种可能性,谢谢。现在我必须弄清楚如何在启动过程的早期非常处替换/破解libc,而无需修改Ubuntu软件包附带的文件(当有新东西出现时,我仍然想升级系统)。 您肯定只需要准备一个修改后的 libc,将其打包为 .deb,然后在您希望能够应用这些策略的系统上安装它(替换默认的 libc)吗?我的假设是现在几乎所有东西都动态链接 libc,所以现有的二进制文件应该很高兴地拿起新的 ldd/ld-linux.so。 好的,这很好,但我不想失去自动更新的简单性。有没有办法“修补”现有的二进制文件?我的意思是用链接器脚本之类的东西替换某些功能?我真的不是链接器特定问题的专家。 我也是.如果链接器在单独的包中与 libc 的其余部分更加分离,那对您来说会很好,因为 libc 更新(无论如何都相当罕见)可能不需要接触链接器......但事情不是实际上是这样。并且用从以前版本的 libc 编译的链接器覆盖特定 libc 的链接器似乎是个坏主意(系统完全损坏的风险?)【参考方案4】:当然,只需遍历 /proc/nnn/exe 以获取正在运行的映像的路径名。只使用带斜杠的,其他的是内核进程。
检查你是否已经处理了那个,否则在你的配置文件中查找新的优先级并使用 renice(8) 来调整它的优先级。
【讨论】:
这算不算“正在运行的进程的轮询”(提问者希望避免的)? 我想,但这可能仍然是最好的方法。 是的 timday,这是我不想做的。但是这个解决方案并没有那么糟糕,我可以接受它。但我不喜欢轮询,也不喜欢不在 O(1) ;) 中运行的算法。这是 O(n) 中的轮询,我不喜欢它,但我确定它会工作。如果链接器破解不起作用,恐怕我必须继续使用这个解决方案。 DigitalRoss,感谢您跳出框框思考;) 我尝试在 /proc 上使用 inotify,不幸的是,它不起作用 :D 真可惜。 由于 inotify 错误,以及缺少任何可插入/可挂钩的东西来创建进程,我接受这个解决方案,因为这就是我要做的:编写一个遍历进程的守护进程,并设置他们的优先事项。【参考方案5】:如果你想把它作为一个内核模块来做,那么你可以考虑制作你自己的二进制加载器。有关示例,请参见以下内核源文件:
$KERNEL_SOURCE/fs/binfmt_elf.c
$KERNEL_SOURCE/fs/binfmt_misc.c
$KERNEL_SOURCE/fs/binfmt_script.c
他们可以让您初步了解从哪里开始。
您可以只修改 ELF 加载程序以检查 ELF 文件中的附加部分,并在找到时使用其内容来更改调度优先级。然后,您甚至不需要管理单独的配置文件,只需将一个新部分添加到您想要以这种方式管理的每个 ELF 可执行文件中,您就完成了。请参阅 binutils 工具的 objcopy/objdump 了解如何将新部分添加到 ELF 文件。
【讨论】:
【参考方案6】:你们中有人对如何完成这项任务有任何想法吗?
作为一个想法,考虑在抱怨模式下使用apparmor。这会将某些消息记录到系统日志中,您可以收听这些消息。
【讨论】:
【参考方案7】:如果有问题的进程是通过执行具有已知路径的可执行文件启动的,您可以使用inotify 机制来监视该文件上的事件。执行它将触发I_OPEN
和I_ACCESS
事件。
不幸的是,这不会告诉您哪个进程导致事件触发,但您可以检查哪个 /proc/*/exe
是相关可执行文件的符号链接,renice
是相关进程 ID。
例如这是使用Linux::Inotify2(在Ubuntu上由liblinux-inotify2-perl
包提供)在Perl中的粗略实现:
perl -MLinux::Inotify2 -e '
use warnings;
use strict;
my $x = shift(@ARGV);
my $w = new Linux::Inotify2;
$w->watch($x, IN_ACCESS, sub
for (glob("/proc/*/exe"))
if (-r $_ && readlink($_) eq $x && m#^/proc/(\d+)/#)
system(@ARGV, $1)
);
1 while $w->poll
' /bin/ls renice
您当然可以将 Perl 代码保存到文件中,例如 onexecuting
,在第一行前面添加 #!/usr/bin/env perl
,使文件可执行,将其放在您的 $PATH
上,然后使用 onexecuting /bin/ls renice
。
然后,您可以使用此实用程序作为实施各种策略来重新定义可执行文件的基础。 (或做其他事情)。
【讨论】:
以上是关于Linux下自动调整进程优先级的主要内容,如果未能解决你的问题,请参考以下文章