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