1 /*
2 * Copyright (c) 2011-2016 Wind River Systems, Inc.
3 * Copyright (c) 2024, Meta
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include "posix_internal.h"
9
10 #include <pthread.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14
15 #include <zephyr/sys/util.h>
16 #include <zephyr/logging/log.h>
17
18 #ifdef CONFIG_THREAD_NAME
19 #define MAX_NAME_LEN CONFIG_THREAD_MAX_NAME_LEN
20 #else
21 #define MAX_NAME_LEN 1
22 #endif
23
24 #define NUM_PHIL CONFIG_POSIX_THREAD_THREADS_MAX
25 #define obj_init_type "POSIX"
26 #define fork_type_str "mutexes"
27
28 BUILD_ASSERT(CONFIG_POSIX_THREAD_THREADS_MAX == CONFIG_MAX_PTHREAD_MUTEX_COUNT);
29 BUILD_ASSERT(CONFIG_DYNAMIC_THREAD_POOL_SIZE == CONFIG_POSIX_THREAD_THREADS_MAX);
30
31 typedef pthread_mutex_t *fork_t;
32
33 LOG_MODULE_REGISTER(posix_philosophers, LOG_LEVEL_INF);
34
35 static pthread_mutex_t forks[NUM_PHIL];
36 static pthread_t threads[NUM_PHIL];
37
fork_init(fork_t frk)38 static inline void fork_init(fork_t frk)
39 {
40 int ret;
41
42 ret = pthread_mutex_init(frk, NULL);
43 if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
44 errno = ret;
45 perror("pthread_mutex_init");
46 __ASSERT(false, "Failed to initialize fork");
47 }
48 }
49
fork(size_t idx)50 static inline fork_t fork(size_t idx)
51 {
52 return &forks[idx];
53 }
54
take(fork_t frk)55 static inline void take(fork_t frk)
56 {
57 int ret;
58
59 ret = pthread_mutex_lock(frk);
60 if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
61 errno = ret;
62 perror("pthread_mutex_lock");
63 __ASSERT(false, "Failed to lock mutex");
64 }
65 }
66
drop(fork_t frk)67 static inline void drop(fork_t frk)
68 {
69 int ret;
70
71 ret = pthread_mutex_unlock(frk);
72 if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
73 errno = ret;
74 perror("pthread_mutex_unlock");
75 __ASSERT(false, "Failed to unlock mutex");
76 }
77 }
78
set_phil_state_pos(int id)79 static void set_phil_state_pos(int id)
80 {
81 if (IS_ENABLED(CONFIG_SAMPLE_DEBUG_PRINTF)) {
82 printk("\x1b[%d;%dH", id + 1, 1);
83 }
84 }
85
print_phil_state(int id,const char * fmt,int32_t delay)86 static void print_phil_state(int id, const char *fmt, int32_t delay)
87 {
88 int ret;
89 int prio;
90 int policy;
91 struct sched_param param;
92
93 ret = pthread_getschedparam(pthread_self(), &policy, ¶m);
94 if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
95 errno = ret;
96 perror("pthread_getschedparam");
97 __ASSERT(false, "Failed to get scheduler params");
98 }
99
100 prio = posix_to_zephyr_priority(param.sched_priority, policy);
101
102 set_phil_state_pos(id);
103
104 printk("Philosopher %d [%s:%s%d] ", id, prio < 0 ? "C" : "P", prio < 0 ? "" : " ", prio);
105
106 if (delay) {
107 printk(fmt, delay < 1000 ? " " : "", delay);
108 } else {
109 printk(fmt, "");
110 }
111
112 printk("\n");
113 }
114
get_random_delay(int id,int period_in_ms)115 static int32_t get_random_delay(int id, int period_in_ms)
116 {
117 int32_t ms;
118 int32_t delay;
119 int32_t uptime;
120 struct timespec ts;
121
122 /*
123 * The random delay is unit-less, and is based on the philosopher's ID
124 * and the current uptime to create some pseudo-randomness. It produces
125 * a value between 0 and 31.
126 */
127 clock_gettime(CLOCK_MONOTONIC, &ts);
128 uptime = ts.tv_sec * MSEC_PER_SEC + (ts.tv_nsec / NSEC_PER_MSEC);
129 delay = (uptime / 100 * (id + 1)) & 0x1f;
130
131 /* add 1 to not generate a delay of 0 */
132 ms = (delay + 1) * period_in_ms;
133
134 return ms;
135 }
136
is_last_philosopher(int id)137 static inline int is_last_philosopher(int id)
138 {
139 return id == (NUM_PHIL - 1);
140 }
141
philosopher(void * arg)142 static void *philosopher(void *arg)
143 {
144 fork_t my_fork1;
145 fork_t my_fork2;
146
147 int my_id = POINTER_TO_INT(arg);
148
149 /* Djkstra's solution: always pick up the lowest numbered fork first */
150 if (is_last_philosopher(my_id)) {
151 my_fork1 = fork(0);
152 my_fork2 = fork(my_id);
153 } else {
154 my_fork1 = fork(my_id);
155 my_fork2 = fork(my_id + 1);
156 }
157
158 while (1) {
159 int32_t delay;
160
161 print_phil_state(my_id, " STARVING ", 0);
162 take(my_fork1);
163 print_phil_state(my_id, " HOLDING ONE FORK ", 0);
164 take(my_fork2);
165
166 delay = get_random_delay(my_id, 25);
167 print_phil_state(my_id, " EATING [ %s%d ms ] ", delay);
168 usleep(delay * USEC_PER_MSEC);
169
170 drop(my_fork2);
171 print_phil_state(my_id, " DROPPED ONE FORK ", 0);
172 drop(my_fork1);
173
174 delay = get_random_delay(my_id, 25);
175 print_phil_state(my_id, " THINKING [ %s%d ms ] ", delay);
176 usleep(delay * USEC_PER_MSEC);
177 }
178
179 return NULL;
180 }
181
new_prio(int phil)182 static int new_prio(int phil)
183 {
184 if (CONFIG_NUM_COOP_PRIORITIES > 0 && CONFIG_NUM_PREEMPT_PRIORITIES > 0) {
185 if (IS_ENABLED(CONFIG_SAMPLE_SAME_PRIO)) {
186 return 0;
187 }
188
189 return -(phil - (NUM_PHIL / 2));
190 }
191
192 if (CONFIG_NUM_COOP_PRIORITIES > 0) {
193 return -phil - 2;
194 }
195
196 if (CONFIG_NUM_PREEMPT_PRIORITIES > 0) {
197 return phil;
198 }
199
200 __ASSERT_NO_MSG("Unsupported scheduler configuration");
201 }
202
init_objects(void)203 static void init_objects(void)
204 {
205 ARRAY_FOR_EACH(forks, i) {
206 LOG_DBG("Initializing fork %zu", i);
207 fork_init(fork(i));
208 }
209 }
210
start_threads(void)211 static void start_threads(void)
212 {
213 int ret;
214 int prio;
215 int policy;
216 struct sched_param param;
217
218 ARRAY_FOR_EACH(forks, i) {
219 LOG_DBG("Initializing philosopher %zu", i);
220 ret = pthread_create(&threads[i], NULL, philosopher, INT_TO_POINTER(i));
221 if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
222 errno = ret;
223 perror("pthread_create");
224 __ASSERT(false, "Failed to create thread");
225 }
226
227 prio = new_prio(i);
228 param.sched_priority = zephyr_to_posix_priority(prio, &policy);
229 ret = pthread_setschedparam(threads[i], policy, ¶m);
230 if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
231 errno = ret;
232 perror("pthread_setschedparam");
233 __ASSERT(false, "Failed to set scheduler params");
234 }
235
236 if (IS_ENABLED(CONFIG_THREAD_NAME)) {
237 char tname[MAX_NAME_LEN];
238
239 snprintf(tname, sizeof(tname), "Philosopher %zu", i);
240 pthread_setname_np(threads[i], tname);
241 }
242 }
243 }
244
245 #define DEMO_DESCRIPTION \
246 "\x1b[2J\x1b[15;1H" \
247 "Demo Description\n" \
248 "----------------\n" \
249 "An implementation of a solution to the Dining Philosophers\n" \
250 "problem (a classic multi-thread synchronization problem).\n" \
251 "This particular implementation demonstrates the usage of multiple\n" \
252 "preemptible and cooperative threads of differing priorities, as\n" \
253 "well as %s %s and thread sleeping.\n", \
254 obj_init_type, fork_type_str
255
display_demo_description(void)256 static void display_demo_description(void)
257 {
258 if (IS_ENABLED(CONFIG_SAMPLE_DEBUG_PRINTF)) {
259 printk(DEMO_DESCRIPTION);
260 }
261 }
262
main(void)263 int main(void)
264 {
265 display_demo_description();
266
267 init_objects();
268 start_threads();
269
270 if (IS_ENABLED(CONFIG_COVERAGE)) {
271 /* Wait a few seconds before main() exit, giving the sample the
272 * opportunity to dump some output before coverage data gets emitted
273 */
274 sleep(5);
275 }
276
277 return 0;
278 }
279