| /* |
| * Copyright (c) 2011-2016 Wind River Systems, Inc. |
| * Copyright (c) 2024, Meta |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "posix_internal.h" |
| |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <zephyr/sys/util.h> |
| #include <zephyr/logging/log.h> |
| |
| #ifdef CONFIG_THREAD_NAME |
| #define MAX_NAME_LEN CONFIG_THREAD_MAX_NAME_LEN |
| #else |
| #define MAX_NAME_LEN 1 |
| #endif |
| |
| #define NUM_PHIL CONFIG_POSIX_THREAD_THREADS_MAX |
| #define obj_init_type "POSIX" |
| #define fork_type_str "mutexes" |
| |
| BUILD_ASSERT(CONFIG_POSIX_THREAD_THREADS_MAX == CONFIG_MAX_PTHREAD_MUTEX_COUNT); |
| BUILD_ASSERT(CONFIG_DYNAMIC_THREAD_POOL_SIZE == CONFIG_POSIX_THREAD_THREADS_MAX); |
| |
| typedef pthread_mutex_t *fork_t; |
| |
| LOG_MODULE_REGISTER(posix_philosophers, LOG_LEVEL_INF); |
| |
| static pthread_mutex_t forks[NUM_PHIL]; |
| static pthread_t threads[NUM_PHIL]; |
| |
| static inline void fork_init(fork_t frk) |
| { |
| int ret; |
| |
| ret = pthread_mutex_init(frk, NULL); |
| if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) { |
| errno = ret; |
| perror("pthread_mutex_init"); |
| __ASSERT(false, "Failed to initialize fork"); |
| } |
| } |
| |
| static inline fork_t fork(size_t idx) |
| { |
| return &forks[idx]; |
| } |
| |
| static inline void take(fork_t frk) |
| { |
| int ret; |
| |
| ret = pthread_mutex_lock(frk); |
| if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) { |
| errno = ret; |
| perror("pthread_mutex_lock"); |
| __ASSERT(false, "Failed to lock mutex"); |
| } |
| } |
| |
| static inline void drop(fork_t frk) |
| { |
| int ret; |
| |
| ret = pthread_mutex_unlock(frk); |
| if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) { |
| errno = ret; |
| perror("pthread_mutex_unlock"); |
| __ASSERT(false, "Failed to unlock mutex"); |
| } |
| } |
| |
| static void set_phil_state_pos(int id) |
| { |
| if (IS_ENABLED(CONFIG_SAMPLE_DEBUG_PRINTF)) { |
| printk("\x1b[%d;%dH", id + 1, 1); |
| } |
| } |
| |
| static void print_phil_state(int id, const char *fmt, int32_t delay) |
| { |
| int ret; |
| int prio; |
| int policy; |
| struct sched_param param; |
| |
| ret = pthread_getschedparam(pthread_self(), &policy, ¶m); |
| if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) { |
| errno = ret; |
| perror("pthread_getschedparam"); |
| __ASSERT(false, "Failed to get scheduler params"); |
| } |
| |
| prio = posix_to_zephyr_priority(param.sched_priority, policy); |
| |
| set_phil_state_pos(id); |
| |
| printk("Philosopher %d [%s:%s%d] ", id, prio < 0 ? "C" : "P", prio < 0 ? "" : " ", prio); |
| |
| if (delay) { |
| printk(fmt, delay < 1000 ? " " : "", delay); |
| } else { |
| printk(fmt, ""); |
| } |
| |
| printk("\n"); |
| } |
| |
| static int32_t get_random_delay(int id, int period_in_ms) |
| { |
| int32_t ms; |
| int32_t delay; |
| int32_t uptime; |
| struct timespec ts; |
| |
| /* |
| * The random delay is unit-less, and is based on the philosopher's ID |
| * and the current uptime to create some pseudo-randomness. It produces |
| * a value between 0 and 31. |
| */ |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| uptime = ts.tv_sec * MSEC_PER_SEC + (ts.tv_nsec / NSEC_PER_MSEC); |
| delay = (uptime / 100 * (id + 1)) & 0x1f; |
| |
| /* add 1 to not generate a delay of 0 */ |
| ms = (delay + 1) * period_in_ms; |
| |
| return ms; |
| } |
| |
| static inline int is_last_philosopher(int id) |
| { |
| return id == (NUM_PHIL - 1); |
| } |
| |
| static void *philosopher(void *arg) |
| { |
| fork_t my_fork1; |
| fork_t my_fork2; |
| |
| int my_id = POINTER_TO_INT(arg); |
| |
| /* Djkstra's solution: always pick up the lowest numbered fork first */ |
| if (is_last_philosopher(my_id)) { |
| my_fork1 = fork(0); |
| my_fork2 = fork(my_id); |
| } else { |
| my_fork1 = fork(my_id); |
| my_fork2 = fork(my_id + 1); |
| } |
| |
| while (1) { |
| int32_t delay; |
| |
| print_phil_state(my_id, " STARVING ", 0); |
| take(my_fork1); |
| print_phil_state(my_id, " HOLDING ONE FORK ", 0); |
| take(my_fork2); |
| |
| delay = get_random_delay(my_id, 25); |
| print_phil_state(my_id, " EATING [ %s%d ms ] ", delay); |
| usleep(delay * USEC_PER_MSEC); |
| |
| drop(my_fork2); |
| print_phil_state(my_id, " DROPPED ONE FORK ", 0); |
| drop(my_fork1); |
| |
| delay = get_random_delay(my_id, 25); |
| print_phil_state(my_id, " THINKING [ %s%d ms ] ", delay); |
| usleep(delay * USEC_PER_MSEC); |
| } |
| |
| return NULL; |
| } |
| |
| static int new_prio(int phil) |
| { |
| if (CONFIG_NUM_COOP_PRIORITIES > 0 && CONFIG_NUM_PREEMPT_PRIORITIES > 0) { |
| if (IS_ENABLED(CONFIG_SAMPLE_SAME_PRIO)) { |
| return 0; |
| } |
| |
| return -(phil - (NUM_PHIL / 2)); |
| } |
| |
| if (CONFIG_NUM_COOP_PRIORITIES > 0) { |
| return -phil - 2; |
| } |
| |
| if (CONFIG_NUM_PREEMPT_PRIORITIES > 0) { |
| return phil; |
| } |
| |
| __ASSERT_NO_MSG("Unsupported scheduler configuration"); |
| } |
| |
| static void init_objects(void) |
| { |
| ARRAY_FOR_EACH(forks, i) { |
| LOG_DBG("Initializing fork %zu", i); |
| fork_init(fork(i)); |
| } |
| } |
| |
| static void start_threads(void) |
| { |
| int ret; |
| int prio; |
| int policy; |
| struct sched_param param; |
| |
| ARRAY_FOR_EACH(forks, i) { |
| LOG_DBG("Initializing philosopher %zu", i); |
| ret = pthread_create(&threads[i], NULL, philosopher, INT_TO_POINTER(i)); |
| if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) { |
| errno = ret; |
| perror("pthread_create"); |
| __ASSERT(false, "Failed to create thread"); |
| } |
| |
| prio = new_prio(i); |
| param.sched_priority = zephyr_to_posix_priority(prio, &policy); |
| ret = pthread_setschedparam(threads[i], policy, ¶m); |
| if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) { |
| errno = ret; |
| perror("pthread_setschedparam"); |
| __ASSERT(false, "Failed to set scheduler params"); |
| } |
| |
| if (IS_ENABLED(CONFIG_THREAD_NAME)) { |
| char tname[MAX_NAME_LEN]; |
| |
| snprintf(tname, sizeof(tname), "Philosopher %zu", i); |
| pthread_setname_np(threads[i], tname); |
| } |
| } |
| } |
| |
| #define DEMO_DESCRIPTION \ |
| "\x1b[2J\x1b[15;1H" \ |
| "Demo Description\n" \ |
| "----------------\n" \ |
| "An implementation of a solution to the Dining Philosophers\n" \ |
| "problem (a classic multi-thread synchronization problem).\n" \ |
| "This particular implementation demonstrates the usage of multiple\n" \ |
| "preemptible and cooperative threads of differing priorities, as\n" \ |
| "well as %s %s and thread sleeping.\n", \ |
| obj_init_type, fork_type_str |
| |
| static void display_demo_description(void) |
| { |
| if (IS_ENABLED(CONFIG_SAMPLE_DEBUG_PRINTF)) { |
| printk(DEMO_DESCRIPTION); |
| } |
| } |
| |
| int main(void) |
| { |
| display_demo_description(); |
| |
| init_objects(); |
| start_threads(); |
| |
| if (IS_ENABLED(CONFIG_COVERAGE)) { |
| /* Wait a few seconds before main() exit, giving the sample the |
| * opportunity to dump some output before coverage data gets emitted |
| */ |
| sleep(5); |
| } |
| |
| return 0; |
| } |