linuxulator: Map scheduler priorities to Linux priorities.

On Linux the valid range of priorities for the SCHED_FIFO and SCHED_RR
scheduling policies is [1,99].  For SCHED_OTHER the single valid priority is
0.  On FreeBSD it is [0,31] for all policies.  Programs are supposed to
query the valid range using sched_get_priority_(min|max), but of course some
programs assume the Linux values are valid.

This commit adds a tunable compat.linux.map_sched_prio.  When enabled
sched_get_priority_(min|max) return the Linux values and sched_setscheduler
and sched_(get|set)param translate between FreeBSD and Linux values.

Because there are more Linux levels than FreeBSD levels, multiple Linux
levels map to a single FreeBSD level, which means pre-emption might not
happen as it does on Linux, so the tunable allows to disable this behaviour.
It is enabled by default because I think it is unlikely that anyone runs
real-time software under Linux emulation on FreeBSD that critically relies
on correct pre-emption.

This fixes FMOD, a commercial sound library used by several games.

PR:		240043
Tested by:	Alex S <iwtcex@gmail.com>
Reviewed by:	dchagin
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D23790
This commit is contained in:
Tijl Coosemans 2020-03-01 13:12:04 +00:00
parent 7e1e491f60
commit f8b9b299a2
2 changed files with 130 additions and 7 deletions

View file

@ -144,6 +144,11 @@ struct l_pselect6arg {
l_size_t ss_len;
};
static bool map_sched_prio = true;
SYSCTL_BOOL(_compat_linux, OID_AUTO, map_sched_prio, CTLFLAG_RDTUN,
&map_sched_prio, 0, "Map scheduler priorities to Linux priorities "
"(not POSIX compliant)");
static int linux_utimensat_nsec_valid(l_long);
@ -1419,6 +1424,33 @@ linux_sched_setscheduler(struct thread *td,
if (error)
return (error);
if (map_sched_prio) {
switch (policy) {
case SCHED_OTHER:
if (sched_param.sched_priority != 0)
return (EINVAL);
sched_param.sched_priority =
PRI_MAX_TIMESHARE - PRI_MIN_TIMESHARE;
break;
case SCHED_FIFO:
case SCHED_RR:
if (sched_param.sched_priority < 1 ||
sched_param.sched_priority >= LINUX_MAX_RT_PRIO)
return (EINVAL);
/*
* Map [1, LINUX_MAX_RT_PRIO - 1] to
* [0, RTP_PRIO_MAX - RTP_PRIO_MIN] (rounding down).
*/
sched_param.sched_priority =
(sched_param.sched_priority - 1) *
(RTP_PRIO_MAX - RTP_PRIO_MIN + 1) /
(LINUX_MAX_RT_PRIO - 1);
break;
}
}
tdt = linux_tdfind(td, args->pid, -1);
if (tdt == NULL)
return (ESRCH);
@ -1462,6 +1494,20 @@ linux_sched_get_priority_max(struct thread *td,
{
struct sched_get_priority_max_args bsd;
if (map_sched_prio) {
switch (args->policy) {
case LINUX_SCHED_OTHER:
td->td_retval[0] = 0;
return (0);
case LINUX_SCHED_FIFO:
case LINUX_SCHED_RR:
td->td_retval[0] = LINUX_MAX_RT_PRIO - 1;
return (0);
default:
return (EINVAL);
}
}
switch (args->policy) {
case LINUX_SCHED_OTHER:
bsd.policy = SCHED_OTHER;
@ -1484,6 +1530,20 @@ linux_sched_get_priority_min(struct thread *td,
{
struct sched_get_priority_min_args bsd;
if (map_sched_prio) {
switch (args->policy) {
case LINUX_SCHED_OTHER:
td->td_retval[0] = 0;
return (0);
case LINUX_SCHED_FIFO:
case LINUX_SCHED_RR:
td->td_retval[0] = 1;
return (0);
default:
return (EINVAL);
}
}
switch (args->policy) {
case LINUX_SCHED_OTHER:
bsd.policy = SCHED_OTHER;
@ -1864,7 +1924,7 @@ linux_sched_setparam(struct thread *td,
{
struct sched_param sched_param;
struct thread *tdt;
int error;
int error, policy;
error = copyin(uap->param, &sched_param, sizeof(sched_param));
if (error)
@ -1874,8 +1934,41 @@ linux_sched_setparam(struct thread *td,
if (tdt == NULL)
return (ESRCH);
if( map_sched_prio ) {
error = kern_sched_getscheduler(td, tdt, &policy);
if (error)
goto out;
switch (policy) {
case SCHED_OTHER:
if (sched_param.sched_priority != 0) {
error = EINVAL;
goto out;
}
sched_param.sched_priority =
PRI_MAX_TIMESHARE - PRI_MIN_TIMESHARE;
break;
case SCHED_FIFO:
case SCHED_RR:
if (sched_param.sched_priority < 1 ||
sched_param.sched_priority >= LINUX_MAX_RT_PRIO) {
error = EINVAL;
goto out;
}
/*
* Map [1, LINUX_MAX_RT_PRIO - 1] to
* [0, RTP_PRIO_MAX - RTP_PRIO_MIN] (rounding down).
*/
sched_param.sched_priority =
(sched_param.sched_priority - 1) *
(RTP_PRIO_MAX - RTP_PRIO_MIN + 1) /
(LINUX_MAX_RT_PRIO - 1);
break;
}
}
error = kern_sched_setparam(td, tdt, &sched_param);
PROC_UNLOCK(tdt->td_proc);
out: PROC_UNLOCK(tdt->td_proc);
return (error);
}
@ -1885,17 +1978,45 @@ linux_sched_getparam(struct thread *td,
{
struct sched_param sched_param;
struct thread *tdt;
int error;
int error, policy;
tdt = linux_tdfind(td, uap->pid, -1);
if (tdt == NULL)
return (ESRCH);
error = kern_sched_getparam(td, tdt, &sched_param);
PROC_UNLOCK(tdt->td_proc);
if (error == 0)
error = copyout(&sched_param, uap->param,
sizeof(sched_param));
if (error) {
PROC_UNLOCK(tdt->td_proc);
return (error);
}
if (map_sched_prio) {
error = kern_sched_getscheduler(td, tdt, &policy);
PROC_UNLOCK(tdt->td_proc);
if (error)
return (error);
switch (policy) {
case SCHED_OTHER:
sched_param.sched_priority = 0;
break;
case SCHED_FIFO:
case SCHED_RR:
/*
* Map [0, RTP_PRIO_MAX - RTP_PRIO_MIN] to
* [1, LINUX_MAX_RT_PRIO - 1] (rounding up).
*/
sched_param.sched_priority =
(sched_param.sched_priority *
(LINUX_MAX_RT_PRIO - 1) +
(RTP_PRIO_MAX - RTP_PRIO_MIN - 1)) /
(RTP_PRIO_MAX - RTP_PRIO_MIN) + 1;
break;
}
} else
PROC_UNLOCK(tdt->td_proc);
error = copyout(&sched_param, uap->param, sizeof(sched_param));
return (error);
}

View file

@ -105,6 +105,8 @@ extern const char *linux_kplatform;
#define LINUX_SCHED_FIFO 1
#define LINUX_SCHED_RR 2
#define LINUX_MAX_RT_PRIO 100
struct l_new_utsname {
char sysname[LINUX_MAX_UTSNAME];
char nodename[LINUX_MAX_UTSNAME];