【问题标题】:When and how to give up cpu in a busy kernel thread loop?在繁忙的内核线程循环中何时以及如何放弃 cpu?
【发布时间】:2017-03-30 16:19:04
【问题描述】:

我正在编写一个 Linux 模块,其中我有一个循环来处理如下工作:

while (1) {
    while (there's work) {
        process_work
    }
    if (should_stop)
        break
    sleep  // wait to be woken up
}

当工作量很大时,会导致软锁定。消息是这样的:

[ 1426.067061] BUG: soft lockup - CPU#3 stuck for 23s! [comp_wqa:2969]
[ 1426.067903] Modules linked in: testmodule(OE+) xt_CHECKSUM ipt_MASQUERADE nf_nat_masquerade_ipv4 tun ip6t_rpfilter ip6t_REJECT ipt_REJECT xt_conntrack ebtable_nat ebtable_broute bridge stp llc ebtable_filter ebtables ip6table_nat nf_conntrack_ipv6 nf_defrag_ipv6 nf_nat_ipv6 ip6table_mangle ip6table_security ip6table_raw ip6table_filter ip6_tables iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack iptable_mangle iptable_security iptable_raw iptable_filter hwmon_vid dm_mirror dm_region_hash dm_log dm_mod snd_hda_codec_realtek snd_hda_codec_hdmi snd_hda_codec_generic intel_powerclamp coretemp intel_rapl kvm eeepc_wmi crc32_pclmul asus_wmi ghash_clmulni_intel sparse_keymap rfkill mxm_wmi aesni_intel wmi lrw snd_hda_intel gf128mul glue_helper snd_hda_codec pcspkr ablk_helper sg
[ 1426.067924]  cryptd shpchp snd_hda_core snd_hwdep snd_seq snd_seq_device snd_pcm tpm_infineon acpi_pad snd_timer mei_me mei snd soundcore nfsd auth_rpcgss nfs_acl lockd grace sunrpc ip_tables ext4 mbcache jbd2 sd_mod crc_t10dif crct10dif_generic crct10dif_pclmul crct10dif_common crc32c_intel serio_raw i915 ahci libahci libata i2c_algo_bit drm_kms_helper drm e1000e ptp pps_core i2c_core video
[ 1426.067939] CPU: 3 PID: 2969 Comm: comp_wqa Tainted: G           OE  ------------   3.10.0-327.28.3.el7.x86_64 #1
[ 1426.067940] Hardware name: ASUS All Series/Z97-A, BIOS 2401 04/24/2015
[ 1426.067941] task: ffff88080f212280 ti: ffff880810a68000 task.ti: ffff880810a68000
[ 1426.067942] RIP: 0010:[<ffffffff8107e11f>]  [<ffffffff8107e11f>] vprintk_emit+0x1bf/0x530
[ 1426.067946] RSP: 0018:ffff880810a6bbc0  EFLAGS: 00000246
[ 1426.067947] RAX: 0000000000000001 RBX: 0000000000000003 RCX: 0000000000000000
[ 1426.067948] RDX: 0000000000000001 RSI: ffff88083fb8f6c8 RDI: 0000000000000246
[ 1426.067948] RBP: ffff880810a6bc20 R08: 0000000000000092 R09: 0000000000007d0d
[ 1426.067949] R10: 0000000000008000 R11: ffffc90023effff8 R12: 0000000000000081
[ 1426.067950] R13: ffffffff81a08020 R14: 000000009176cc6c R15: 0000000000000000
[ 1426.067951] FS:  0000000000000000(0000) GS:ffff88083fb80000(0000) knlGS:0000000000000000
[ 1426.067951] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 1426.067952] CR2: 00007f42411ff00e CR3: 000000000194a000 CR4: 00000000001407e0
[ 1426.067953] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 1426.067954] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[ 1426.067954] Stack:
[ 1426.067955]  ffffffff81cae082 0000000000000071 0000000000000000 ffff880810a6bc40
[ 1426.067956]  ffffffffa07a45a0 000000008116c24e 0000000000000246 ffff8807dfbba800
[ 1426.067958]  ffff880810a70000 ffff8807dfbc5030 ffff8807dfbc4e00 ffff8807e65b3000
[ 1426.067959] Call Trace:

所以经过一番谷歌搜索后,我将代码更改为以下内容:

while (1) {
    while (there's work) {
        process_work
        cond_resched()
    }
    if (should_stop)
        break
    sleep  // wait to be woken up
}

使用此代码,软锁定发生的可能性较小。但是,它仍然发生在负载较重的情况下。我想如果这个线程长期占用cpu,那么cond_resched会放弃cpu。我想我错了。

