信号量相关接口

创建信号量

当创建一个信号量时,内核首先创建一个信号量控制块,然后对该控制块进行基本的初始化工作,创建信号量使用下面的函数接口:

  1. rt_sem_t rt_sem_create (const char* name, rt_uint32_t value, rt_uint8_t flag);

当调用这个函数时,系统将先分配一个semaphore对象,并初始化这个对象,然后初始化IPC对象以及与semaphore相关的部分。在创建信号量指定的参数中,信号量标志参数决定了当信号量不可用时,多个线程等待的排队方式。当选择FIFO方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当选择PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。

函数参数


  1. 参数 描述

  1. name 信号量名称;
  2.  
  3. value 信号量初始值;
  4.  
  5. flag 信号量标志,取值可以使用如下类型:

  1. #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO方式*/
  2. #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/

函数返回

创建成功返回创建的信号量控制块指针;否则返回RT_NULL。

创建信号量的例程如下例所示:

  1. /*
  2. * 程序清单:动态信号量
  3. *
  4. * 这个例子中将创建一个动态信号量(初始值为0)及一个动态线程,在这个动态线程中
  5. * 将试图采用超时方式去持有信号量,应该超时返回。然后这个线程释放一次信号量,
  6. * 并在后面继续采用永久等待方式去持有信号量, 成功获得信号量后返回。
  7. */
  8. #include <rtthread.h>
  9. #include "tc_comm.h"
  10.  
  11. /* 指向线程控制块的指针 */
  12. static rt_thread_t tid = RT_NULL;
  13. /* 指向信号量的指针 */
  14. static rt_sem_t sem = RT_NULL;
  15. /* 线程入口 */
  16. static void thread_entry(void* parameter)
  17. {
  18. rt_err_t result;
  19. rt_tick_t tick;
  20.  
  21. /* 获得当前的OS Tick */
  22. tick = rt_tick_get();
  23.  
  24. /* 试图持有一个信号量,如果10个OS Tick依然没拿到,则超时返回 */
  25. result = rt_sem_take(sem, 10);
  26. if (result == -RT_ETIMEOUT)
  27. {
  28. /* 判断是否刚好过去10个OS Tick */
  29. if (rt_tick_get() - tick != 10)
  30. {
  31. /* 如果失败,则测试失败 */
  32. tc_done(TC_STAT_FAILED);
  33. rt_sem_delete(sem);
  34. return;
  35. }
  36. rt_kprintf("take semaphore timeout\n");
  37. }
  38. else
  39. {
  40. /* 因为并没释放信号量,应该是超时返回,否则测试失败 */
  41. tc_done(TC_STAT_FAILED);
  42. rt_sem_delete(sem);
  43. return;
  44. }
  45.  
  46. /* 释放一次信号量 */
  47. rt_sem_release(sem);
  48.  
  49. /* 继续持有信号量,并永远等待直到持有到信号量 */
  50. result = rt_sem_take(sem, RT_WAITING_FOREVER);
  51. if (result != RT_EOK)
  52. {
  53. /* 返回不正确,测试失败 */
  54. tc_done(TC_STAT_FAILED);
  55. rt_sem_delete(sem);
  56. return;
  57. }
  58.  
  59. /* 测试成功 */
  60. tc_done(TC_STAT_PASSED);
  61. /* 删除信号量 */
  62. rt_sem_delete(sem);
  63. }
  64.  
  65. int semaphore_dynamic_init()
  66. {
  67. /* 创建一个信号量,初始值是0 */
  68. sem = rt_sem_create("sem", 0, RT_IPC_FLAG_FIFO);
  69. if (sem == RT_NULL)
  70. {
  71. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  72. return 0;
  73. }
  74.  
  75. /* 创建线程 */
  76. tid = rt_thread_create("thread",
  77. thread_entry, RT_NULL, /* 线程入口是thread_entry, 参数RT_NULL */
  78. THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
  79. if (tid != RT_NULL)
  80. rt_thread_startup(tid);
  81. else
  82. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  83.  
  84. return 0;
  85. }
  86.  
  87. #ifdef RT_USING_TC
  88. static void _tc_cleanup()
  89. {
  90. /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
  91. rt_enter_critical();
  92.  
  93. /* 删除线程 */
  94. if (tid != RT_NULL && tid->stat != RT_THREAD_CLOSE)
  95. {
  96. rt_thread_delete(tid);
  97.  
  98. /* 删除信号量 */
  99. rt_sem_delete(sem);
  100. }
  101.  
  102. /* 调度器解锁 */
  103. rt_exit_critical();
  104.  
  105. /* 设置TestCase状态 */
  106. tc_done(TC_STAT_PASSED);
  107. }
  108.  
  109. int _tc_semaphore_dynamic()
  110. {
  111. /* 设置TestCase清理回调函数 */
  112. tc_cleanup(_tc_cleanup);
  113. semaphore_dynamic_init();
  114.  
  115. /* 返回TestCase运行的最长时间 */
  116. return 100;
  117. }
  118. /* 输出函数命令到finsh shell中 */
  119. FINSH_FUNCTION_EXPORT(_tc_semaphore_dynamic, a dynamic semaphore example);
  120. #else
  121. /* 用户应用入口 */
  122. int rt_application_init()
  123. {
  124. semaphore_dynamic_init();
  125.  
  126. return 0;
  127. }
  128. #endif

删除信号量

系统不再使用信号量时,可通过删除信号量以释放系统资源。删除信号量使用下面的函数接口:

  1. rt_err_t rt_sem_delete (rt_sem_t sem);

调用这个函数时,系统将删除这个信号量。如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是-RT_ERROR),然后再释放信号量的内存资源。


  1. 参数 描述

  1. sem rt_sem_create创建处理的信号量对象。

