/* * Copyright (c) 2011-2016 Wind River Systems, Inc. * Copyright (c) 2024, Meta * * SPDX-License-Identifier: Apache-2.0 */ #include "posix_internal.h" #include #include #include #include #include #include #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; }