我想知道应该如何避免软锁定,同时又不至于太闲(我希望模块处理大量工作而没有长时间的延迟)。

在考虑了更多之后,我意识到我想要的只是让一个 cpu 核心运行一个专用线程,而不会被中断。内核似乎不直接支持这一点。有一个名为watchdog_thresh 的内核参数决定了一个线程可以连续运行多少秒。我读过其他帖子表明这种软锁定是无害的。而且我现在更深入地了解,我的驱动程序的性能在很大程度上取决于单 CPU 内核的性能,因为我必须使用单线程来处理工作。

【问题讨论】:

  • @sawdust 也许我没能说清楚。在内核中,如果一个线程长时间连续运行(取决于配置,20 秒或 60 秒等),内核将打印消息说该线程已卡住 23 秒等。
  • 如果您只调用schedule() 而不是cond_resched() 会发生什么?或者这是否会过多地降低工作量?
  • 请注意,如果 CONFIG_PREEMPT 被定义,cond_resched() 是一个空操作。
  • @IanAbbott schedule() 会使线程进入休眠状态,需要有其他线程唤醒它,否则它将无限期休眠。
  • @IanAbbott 我不认为cond_resched() 在定义CONFIG_PREEMPT 时是空操作。你能提供更多关于这方面的信息吗?

标签: linux-kernel linux-device-driver kernel-module


【解决方案1】:

虽然内核线程可能持有一个 CPU 以对抗 调度算法,但线程可以放弃 CPU 只有在调度时算法决定这样做。另请参阅 this question,其中解释了类似的事情。

您需要调整您的内核线程,以便它可以被其他人抢占。一些可能的解决方案:

  • 线程优先级较低或更改调度策略。为此使用sched_setscheduler 函数。

  • 在处理完几个(比如 10 个)“作品”后,暂停线程一小段时间。

【讨论】:

  • 感谢您的回答。就像我在问题细节中所说的,我想充分利用 CPU。问题是,如果我只想让它工作,我可以使用内核提供的工作队列。我想如果我自己实现循环逻辑,我可能会得到更好的结果,这意味着可以完成更多的“工作”。
  • I want to utilize the cpu to the fullest. 没有适用于任何 CPU 工作负载的通用方法。您问自己的主要问题是:“假设在我处理工作时,一些 other 线程已准备就绪,在哪些情况下我想给一个 CPU 到那个线程?”。回答你应该通过调度参数实现的问题。
【解决方案2】:

编写用户空间代码和内核代码之间存在根本区别。糟糕的用户代码(关于调度)通常由内核处理(读取更正),而糟糕的内核代码是致命的——在编写内核代码时有很多方法可以杀死整个机器。 所以你的问题的答案是你必须首先在纸上设计这个。具体来说,与编写用户代码不同,您必须考虑调度并设计适合您的东西。有两个基本问题要问,当然要回答:

  1. 我的任务何时运行?
  2. 系统上的所有其他任务何时运行?

请注意,您还没有回答第二个问题。 一旦你有了答案(他们将定义 CPU 如何从一个任务迁移到另一个任务),你就可以开始考虑如何实现它。 确保您了解当前的行为;它将真正帮助您了解相关概念。软锁定意味着您的任务是 1)持有 CPU 和 2)不允许内核抢占它(很长一段时间)。找出为什么这个任务不能被抢占(希望它没有持有自旋锁)。您提到要避免“闲置”;我不确定您的意思是“您的任务未运行”还是“CPU 空闲”。这是两种非常不同的情况——你的任务必须让系统中的所有其他任务运行(如上所示),所以当其他任务运行时它肯定不会运行,但你不一定有空闲的 CPU ,如果你有“很多工作”。如果您的目标是避免后者,那么您是对的——这通常是设计不佳的结果(抛出 msleep(),而不是花时间制定适当的调度算法/参数)。

正如我所说,在您编写一行代码之前,第一步是能够描述您的任务将如何以及何时运行。

【讨论】:

  • 感谢您的回复。如今,许多机器都有多个内核。就我而言,我真的不需要关心其他线程。他们可以获得他们想要的cpu​​时间。这么说吧,我其实是想给一个cpu核心来运行这个线程,我不想和其他线程共享这个核心。问题是我必须按顺序处理工作。
  • @coderfive 听起来您已经意识到软锁定是针对每个 CPU 的,因此您不能只是接管它。但是您可以降低任务的优先级,让软锁定看门狗在需要运行时运行,从而消除锁定。因此,我观察到您确实需要关心其他线程。
猜你喜欢
  • 1970-01-01
  • 2010-11-14
  • 2012-12-08
  • 2016-05-11
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-29
相关资源
最近更新 更多