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 尊重我的线程的优先级,我也有困难
- 请参阅问题末尾的 MCVE。
- 请参阅答案中修改后的 MCVE
这种情况是由于需要在 Linux PC 下模拟嵌入式代码以执行集成测试
具有 fifo 优先级 10
的 main
线程将启动线程 divisor
和 ratio
.
divisor
线程应该得到 priority 2
,这样带有 priority 1
的 ratio
线程将不会在 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
第一次实验是在 Virtualbox 环境下用 2 个 vCPUs(gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0, GNU gdb (Ubuntu 8.1- 0ubuntu3.2) 8.1.0.20180409-git) 代码行为在正常执行下几乎 OK
但在 GDB 下 NOK
。
Native Ubuntu 20.04 上的其他实验显示非常频繁的 NOK
行为,即使在使用 I3-1005 2C/4T (gcc (Ubuntu 9.3. 0-10ubuntu2) 9.3.0, GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1 )
基本编译:
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 中对此进行了解释(几乎所有答案都可能相关)。
所以就我而言,我做到了
sudo setcap cap_sys_nice+ep /usr/bin/gdb
- 用
set startup-with-shell off
创建一个 ~/.gdbinit
结果我得到了:
(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)
所以结论和问题
- 我认为唯一的问题来自 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,¶m);
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,¶m);
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,¶m);
std::thread thr_ratio(ratio);
std::thread thr_divisor(divisor);
thr_ratio.join();
thr_divisor.join();
return 0;
}
您的 MCVE 有一些明显的错误:
您在 b
上存在数据竞争,即未定义的行为,因此 任何事情 都可能发生。
您预计 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,¶m);
pthread_create(&thr[0],&attr,divisor,nullptr);
param.sched_priority = 1;
pthread_attr_setschedparam(&attr,¶m);
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
TL;DR
在multiprocessors/multicores 引擎上,可以在多个执行单元上安排多个RT SCHED_FIFO 线程。因此优先级为 60 的线程和优先级为 40 的线程可能 运行 同时在 2 个不同的内核上。
这可能是违反直觉的,尤其是在模拟嵌入式系统时(通常像今天一样)运行 在单核处理器上并依赖于严格的优先级执行。
请参阅此 post 中的
原问题描述
即使使用非常简单的代码来使 Linux 使用调度策略 SCHED_FIFO 尊重我的线程的优先级,我也有困难
- 请参阅问题末尾的 MCVE。
- 请参阅答案中修改后的 MCVE
这种情况是由于需要在 Linux PC 下模拟嵌入式代码以执行集成测试
具有 fifo 优先级 10
的 main
线程将启动线程 divisor
和 ratio
.
divisor
线程应该得到 priority 2
,这样带有 priority 1
的 ratio
线程将不会在 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
第一次实验是在 Virtualbox 环境下用 2 个 vCPUs(gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0, GNU gdb (Ubuntu 8.1- 0ubuntu3.2) 8.1.0.20180409-git) 代码行为在正常执行下几乎
OK
但在 GDB 下NOK
。Native Ubuntu 20.04 上的其他实验显示非常频繁的
NOK
行为,即使在使用 I3-1005 2C/4T (gcc (Ubuntu 9.3. 0-10ubuntu2) 9.3.0, GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1 )
基本编译:
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 中对此进行了解释(几乎所有答案都可能相关)。
所以就我而言,我做到了
sudo setcap cap_sys_nice+ep /usr/bin/gdb
- 用
set startup-with-shell off
创建一个 ~/.gdbinit
结果我得到了:
(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)
所以结论和问题
- 我认为唯一的问题来自 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,¶m);
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,¶m);
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,¶m);
std::thread thr_ratio(ratio);
std::thread thr_divisor(divisor);
thr_ratio.join();
thr_divisor.join();
return 0;
}
您的 MCVE 有一些明显的错误:
您在
b
上存在数据竞争,即未定义的行为,因此 任何事情 都可能发生。您预计
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,¶m);
pthread_create(&thr[0],&attr,divisor,nullptr);
param.sched_priority = 1;
pthread_attr_setschedparam(&attr,¶m);
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