From 8ae2e95643ee7ba3f46b0e8a73df1466f23aa7be Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 9 Jan 2019 09:26:42 +0700 Subject: [PATCH] libroot: add [gs]etpriority implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented against POSIX-1.2013. The implementation POSIX requirement thats setpriority() shall affect the priority of all system scope threads only extends to POSIX threads. This is implemented by modifying the default attributes for newly spawned pthreads. It is not possible to modify the default pthread attributes for different processes with the current implementation, as default pthread attributes are implemented in user-space. As a result, PRIO_PROCESS for which and 0 for who is the only supported combination for setpriority(). While it is possible to move the default attributes to the kernel, it is chosen not to so as to keep the pthread implementation user-space only. POSIX requires that lowering the nice value (increasing priority) can be done only by processes with appropriate privileges. However, as Haiku currently doesn't harbor any restrictions in setting the thread priority, this is not implemented. It is possible to have small precision errors when converting from Unix- style thread priority to Be-style. For example, the following program outputs "17" instead of the expected "18": #include #include int main() { setpriority(PRIO_PROCESS, 0, 18); printf("%d\n", getpriority(PRIO_PROCESS, 0)); return 0; } The underlying reason is because when you setpriority() both 18 and 19 are converted to the Be-style "2". This problem should not happen with priority levels lower than or equal to 20, when the Be notation is more precise than the Unix-style. Done as a part of GCI 2014. Fixes #2817. Signed-off-by: Timothy Gu Co-authored-by: Leorize Change-Id: Ie14f105b00fe8563d16b3562748e1c2e56c873a6 Reviewed-on: https://review.haiku-os.org/c/78 Reviewed-by: Jérôme Duval Reviewed-by: waddlesplash --- headers/posix/limits.h | 5 +- headers/posix/sys/resource.h | 5 +- headers/private/libroot/pthread_private.h | 1 + src/system/libroot/posix/pthread/pthread.cpp | 9 +- src/system/libroot/posix/sys/Jamfile | 1 + src/system/libroot/posix/sys/priority.c | 194 +++++++++++++++++++ 6 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 src/system/libroot/posix/sys/priority.c diff --git a/headers/posix/limits.h b/headers/posix/limits.h index d2ba5d509a..542f540ade 100644 --- a/headers/posix/limits.h +++ b/headers/posix/limits.h @@ -76,11 +76,14 @@ #define _POSIX2_LINE_MAX (2048) +#ifdef _XOPEN_SOURCE /* The XSI name for PAGESIZE, with extra underscore included. Only define if * _XOPEN_SOURCE was requested, otherwise it could conflict with the application. */ -#ifdef _XOPEN_SOURCE #define PAGE_SIZE PAGESIZE +/* XSI extension: Default process priority. This is used by the implementation + * of getpriority(), setpriority() and nice(). */ +#define NZERO 20 #endif /* _GCC_LIMITS_H_ is defined by GCC's internal limits.h to avoid diff --git a/headers/posix/sys/resource.h b/headers/posix/sys/resource.h index 96ac924a20..c27cfda0d9 100644 --- a/headers/posix/sys/resource.h +++ b/headers/posix/sys/resource.h @@ -64,9 +64,8 @@ extern int getrusage(int who, struct rusage *rusage); extern int getrlimit(int resource, struct rlimit * rlp); extern int setrlimit(int resource, const struct rlimit * rlp); -/* ToDo: The following POSIX calls are missing (in BeOS as well): - * int getpriority(int which, id_t who); - * int setpriority(int which, id_t who, int priority); */ +extern int getpriority(int which, id_t who); +extern int setpriority(int which, id_t who, int priority); __END_DECLS diff --git a/headers/private/libroot/pthread_private.h b/headers/private/libroot/pthread_private.h index 953315b60c..41b3401bcd 100644 --- a/headers/private/libroot/pthread_private.h +++ b/headers/private/libroot/pthread_private.h @@ -90,6 +90,7 @@ status_t __pthread_init_creation_attributes( status_t (*entryFunction)(void*, void*), void* argument1, void* argument2, const char* name, struct thread_creation_attributes* attributes); +void __pthread_set_default_priority(int32 priority); #ifdef __cplusplus } diff --git a/src/system/libroot/posix/pthread/pthread.cpp b/src/system/libroot/posix/pthread/pthread.cpp index 6bd62f9f36..59e4c1ea99 100644 --- a/src/system/libroot/posix/pthread/pthread.cpp +++ b/src/system/libroot/posix/pthread/pthread.cpp @@ -23,7 +23,7 @@ #include -static const pthread_attr pthread_attr_default = { +static pthread_attr pthread_attr_default = { PTHREAD_CREATE_JOINABLE, B_NORMAL_PRIORITY, USER_STACK_SIZE, @@ -132,6 +132,13 @@ __pthread_init_creation_attributes(const pthread_attr_t* pthreadAttributes, } +void +__pthread_set_default_priority(int32 priority) +{ + pthread_attr_default.sched_priority = priority; +} + + // #pragma mark - public API diff --git a/src/system/libroot/posix/sys/Jamfile b/src/system/libroot/posix/sys/Jamfile index c4f9a29c3c..d7f4bea3b5 100644 --- a/src/system/libroot/posix/sys/Jamfile +++ b/src/system/libroot/posix/sys/Jamfile @@ -21,6 +21,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { mkfifo.c mknod.c mman.cpp + priority.c rlimit.c select.c stat.c diff --git a/src/system/libroot/posix/sys/priority.c b/src/system/libroot/posix/sys/priority.c new file mode 100644 index 0000000000..fae2b7a8cb --- /dev/null +++ b/src/system/libroot/posix/sys/priority.c @@ -0,0 +1,194 @@ +/* + * Copyright 2019 Leorize + * Copyright 2015 Tiancheng "Timothy" Gu, timothygu99@gmail.com + * Copyright 2001-2002 François Revol (mmu_man) + * All rights reserved. Distributed under the terms of the MIT License. + */ + + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 /* for NZERO */ +#endif + +#include "pthread_private.h" + +#include +#include + +#include +#include // for MAX() and MIN() macros +#include // RETURN_AND_SET_ERRNO() + +// Part of this file is adapted from src/bin/renice.c, by François Revol. + +// Some notes on different priority notations: +// +// Notation system | Real-time | High Prio | Default | Low Prio | +// -------------------------|----------:|----------:|--------:|---------:| +// BeOS/Haiku | 120* | 99 | 10 | 1** | +// UNIX [gs]etpriority()*** | N.A. | -20 | 0 | 19 | +// UNIX internal | N.A. | 0 | 20 | 39 | +// +// * Note that BeOS/Haiku does not have an absolute highest priority value +// (else than the maximum of int32), and (B_REAL_TIME_PRIORITY - 1) is +// the highest non-real-time priority and usually used as the priority +// limit. On UNIX systems there is no such concept as "real-time." +// ** 0 only for idle_thread +// *** Also used for UNIX nice(1) and nice(3) + +#ifdef CLIP +#undef CLIP +#endif +#define CLIP(n, max, min) MAX(MIN((n), (max)), (min)) + +// In these contexts, "min" does not mean "lowest value," but "lowest +// priority," which is the maximum value in UNIX priority notation. +// "Zero" means normal priority, which in UNIX function notation is 0. +// POSIX specification also refers to it as "NZERO." +#define NMAX 0 +#define NMIN (NZERO * 2 - 1) +#define CLIP_TO_UNIX(n) CLIP(n, NMIN, NMAX) + +#define BZERO B_NORMAL_PRIORITY +#define BMAX (B_REAL_TIME_DISPLAY_PRIORITY - 1) +#define BMIN 1 +#define CLIP_TO_BEOS(n) CLIP(n, BMAX, BMIN) + +// To accurately convert the notation values, we need to use an exponential +// function: +// +// f(x) = 99e^(-0.116x) +// +// where f() represents the BeOS priority value, and x represents the Unix +// priority value. +// +// But that's too complicated. And slow. So we use a simple piecewise linear +// approach here, by a simple rescaling of the values. + +// returns an equivalent UNIX priority for a given BeOS priority. +static int32 +prio_be_to_unix(int32 prio) +{ + int out; + if (prio >= BZERO) + out = NZERO + - ((prio - BZERO) * NZERO + (BMAX - BZERO) / 2) / (BMAX - BZERO); + // `(BMAX - BZERO) / 2` for rounding + else + out = NZERO + + ((BZERO - prio) * (NZERO - 1)) / (BZERO - BMIN) + + 1; + // `+1` for rounding + return CLIP_TO_UNIX(out); +} + + +// returns an equivalent BeOS priority for a given UNIX priority. +static int32 +prio_unix_to_be(int32 prio) +{ + int out; + // Do not need to care about rounding + if (prio >= NZERO) + out = BZERO - ((prio - NZERO) * (BZERO - BMIN)) / (NZERO - 1); + else + out = BZERO + ((NZERO - prio) * (BMAX - BZERO)) / (NZERO); + return CLIP_TO_BEOS(out); +} + + +int +getpriority(int which, id_t who) +{ + bool found = false; + int out = -100; + + if (who < 0) + RETURN_AND_SET_ERRNO(EINVAL); + switch (which) { + case PRIO_PROCESS: + { + int32 th_cookie = 0; + thread_info thread; + + while (get_next_thread_info(who, &th_cookie, &thread) == B_OK) { + if (thread.priority > out) { + found = true; + out = thread.priority; + } + } + break; + } + case PRIO_PGRP: + { + int32 team_cookie = 0, th_cookie = 0; + team_info team; + thread_info thread; + + who = who == 0 ? getpgrp() : who; + while (get_next_team_info(&team_cookie, &team) == B_OK) { + if (getpgid(team.team) != who) + continue; + th_cookie = 0; + while (get_next_thread_info(team.team, &th_cookie, &thread) + == B_OK) { + if (thread.priority > out) { + found = true; + out = thread.priority; + } + } + } + break; + } + case PRIO_USER: + { + // `who` (id_t) is int32, but uid_t is uint32, so using this + // indirection to get rid of compiler warnings + // `who` can never be negative because of the `who < 0` check + // above. + uid_t euid = who == 0 ? geteuid() : (uid_t)who; + int32 team_cookie = 0, th_cookie = 0; + team_info team; + thread_info thread; + + while (get_next_team_info(&team_cookie, &team) == B_OK) { + if (team.uid != euid) + continue; + th_cookie = 0; + while (get_next_thread_info(team.team, &th_cookie, &thread) + == B_OK) { + if (thread.priority > out) { + found = true; + out = thread.priority; + } + } + } + break; + } + default: + RETURN_AND_SET_ERRNO(EINVAL); + } + if (!found) + RETURN_AND_SET_ERRNO(ESRCH); + return prio_be_to_unix(out) - NZERO; +} + + +int +setpriority(int which, id_t who, int value) +{ + int32 th_cookie = 0; + thread_info thread; + + // TODO: implement for other processes + if (who != 0 && which != PRIO_PROCESS) + RETURN_AND_SET_ERRNO(EINVAL); + value = value > NMIN ? NMIN : CLIP_TO_UNIX(value + NZERO); + value = prio_unix_to_be(value); + + __pthread_set_default_priority(value); + while (get_next_thread_info(B_CURRENT_TEAM, &th_cookie, &thread) == B_OK) + set_thread_priority(thread.thread, value); + + return 0; +}