diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/SConscript" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/SConscript" new file mode 100644 index 0000000000000000000000000000000000000000..5fed19d1eae515703de289b925ddcaaf327b48b0 --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/SConscript" @@ -0,0 +1,10 @@ +from building import * +import os + +cwd = GetCurrentDir() +src = Glob('*.c') +CPPPATH = [cwd] + +group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/event_work.c" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/event_work.c" new file mode 100644 index 0000000000000000000000000000000000000000..783920a104eaa525c173d8be4ffe283195860d3c --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/event_work.c" @@ -0,0 +1,96 @@ +//---------------事件集--------------- + +#include //核心头文件 + +#define THREAD_PRIORITY 9//线程优先级 +#define THREAD_TIMESLICE 5//时间片大小 + +#define EVENT_FLAG2 (1 << 2) //事件2(二进制 00000100) +#define EVENT_FLAG6 (1 << 6) //事件6(二进制 01000000) + +/* 事件控制块 */ +static struct rt_event event; + +/* 线程1入口函数 */ +static void thread1_entry(void* para) +{ + rt_uint32_t event_rec; + + /* 第一次接收事件,事件2或事件6任意一个可以触发线程1,接收完后清除事件标志 */ + if(rt_event_recv(&event,//事件集对象的句柄 + (EVENT_FLAG2 | EVENT_FLAG6),//接收线程感兴趣的事件 + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,//接收选项 + RT_WAITING_FOREVER,//指定超时时间 + &event_rec) == RT_EOK)//指向接收到的事件 + { + rt_kprintf("thread1: OR rec event 0x%x\n", event_rec); + } + + /* 第二次接收事件,事件3和事件5均发生时才可以触发线程1,接收完后清除事件标志 */ + if(rt_event_recv(&event, + EVENT_FLAG2 | EVENT_FLAG6, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, + &event_rec) == RT_EOK) + { + rt_kprintf("thread1: AND rec event 0x%x\n", event_rec); + } +} + +/* 线程2入口函数 */ +static void thread2_entry(void* para) +{ + rt_kprintf("thread2: send event2\n"); + rt_event_send(&event, EVENT_FLAG2); + + rt_kprintf("thread2: send event6\n"); + rt_event_send(&event, EVENT_FLAG6); + + rt_kprintf("thread2: send event2\n"); + rt_event_send(&event, EVENT_FLAG2); +} + +static char thread1_stack[1024];//静态分配线程栈 +static struct rt_thread thread1;//静态定义线程控制块 +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 互斥量示例的初始化 */ +int event_work(void) +{ + rt_err_t result; + + /* 初始化事件对象 */ + result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO); + + //初始化线程1 + rt_thread_init(&thread1,//线程句柄 + "thread1",//线程名称 + thread1_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread1_stack[0],//线程栈起始地址 + sizeof(thread1_stack),//线程栈大小 + THREAD_PRIORITY - 1,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程1 + rt_thread_startup(&thread1); + + //初始化线程2 + rt_thread_init(&thread2,//线程句柄 + "thread2",//线程名称 + thread2_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread2_stack[0],//线程栈起始地址 + sizeof(thread2_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程2 + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(event_work, event work); diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/mailbox_work.c" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/mailbox_work.c" new file mode 100644 index 0000000000000000000000000000000000000000..6220d61edabe7c252f4096eccc2ceff5798a3623 --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/mailbox_work.c" @@ -0,0 +1,93 @@ +//---------------邮箱--------------- + +#include //核心头文件 + +#define THREAD_PRIORITY 10//线程优先级 +#define THREAD_TIMESLICE 5//时间片大小 + +/* 邮箱控制块 */ +static struct rt_mailbox mail; +/* 用于放邮件的内存池 */ +static char mail_space[128]; +static char mail_str[] = "I'm a mail!"; + +/* 线程1入口函数 */ +static void thread1_entry(void* para) +{ + char *str; + + while(1) + { + /* 从邮箱中收取邮件 */ + if(rt_mb_recv(&mail, (rt_ubase_t *)&str, RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str); + } + } + /* 执行邮箱对象脱离 */ + rt_mb_detach(&mail); +} + +static void thread2_entry(void* para) +{ + rt_uint8_t count = 0; + + while(count < 10) + { + count++; + /* 发送mb_str1地址到邮箱中 */ + rt_mb_send(&mail, (rt_uint32_t)&mail_str); + } + + /* 发送邮件告诉线程1,线程2已经运行结束 */ + rt_mb_send(&mail, (rt_uint32_t)&mail_str); +} + +static char thread1_stack[1024];//静态分配线程栈 +static struct rt_thread thread1;//静态定义线程控制块 +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 邮箱示例的初始化 */ +int mailbox_work(void) +{ + rt_err_t result; + + /* 初始化一个mailbox */ + result = rt_mb_init(&mail, + "mail", + &mail_space[0], + sizeof(mail_space) / sizeof(rt_ubase_t), + RT_IPC_FLAG_PRIO); + + //初始化线程1 + rt_thread_init(&thread1,//线程句柄 + "thread1",//线程名称 + thread1_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread1_stack[0],//线程栈起始地址 + sizeof(thread1_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程1 + rt_thread_startup(&thread1); + + //初始化线程2 + rt_thread_init(&thread2,//线程句柄 + "thread2",//线程名称 + thread2_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread2_stack[0],//线程栈起始地址 + sizeof(thread2_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程2 + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mailbox_work, mailbox work); diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/msgq_work.c" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/msgq_work.c" new file mode 100644 index 0000000000000000000000000000000000000000..ca302653474b221ca59eebf7cfcd5bd04595b309 --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/msgq_work.c" @@ -0,0 +1,123 @@ +//---------------消息队列--------------- + +#include //核心头文件 + +#define THREAD_PRIORITY 25//线程优先级 +#define THREAD_TIMESLICE 5//时间片大小 + +/* 消息队列控制块 */ +static struct rt_messagequeue message; +/* 消息队列中用到的放置消息的内存池 */ +static rt_uint8_t message_space[2048]; + +/* 线程1入口函数 */ +static void thread1_entry(void* para) +{ + //定义接收缓冲区buf和计数器cnt + char buf = 0; + rt_uint8_t cnt = 0; + + while(1) + { + /* 从消息队列中接收消息 */ + if (rt_mq_recv(&message, &buf, sizeof(buf), RT_WAITING_FOREVER) > 0) + { + rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf); + if(cnt == 10) + { + break; + } + } + cnt++; + rt_thread_mdelay(50); + } + rt_kprintf("thread1: detach mq \n"); + rt_mq_detach(&message); +} + +/* 线程2入口函数 */ +static void thread2_entry(void* para) +{ + int result; + char buf = 'a'; + rt_uint8_t cnt = 0; + + while(1) + { + if(cnt == 5) + { + /* 发送紧急消息到消息队列中 */ + result = rt_mq_urgent(&message, &buf, 1); + if(result == RT_EOK) + { + rt_kprintf("thread2: send urgent message - %c\n", buf); + } + } + else if(cnt > 10)/* 发送10次消息之后退出 */ + { + rt_kprintf("message queue stop send, thread2 quit\n"); + break; + } + else + { + /* 发送消息到消息队列中 */ + result = rt_mq_send(&message, &buf, 1); + + rt_kprintf("thread2: send message - %c\n", buf); + } + buf++; + cnt++; + /* 延时5ms */ + rt_thread_mdelay(5); + } +} + +static char thread1_stack[1024];//静态分配线程栈 +static struct rt_thread thread1;//静态定义线程控制块 +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 消息队列示例的初始化 */ +int msgq_work(void) +{ + rt_err_t result; + + /* 初始化消息队列 */ + result = rt_mq_init(&message, + "message", + &message_space[0], /* 内存池指向msg_pool */ + 1, /* 每个消息的大小是 1 字节 */ + sizeof(message_space), /* 内存池的大小是msg_pool的大小 */ + RT_IPC_FLAG_PRIO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */ + + //初始化线程1 + rt_thread_init(&thread1,//线程句柄 + "thread1",//线程名称 + thread1_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread1_stack[0],//线程栈起始地址 + sizeof(thread1_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程1 + rt_thread_startup(&thread1); + + //初始化线程2 + rt_thread_init(&thread2,//线程句柄 + "thread2",//线程名称 + thread2_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread2_stack[0],//线程栈起始地址 + sizeof(thread2_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程2 + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(msgq_work, msgq work); diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/mutex_work.c" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/mutex_work.c" new file mode 100644 index 0000000000000000000000000000000000000000..82a5662b4b016ab4552a1b24b6e3bdeefb94fb15 --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/mutex_work.c" @@ -0,0 +1,92 @@ +//---------------互斥量--------------- + +#include //核心头文件 + +#define THREAD_PRIORITY 8//线程优先级 +#define THREAD_TIMESLICE 5//时间片大小 + +/* 指向互斥量的指针 */ +static rt_mutex_t mutex = RT_NULL;//动态互斥锁句柄 +static rt_uint8_t number1, number2 = 0;//共享资源 + +//为了避免命名冲突,使用 static 修饰线程入口函数,使其仅在当前文件可见,成为文件局部函数。 +/* 线程1入口函数 */ +static void thread1_entry(void* para) +{ + while(1) + { + /* 线程1获取到互斥量后,先后对number1、number2进行加1操作,然后释放互斥量 */ + rt_mutex_take(mutex, RT_WAITING_FOREVER);//获取互斥锁 + number1++; + //如果没有互斥锁保护,其他线程有可能在这10毫秒内介入,导致两个变量的值不一致 + rt_thread_mdelay(10); + number2++; + rt_mutex_release(mutex);//释放互斥锁 + } +} + +/* 线程2入口函数 */ +static void thread2_entry(void* para) +{ + while(1) + { + /* 线程2获取到互斥量后,检查number1、number2的值是否相同,相同则表示mutex起到了锁的作用 */ + rt_mutex_take(mutex, RT_WAITING_FOREVER);//获取互斥锁 + if(number1 == number2) + { + rt_kprintf("mutex protect ,number1 = mumber2 is %d\n", number1); + } + //让线程2也修改这些变量,可以更好地模拟真实的多线程竞争环境 + number1++; + number2++; + rt_mutex_release(mutex);//释放互斥锁 + if(number1 > 20) + { + return; + } + } +} + +static char thread1_stack[1024];//静态分配线程栈 +static struct rt_thread thread1;//静态定义线程控制块 +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 互斥量示例的初始化 */ +int mutex_work(void) +{ + /* 创建一个动态互斥量 */ + mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO);//互斥量名称 标志(已作废) + + //初始化线程1 + rt_thread_init(&thread1,//线程句柄 + "thread1",//线程名称 + thread1_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread1_stack[0],//线程栈起始地址 + sizeof(thread1_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程1 + rt_thread_startup(&thread1); + + //初始化线程2 + //线程2优先级更高,强制让数据竞争更容易发生,证明互斥锁的必要性 + rt_thread_init(&thread2,//线程句柄 + "thread2",//线程名称 + thread2_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread2_stack[0],//线程栈起始地址 + sizeof(thread2_stack),//线程栈大小 + THREAD_PRIORITY - 1,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程2 + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mutex_work, mutex work); diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/semaphore_work.c" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/semaphore_work.c" new file mode 100644 index 0000000000000000000000000000000000000000..8f7767fb68d97c219df6a686f8f9e0d05f558887 --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/semaphore_work.c" @@ -0,0 +1,87 @@ +//---------------信号量--------------- + +#include //核心头文件 + +#define THREAD_PRIORITY 25//线程优先级 +#define THREAD_TIMESLICE 5//时间片大小 + +/* 指向信号量的指针 */ +static rt_sem_t sem = RT_NULL;//信号量指针 + +//为了避免命名冲突,使用 static 修饰线程入口函数,使其仅在当前文件可见,成为文件局部函数。 +/* 线程1入口函数 */ +static void thread1_entry(void* para) +{ + static rt_uint8_t count = 0; + + // while(1) 是实现周期性任务的核心机制,确保线程在达到终止条件前持续运行。 + for(count = 0;count < 10;count++) + { + /* count每计数1次,就释放一次信号量 */ + rt_kprintf("thread1 release a semaphore.\n"); + rt_sem_release(sem);//释放信号量 + } +} + +/* 线程2入口函数 */ +static void thread2_entry(void* para) +{ + //用于表示 RT-Thread 内核、驱动或组件的操作结果状态(成功/失败及具体错误原因)。 + static rt_err_t result; + static rt_uint8_t number = 0; + + // while(1) 是实现周期性任务的核心机制,确保线程在达到终止条件前持续运行。 + while(1) + { + /* 永久方式等待信号量,获取到信号量,则执行number自加的操作 */ + result = rt_sem_take(sem, RT_WAITING_FOREVER); + if(result == RT_EOK) + { + number++; + rt_kprintf("thread2 take a semaphore. number = %d\n", number); + } + } +} + +static char thread1_stack[1024];//静态分配线程栈 +static struct rt_thread thread1;//静态定义线程控制块 +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 信号量示例的初始化 */ +int semaphore_work() +{ + /* 创建一个信号量,初始值是0 */ + sem = rt_sem_create("sem", 0, RT_IPC_FLAG_PRIO);//信号量名称 信号量初始值 信号量标志 + + //初始化线程1 + rt_thread_init(&thread1,//线程句柄 + "thread1",//线程名称 + thread1_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread1_stack[0],//线程栈起始地址 + sizeof(thread1_stack),//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程1 + rt_thread_startup(&thread1); + + //初始化线程2 + rt_thread_init(&thread2,//线程句柄 + "thread2",//线程名称 + thread2_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + &thread2_stack[0],//线程栈起始地址 + sizeof(thread2_stack),//线程栈大小 + THREAD_PRIORITY - 1,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + //启动线程2 + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(semaphore_work, semaphore work); diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/signal_work.c" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/signal_work.c" new file mode 100644 index 0000000000000000000000000000000000000000..5d5ec6965bf64c147bc83e42c8f7764712ef7cfd --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\344\275\234\344\270\232/Day3/IPC_work/signal_work.c" @@ -0,0 +1,64 @@ +//---------------信号--------------- + +#include //核心头文件 + +#define THREAD_PRIORITY 25//线程优先级 +#define THREAD_STACK_SIZE 512//线程栈大小 +#define THREAD_TIMESLICE 5//时间片大小 + +/* 线程1的信号处理函数 */ +static void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} + +/* 线程1的入口函数 */ +static void thread1_entry(void* para) +{ + int cnt = 0; + + /* 安装信号 */ + //将SIGUSR1信号与处理函数thread1_signal_handler绑定 + rt_signal_install(SIGUSR1, thread1_signal_handler);// + //解除对该信号的屏蔽,使其能够被接收 + rt_signal_unmask(SIGUSR1); + + /* 运行15次 */ + while(cnt < 15) + { + /* 线程1采用低优先级运行,一直打印计数值 */ + rt_kprintf("thread1 count : %d\n", cnt); + + cnt++; + rt_thread_mdelay(100); + } +} + +static rt_thread_t tid1 = RT_NULL; + +/* 信号示例的初始化 */ +int signal_work(void) +{ + /* 创建线程1 */ + tid1 = rt_thread_create("thread1",//线程名称 + thread1_entry,//线程入口函数 + RT_NULL,//线程入口函数参数 + THREAD_STACK_SIZE,//线程栈大小 + THREAD_PRIORITY,//线程优先级 + THREAD_TIMESLICE);//线程时间片大小 + + if(tid1 != RT_NULL) + { + rt_thread_startup(tid1); + } + + rt_thread_mdelay(300); + + /* 发送信号 SIGUSR1 给线程1 */ + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(signal_work, signal work); diff --git "a/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\347\254\224\350\256\260/\347\272\277\347\250\213\351\227\264\345\220\214\346\255\245\345\222\214\351\200\232\344\277\241.md" "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\347\254\224\350\256\260/\347\272\277\347\250\213\351\227\264\345\220\214\346\255\245\345\222\214\351\200\232\344\277\241.md" new file mode 100644 index 0000000000000000000000000000000000000000..c6398a03cd3954eafa2f7e156c20467574430fbf --- /dev/null +++ "b/2025/\347\254\2545\347\273\204(CanMV-K230)/\351\230\256\347\247\213\345\251\225/\347\254\224\350\256\260/\347\272\277\347\250\213\351\227\264\345\220\214\346\255\245\345\222\214\351\200\232\344\277\241.md" @@ -0,0 +1,258 @@ +# 线程间同步和通信 + +## 线程间同步 + +### 信号量 + +#### 信号量工作机制 + +信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。 + +信号量工作示意图如下图所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。 + +![信号量工作示意图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_work.png) + +#### 信号量控制块 + +在 RT-Thread 中,信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 struct rt_semaphore 表示。另外一种 C 表达方式 rt_sem_t,表示的是信号量的句柄,在 C 语言中的实现是指向信号量控制块的指针。信号量控制块结构的详细定义如下: + +```C +struct rt_semaphore +{ + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + rt_uint16_t value; /* 信号量的值 */ +}; +/* rt_sem_t 是指向 semaphore 结构体的指针类型 */ +typedef struct rt_semaphore* rt_sem_t; + +``` + +rt_semaphore 对象从 rt_ipc_object 中派生,由 IPC 容器所管理,信号量的最大值是 65535。 + +#### 信号量的管理方式 + +信号量控制块中含有信号量相关的重要参数,在信号量各种状态间起到纽带的作用。信号量相关接口如下图所示,对一个信号量的操作包含:创建 / 初始化信号量、获取信号量、释放信号量、删除 / 脱离信号量。 + +![信号量相关接口](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_ops.png) + +#### 信号量的使用场合 + +信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程、中断与线程间的同步中。 + +### 互斥量 + +#### 互斥量工作机制 + +互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。 + +互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,如下图时所示。这个特性与一般的二值信号量有很大的不同:在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。 + +![互斥量工作示意图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_work.png) + +使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓优先级翻转,即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。 + +![优先级反转 (M 为信号量)](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inversion.png) + +在 RT-Thread 操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承协议 (Sha, 1990)。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。 + +![优先级继承 (M 为互斥量)](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inherit.png) + +#### 互斥量控制块 + +在 RT-Thread 中,互斥量控制块是操作系统用于管理互斥量的一个数据结构,由结构体 struct rt_mutex 表示。另外一种 C 表达方式 rt_mutex_t,表示的是互斥量的句柄,在 C 语言中的实现是指互斥量控制块的指针。互斥量控制块结构的详细定义请见以下代码: + +```C +struct rt_mutex + { + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + + rt_uint16_t value; /* 互斥量的值 */ + rt_uint8_t original_priority; /* 持有线程的原始优先级 */ + rt_uint8_t hold; /* 持有线程的持有次数 */ + struct rt_thread *owner; /* 当前拥有互斥量的线程 */ + }; + /* rt_mutext_t 为指向互斥量结构体的指针类型 */ + typedef struct rt_mutex* rt_mutex_t; + +``` + +rt_mutex 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +#### 互斥量的管理方式 + +互斥量控制块中含有互斥相关的重要参数,在互斥量功能的实现中起到重要的作用。互斥量相关接口如下图所示,对一个互斥量的操作包含:创建 / 初始化互斥量、获取互斥量、释放互斥量、删除 / 脱离互斥量。 + +![互斥量相关接口](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_ops.png) + +#### 互斥量的使用场合 + +互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于: + +(1)线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。 + +(2)可能会由于多线程同步而造成优先级翻转的情况。 + +### 事件集 + +#### 事件集工作机制 + +事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。 + +RT-Thread 定义的事件集有以下特点: + +1)事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件; + +2)事件仅用于同步,不提供数据传输功能; + +3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。 + +在 RT-Thread 中,每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。 + +![事件集工作示意图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_work.png) + +#### 事件集控制块 + +在 RT-Thread 中,事件集控制块是操作系统用于管理事件的一个数据结构,由结构体 struct rt_event 表示。另外一种 C 表达方式 rt_event_t,表示的是事件集的句柄,在 C 语言中的实现是事件集控制块的指针。事件集控制块结构的详细定义请见以下代码: + +```C +struct rt_event +{ + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + + /* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */ + rt_uint32_t set; +}; +/* rt_event_t 是指向事件结构体的指针类型 */ +typedef struct rt_event* rt_event_t; + +``` + +rt_event 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +#### 事件集的管理方式 + +事件集控制块中含有与事件集相关的重要参数,在事件集功能的实现中起重要的作用。事件集相关接口如下图所示,对一个事件集的操作包含:创建 / 初始化事件集、发送事件、接收事件、删除 / 脱离事件集。 + +![事件相关接口](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_ops.png) + +#### 事件集的使用场合 + +事件集可使用于多种场合,它能够在一定程度上替代信号量,用于线程间同步。一个线程或中断服务例程发送一个事件给事件集对象,而后等待的线程被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作在事件未清除前,是不可累计的,而信号量的释放动作是累计的。事件的另一个特性是,接收线程可等待多种事件,即多个事件对应一个线程或多个线程。同时按照线程等待的参数,可选择是 “逻辑或” 触发还是 “逻辑与” 触发。这个特性也是信号量等所不具备的,信号量只能识别单一的释放动作,而不能同时等待多种类型的释放。如下图所示为多事件接收示意图: + +![多事件接收示意图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_use.png) + +## 线程间通信 + +### 邮箱 + +邮箱服务是实时操作系统中一种典型的线程间通信方法。举一个简单的例子,有两个线程,线程 1 检测按键状态并发送,线程 2 读取按键状态并根据按键的状态相应地改变 LED 的亮灭。这里就可以使用邮箱的方式进行通信,线程 1 将按键的状态作为邮件发送到邮箱,线程 2 在邮箱中读取邮件获得按键状态并对 LED 执行亮灭操作。 + +这里的线程 1 也可以扩展为多个线程。例如,共有三个线程,线程 1 检测并发送按键状态,线程 2 检测并发送 ADC 采样信息,线程 3 则根据接收的信息类型不同,执行不同的操作。 + +#### 邮箱的工作机制 + +RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如下图所示,线程或中断服务例程把一封 4 字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。 + +![邮箱工作示意图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_work.png) + +#### 邮箱控制块 + +在 RT-Thread 中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体 struct rt_mailbox 表示。另外一种 C 表达方式 rt_mailbox_t,表示的是邮箱的句柄,在 C 语言中的实现是邮箱控制块的指针。邮箱控制块结构的详细定义请见以下代码: + +```C +struct rt_mailbox +{ + struct rt_ipc_object parent; + + rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */ + rt_uint16_t size; /* 邮箱缓冲区的大小 */ + + rt_uint16_t entry; /* 邮箱中邮件的数目 */ + rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */ + rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ +}; +typedef struct rt_mailbox* rt_mailbox_t; + +``` + +rt_mailbox 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +#### 邮箱的管理方式 + +邮箱控制块是一个结构体,其中含有事件相关的重要参数,在邮箱的功能实现中起重要的作用。邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。 + +![邮箱相关接口](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_ops.png) + +### 消息队列 + +消息队列是另一种常用的线程间通讯方式,是邮箱的扩展。可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。 + +#### 消息队列的工作机制 + +消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。 + +如下图所示,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO)。 + +![消息队列工作示意图](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_work.png) + +#### 消息队列控制块 + +在 RT-Thread 中,消息队列控制块是操作系统用于管理消息队列的一个数据结构,由结构体 struct rt_messagequeue 表示。另外一种 C 表达方式 rt_mq_t,表示的是消息队列的句柄,在 C 语言中的实现是消息队列控制块的指针。消息队列控制块结构的详细定义请见以下代码: + +```C +struct rt_messagequeue +{ + struct rt_ipc_object parent; + + void* msg_pool; /* 指向存放消息的缓冲区的指针 */ + + rt_uint16_t msg_size; /* 每个消息的长度 */ + rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */ + + rt_uint16_t entry; /* 队列中已有的消息数 */ + + void* msg_queue_head; /* 消息链表头 */ + void* msg_queue_tail; /* 消息链表尾 */ + void* msg_queue_free; /* 空闲消息链表 */ + + rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ +}; +typedef struct rt_messagequeue* rt_mq_t; + +``` + +#### 消息队列的管理方式 + +消息队列控制块是一个结构体,其中含有消息队列相关的重要参数,在消息队列的功能实现中起重要的作用。消息队列的相关接口如下图所示,对一个消息队列的操作包含:创建消息队列 - 发送消息 - 接收消息 - 删除消息队列。 + +![消息队列相关接口](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.png) + +### 信号 + +信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。 + +#### 信号的工作机制 + +信号在 RT-Thread 中用作异步通信,POSIX 标准定义了 sigset_t 类型来定义一个信号集,然而 sigset_t 类型在不同的系统可能有不同的定义方式,在 RT-Thread 中,将 sigset_t 定义成了 unsigned long 型,并命名为 rt_sigset_t,应用程序能够使用的信号为 SIGUSR1(10)和 SIGUSR2(12)。 + +信号本质是软中断,用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。一个线程不必通过任何操作来等待信号的到达,事实上,线程也不知道信号到底什么时候到达,线程之间可以互相通过调用 rt_thread_kill() 发送软中断信号。 + +收到信号的线程对各种信号有不同的处理方法,处理方法可以分为三类: + +第一种是类似中断的处理程序,对于需要处理的信号,线程可以指定处理函数,由该函数来处理。 + +第二种方法是,忽略某个信号,对该信号不做任何处理,就像未发生过一样。 + +第三种方法是,对该信号的处理保留系统的默认值。 + +如下图所示,假设线程 1 需要对信号进行处理,首先线程 1 安装一个信号并解除阻塞,并在安装的同时设定了对信号的异常处理方式;然后其他线程可以给线程 1 发送信号,触发线程 1 对该信号的处理。 + +![信号工作机制](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_work.png) + +当信号被传递给线程 1 时,如果它正处于挂起状态,那会把状态改为就绪状态去处理对应的信号。如果它正处于运行状态,那么会在它当前的线程栈基础上建立新栈帧空间去处理对应的信号,需要注意的是使用的线程栈大小也会相应增加。 + +#### 信号的管理方式 + +对于信号的操作,有以下几种:安装信号、阻塞信号、阻塞解除、信号发送、信号等待。信号的接口详见下图: + +![信号相关接口](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_ops.png) \ No newline at end of file