函数返回

RT_EOK

初始化信号量

对于静态信号量对象,它的内存空间在编译时期就被编译器分配出来,放在数据段或ZI段上,此时使用信号量就不再需要使用rt_sem_create接口来创建它,而只需在使用前对它进行初始化即可。初始化信号量对象可使用下面的函数接口:

  1. rt_err_t rt_sem_init (rt_sem_t sem, const char* name, rt_uint32_t value, rt_uint8_t flag);

当调用这个函数时,系统将对这个semaphore对象进行初始化,然后初始化IPC对象以及与semaphore相关的部分。在初始化信号量指定的参数中,信号量标志参数决定了当信号量不可用时,多个线程等待的方式。当选择FIFO方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当选择PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。

函数参数


  1. 参数 描述

  1. sem 信号量对象的句柄;
  2.  
  3. name 信号量名称;
  4.  
  5. value 信号量初始值;
  6.  
  7. flag 信号量标志。

  1. #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO方式*/
  2. #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/

函数返回

RT_EOK

初始化信号量的例程如下例所示:

  1. /*
  2. * 程序清单:静态信号量
  3. *
  4. * 这个例子中将创建一个静态信号量(初始值为0 )及一个静态线程,在这个静态线程中
  5. * 将试图采用超时方式去获取信号量,应该超时返回。然后这个线程释放一次信号量,并
  6. * 在后面继续采用永久等待方式去获取信号量, 成功获得信号量后返回。
  7. */
  8. #include <rtthread.h>
  9. #include "tc_comm.h"
  10.  
  11. /* 线程控制块及栈 */
  12. static struct rt_thread thread;
  13. static rt_uint8_t thread_stack[THREAD_STACK_SIZE];
  14. /* 信号量控制块 */
  15. static struct rt_semaphore sem;
  16.  
  17. /* 线程入口 */
  18. static void thread_entry(void* parameter)
  19. {
  20. rt_err_t result;
  21. rt_tick_t tick;
  22.  
  23. /* 获得当前的OS Tick */
  24. tick = rt_tick_get();
  25.  
  26. /* 试图持有信号量,最大等待10个OS Tick后返回 */
  27. result = rt_sem_take(&sem, 10);
  28. if (result == -RT_ETIMEOUT)
  29. {
  30. /* 超时后判断是否刚好是10个OS Tick */
  31. if (rt_tick_get() - tick != 10)
  32. {
  33. tc_done(TC_STAT_FAILED);
  34. rt_sem_detach(&sem);
  35. return;
  36. }
  37. rt_kprintf("take semaphore timeout\n");
  38. }
  39. else
  40. {
  41. /* 因为没有其他地方释放信号量,所以不应该成功持有信号量,否则测试失败 */
  42. tc_done(TC_STAT_FAILED);
  43. rt_sem_detach(&sem);
  44. return;
  45. }
  46.  
  47. /* 释放一次信号量 */
  48. rt_sem_release(&sem);
  49.  
  50. /* 永久等待方式持有信号量 */
  51. result = rt_sem_take(&sem, RT_WAITING_FOREVER);
  52. if (result != RT_EOK)
  53. {
  54. /* 不成功则测试失败 */
  55. tc_done(TC_STAT_FAILED);
  56. rt_sem_detach(&sem);
  57. return;
  58. }
  59.  
  60. /* 测试通过 */
  61. tc_done(TC_STAT_PASSED);
  62. /* 脱离信号量对象 */
  63. rt_sem_detach(&sem);
  64. }
  65.  
  66. int semaphore_static_init()
  67. {
  68. rt_err_t result;
  69.  
  70. /* 初始化信号量,初始值是0 */
  71. result = rt_sem_init(&sem, "sem", 0, RT_IPC_FLAG_FIFO);
  72. if (result != RT_EOK)
  73. {
  74. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  75. return 0;
  76. }
  77.  
  78. /* 初始化线程1 */
  79. result = rt_thread_init(&thread, "thread", /* 线程名:thread */
  80. thread_entry, RT_NULL, /* 线程的入口是thread_entry,参数是RT_NULL*/
  81. &thread_stack[0], sizeof(thread_stack), /* 线程栈thread_stack */
  82. THREAD_PRIORITY, 10);
  83. if (result == RT_EOK) /* 如果返回正确,启动线程1 */
  84. rt_thread_startup(&thread);
  85. else
  86. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  87.  
  88. return 0;
  89. }
  90.  
  91. #ifdef RT_USING_TC
  92. static void _tc_cleanup()
  93. {
  94. /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
  95. rt_enter_critical();
  96.  
  97. /* 执行线程脱离 */
  98. if (thread.stat != RT_THREAD_CLOSE)
  99. {
  100. rt_thread_detach(&thread);
  101.  
  102. /* 执行信号量对象脱离 */
  103. rt_sem_detach(&sem);
  104. }
  105.  
  106. /* 调度器解锁 */
  107. rt_exit_critical();
  108.  
  109. /* 设置TestCase状态 */
  110. tc_done(TC_STAT_PASSED);
  111. }
  112.  
  113. int _tc_semaphore_static()
  114. {
  115. /* 设置TestCase清理回调函数 */
  116. tc_cleanup(_tc_cleanup);
  117. semaphore_static_init();
  118.  
  119. /* 返回TestCase运行的最长时间 */
  120. return 100;
  121. }
  122. /* 输出函数命令到finsh shell中 */
  123. FINSH_FUNCTION_EXPORT(_tc_semaphore_static, a static semaphore example);
  124. #else
  125. /* 用户应用入口 */
  126. int rt_application_init()
  127. {
  128. thread_static_init();
  129.  
  130. return 0;
  131. }
  132. #endif

