Linux 不尊重 SCHED_FIFO 优先权? (正常或 GDB 执行)

Linux not respecting SCHED_FIFO priority ? ( normal or GDB execution )

TL;DR

在multiprocessors/multicores 引擎上,可以在多个执行单元上安排多个RT SCHED_FIFO 线程。因此优先级为 60 的线程和优先级为 40 的线程可能 运行 同时在 2 个不同的内核上。

这可能是违反直觉的,尤其是在模拟嵌入式系统时(通常像今天一样)运行 在单核处理器上并依赖于严格的优先级执行。

请参阅此 post 中的 了解摘要


原问题描述

即使使用非常简单的代码来使 Linux 使用调度策略 SCHED_FIFO 尊重我的线程的优先级,我也有困难

这种情况是由于需要在 Linux PC 下模拟嵌入式代码以执行集成测试

具有 fifo 优先级 10main 线程将启动线程 divisorratio.

divisor 线程应该得到 priority 2,这样带有 priority 1ratio 线程将不会在 b 得到一个合适的值之前评估 a/b(这是仅针对 MCVE 的完全假设场景,而不是带有信号量或条件变量的真实案例。

潜在先决条件您需要成为 root 或更好地 setcap 程序以便可以更改调度策略和优先级

sudo setcap cap_sys_nice+ep main

johndoe@VirtualBox:~/Code/gdb_sched_fifo$ getcap main
main = cap_sys_nice+ep

基本编译:

johndoe@VirtualBox:~/Code/gdb_sched_fifo$ g++ main.cc -o main -pthread

如果没有 root 或 setcap,有时可以正常执行有时不能正常执行

johndoe@VirtualBox:~/Code/gdb_sched_fifo$ ./main
Problem with setschedparam: Operation not permitted(1)  <<-- err msg if no root or setcap
Result: 0.333333 or Result: Inf                         <<-- 1/3 or div by 0

正常执行正常(例如使用 setcap )

johndoe@VirtualBox:~/Code/gdb_sched_fifo$ ./main
Result: 0.333333

现在如果你想调试这个程序,你会再次收到错误消息。

(gdb) run
Starting program: /home/johndoe/Code/gdb_sched_fifo/main 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7f929a6a9700 (LWP 2633)]
Problem with setschedparam: Operation not permitted(1)     <<--- ERROR MSG
Result: inf                                                <<--- DIV BY 0
[New Thread 0x7f9299ea8700 (LWP 2634)]
[Thread 0x7f929a6a9700 (LWP 2633) exited]
[Thread 0x7f9299ea8700 (LWP 2634) exited]
[Inferior 1 (process 2629) exited normally]

在这个问题 gdb appears to ignore executable capabilities 中对此进行了解释(几乎所有答案都可能相关)。

所以就我而言,我做到了

结果我得到了:

(gdb) run
Starting program: /home/johndoe/Code/gdb_sched_fifo/main 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff6e85700 (LWP 2691)]
Result: inf                              <<-- NO ERR MSG but DIV BY 0 
[New Thread 0x7ffff6684700 (LWP 2692)]
[Thread 0x7ffff6e85700 (LWP 2691) exited]
[Thread 0x7ffff6684700 (LWP 2692) exited]
[Inferior 1 (process 2687) exited normally]
(gdb) 

所以结论和问题

我看到其他与RT相关的问题SCHED_FIFO没有被尊重,但我发现答案没有或不清楚的结论。我的 MCVE 也小得多,潜在的副作用也更少

SCHED_FIFO higher priority thread is getting preempted by the SCHED_FIFO lower priority thread?

评论带来了一些答案,但我仍然不相信......(......它应该这样工作)

MCVE:

#include <iostream>
#include <thread>
#include <cstring>

double a = 1.0F;
double b = 0.0F;

void ratio(void)
{
    struct sched_param param;
    param.sched_priority = 1;
    int ret = pthread_setschedparam(pthread_self(),SCHED_FIFO,&param);
        if ( 0 != ret )
    std::cout << "Problem with setschedparam: " << std::strerror(errno) << '(' << errno << ')' << "\n" << std::flush;

    std::cout << "Result: " << a/b << "\n" << std::flush;
}

void divisor(void)
{
    struct sched_param param;
    param.sched_priority = 2;
    pthread_setschedparam(pthread_self(),SCHED_FIFO,&param);

    b = 3.0F;

    std::this_thread::sleep_for(std::chrono::milliseconds(2000u));
}


int main(int argc, char * argv[])
{
    struct sched_param param;
    param.sched_priority = 10;
    pthread_setschedparam(pthread_self(),SCHED_FIFO,&param);

    std::thread thr_ratio(ratio);
    std::thread thr_divisor(divisor);

    thr_ratio.join();
    thr_divisor.join();

    return 0;
}

您的 MCVE 有一些明显的错误:

  1. 您在 b 上存在数据竞争,即未定义的行为,因此 任何事情 都可能发生。

  2. 您预计 divisor 线程将在 ratio 线程到达 之前完成 pthread_setschedparam 调用 计算比率。

    但是绝对不能保证第一个线程不会 运行 在第二个线程创建之前很久就完成。

    确实这就是 GDB 下可能发生的事情:它必须捕获线程创建和销毁事件以跟踪所有线程,因此 GDB 下的线程创建是 显着比外面慢。

