diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_package_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_package_terminal.png" new file mode 100644 index 0000000000000000000000000000000000000000..0af7f13442bdf04317ae39b7901eda02cabaa715 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_package_terminal.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_parking_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_parking_terminal.png" new file mode 100644 index 0000000000000000000000000000000000000000..ecafff48fcff9d9050f69171ab4291928bdd5def Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_parking_terminal.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ps_cleanup.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ps_cleanup.png" new file mode 100644 index 0000000000000000000000000000000000000000..d750eb5b88591db1f42e7367329effd2c99f6c91 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ps_cleanup.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_startup_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_startup_terminal.png" new file mode 100644 index 0000000000000000000000000000000000000000..63c1840e841bf140546c0775c67a5964c260465c Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_startup_terminal.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ticket_terminal.png" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ticket_terminal.png" new file mode 100644 index 0000000000000000000000000000000000000000..5c31ea3a23bea8ca81a7e0052f2662dee19de655 Binary files /dev/null and "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/assets/day3_ticket_terminal.png" differ diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/package_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/package_demo.c" new file mode 100644 index 0000000000000000000000000000000000000000..2207040e9db0d28c67ca6c9c2593606ce2cb46ed --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/package_demo.c" @@ -0,0 +1,270 @@ +/* Day3 exercise: two producers and one message-queue sorting consumer. */ +#include + +#define PACKAGE_PRODUCER_COUNT 2 +#define PACKAGE_COUNT_PER_THREAD 5 +#define PACKAGE_QUEUE_CAPACITY 5 +#define PACKAGE_STACK_SIZE 1024 + +struct package_msg +{ + rt_uint32_t id; + rt_uint8_t region; + rt_uint16_t weight; + rt_uint8_t end; +}; + +#define PACKAGE_MSG_SIZE RT_ALIGN(sizeof(struct package_msg), RT_ALIGN_SIZE) + +struct package_producer +{ + rt_uint32_t base_id; + rt_uint8_t region; +}; + +static struct rt_messagequeue package_queue; +static struct rt_mutex package_stat_lock; +static ALIGN(RT_ALIGN_SIZE) rt_uint8_t package_pool[PACKAGE_MSG_SIZE * PACKAGE_QUEUE_CAPACITY]; +static struct rt_thread package_threads[PACKAGE_PRODUCER_COUNT + 1]; +static ALIGN(RT_ALIGN_SIZE) rt_uint8_t package_stacks[PACKAGE_PRODUCER_COUNT + 1][PACKAGE_STACK_SIZE]; +static struct package_producer package_producers[PACKAGE_PRODUCER_COUNT] = { + {1001, 1}, + {2001, 2}, +}; +static rt_uint32_t package_end_count; +static rt_uint32_t package_total; +static rt_uint32_t package_region_count[4]; +static rt_uint32_t package_total_weight; +static rt_uint32_t package_send_failed; +static rt_uint32_t package_finished; +static rt_uint32_t package_expected; +static rt_bool_t package_abort; +static rt_bool_t package_running; + +static const char *package_region_name(rt_uint8_t region) +{ + switch (region) + { + case 1: return "east"; + case 2: return "south"; + case 3: return "north"; + default: return "unknown"; + } +} + +static void package_cleanup(void) +{ + rt_mq_detach(&package_queue); + rt_mutex_detach(&package_stat_lock); + package_running = RT_FALSE; +} + +static void package_detach_threads(rt_uint32_t first, rt_uint32_t count) +{ + rt_uint32_t index; + + for (index = first; index < count; index++) + rt_thread_detach(&package_threads[index]); +} + +static void package_finish(void) +{ + rt_bool_t last = RT_FALSE; + + rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); + package_finished++; + if (package_finished == package_expected) + last = RT_TRUE; + rt_mutex_release(&package_stat_lock); + + if (last) + { + package_cleanup(); + rt_kprintf("package_demo finished\n"); + } +} + +static rt_err_t package_send_retry(const struct package_msg *message) +{ + rt_err_t result; + + while (!package_abort) + { + result = rt_mq_send(&package_queue, message, sizeof(*message)); + if (result == RT_EOK) + return RT_EOK; + + rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); + package_send_failed++; + rt_mutex_release(&package_stat_lock); + rt_kprintf("package send failed, retrying\n"); + rt_thread_mdelay(80); + } + + return -RT_ERROR; +} + +static void package_producer_entry(void *parameter) +{ + struct package_producer *producer = (struct package_producer *)parameter; + struct package_msg message; + rt_uint32_t index; + + for (index = 0; index < PACKAGE_COUNT_PER_THREAD; index++) + { + message.id = producer->base_id + index; + message.region = producer->region; + message.weight = (rt_uint16_t)(700 + producer->region * 100 + index * 30); + message.end = 0; + if (package_send_retry(&message) != RT_EOK) + break; + rt_kprintf("producer %u sent package %u\n", + producer->base_id, message.id); + rt_thread_mdelay(5); + } + + message.id = 0; + message.region = 0; + message.weight = 0; + message.end = 1; + package_send_retry(&message); + package_finish(); +} + +static void package_consumer_entry(void *parameter) +{ + struct package_msg message; + rt_err_t result; + + (void)parameter; + while (!package_abort && package_end_count < PACKAGE_PRODUCER_COUNT) + { + result = rt_mq_recv(&package_queue, &message, sizeof(message), + RT_TICK_PER_SECOND); + if (result != RT_EOK) + { + if (result != -RT_ETIMEOUT) + rt_kprintf("package receive failed: %d\n", result); + continue; + } + + if (message.end) + { + package_end_count++; + rt_kprintf("producer end received: %u/%u\n", + package_end_count, PACKAGE_PRODUCER_COUNT); + continue; + } + + package_total++; + package_region_count[message.region]++; + package_total_weight += message.weight; + rt_kprintf("package %u -> %s, weight: %u g\n", + message.id, package_region_name(message.region), + message.weight); + rt_thread_mdelay(50); + } + + rt_kprintf("total packages: %u\n", package_total); + rt_kprintf("east: %u, south: %u, north: %u\n", + package_region_count[1], package_region_count[2], + package_region_count[3]); + rt_kprintf("total weight: %u g\n", package_total_weight); + rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); + rt_kprintf("send failed: %u\n", package_send_failed); + rt_mutex_release(&package_stat_lock); + package_finish(); +} + +int package_demo(void) +{ + rt_err_t result; + rt_uint32_t index; + + if (package_running) + { + rt_kprintf("package_demo is already running\n"); + return -RT_EBUSY; + } + + result = rt_mq_init(&package_queue, "pkgmq", package_pool, + PACKAGE_MSG_SIZE, sizeof(package_pool), + RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("package queue init failed: %d\n", result); + return result; + } + + result = rt_mutex_init(&package_stat_lock, "pkglk", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_mq_detach(&package_queue); + rt_kprintf("package mutex init failed: %d\n", result); + return result; + } + + package_end_count = 0; + package_total = 0; + package_region_count[1] = 0; + package_region_count[2] = 0; + package_region_count[3] = 0; + package_total_weight = 0; + package_send_failed = 0; + package_finished = 0; + package_expected = PACKAGE_PRODUCER_COUNT + 1; + package_abort = RT_FALSE; + package_running = RT_TRUE; + + for (index = 0; index < PACKAGE_PRODUCER_COUNT; index++) + { + result = rt_thread_init(&package_threads[index], "producer", + package_producer_entry, &package_producers[index], + package_stacks[index], PACKAGE_STACK_SIZE, + 18, 5); + if (result != RT_EOK) + { + package_detach_threads(0, index); + package_cleanup(); + rt_kprintf("producer %u init failed: %d\n", index + 1, result); + return result; + } + } + + result = rt_thread_init(&package_threads[PACKAGE_PRODUCER_COUNT], + "sorter", package_consumer_entry, RT_NULL, + package_stacks[PACKAGE_PRODUCER_COUNT], + PACKAGE_STACK_SIZE, 16, 5); + if (result != RT_EOK) + { + package_detach_threads(0, PACKAGE_PRODUCER_COUNT); + package_cleanup(); + rt_kprintf("sorter init failed: %d\n", result); + return result; + } + + for (index = 0; index < PACKAGE_PRODUCER_COUNT + 1; index++) + { + result = rt_thread_startup(&package_threads[index]); + if (result != RT_EOK) + { + rt_bool_t cleanup_now; + + rt_mutex_take(&package_stat_lock, RT_WAITING_FOREVER); + package_abort = RT_TRUE; + package_expected = index; + cleanup_now = (package_finished == package_expected); + rt_mutex_release(&package_stat_lock); + package_detach_threads(index, PACKAGE_PRODUCER_COUNT + 1); + if (cleanup_now) + package_cleanup(); + rt_kprintf("package thread %u startup failed: %d\n", index, result); + return result; + } + } + + rt_kprintf("package_demo started: queue=%u, producers=%u\n", + PACKAGE_QUEUE_CAPACITY, PACKAGE_PRODUCER_COUNT); + return RT_EOK; +} +MSH_CMD_EXPORT(package_demo, message queue package demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/parking_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/parking_demo.c" new file mode 100644 index 0000000000000000000000000000000000000000..cda1f876e3f01e56bf3934dc3825076bdbb44baf --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/parking_demo.c" @@ -0,0 +1,175 @@ +/* Day3 exercise: counting semaphore as a parking-space resource limit. */ +#include + +#define PARKING_CAR_COUNT 5 +#define PARKING_SPACE_COUNT 3 +#define PARKING_TIMEOUT_TICKS (2 * RT_TICK_PER_SECOND) +#define PARKING_STACK_SIZE 1024 + +struct parking_car +{ + rt_uint32_t id; + rt_uint32_t stay_ms; +}; + +static struct rt_semaphore parking_spaces; +static struct rt_mutex parking_stat_lock; +static struct rt_thread parking_threads[PARKING_CAR_COUNT]; +static ALIGN(RT_ALIGN_SIZE) rt_uint8_t parking_stacks[PARKING_CAR_COUNT][PARKING_STACK_SIZE]; +static struct parking_car parking_cars[PARKING_CAR_COUNT]; +static rt_uint32_t parking_used; +static rt_uint32_t parking_finished; +static rt_uint32_t parking_expected; +static rt_bool_t parking_abort; +static rt_bool_t parking_running; + +static void parking_cleanup(void) +{ + rt_sem_detach(&parking_spaces); + rt_mutex_detach(&parking_stat_lock); + parking_running = RT_FALSE; +} + +static void parking_detach_threads(rt_uint32_t first, rt_uint32_t count) +{ + rt_uint32_t index; + + for (index = first; index < count; index++) + rt_thread_detach(&parking_threads[index]); +} + +static void parking_finish(void) +{ + rt_bool_t last = RT_FALSE; + + rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); + parking_finished++; + if (parking_finished == parking_expected) + last = RT_TRUE; + rt_mutex_release(&parking_stat_lock); + + if (last) + { + parking_cleanup(); + rt_kprintf("parking_demo finished\n"); + } +} + +static void parking_car_entry(void *parameter) +{ + struct parking_car *car = (struct parking_car *)parameter; + rt_err_t result; + + while (!parking_abort) + { + result = rt_sem_take(&parking_spaces, PARKING_TIMEOUT_TICKS); + if (result == RT_EOK) + { + if (parking_abort) + { + rt_sem_release(&parking_spaces); + break; + } + + rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); + parking_used++; + rt_kprintf("car%u entered, used spaces: %u\n", + car->id, parking_used); + rt_mutex_release(&parking_stat_lock); + + rt_thread_mdelay(car->stay_ms); + + rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); + parking_used--; + rt_kprintf("car%u left, used spaces: %u\n", + car->id, parking_used); + rt_mutex_release(&parking_stat_lock); + rt_sem_release(&parking_spaces); + break; + } + + if (parking_abort) + break; + + rt_kprintf("car%u waiting for a space\n", car->id); + rt_thread_mdelay(300); + } + + parking_finish(); +} + +int parking_demo(void) +{ + rt_err_t result; + rt_uint32_t index; + + if (parking_running) + { + rt_kprintf("parking_demo is already running\n"); + return -RT_EBUSY; + } + + result = rt_sem_init(&parking_spaces, "parksem", + PARKING_SPACE_COUNT, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("parking semaphore init failed: %d\n", result); + return result; + } + + result = rt_mutex_init(&parking_stat_lock, "parklock", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_sem_detach(&parking_spaces); + rt_kprintf("parking mutex init failed: %d\n", result); + return result; + } + + parking_used = 0; + parking_finished = 0; + parking_expected = PARKING_CAR_COUNT; + parking_abort = RT_FALSE; + parking_running = RT_TRUE; + + for (index = 0; index < PARKING_CAR_COUNT; index++) + { + parking_cars[index].id = index + 1; + parking_cars[index].stay_ms = 700 + index * 180; + result = rt_thread_init(&parking_threads[index], "car", + parking_car_entry, &parking_cars[index], + parking_stacks[index], PARKING_STACK_SIZE, + 18, 5); + if (result != RT_EOK) + { + parking_detach_threads(0, index); + parking_cleanup(); + rt_kprintf("car%u thread init failed: %d\n", index + 1, result); + return result; + } + } + + for (index = 0; index < PARKING_CAR_COUNT; index++) + { + result = rt_thread_startup(&parking_threads[index]); + if (result != RT_EOK) + { + rt_bool_t cleanup_now; + + rt_mutex_take(&parking_stat_lock, RT_WAITING_FOREVER); + parking_abort = RT_TRUE; + parking_expected = index; + cleanup_now = (parking_finished == parking_expected); + rt_mutex_release(&parking_stat_lock); + parking_detach_threads(index, PARKING_CAR_COUNT); + if (cleanup_now) + parking_cleanup(); + rt_kprintf("car%u thread startup failed: %d\n", index + 1, result); + return result; + } + } + + rt_kprintf("parking_demo started: spaces=%u, cars=%u\n", + PARKING_SPACE_COUNT, PARKING_CAR_COUNT); + return RT_EOK; +} +MSH_CMD_EXPORT(parking_demo, counting semaphore parking demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/startup_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/startup_demo.c" new file mode 100644 index 0000000000000000000000000000000000000000..e9dddd03c0a3a4e6fa3e34119148cc265ccf4509 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/startup_demo.c" @@ -0,0 +1,224 @@ +/* Day3 exercise: event-set startup barrier with a bounded first wait. */ +#include + +#define EVENT_NET_READY (1u << 0) +#define EVENT_SENSOR_READY (1u << 1) +#define EVENT_STORAGE_READY (1u << 2) +#define EVENT_ALL_READY (EVENT_NET_READY | EVENT_SENSOR_READY | EVENT_STORAGE_READY) +#define STARTUP_THREAD_COUNT 4 +#define STARTUP_STACK_SIZE 1024 + +struct startup_module +{ + const char *name; + rt_uint32_t event_bit; + rt_uint32_t delay_ms; +}; + +static struct rt_event startup_event; +static struct rt_mutex startup_lock; +static struct rt_thread startup_threads[STARTUP_THREAD_COUNT]; +static ALIGN(RT_ALIGN_SIZE) rt_uint8_t startup_stacks[STARTUP_THREAD_COUNT][STARTUP_STACK_SIZE]; +static struct startup_module startup_modules[3] = { + {"network", EVENT_NET_READY, 700}, + {"sensor", EVENT_SENSOR_READY, 1400}, + {"storage", EVENT_STORAGE_READY, 5000}, +}; +static rt_uint32_t startup_ready_mask; +static rt_uint32_t startup_finished; +static rt_uint32_t startup_expected; +static rt_bool_t startup_abort; +static rt_bool_t startup_running; + +static const char *startup_missing_name(rt_uint32_t bit) +{ + if ((bit & EVENT_NET_READY) != 0) + return "network"; + if ((bit & EVENT_SENSOR_READY) != 0) + return "sensor"; + if ((bit & EVENT_STORAGE_READY) != 0) + return "storage"; + return "none"; +} + +static void startup_cleanup(void) +{ + rt_event_detach(&startup_event); + rt_mutex_detach(&startup_lock); + startup_running = RT_FALSE; +} + +static void startup_detach_threads(rt_uint32_t first, rt_uint32_t count) +{ + rt_uint32_t index; + + for (index = first; index < count; index++) + rt_thread_detach(&startup_threads[index]); +} + +static void startup_finish(void) +{ + rt_bool_t last = RT_FALSE; + + rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); + startup_finished++; + if (startup_finished == startup_expected) + last = RT_TRUE; + rt_mutex_release(&startup_lock); + + if (last) + { + startup_cleanup(); + rt_kprintf("startup_demo finished\n"); + } +} + +static void startup_module_entry(void *parameter) +{ + struct startup_module *module = (struct startup_module *)parameter; + + rt_thread_mdelay(module->delay_ms); + if (startup_abort) + { + startup_finish(); + return; + } + + rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); + startup_ready_mask |= module->event_bit; + rt_mutex_release(&startup_lock); + rt_kprintf("%s ready\n", module->name); + rt_event_send(&startup_event, module->event_bit); + startup_finish(); +} + +static void startup_business_entry(void *parameter) +{ + rt_uint32_t recved = 0; + rt_uint32_t ready_mask; + rt_err_t result; + + (void)parameter; + if (startup_abort) + goto finish; + + result = rt_event_recv(&startup_event, EVENT_ALL_READY, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + 3 * RT_TICK_PER_SECOND, &recved); + if (result == -RT_ETIMEOUT) + { + rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); + ready_mask = startup_ready_mask; + rt_mutex_release(&startup_lock); + rt_kprintf("wait modules timeout, %s is not ready\n", + startup_missing_name(EVENT_ALL_READY & ~ready_mask)); + } + else if (result != RT_EOK) + { + rt_kprintf("startup event wait failed: %d\n", result); + } + + if (startup_abort) + goto finish; + + result = rt_event_recv(&startup_event, EVENT_ALL_READY, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &recved); + if (result == RT_EOK) + { + rt_kprintf("all modules are ready\n"); + rt_kprintf("business task started\n"); + } + else + { + rt_kprintf("startup final wait failed: %d\n", result); + } + +finish: + startup_finish(); +} + +int startup_demo(void) +{ + rt_err_t result; + rt_uint32_t index; + + if (startup_running) + { + rt_kprintf("startup_demo is already running\n"); + return -RT_EBUSY; + } + + result = rt_event_init(&startup_event, "stup_evt", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("startup event init failed: %d\n", result); + return result; + } + result = rt_mutex_init(&startup_lock, "stup_lk", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_event_detach(&startup_event); + rt_kprintf("startup mutex init failed: %d\n", result); + return result; + } + + startup_ready_mask = 0; + startup_finished = 0; + startup_expected = STARTUP_THREAD_COUNT; + startup_abort = RT_FALSE; + startup_running = RT_TRUE; + + for (index = 0; index < 3; index++) + { + result = rt_thread_init(&startup_threads[index], "initmod", + startup_module_entry, &startup_modules[index], + startup_stacks[index], STARTUP_STACK_SIZE, + 17, 5); + if (result != RT_EOK) + { + startup_detach_threads(0, index); + startup_cleanup(); + rt_kprintf("startup module thread init failed: %d\n", result); + return result; + } + } + + result = rt_thread_init(&startup_threads[3], "business", + startup_business_entry, RT_NULL, + startup_stacks[3], STARTUP_STACK_SIZE, + 16, 5); + if (result != RT_EOK) + { + startup_detach_threads(0, 3); + startup_cleanup(); + rt_kprintf("business thread init failed: %d\n", result); + return result; + } + + for (index = 0; index < STARTUP_THREAD_COUNT; index++) + { + result = rt_thread_startup(&startup_threads[index]); + if (result != RT_EOK) + { + rt_bool_t cleanup_now; + + rt_mutex_take(&startup_lock, RT_WAITING_FOREVER); + startup_abort = RT_TRUE; + startup_expected = index; + cleanup_now = (startup_finished == startup_expected); + rt_mutex_release(&startup_lock); + startup_detach_threads(index, STARTUP_THREAD_COUNT); + if (cleanup_now) + startup_cleanup(); + else + rt_event_send(&startup_event, EVENT_ALL_READY); + rt_kprintf("startup thread %u startup failed: %d\n", index, result); + return result; + } + } + + rt_kprintf("startup_demo started\n"); + return RT_EOK; +} +MSH_CMD_EXPORT(startup_demo, event startup demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/ticket_demo.c" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/ticket_demo.c" new file mode 100644 index 0000000000000000000000000000000000000000..edc5708c048c078a1f999762c33677e706bdc2f7 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/ticket_demo.c" @@ -0,0 +1,153 @@ +/* Day3 exercise: mutex-protected ticket transactions. */ +#include + +#define TICKET_WINDOW_COUNT 2 +#define TICKET_TOTAL 20 +#define TICKET_STACK_SIZE 1024 + +struct ticket_window +{ + char name; +}; + +static struct rt_mutex ticket_lock; +static struct rt_thread ticket_threads[TICKET_WINDOW_COUNT]; +static ALIGN(RT_ALIGN_SIZE) rt_uint8_t ticket_stacks[TICKET_WINDOW_COUNT][TICKET_STACK_SIZE]; +static struct ticket_window ticket_windows[TICKET_WINDOW_COUNT] = {{'A'}, {'B'}}; +static rt_uint8_t ticket_sold[TICKET_TOTAL + 1]; +static rt_int32_t ticket_count; +static rt_uint32_t ticket_finished; +static rt_uint32_t ticket_expected; +static rt_bool_t ticket_abort; +static rt_bool_t ticket_running; + +static void ticket_cleanup(void) +{ + rt_mutex_detach(&ticket_lock); + ticket_running = RT_FALSE; +} + +static void ticket_detach_threads(rt_uint32_t first, rt_uint32_t count) +{ + rt_uint32_t index; + + for (index = first; index < count; index++) + rt_thread_detach(&ticket_threads[index]); +} + +static void ticket_finish(void) +{ + rt_bool_t last = RT_FALSE; + + rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); + ticket_finished++; + if (ticket_finished == ticket_expected) + last = RT_TRUE; + rt_mutex_release(&ticket_lock); + + if (last) + { + ticket_cleanup(); + rt_kprintf("ticket_demo finished: remaining=%d\n", ticket_count); + } +} + +static void ticket_window_entry(void *parameter) +{ + struct ticket_window *window = (struct ticket_window *)parameter; + rt_int32_t ticket_no; + + while (!ticket_abort) + { + rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); + if (ticket_abort) + { + rt_mutex_release(&ticket_lock); + break; + } + + if (ticket_count <= 0) + { + rt_mutex_release(&ticket_lock); + break; + } + + /* Check, allocate, record, and decrement form one transaction. */ + ticket_no = ticket_count; + ticket_sold[ticket_no]++; + ticket_count--; + rt_kprintf("window %c sold ticket %d\n", window->name, ticket_no); + rt_mutex_release(&ticket_lock); + rt_thread_mdelay(30); + } + + ticket_finish(); +} + +int ticket_demo(void) +{ + rt_err_t result; + rt_uint32_t index; + + if (ticket_running) + { + rt_kprintf("ticket_demo is already running\n"); + return -RT_EBUSY; + } + + result = rt_mutex_init(&ticket_lock, "ticketlk", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("ticket mutex init failed: %d\n", result); + return result; + } + + rt_memset(ticket_sold, 0, sizeof(ticket_sold)); + ticket_count = TICKET_TOTAL; + ticket_finished = 0; + ticket_expected = TICKET_WINDOW_COUNT; + ticket_abort = RT_FALSE; + ticket_running = RT_TRUE; + + for (index = 0; index < TICKET_WINDOW_COUNT; index++) + { + result = rt_thread_init(&ticket_threads[index], "window", + ticket_window_entry, &ticket_windows[index], + ticket_stacks[index], TICKET_STACK_SIZE, + 19, 5); + if (result != RT_EOK) + { + ticket_detach_threads(0, index); + ticket_cleanup(); + rt_kprintf("window %c thread init failed: %d\n", + ticket_windows[index].name, result); + return result; + } + } + + for (index = 0; index < TICKET_WINDOW_COUNT; index++) + { + result = rt_thread_startup(&ticket_threads[index]); + if (result != RT_EOK) + { + rt_bool_t cleanup_now; + + rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); + ticket_abort = RT_TRUE; + ticket_expected = index; + cleanup_now = (ticket_finished == ticket_expected); + rt_mutex_release(&ticket_lock); + ticket_detach_threads(index, TICKET_WINDOW_COUNT); + if (cleanup_now) + ticket_cleanup(); + rt_kprintf("window %c thread startup failed: %d\n", + ticket_windows[index].name, result); + return result; + } + } + + rt_kprintf("ticket_demo started: total=%d, windows=%u\n", + TICKET_TOTAL, TICKET_WINDOW_COUNT); + return RT_EOK; +} +MSH_CMD_EXPORT(ticket_demo, mutex ticket demo); diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" new file mode 100644 index 0000000000000000000000000000000000000000..ade8b08f75781e1acee9e24a9b77324a5123b2f0 --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232.md" @@ -0,0 +1,224 @@ +# 第三天作业:线程间同步与通信 + +## 一、作业目标 + +本日使用 RT-Thread V4.1.1 的四类 IPC 完成四个独立 MSH 实验:计数信号量、互斥量、事件集和消息队列。每个实验至少创建两个线程,检查 IPC 对象和线程初始化/启动返回值,并在结束后清理静态对象。 + +邮箱属于课程扩展内容,本次不作为必做实验提交。 + +## 二、提交源码 + +```text +parking_demo.c -> parking_demo +ticket_demo.c -> ticket_demo +startup_demo.c -> startup_demo +package_demo.c -> package_demo +``` + +工程权威源码位于: + +```text +D:\RT-ThreadStudio\workspace\RSOC2026_IPC\applications\ +``` + +本目录中的四个 C 文件是提交副本,保持与工程源码同步;构建产物不提交。 + +## 三、实验设计 + +### 1. `parking_demo` + +初始化值为 3 的计数信号量表示停车位,创建 5 个车辆线程。车辆以 2 秒 Tick 超时申请车位,获得资源后停留并释放。`parking_used` 由互斥量保护,保证显示的进入数量不会超过 3。 + +### 2. `ticket_demo` + +创建 A、B 两个售票线程,共售 20 张票。互斥量保护“检查余票、分配票号、记录票号、减少余票”的完整事务,而不是只保护减法操作。最终要求剩余票数为 0,且每个票号只出现一次。 + +### 3. `startup_demo` + +网络、传感器和存储线程分别发送三个事件位。业务线程先用 `RT_EVENT_FLAG_AND` 等待 3 秒,存储线程因延时 5 秒而触发一次超时;随后业务线程再次等待全部事件,输出业务启动信息。`startup_ready_mask` 用于解释超时原因,事件集本身只承担同步功能。 + +### 4. `package_demo` + +消息结构包含编号、区域、重量和结束标志。两个生产线程各发送 5 条快递消息,并各发送一条结束消息;分拣线程收到两条结束消息后统计总数、区域数量、总重量和发送失败次数。消息池按 `RT_ALIGN_SIZE` 对齐,队列容量为 5 条消息。 + +## 四、MSH 操作 + +固件启动后,在串口 `msh >` 中逐项执行: + +```text +help +parking_demo +ticket_demo +startup_demo +package_demo +``` + +每个命令应单独运行。上一个实验结束并完成对象脱离后再重复执行同一命令,避免同名线程或 IPC 对象残留。当前代码通过运行标志拒绝运行中的重复启动。 + +## 五、构建修改 + +`RSOC2026_IPC` 原工程只收集了 `main.c`,因此新增: + +```text +applications/SConscript +drivers/SConscript +libraries/SConscript +``` + +同时补充 `rtconfig.py` 的 Cortex-M4 编译参数、汇编参数、链接脚本和 BIN 后处理命令。`Debug/*` 是自动生成目录,不手工修改。 + +## 六、验证结果 + +已完成:四个 Day3 C 文件被 SCons 收集并编译,BSP、HAL、CMSIS、RT-Thread 内核和示例包编译通过,链接成功并生成 `rt-thread.elf`,同时生成 `rtthread.bin`。 + +已完成:用户在 RT-Thread Studio 中确认最新固件已经通过 ST-Link 下载并启动;串口终端已依次验证 `parking_demo`、`ticket_demo`、`startup_demo` 和 `package_demo`,并在最后使用 `ps` 检查实验线程已经退出。 + +本节以下内容只记录实际截图中出现的串口证据。构建成功、下载成功、串口命令成功和实体硬件行为属于不同证据层级,不能相互替代。 + +## 七、源码与运行逻辑对应 + +### 1. 停车场:计数信号量限制资源数量 + +源码:[`parking_demo.c`](parking_demo.c) + +初始化时使用静态信号量,初始计数为 3;另用互斥量保护 `parking_used`,因为信号量负责限制资源数量,不应该直接读取其内部计数作为业务统计: + +~~~ +result = rt_sem_init(&parking_spaces, "parksem", + PARKING_SPACE_COUNT, RT_IPC_FLAG_FIFO); +result = rt_sem_take(&parking_spaces, PARKING_TIMEOUT_TICKS); +~~~ + +每辆车最多等待 `2 * RT_TICK_PER_SECOND`。获取成功后先在互斥量保护下增加 `parking_used`,停车延时结束后再减少统计并释放信号量;获取超时则打印等待信息,延时 300 ms 后重试。5 个车辆线程全部调用 `parking_finish()`,最后一个线程负责脱离信号量和互斥量,并输出 `parking_demo finished`。 + +运行逻辑为:3 个车位先被占满,后续车辆等待;已有车辆离开并释放信号量后,等待车辆获得唤醒机会进入。截图中最大占用数为 3,5 辆车全部离场,最终占用数为 0。 + +### 2. 售票系统:互斥量保护完整事务 + +源码:[`ticket_demo.c`](ticket_demo.c) + +售票线程持有互斥量时,同时完成检查余票、取得票号、记录票号、减少余票和打印结果,避免两个窗口在检查和修改之间交错: + +~~~ +rt_mutex_take(&ticket_lock, RT_WAITING_FOREVER); +if (ticket_count <= 0) +{ + rt_mutex_release(&ticket_lock); + break; +} + +ticket_no = ticket_count; +ticket_sold[ticket_no]++; +ticket_count--; +rt_kprintf("window %c sold ticket %d\n", window->name, ticket_no); +rt_mutex_release(&ticket_lock); +~~~ + +A、B 两个窗口分别运行同一入口函数,共享 `ticket_count` 和 `ticket_sold[]`。票数变为 0 后两个线程退出,完成计数达到预期值的最后一个线程脱离互斥量并输出剩余票数。截图显示票号从 20 到 1,未出现重复号码,最终 `remaining=0`。 + +### 3. 启动条件:事件集实现 AND 屏障 + +源码:[`startup_demo.c`](startup_demo.c) + +网络、传感器和存储线程分别发送三个事件位,业务线程先进行 3 秒有界等待,再进行永久等待: + +~~~ +result = rt_event_recv(&startup_event, EVENT_ALL_READY, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + 3 * RT_TICK_PER_SECOND, &recved); +~~~ + +模块延时分别为 700 ms、1400 ms 和 5000 ms,因此业务线程第一次等待会因为存储模块尚未完成而超时。超时后,代码读取受互斥量保护的 `startup_ready_mask`,明确打印未就绪模块;之后再次使用 `RT_EVENT_FLAG_AND` 永久等待全部事件。三个模块都发送事件后,业务线程输出 `all modules are ready` 和 `business task started`。 + +### 4. 快递分拣:消息队列传递结构化消息 + +源码:[`package_demo.c`](package_demo.c) + +消息结构同时携带编号、区域、重量和结束标志,消息池按照对齐后的单条消息大小分配 5 条容量: + +~~~ +struct package_msg +{ + rt_uint32_t id; + rt_uint8_t region; + rt_uint16_t weight; + rt_uint8_t end; +}; + +#define PACKAGE_MSG_SIZE RT_ALIGN(sizeof(struct package_msg), RT_ALIGN_SIZE) +~~~ + +两个生产者分别生成 `1001~1005` 和 `2001~2005`。消息队列满时,`rt_mq_send()` 返回错误,代码在互斥量保护下增加 `package_send_failed`,打印重试信息并延时 80 ms,直到消息成功发送。每个生产者最后发送一条 `end=1` 的结束消息,分拣线程收到两条结束消息后才输出汇总并退出,因此不会遗漏另一个生产者的消息。 + +截图中队列容量为 5,出现 `package send failed, retrying` 说明生产速度暂时超过消费速度;重试最终成功,统计结果为 10 条消息、东部 5 条、南部 5 条、北部 0 条、总重量 9100 g。 + +## 八、串口运行截图相关讲解 + +### 1. 停车场运行 + +![parking_demo 串口运行结果](assets/day3_parking_terminal.png) + +图中先执行 `ps` 确认系统线程状态,再运行 `parking_demo`。`car1`、`car2`、`car3` 先占满 3 个车位;车辆离场释放信号量后,`car4`、`car5` 依次进入。整个过程的 `used spaces` 没有超过 3,最后输出 `parking_demo finished`。 + +### 2. 售票运行 + +![ticket_demo 串口运行结果](assets/day3_ticket_terminal.png) + +图中两个窗口交替取得互斥量,票号从 20 递减到 1,最终输出: + +~~~ +ticket_demo finished: remaining=0 +~~~ + +`ticket_demo started` 出现在前两条售票日志之后,是线程启动后立即获得调度造成的输出顺序差异,不影响互斥量保护的售票事务。 + +### 3. 启动条件运行 + +![startup_demo 串口运行结果](assets/day3_startup_terminal.png) + +图中先出现 `network ready` 和 `sensor ready`,3 秒有界等待到期后打印 `storage is not ready`;5 秒延时结束后存储线程发送事件,业务线程收到全部事件并输出 `all modules are ready`、`business task started`,最后实验完成。 + +### 4. 消息队列运行 + +![package_demo 串口运行结果](assets/day3_package_terminal.png) + +图中可以看到两个生产者发送 10 条消息,分拣线程按区域输出消息内容。由于队列容量只有 5,运行中出现 9 次发送失败重试;这不是消息丢失,而是非阻塞 `rt_mq_send()` 在队列满时返回错误后的应用层重试。最终输出: + +~~~ +producer end received: 2/2 +total packages: 10 +east: 5, south: 5, north: 0 +total weight: 9100 g +send failed: 9 +package_demo finished +~~~ + +### 5. 实验线程清理 + +![四个实验完成后的 ps](assets/day3_ps_cleanup.png) + +最后一次 `ps` 只剩 `tshell`、`tidle0`、`timer` 和 `main` 等系统线程,没有残留 `car`、`window`、`initmod`、`business`、`producer` 或 `sorter`。这说明截图中已运行的实验线程均已退出,并完成了对应静态 IPC 对象的脱离。 + +## 九、分层验证结论 + +| 证据层级 | 本次结果 | 结论边界 | +| --- | --- | --- | +| 源码 | 四个提交副本与工程源码同步,分别导出四个 MSH 命令 | 证明实现了对应 IPC 逻辑,不能单独证明板上运行 | +| 构建 | SCons 构建成功,生成 `rt-thread.elf` 和 `rtthread.bin` | 证明编译、链接和固件产物成功 | +| 下载 | RT-Thread Studio 中已确认最新固件下载成功 | 证明固件已写入并启动,不能替代每条串口命令证据 | +| 串口 | 四个 MSH 命令均输出 `finished`,统计值符合设计;最终 `ps` 无实验线程残留 | 证明本次截图中的线程、IPC 和清理路径实际执行 | +| 实体硬件 | 本次材料是串口终端截图 | 未单独证明 LED、按键或其他外设的物理现象 | + +## 十、提交资源 + +本次补充的截图资源位于 `assets/`: + +~~~ +day3_parking_terminal.png +day3_ticket_terminal.png +day3_startup_terminal.png +day3_package_terminal.png +day3_ps_cleanup.png +~~~ + +截图均由用户提供的原始终端截图按 80% 比例缩小后保存,Markdown 使用相对路径引用,便于在仓库页面和本地预览中查看。 diff --git "a/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..7bcf98fcbfe3b205833b87014566854fc8ee875f --- /dev/null +++ "b/2026/\347\254\2544\347\273\204/\346\235\216\345\272\255\350\215\243/\347\254\224\350\256\260/\347\254\254\344\270\211\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1,312 @@ +# 第三天笔记:RT-Thread 内核 IPC + +日期:2026-08-19 + +## 一、课程主线 + +IPC(Inter-Process Communication)用于线程之间的同步与通信。Day3 重点理解:资源不可用时线程如何阻塞,资源就绪后如何恢复;同步对象和通信对象的区别;永久等待、非阻塞尝试、超时等待、紧急发送和对象销毁的行为。 + +| 类型 | 对象 | 主要用途 | +| --- | --- | --- | +| 线程同步 | 信号量 | 事件通知、资源计数 | +| 线程同步 | 互斥量 | 保护临界区和共享资源 | +| 线程同步 | 事件集 | 表达一个或多个同步条件 | +| 线程通信 | 邮箱 | 传递固定宽度的值或指针 | +| 线程通信 | 消息队列 | 传递固定长度的结构化消息 | + +![RT-Thread 内核对象层级](assets/day2_kernel_object_hierarchy.png) + +该图用于理解线程、定时器和 IPC 对象都属于 RT-Thread 内核对象体系,不是某个 IPC 命令已经运行的证据。 + +## 二、临界区与阻塞模型 + +临界区是访问共享资源的一段代码,同一时刻只能由一个执行流进入。共享变量、设备寄存器、环形缓冲区和串口输出状态都可能成为临界资源。锁的范围应覆盖完整事务,例如“检查条件、修改状态、记录结果”;持锁期间不应进行长时间延时或永久等待。 + +| 等待方式 | 资源不可用时的行为 | 适用场景 | +| --- | --- | --- | +| 永久阻塞 | 线程挂起,直到资源可用 | 必须等待的工作线程 | +| 非阻塞尝试 | 立即返回成功或失败 | 状态探测、不能等待的路径 | +| 超时等待 | 等待指定 Tick,超时返回 | 需要有界响应时间的任务 | + +```text +调用 take/recv + -> 资源不可用 + -> 当前线程进入挂起态 + -> 调度器运行其他就绪线程 + -> 资源到达或等待超时 + -> 当前线程重新进入就绪队列 +``` + +`state=suspend` 只能说明线程当前没有参与调度,不能单独说明它是在延时、等待 IPC,还是被显式挂起。 + +## 三、信号量 + +信号量通过计数值表示可用令牌数量,没有线程所有权,适合线程同步和资源计数,不适合代替互斥量保护复杂临界区。二值信号量通常表示事件未到达或已到达;计数信号量表示多个同类资源。 + +```text +初始值为 0 + -> rt_sem_take() 没有令牌 + -> 当前线程阻塞 + -> 其他线程 rt_sem_release() + -> 等待线程被唤醒并获取令牌 +``` + +主要 API: + +```c +rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); +rt_err_t rt_sem_delete(rt_sem_t sem); +rt_err_t rt_sem_init(rt_sem_t sem, const char *name, + rt_uint32_t value, rt_uint8_t flag); +rt_err_t rt_sem_detach(rt_sem_t sem); +rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout); +rt_err_t rt_sem_trytake(rt_sem_t sem); +rt_err_t rt_sem_release(rt_sem_t sem); +``` + +`RT_WAITING_FOREVER` 表示永久等待;`rt_sem_trytake()` 或超时参数为 0 表示非阻塞尝试。静态对象使用 `init -> detach`,动态对象使用 `create -> delete`。 + +```c +static struct rt_semaphore sem; + +rt_sem_init(&sem, "sem", 0, RT_IPC_FLAG_FIFO); +if (rt_sem_take(&sem, RT_WAITING_FOREVER) == RT_EOK) +{ + rt_kprintf("event received\\n"); +} +rt_sem_release(&sem); +rt_sem_detach(&sem); +``` + +信号量释放不要求释放者就是获取者,这一点与互斥量不同。信号量内部计数不能直接当作业务统计变量使用。 + +## 四、互斥量 + +互斥量是带所有权的锁,主要用于保护临界区。成功获取互斥量的线程成为持有者,通常只有持有者才能释放。RT-Thread 的互斥量记录持有者和持有次数,支持同一线程递归获取,并通过优先级继承降低优先级翻转的影响。 + +| 对比项 | 信号量 | 互斥量 | +| --- | --- | --- | +| 所有权 | 无 | 有 | +| 主要用途 | 同步、资源计数 | 临界区保护 | +| 释放限制 | 不依赖获取者身份 | 通常由持有者释放 | +| 优先级继承 | 无互斥所有权语义 | 支持 | + +优先级翻转过程是:低优先级线程持锁,高优先级线程等待,中优先级线程持续抢占低优先级线程。互斥量会临时提升锁持有者的优先级,但不能替代合理的锁粒度、锁顺序和有界临界区设计。 + +主要 API: + +```c +rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); +rt_err_t rt_mutex_delete(rt_mutex_t mutex); +rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); +rt_err_t rt_mutex_detach(rt_mutex_t mutex); +rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); +rt_err_t rt_mutex_trytake(rt_mutex_t mutex); +rt_err_t rt_mutex_release(rt_mutex_t mutex); +``` + +```c +static struct rt_mutex lock; +static rt_int32_t value; + +rt_mutex_init(&lock, "lock", RT_IPC_FLAG_PRIO); +if (rt_mutex_take(&lock, RT_WAITING_FOREVER) == RT_EOK) +{ + value++; + rt_mutex_release(&lock); +} +rt_mutex_detach(&lock); +``` + +“检查条件、修改数据、记录结果”应放在同一个临界区中。只给某一条赋值语句加锁,仍可能在检查和修改之间产生竞态。 + +## 五、事件集 + +事件集只表达同步条件,不携带业务数据。当前 RT-Thread 使用 32 位事件集合,每一位表示一个独立事件。事件位是状态,不是消息队列;重复发送同一位不会按次数排队。 + +```c +#define EVENT_NET_READY (1u << 0) +#define EVENT_SENSOR_READY (1u << 1) +#define EVENT_STORAGE_READY (1u << 2) +``` + +| 选项 | 含义 | +| --- | --- | +| `RT_EVENT_FLAG_OR` | 指定事件中任意一位满足即可返回 | +| `RT_EVENT_FLAG_AND` | 指定的所有事件位都满足后才返回 | +| `RT_EVENT_FLAG_CLEAR` | 成功接收后清除匹配的事件位 | + +主要 API: + +```c +rt_event_t rt_event_create(const char *name, rt_uint8_t flag); +rt_err_t rt_event_delete(rt_event_t event); +rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag); +rt_err_t rt_event_detach(rt_event_t event); +rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); +rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, + rt_uint8_t option, rt_int32_t timeout, + rt_uint32_t *recved); +``` + +```c +static struct rt_event event; +rt_uint32_t recved; + +rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); +rt_event_send(&event, EVENT_NET_READY); +rt_event_recv(&event, + EVENT_NET_READY | EVENT_SENSOR_READY, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &recved); +rt_event_detach(&event); +``` + +事件集只负责同步条件,不负责保存模块名称、数据内容或事件发生次数。超时后若要说明未完成条件,应由应用层维护状态掩码并设置同步边界。 + +## 六、邮箱 + +邮箱是轻量的线程通信对象,每个槽位保存一个 `rt_ubase_t` 宽度的值。在 32 位 MCU 上通常为 4 字节,可以传递整数或指针。通过指针传递结构体时,指向的对象必须在接收线程使用期间保持有效。 + +主要 API: + +```c +rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag); +rt_err_t rt_mb_delete(rt_mailbox_t mb); +rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, + void *msg_pool, rt_size_t size, rt_uint8_t flag); +rt_err_t rt_mb_detach(rt_mailbox_t mb); +rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value); +rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_ubase_t value, rt_int32_t timeout); +rt_err_t rt_mb_urgent(rt_mailbox_t mb, rt_ubase_t value); +rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout); +``` + +```c +static struct rt_mailbox mailbox; +static rt_ubase_t pool[4]; +rt_ubase_t value; + +rt_mb_init(&mailbox, "mail", pool, 4, RT_IPC_FLAG_FIFO); +rt_mb_send(&mailbox, 0x1234); +rt_mb_recv(&mailbox, &value, RT_WAITING_FOREVER); +rt_mb_detach(&mailbox); +``` + +普通发送在邮箱满时立即返回;`rt_mb_send_wait()` 可以等待空位;`rt_mb_urgent()` 将邮件插入队首。带等待的接口不能在中断上下文中使用。不能发送线程栈上即将失效的临时对象地址。 + +## 七、消息队列 + +消息队列用于传递固定最大长度的消息。发送时复制消息内容,接收时复制到接收缓冲区,适合传递结构体、串口帧和传感器数据。消息池必须容纳指定数量的、按 `RT_ALIGN_SIZE` 对齐后的消息块。 + +主要 API: + +```c +rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, + rt_size_t max_msgs, rt_uint8_t flag); +rt_err_t rt_mq_delete(rt_mq_t mq); +rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, + rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag); +rt_err_t rt_mq_detach(rt_mq_t mq); +rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size); +rt_err_t rt_mq_send_wait(rt_mq_t mq, const void *buffer, + rt_size_t size, rt_int32_t timeout); +rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size); +rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, + rt_size_t size, rt_int32_t timeout); +``` + +```c +struct sensor_msg +{ + rt_uint32_t id; + rt_uint16_t value; +}; + +#define MSG_SIZE RT_ALIGN(sizeof(struct sensor_msg), RT_ALIGN_SIZE) +static struct rt_messagequeue queue; +static rt_uint8_t pool[MSG_SIZE * 5]; +struct sensor_msg tx = {1001, 250}; +struct sensor_msg rx; + +rt_mq_init(&queue, "sensorq", pool, MSG_SIZE, + sizeof(pool), RT_IPC_FLAG_FIFO); +rt_mq_send(&queue, &tx, sizeof(tx)); +rt_mq_recv(&queue, &rx, sizeof(rx), RT_WAITING_FOREVER); +rt_mq_detach(&queue); +``` + +`rt_mq_send()` 在队列满时立即返回错误;`rt_mq_send_wait()` 可以在限定时间内等待空间;`rt_mq_urgent()` 将消息插入队首。生产者必须检查发送结果。 + +## 八、五类 IPC 的区别 + +| 对象 | 是否携带业务数据 | 是否有所有权 | 典型等待条件 | 典型应用 | +| --- | --- | --- | --- | --- | +| 信号量 | 否 | 否 | 令牌数量大于 0 | 事件通知、资源计数 | +| 互斥量 | 否 | 有 | 锁未被其他线程持有 | 共享资源保护 | +| 事件集 | 否 | 否 | OR 或 AND 条件满足 | 多条件同步 | +| 邮箱 | 一个值或指针 | 否 | 邮箱中存在邮件 | 轻量通知、指针传递 | +| 消息队列 | 固定长度数据 | 否 | 队列中存在消息 | 生产者消费者、结构体传递 | + +选择原则:通知或限制资源数量使用信号量;保护共享数据一致性使用互斥量;等待多个条件使用事件集;传递一个值或指针使用邮箱;复制结构化数据使用消息队列。 + +## 九、动态与静态对象生命周期 + +```text +动态对象:create -> 使用 -> delete +静态对象:init -> 使用 -> detach +``` + +动态对象由系统堆分配控制块和相关内存,适合数量或生命周期在运行时确定的场景,但必须处理内存不足和删除时的等待线程唤醒问题。 + +静态对象由应用提供控制块和缓冲区,边界更明确。执行 `detach` 或 `delete` 前,必须确保没有线程继续等待或访问对象。动态对象不能使用 `detach`,静态对象不能使用 `delete`。 + +## 十、RT-Thread 工程中的 IPC + +本地工程: + +```text +D:\RT-ThreadStudio\workspace\RSOC2026_IPC +``` + +`rtconfig.h` 已启用: + +```text +RT_USING_SEMAPHORE +RT_USING_MUTEX +RT_USING_EVENT +RT_USING_MAILBOX +RT_USING_MESSAGEQUEUE +RT_USING_MSH +FINSH_USING_MSH +``` + +工程中的 `kernel_samples-latest` 提供信号量、互斥量、事件集、邮箱、邮箱紧急发送和消息队列示例,并通过 MSH 导出对应命令。重复运行示例前,应确认上一次示例已经结束并完成对象清理,避免固定名称对象残留。 + +## 十一、验证边界 + +| 证据层级 | 可以说明什么 | 不能替代什么 | +| --- | --- | --- | +| 配置 | IPC 相关宏已启用 | 不能证明对象已经运行 | +| 源码 | API 调用和对象关系存在 | 不能证明固件已下载 | +| 构建 | 源码已编译、链接产物已生成 | 不能证明串口输出 | +| 下载 | 固件已写入并复位启动 | 不能证明每个命令已执行 | +| 串口 | MSH 回显、线程日志和 IPC 输出 | 不能单独证明实体硬件现象 | +| 硬件 | 可重复的板卡现象 | 不能替代源码和串口证据 | + +当前本地工程已完成 IPC 源码收集、编译、链接以及 `rt-thread.elf` 和 `rtthread.bin` 生成;下载、串口命令和实体运行需要单独记录,不能由构建结果推断。 + +## 十二、官方资料与工程源码 + +- [RT-Thread IPC:信号量](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%bf%a1%e5%8f%b7%e9%87%8f) +- [RT-Thread IPC:互斥量](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%ba%92%e6%96%a5%e9%87%8f) +- [RT-Thread IPC:事件集](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1?id=%e4%ba%8b%e4%bb%b6%e9%9b%86) +- [RT-Thread IPC:邮箱和消息队列](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2) + +本地对应源码: + +```text +D:\RT-ThreadStudio\workspace\RSOC2026_IPC\rt-thread\include\rtthread.h +D:\RT-ThreadStudio\workspace\RSOC2026_IPC\rt-thread\src\ipc.c +D:\RT-ThreadStudio\workspace\RSOC2026_IPC\packages\kernel_samples-latest\zh\ +```