脱离信号量

脱离信号量就是让信号量对象从内核对象管理器中移除掉。脱离信号量使用下面的函数接口:

  1. rt_err_t rt_sem_detach (rt_sem_t sem);

使用该函数后,内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中删除。原来挂起在信号量上的等待线程将获得-RT_ERROR 的返回值。


  1. 参数 描述

  1. sem 信号量对象的句柄。

函数返回

RT_EOK

获取信号量

线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,线程将获得信号量,并且相应的信号量值都会减1,获取信号量使用下面的函数接口:

  1. rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

在调用这个函数时,如果信号量的值等于零,那么说明当前信号量资源实例不可用,申请该信号量的线程将根据time参数的情况选择直接返回、或挂起等待一段时间、或永久等待,直到其他线程或中断释放该信号量。如果在参数time指定的时间内依然得不到信号量,线程将超时返回,返回值是-RT_ETIMEOUT。

函数参数


  1. 参数 描述

  1. sem 信号量对象的句柄;
  2.  
  3. time 指定的等待时间,单位是操作系统时钟节拍(OS Tick)。

函数返回

成功获得信号量返回RT_EOK;超时依然未获得信号量返回-RT_ETIMEOUT;其他错误返回-RT_ERROR。

无等待获取信号量

当用户不想在申请的信号量上挂起线程进行等待时,可以使用无等待方式获取信号量,无等待获取信号量使用下面的函数接口:

  1. rt_err_t rt_sem_trytake(rt_sem_t sem);

