1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22
23 #ifdef __LINUX__
24
25 #include "SDL_error.h"
26 #include "SDL_stdinc.h"
27 #include "SDL_thread.h"
28
29 #if !SDL_THREADS_DISABLED
30 #include <sys/time.h>
31 #include <sys/resource.h>
32 #include <pthread.h>
33 #include "SDL_system.h"
34
35 /* RLIMIT_RTTIME requires kernel >= 2.6.25 and is in glibc >= 2.14 */
36 #ifndef RLIMIT_RTTIME
37 #define RLIMIT_RTTIME 15
38 #endif
39
40 #include "SDL_dbus.h"
41
42 #if SDL_USE_LIBDBUS
43 #include <sched.h>
44
45 /* d-bus queries to org.freedesktop.RealtimeKit1. */
46 #define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
47 #define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1"
48 #define RTKIT_DBUS_INTERFACE "org.freedesktop.RealtimeKit1"
49
50 static pthread_once_t rtkit_initialize_once = PTHREAD_ONCE_INIT;
51 static Sint32 rtkit_min_nice_level = -20;
52 static Sint32 rtkit_max_realtime_priority = 99;
53
54 static void
rtkit_initialize()55 rtkit_initialize()
56 {
57 SDL_DBusContext *dbus = SDL_DBus_GetContext();
58
59 /* Try getting minimum nice level: this is often greater than PRIO_MIN (-20). */
60 if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MinNiceLevel",
61 DBUS_TYPE_INT32, &rtkit_min_nice_level)) {
62 rtkit_min_nice_level = -20;
63 }
64
65 /* Try getting maximum realtime priority: this can be less than the POSIX default (99). */
66 if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MaxRealtimePriority",
67 DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) {
68 rtkit_max_realtime_priority = 99;
69 }
70 }
71
72 static SDL_bool
rtkit_initialize_thread()73 rtkit_initialize_thread()
74 {
75 // Following is an excerpt from rtkit README that outlines the requirements
76 // a thread must meet before making rtkit requests:
77 //
78 // * Only clients with RLIMIT_RTTIME set will get RT scheduling
79 //
80 // * RT scheduling will only be handed out to processes with
81 // SCHED_RESET_ON_FORK set to guarantee that the scheduling
82 // settings cannot 'leak' to child processes, thus making sure
83 // that 'RT fork bombs' cannot be used to bypass RLIMIT_RTTIME
84 // and take the system down.
85 //
86 // * Limits are enforced on all user controllable resources, only
87 // a maximum number of users, processes, threads can request RT
88 // scheduling at the same time.
89 //
90 // * Only a limited number of threads may be made RT in a
91 // specific time frame.
92 //
93 // * Client authorization is verified with PolicyKit
94
95 int err;
96 struct rlimit rlimit;
97 int nLimit = RLIMIT_RTTIME;
98 pid_t nPid = 0; //self
99 int nSchedPolicy = sched_getscheduler(nPid) | SCHED_RESET_ON_FORK;
100 struct sched_param schedParam = {};
101
102 // Requirement #1: Set RLIMIT_RTTIME
103 err = getrlimit(nLimit, &rlimit);
104 if (err)
105 {
106 return SDL_FALSE;
107 }
108
109 rlimit.rlim_cur = rlimit.rlim_max;
110 err = setrlimit(nLimit, &rlimit);
111 if (err)
112 {
113 return SDL_FALSE;
114 }
115
116 // Requirement #2: Add SCHED_RESET_ON_FORK to the scheduler policy
117 err = sched_getparam(nPid, &schedParam);
118 if (err)
119 {
120 return SDL_FALSE;
121 }
122
123 err = sched_setscheduler(nPid, nSchedPolicy, &schedParam);
124 if (err)
125 {
126 return SDL_FALSE;
127 }
128
129 return SDL_TRUE;
130 }
131
132 static SDL_bool
rtkit_setpriority_nice(pid_t thread,int nice_level)133 rtkit_setpriority_nice(pid_t thread, int nice_level)
134 {
135 Uint64 ui64 = (Uint64)thread;
136 Sint32 si32 = (Sint32)nice_level;
137 SDL_DBusContext *dbus = SDL_DBus_GetContext();
138
139 pthread_once(&rtkit_initialize_once, rtkit_initialize);
140
141 if (si32 < rtkit_min_nice_level)
142 si32 = rtkit_min_nice_level;
143
144 // We always perform the thread state changes necessary for rtkit.
145 // This wastes some system calls if the state is already set but
146 // typically code sets a thread priority and leaves it so it's
147 // not expected that this wasted effort will be an issue.
148 // We also do not quit if this fails, we let the rtkit request
149 // go through to determine whether it really needs to fail or not.
150 rtkit_initialize_thread();
151
152 if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
153 RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadHighPriority",
154 DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID,
155 DBUS_TYPE_INVALID)) {
156 return SDL_FALSE;
157 }
158 return SDL_TRUE;
159 }
160
161 static SDL_bool
rtkit_setpriority_realtime(pid_t thread,int rt_priority)162 rtkit_setpriority_realtime(pid_t thread, int rt_priority)
163 {
164 Uint64 ui64 = (Uint64)thread;
165 Sint32 si32 = (Sint32)rt_priority;
166 SDL_DBusContext *dbus = SDL_DBus_GetContext();
167
168 pthread_once(&rtkit_initialize_once, rtkit_initialize);
169
170 if (si32 > rtkit_max_realtime_priority)
171 si32 = rtkit_max_realtime_priority;
172
173 // We always perform the thread state changes necessary for rtkit.
174 // This wastes some system calls if the state is already set but
175 // typically code sets a thread priority and leaves it so it's
176 // not expected that this wasted effort will be an issue.
177 // We also do not quit if this fails, we let the rtkit request
178 // go through to determine whether it really needs to fail or not.
179 rtkit_initialize_thread();
180
181 if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
182 RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadRealtime",
183 DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID,
184 DBUS_TYPE_INVALID)) {
185 return SDL_FALSE;
186 }
187 return SDL_TRUE;
188 }
189 #else
190
191 #define rtkit_max_realtime_priority 99
192
193 #endif /* dbus */
194 #endif /* threads */
195
196 /* this is a public symbol, so it has to exist even if threads are disabled. */
197 int
SDL_LinuxSetThreadPriority(Sint64 threadID,int priority)198 SDL_LinuxSetThreadPriority(Sint64 threadID, int priority)
199 {
200 #if SDL_THREADS_DISABLED
201 return SDL_Unsupported();
202 #else
203 if (setpriority(PRIO_PROCESS, (id_t)threadID, priority) == 0) {
204 return 0;
205 }
206
207 #if SDL_USE_LIBDBUS
208 /* Note that this fails you most likely:
209 * Have your process's scheduler incorrectly configured.
210 See the requirements at:
211 http://git.0pointer.net/rtkit.git/tree/README#n16
212 * Encountered dbus/polkit security restrictions. Note
213 that the RealtimeKit1 dbus endpoint is inaccessible
214 over ssh connections for most common distro configs.
215 You might want to check your local config for details:
216 /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
217
218 README and sample code at: http://git.0pointer.net/rtkit.git
219 */
220 if (rtkit_setpriority_nice((pid_t)threadID, priority)) {
221 return 0;
222 }
223 #endif
224
225 return SDL_SetError("setpriority() failed");
226 #endif
227 }
228
229 /* this is a public symbol, so it has to exist even if threads are disabled. */
230 int
SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID,int sdlPriority,int schedPolicy)231 SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
232 {
233 #if SDL_THREADS_DISABLED
234 return SDL_Unsupported();
235 #else
236 int osPriority;
237
238 if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
239 if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
240 osPriority = 1;
241 } else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
242 osPriority = rtkit_max_realtime_priority * 3 / 4;
243 } else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
244 osPriority = rtkit_max_realtime_priority;
245 } else {
246 osPriority = rtkit_max_realtime_priority / 2;
247 }
248 } else {
249 if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
250 osPriority = 19;
251 } else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
252 osPriority = -10;
253 } else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
254 osPriority = -20;
255 } else {
256 osPriority = 0;
257 }
258
259 if (setpriority(PRIO_PROCESS, (id_t)threadID, osPriority) == 0) {
260 return 0;
261 }
262 }
263
264 #if SDL_USE_LIBDBUS
265 /* Note that this fails you most likely:
266 * Have your process's scheduler incorrectly configured.
267 See the requirements at:
268 http://git.0pointer.net/rtkit.git/tree/README#n16
269 * Encountered dbus/polkit security restrictions. Note
270 that the RealtimeKit1 dbus endpoint is inaccessible
271 over ssh connections for most common distro configs.
272 You might want to check your local config for details:
273 /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
274
275 README and sample code at: http://git.0pointer.net/rtkit.git
276 */
277 if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
278 if (rtkit_setpriority_realtime((pid_t)threadID, osPriority)) {
279 return 0;
280 }
281 } else {
282 if (rtkit_setpriority_nice((pid_t)threadID, osPriority)) {
283 return 0;
284 }
285 }
286 #endif
287
288 return SDL_SetError("setpriority() failed");
289 #endif
290 }
291
292 #endif /* __LINUX__ */
293
294 /* vi: set ts=4 sw=4 expandtab: */
295