要解决第二个问题,添加一个计数信号量,并让两个线程 randevu after 各自执行 pthread_setschedparam 调用。

我尝试了很多解决方案,但从未得到 'No defect' 代码。另请参阅 post

中的

最佳率但不完美的代码是下面传统[=51] =] pthread C 语言,允许 从一开始就创建具有正确属性的线程。

我仍然惊讶地发现即使使用此代码我仍然会出错(与问题 MCVE 相同,但使用纯 pthread...API)。

为了强调代码,我找到了以下序列

$ seq 1000 | parallel ./main | grep inf
Result: inf
Result: inf
....

inf 表示除以 0 结果错误。在我的情况下,缺陷大约是 10/1000。

for i in {1..1000}; do ./main ; done | grep inf这样的命令更长

线程从高优先级到低优先级启动

所以现在除数线程

  • 首先创建
  • 具有更高的 RT 优先级(2 > 1 > 主要停留在 SCHED_OTHER 非 RT 调度)。

所以我想知道为什么我仍然被 0 除...

最后我试着减少任务集。当

时运行没问题
$ taskset -pc 0 $$
pid 2414's current affinity list: 0,1
pid 2414's new affinity list: 0
$ for i in {1..1000}; do ./main_oss ; done   <<-- no need for parallel in this case
Result: 0.333333
Result: 0.333333
Result: 0.333333
Result: 0.333333
Result: 0.333333
...

但是一旦超过 1 个 CPU 缺陷又回来了

$ taskset -pc 0,1 $$
pid 2414's current affinity list: 0
pid 2414's new affinity list: 0,1
$ seq 1000 | parallel ./main_oss
Result: 0.333333          | <<-- display by group of 2
Result: 0.333333          |
Result: inf             |   <<--
Result: 0.333333        |
...

当线程属于同一个父进程时,为什么我们 运行 低优先级 RT SCHED_FIFO 线程在另一个 CPU 上 = ?

遗憾的是 PTHREAD_SCOPE_PROCESS 在 Linux

上不受支持
#include <iostream>
#include <thread>
#include <cstring>
#include <pthread.h>

double a = 1.0F;
double b = 0.0F;

void * ratio(void*)
{
    std::cout << "Result: " << a/b << "\n" << std::flush;
    return nullptr;
}

void * divisor(void*)
{
    b = 3.0F;
    std::this_thread::sleep_for(std::chrono::milliseconds(500u));
    return nullptr;
}


int main(int agrc, char * argv[])
{
    struct sched_param param;

    pthread_t thr[2];
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setschedpolicy(&attr,SCHED_FIFO);
    pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);

    param.sched_priority = 2;
    pthread_attr_setschedparam(&attr,&param);
    pthread_create(&thr[0],&attr,divisor,nullptr);

    param.sched_priority = 1;
    pthread_attr_setschedparam(&attr,&param);
    pthread_create(&thr[1],&attr,ratio,nullptr);  

    pthread_join(thr[0],nullptr);
    pthread_join(thr[1],nullptr);

    return 0;
} 

收集我在调试中遇到的剩余问题的新答案。

当我使用 exec-wrapper 脚本执行我的目标二进制文件时,像 Setting application affinity in gdb / Markus Ahlberg 这样的答案或像 gdb 这样的问题不会中断 给出了一个使用 GDB 选项的解决方案 exec-wrapper 但后来我(总是)无法在我的代码中设置断点(甚至尝试我自己的包装器)

我终于又回到了这个解决方案Setting application affinity in gdb / Craig Scratchley

最初的问题

$ ./main
Result: inf

run-time

的解决方案
taskset -c 0 ./main
Result: 0.333333

但用于调试

gdb -ex 'set exec-wrapper taskset -c 0' ./main
--> mixed result depending on conditions (native/virtualized ? Number of cores ? ) 
sometimes 0.333333 sometimes inf
--> problem to set breakpoints
--> still work to do for me to summarize this issue

taskset -c 0 gdb main
...
(gdb) r
...
Result: inf

最后

taskset -c N chrt 99 gdb main <<-- where N is a core number (*)
...                           <<-- 99 denotes here "your higher prio in your system"
(gdb) r
...
Result: 0.333333
  • 我在上面写了 N 是因为如果你的程序 main 设置它对处理器 M 的亲和力并且你将 gdb 亲和力设置为 N,你可能会遇到同样的原始问题
  • 我只为 GDB 写了 chrt 99,即使我对 SCHED_FIFO 而不是 SCHED_RR 感兴趣,因为我体验过 gdb(或 IDE 见下文)如果使用了选项 -f(对于 fifo),则冻结。我怀疑 roud robin 机制更安全,因为线程总是会在某个时候释放

如果你有 IDE(但不知道如何在这个 IDE 中正确设置 gdb),我可以做到

taskset -c N chrt 99 code