POSIX 线程调度
调度行为受两个因素影响:调度策略和任务优先级。每个任务都有一个优先级,系统为每个允许的优先级维护一个就绪任务列表,列表具有某种顺序,表首任务和表尾任务,如果有新的任务就绪将被置于此列表的合适位置(这通常需要根据调度策略去选择)。
对于实时系统,响应速度是最重要的,因此,作为 POSIX 基本定义的实时扩展,POSIX 1003.1b 定义的调度策略都是基于优先级的。其他评价指标,如公平、吞吐量等则是次要的。
POSIX 标准规定优先级高的线程优先级数字大。SylixOS 中优先级与之相反,因此 SylixOS 进行了优先级转换,定义如下:
#include <posix/include/px_sched_param.h>
#define PX_PRIORITY_CONVERT(prio) (LW_PRIO_LOWEST - (prio))
此宏对于应用开发不需要关心,但是了解此宏可以清楚 POSIX 优先级和 SylixOS 优先级之间的关系。
POSIX 定义结构体 sched_param 来表示调度相关参数,如下所示是 SylixOS 中的实现:
sched_param 结构体
struct sched_param {
int sched_priority; /* POSIX 调度优先级 */
/* SCHED_SPORADIC parameter */
int sched_ss_low_priority; /* Low scheduling priority for */
/* sporadic server. */
struct timespec sched_ss_repl_period; /* Replenishment period for */
/* sporadic server. */
struct timespec sched_ss_init_budget; /* Initial budget for sporadic */
/* server. */
int sched_ss_max_repl; /* Max pending replenishments */
/* for sporadic server. */
……
};
注意:
目前 SylixOS 仅支持该结构体的优先级设置,其他为保留项。
POSIX 定义了以下函数用来动态地选择线程的调度策略(SCHED_FIFO 和 SCHED_RR)。
#include <sched.h>
int sched_setscheduler(pid_t pid,
int iPolicy,
const struct sched_param *pschedparam);
int sched_getscheduler(pid_t pid);
函数 sched_setscheduler 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 pid 是进程 ID。
- 参数 iPolicy 是调度策略。
- 参数 pschedparam 是调度器参数。
函数 sched_getscheduler 原型分析:
- 此函数成功返回调度器策略(SCHED_FIFO、SCHED_RR),失败返回-1 并设置错误号。
- 参数 pid 是进程 ID。
在“线程属性”章节,我们介绍调度策略时了解到可以通过调用 pthread_attr_setschedpolicy 函数设置线程的调度策略,这种方法是在线程创建之前设置的,也就是说是一种静态改变的方法,而通过调用 sched_setscheduler 函数,则提供了一种动态改变线程优先级的方法。调用 sched_getscheduler 函数可以获得指定线程的调度策略。
需要注意的是,sched_setscheduler 函数在设置调度策略的同时会设置线程的优先级,稍后我们会介绍 POSIX 线程优先级应该满足什么样的范围值。
调用下面两个函数可以获得 POSIX 线程优先级的最大和最小值,应用程序设置的优先级应该在这两个值的范围内且不应该包括这两个值。
#include <sched.h>
int sched_get_priority_max(int iPolicy);
int sched_get_priority_min(int iPolicy);
函数 sched_get_priority_max 原型分析:
- 此函数返回 POSIX 最大优先级值。
- 参数 iPolicy 是调度策略。
函数 sched_get_priority_min 原型分析:
- 此函数返回 POSIX 最小优先级值。
- 参数 iPolicy 是调度策略。
POSIX 允许采用不同的调度策略时定义不同的优先级范围,对于当前 SylixOS 的实现,所有的调度策略都具有相同的优先级范围。
调用 sched_setscheduler 函数在设置调度策略的同时也设置了进程优先级,实际上,也可以通过调用 sched_setparam 函数来设置进程的优先级,调用 sched_getparam 函数可以获得指定进程的优先级。
#include <sched.h>
int sched_setparam(pid_t pid, const struct sched_param *pschedparam);
int sched_getparam(pid_t pid, struct sched_param *pschedparam);
函数 sched_setparam 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 pid 是进程 ID。
- 参数 pschedparam 是调度参数。
函数 sched_getparam 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 pid 是进程 ID。
- 输出参数 pschedparam 返回调度参数。
如果 pid 等于 0,则设置当前线程的优先级,需要注意的是,如果设置的优先级和指定线程的当前优先级相同,则什么也不做并返回 0。
#include <sched.h>
int sched_rr_get_interval(pid_t pid, struct timespec *interval);
int sched_yield(void);
函数 sched_rr_get_interval 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 pid 是进程 ID。
- 输出参数 interval 返回进程或者线程剩余的时间。
调用 sched_rr_get_interval 函数将返回指定线程的时间片大小(timespec 类型时间值),该函数只有调度策略为 SCHED_RR 时有效,否则返回-1 并置 errno 为 EINVAL。调用 sched_yield 函数将主动放弃一次处理器。
下面程序展示了 POSIX 线程调度函数的使用。
#include <sched.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
struct sched_param param;
struct sched_param newparam;
struct timespec time;
int ret;
fprintf(stdout, "Max prio: %d, Min: %d\n",
sched_get_priority_max(SCHED_RR),
sched_get_priority_min(SCHED_RR));
ret = sched_getparam(0, ¶m);
if (ret != 0) {
perror("sched_getparam");
return (-1);
}
fprintf(stdout, "old prio: %d\n", param.sched_priority);
param.sched_priority = 40;
ret = sched_setscheduler(0, SCHED_RR, ¶m);
if (ret != 0) {
return (-1);
}
ret = sched_getparam(0, &newparam);
if (ret != 0) {
perror("sched_getparam");
return (-1);
}
fprintf(stdout, "new prio: %d\n", newparam.sched_priority);
sched_rr_get_interval(0, &time);
fprintf(stdout, "time slice %lld(s):%ld(ns)\n", time.tv_sec, time.tv_nsec);
return (0);
}
在 SylixOS Shell 中运行程序。运行结果显示 SylixOS 中 POSIX 线程可用优先级的范围是 1 到 254,这说明了优先级 0 和优先级 255 是 SylixOS 内核不建议使用的。
# ./sched_test
Max prio: 254, Min prio: 1
old prio: 55
new prio: 40
time slice 0(s):8000000(ns)