1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2025 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 | #include <pthread.h> |
24 | |
25 | #ifdef HAVE_PTHREAD_NP_H |
26 | #include <pthread_np.h> |
27 | #endif |
28 | |
29 | #ifdef HAVE_SIGNAL_H |
30 | #include <signal.h> |
31 | #endif |
32 | #include <errno.h> |
33 | |
34 | #ifdef SDL_PLATFORM_LINUX |
35 | #include <sys/time.h> |
36 | #include <sys/resource.h> |
37 | #include <sys/syscall.h> |
38 | #include <unistd.h> |
39 | |
40 | #include "../../core/linux/SDL_dbus.h" |
41 | #endif // SDL_PLATFORM_LINUX |
42 | |
43 | #if (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN) |
44 | #include <dlfcn.h> |
45 | #ifndef RTLD_DEFAULT |
46 | #define RTLD_DEFAULT NULL |
47 | #endif |
48 | #endif |
49 | |
50 | #include "../SDL_thread_c.h" |
51 | #include "../SDL_systhread.h" |
52 | #ifdef SDL_PLATFORM_ANDROID |
53 | #include "../../core/android/SDL_android.h" |
54 | #endif |
55 | |
56 | #ifdef SDL_PLATFORM_HAIKU |
57 | #include <kernel/OS.h> |
58 | #endif |
59 | |
60 | #ifdef HAVE_SIGNAL_H |
61 | // List of signals to mask in the subthreads |
62 | static const int sig_list[] = { |
63 | SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGWINCH, |
64 | SIGVTALRM, SIGPROF, 0 |
65 | }; |
66 | #endif |
67 | |
68 | static void *RunThread(void *data) |
69 | { |
70 | #ifdef SDL_PLATFORM_ANDROID |
71 | Android_JNI_SetupThread(); |
72 | #endif |
73 | SDL_RunThread((SDL_Thread *)data); |
74 | return NULL; |
75 | } |
76 | |
77 | #if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN) |
78 | static bool checked_setname = false; |
79 | static int (*ppthread_setname_np)(const char *) = NULL; |
80 | #elif (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN) |
81 | static bool checked_setname = false; |
82 | static int (*ppthread_setname_np)(pthread_t, const char *) = NULL; |
83 | #endif |
84 | bool SDL_SYS_CreateThread(SDL_Thread *thread, |
85 | SDL_FunctionPointer pfnBeginThread, |
86 | SDL_FunctionPointer pfnEndThread) |
87 | { |
88 | pthread_attr_t type; |
89 | |
90 | // do this here before any threads exist, so there's no race condition. |
91 | #if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN) |
92 | if (!checked_setname) { |
93 | void *fn = dlsym(RTLD_DEFAULT, "pthread_setname_np" ); |
94 | #if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) |
95 | ppthread_setname_np = (int (*)(const char *))fn; |
96 | #elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) |
97 | ppthread_setname_np = (int (*)(pthread_t, const char *))fn; |
98 | #endif |
99 | checked_setname = true; |
100 | } |
101 | #endif |
102 | |
103 | // Set the thread attributes |
104 | if (pthread_attr_init(&type) != 0) { |
105 | return SDL_SetError("Couldn't initialize pthread attributes" ); |
106 | } |
107 | pthread_attr_setdetachstate(&type, PTHREAD_CREATE_JOINABLE); |
108 | |
109 | // Set caller-requested stack size. Otherwise: use the system default. |
110 | if (thread->stacksize) { |
111 | pthread_attr_setstacksize(&type, thread->stacksize); |
112 | } |
113 | |
114 | // Create the thread and go! |
115 | if (pthread_create(&thread->handle, &type, RunThread, thread) != 0) { |
116 | return SDL_SetError("Not enough resources to create thread" ); |
117 | } |
118 | |
119 | return true; |
120 | } |
121 | |
122 | void SDL_SYS_SetupThread(const char *name) |
123 | { |
124 | #ifdef HAVE_SIGNAL_H |
125 | int i; |
126 | sigset_t mask; |
127 | #endif |
128 | |
129 | if (name) { |
130 | #if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN) |
131 | SDL_assert(checked_setname); |
132 | if (ppthread_setname_np) { |
133 | #if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) |
134 | ppthread_setname_np(name); |
135 | #elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) |
136 | if (ppthread_setname_np(pthread_self(), name) == ERANGE) { |
137 | char namebuf[16]; // Limited to 16 char |
138 | SDL_strlcpy(namebuf, name, sizeof(namebuf)); |
139 | ppthread_setname_np(pthread_self(), namebuf); |
140 | } |
141 | #endif |
142 | } |
143 | #elif defined(HAVE_PTHREAD_SETNAME_NP) |
144 | #ifdef SDL_PLATFORM_NETBSD |
145 | pthread_setname_np(pthread_self(), "%s" , name); |
146 | #else |
147 | if (pthread_setname_np(pthread_self(), name) == ERANGE) { |
148 | char namebuf[16]; // Limited to 16 char |
149 | SDL_strlcpy(namebuf, name, sizeof(namebuf)); |
150 | pthread_setname_np(pthread_self(), namebuf); |
151 | } |
152 | #endif |
153 | #elif defined(HAVE_PTHREAD_SET_NAME_NP) |
154 | pthread_set_name_np(pthread_self(), name); |
155 | #elif defined(SDL_PLATFORM_HAIKU) |
156 | // The docs say the thread name can't be longer than B_OS_NAME_LENGTH. |
157 | char namebuf[B_OS_NAME_LENGTH]; |
158 | SDL_strlcpy(namebuf, name, sizeof(namebuf)); |
159 | rename_thread(find_thread(NULL), namebuf); |
160 | #endif |
161 | } |
162 | |
163 | #ifdef HAVE_SIGNAL_H |
164 | // Mask asynchronous signals for this thread |
165 | sigemptyset(&mask); |
166 | for (i = 0; sig_list[i]; ++i) { |
167 | sigaddset(&mask, sig_list[i]); |
168 | } |
169 | pthread_sigmask(SIG_BLOCK, &mask, 0); |
170 | #endif |
171 | |
172 | #ifdef PTHREAD_CANCEL_ASYNCHRONOUS |
173 | // Allow ourselves to be asynchronously cancelled |
174 | { |
175 | int oldstate; |
176 | pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldstate); |
177 | } |
178 | #endif |
179 | } |
180 | |
181 | SDL_ThreadID SDL_GetCurrentThreadID(void) |
182 | { |
183 | return (SDL_ThreadID)pthread_self(); |
184 | } |
185 | |
186 | bool SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) |
187 | { |
188 | #ifdef SDL_PLATFORM_RISCOS |
189 | // FIXME: Setting thread priority does not seem to be supported |
190 | return true; |
191 | #else |
192 | struct sched_param sched; |
193 | int policy; |
194 | int pri_policy; |
195 | pthread_t thread = pthread_self(); |
196 | const char *policyhint = SDL_GetHint(SDL_HINT_THREAD_PRIORITY_POLICY); |
197 | const bool timecritical_realtime_hint = SDL_GetHintBoolean(SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL, false); |
198 | |
199 | if (pthread_getschedparam(thread, &policy, &sched) != 0) { |
200 | return SDL_SetError("pthread_getschedparam() failed" ); |
201 | } |
202 | |
203 | /* Higher priority levels may require changing the pthread scheduler policy |
204 | * for the thread. SDL will make such changes by default but there is |
205 | * also a hint allowing that behavior to be overridden. */ |
206 | switch (priority) { |
207 | case SDL_THREAD_PRIORITY_LOW: |
208 | case SDL_THREAD_PRIORITY_NORMAL: |
209 | pri_policy = SCHED_OTHER; |
210 | break; |
211 | case SDL_THREAD_PRIORITY_HIGH: |
212 | case SDL_THREAD_PRIORITY_TIME_CRITICAL: |
213 | #if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) |
214 | // Apple requires SCHED_RR for high priority threads |
215 | pri_policy = SCHED_RR; |
216 | break; |
217 | #else |
218 | pri_policy = SCHED_OTHER; |
219 | break; |
220 | #endif |
221 | default: |
222 | pri_policy = policy; |
223 | break; |
224 | } |
225 | |
226 | if (timecritical_realtime_hint && priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { |
227 | pri_policy = SCHED_RR; |
228 | } |
229 | |
230 | if (policyhint) { |
231 | if (SDL_strcmp(policyhint, "current" ) == 0) { |
232 | // Leave current thread scheduler policy unchanged |
233 | } else if (SDL_strcmp(policyhint, "other" ) == 0) { |
234 | policy = SCHED_OTHER; |
235 | } else if (SDL_strcmp(policyhint, "rr" ) == 0) { |
236 | policy = SCHED_RR; |
237 | } else if (SDL_strcmp(policyhint, "fifo" ) == 0) { |
238 | policy = SCHED_FIFO; |
239 | } else { |
240 | policy = pri_policy; |
241 | } |
242 | } else { |
243 | policy = pri_policy; |
244 | } |
245 | |
246 | #ifdef SDL_PLATFORM_LINUX |
247 | { |
248 | pid_t linuxTid = syscall(SYS_gettid); |
249 | return SDL_SetLinuxThreadPriorityAndPolicy(linuxTid, priority, policy); |
250 | } |
251 | #else |
252 | if (priority == SDL_THREAD_PRIORITY_LOW) { |
253 | sched.sched_priority = sched_get_priority_min(policy); |
254 | } else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { |
255 | sched.sched_priority = sched_get_priority_max(policy); |
256 | } else { |
257 | int min_priority = sched_get_priority_min(policy); |
258 | int max_priority = sched_get_priority_max(policy); |
259 | |
260 | #if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) |
261 | if (min_priority == 15 && max_priority == 47) { |
262 | // Apple has a specific set of thread priorities |
263 | if (priority == SDL_THREAD_PRIORITY_HIGH) { |
264 | sched.sched_priority = 45; |
265 | } else { |
266 | sched.sched_priority = 37; |
267 | } |
268 | } else |
269 | #endif // SDL_PLATFORM_MACOS || SDL_PLATFORM_IOS || SDL_PLATFORM_TVOS |
270 | { |
271 | sched.sched_priority = (min_priority + (max_priority - min_priority) / 2); |
272 | if (priority == SDL_THREAD_PRIORITY_HIGH) { |
273 | sched.sched_priority += ((max_priority - min_priority) / 4); |
274 | } |
275 | } |
276 | } |
277 | if (pthread_setschedparam(thread, policy, &sched) != 0) { |
278 | return SDL_SetError("pthread_setschedparam() failed" ); |
279 | } |
280 | return true; |
281 | #endif // linux |
282 | #endif // #if SDL_PLATFORM_RISCOS |
283 | } |
284 | |
285 | void SDL_SYS_WaitThread(SDL_Thread *thread) |
286 | { |
287 | pthread_join(thread->handle, 0); |
288 | } |
289 | |
290 | void SDL_SYS_DetachThread(SDL_Thread *thread) |
291 | { |
292 | pthread_detach(thread->handle); |
293 | } |
294 | |