这个函数与rt_sem_take(sem, 0) 的作用相同,即当线程申请的信号量资源实例不可用的时候,它不会等待在该信号量上,而是直接返回-RT_ETIMEOUT。

函数参数


  1. 参数 描述

  1. sem 信号量对象的句柄。

函数返回

成功获取信号量返回RT_EOK;否则返回RT_ETIMEOUT。

释放信号量

当线程完成资源的访问后,应尽快释放它持有的信号量,使得其他线程能获得该信号量。释放信号量使用下面的函数接口:

  1. rt_err_t rt_sem_release(rt_sem_t sem);

当信号量的值等于零时,并且有线程等待这个信号量时,将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量。否则将把信号量的值加一。

函数参数


  1. 参数 描述

  1. sem 信号量对象的句柄。

函数返回

RT_EOK

下面是一个使用信号量的例程,如下例所示:

  1. /*
  2. * 程序清单:生产者消费者例子
  3. *
  4. * 这个例子中将创建两个线程用于实现生产者消费者问题
  5. */
  6. #include <rtthread.h>
  7. #include "tc_comm.h"
  8.  
  9. /* 定义最大5个元素能够被产生 */
  10. #define MAXSEM 5
  11.  
  12. /* 用于放置生产的整数数组 */
  13. rt_uint32_t array[MAXSEM];
  14. /* 指向生产者、消费者在array数组中的读写位置 */
  15. static rt_uint32_t set, get;
  16.  
  17. /* 指向线程控制块的指针 */
  18. static rt_thread_t producer_tid = RT_NULL;
  19. static rt_thread_t consumer_tid = RT_NULL;
  20.  
  21. struct rt_semaphore sem_lock;
  22. struct rt_semaphore sem_empty, sem_full;
  23.  
  24. /* 生产者线程入口 */
  25. void producer_thread_entry(void* parameter)
  26. {
  27. rt_int32_t cnt = 0;
  28.  
  29. /* 运行100次 */
  30. while( cnt < 100)
  31. {
  32. /* 获取一个空位 */
  33. rt_sem_take(&sem_empty, RT_WAITING_FOREVER);
  34.  
  35. /* 修改array内容,上锁 */
  36. rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
  37. array[set%MAXSEM] = cnt + 1;
  38. rt_kprintf("the producer generates a number: %d\n",
  39. array[set%MAXSEM]);
  40. set++;
  41. rt_sem_release(&sem_lock);
  42.  
  43. /* 发布一个满位 */
  44. rt_sem_release(&sem_full);
  45. cnt++;
  46.  
  47. /* 暂停一段时间 */
  48. rt_thread_delay(50);
  49. }
  50.  
  51. rt_kprintf("the producer exit!\n");
  52. }
  53.  
  54. /* 消费者线程入口 */
  55. void consumer_thread_entry(void* parameter)
  56. {
  57. rt_uint32_t no;
  58. rt_uint32_t sum = 0;
  59.  
  60. /* 第n个线程,由入口参数传进来 */
  61. no = (rt_uint32_t)parameter;
  62.  
  63. while(1)
  64. {
  65. /* 获取一个满位 */
  66. rt_sem_take(&sem_full, RT_WAITING_FOREVER);
  67.  
  68. /* 临界区,上锁进行操作 */
  69. rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
  70. sum += array[get%MAXSEM];
  71. rt_kprintf("the consumer[%d] get a number:%d\n",
  72. no, array[get%MAXSEM]);
  73. get++;
  74. rt_sem_release(&sem_lock);
  75.  
  76. /* 释放一个空位 */
  77. rt_sem_release(&sem_empty);
  78.  
  79. /* 生产者生产到100个数目,停止,消费者线程相应停止 */
  80. if (get == 100) break;
  81.  
  82. /* 暂停一小会时间 */
  83. rt_thread_delay(10);
  84. }
  85.  
  86. rt_kprintf("the consumer[%d] sum is %d \n ", no, sum);
  87. rt_kprintf("the consumer[%d] exit!\n");
  88. }
  89.  
  90. int semaphore_producer_consumer_init()
  91. {
  92. /* 初始化3个信号量 */
  93. rt_sem_init(&sem_lock , "lock", 1, RT_IPC_FLAG_FIFO);
  94. rt_sem_init(&sem_empty,"empty", MAXSEM, RT_IPC_FLAG_FIFO);
  95. rt_sem_init(&sem_full , "full", 0, RT_IPC_FLAG_FIFO);
  96.  
  97. /* 创建线程1 */
  98. producer_tid = rt_thread_create("producer",
  99. producer_thread_entry, /* 线程入口是producer_thread_entry */
  100. RT_NULL, /* 入口参数是RT_NULL */
  101. THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
  102. if (producer_tid != RT_NULL)
  103. rt_thread_startup(producer_tid);
  104. else
  105. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  106.  
  107. /* 创建线程2 */
  108. consumer_tid = rt_thread_create("consumer",
  109. consumer_thread_entry,/* 线程入口是consumer_thread_entry */
  110. RT_NULL, /* 入口参数是RT_NULL */
  111. THREAD_STACK_SIZE, THREAD_PRIORITY + 1, THREAD_TIMESLICE);
  112. if (consumer_tid != RT_NULL)
  113. rt_thread_startup(consumer_tid);
  114. else
  115. tc_stat(TC_STAT_END | TC_STAT_FAILED);
  116.  
  117. return 0;
  118. }
  119.  
  120. #ifdef RT_USING_TC
  121. static void _tc_cleanup()
  122. {
  123. /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
  124. rt_enter_critical();
  125.  
  126. /* 删除线程 */
  127. if (producer_tid != RT_NULL && producer_tid->stat != RT_THREAD_CLOSE)
  128. rt_thread_delete(producer_tid);
  129. if (consumer_tid != RT_NULL && consumer_tid->stat != RT_THREAD_CLOSE)
  130. rt_thread_delete(consumer_tid);
  131.  
  132. /* 调度器解锁 */
  133. rt_exit_critical();
  134.  
  135. /* 设置TestCase状态 */
  136. tc_done(TC_STAT_PASSED);
  137. }
  138.  
  139. int _tc_semaphore_producer_consumer()
  140. {
  141. /* 设置TestCase清理回调函数 */
  142. tc_cleanup(_tc_cleanup);
  143. semaphore_producer_consumer_init();
  144.  
  145. /* 返回TestCase运行的最长时间 */
  146. return 100;
  147. }
  148. /* 输出函数命令到finsh shell中 */
  149. FINSH_FUNCTION_EXPORT(_tc_semaphore_producer_consumer, producer and consumer example);
  150. #else
  151. /* 用户应用入口 */
  152. int rt_application_init()
  153. {
  154. semaphore_producer_consumer_init();
  155.  
  156. return 0;
  157. }
  158. #endif

在这个例子中,semaphore是作为一种锁的形式存在,当要访问临界资源:ring buffer时,通过持有semaphore 的形式阻止其他线程进入(如果其他线程也打算进入,将在这里被挂起)。