NuttX的学习笔记 12

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yt454287063/article/details/81230483

#2.5 Counting Semaphore Interfaces

Semaphores. Semaphores are the basis for synchronization and mutual exclusion in NuttX. NuttX supports POSIX semaphores.

信号量,信号量是NuttX中同步和互斥的基础。 NuttX支持POSIX信号量。

Semaphores are the preferred mechanism for gaining exclusive access to a resource. sched_lock() and sched_unlock() can also be used for this purpose. However, sched_lock() and sched_unlock() have other undesirable side-affects in the operation of the system: sched_lock() also prevents higher-priority tasks from running that do not depend upon the semaphore-managed resource and, as a result, can adversely affect system response times.

信号是获得对资源的独占访问的首选机制,sched_lock()sched_unlock()也可以用于此目的,然而sched_lock()sched_unlock()在系统的操作中具有其他不期望的副作用:sched_lock()还可以阻止优先级更高的、不依赖于信号量管理的任务运行,因此可能会对系统响应时间产生负面影响。

Priority Inversion. Proper use of semaphores avoids the issues of sched_lock(). However, consider the following example:
Some low-priority task, Task C, acquires a semaphore in order to get exclusive access to a protected resource.
Task C is suspended to allow some high-priority task,Task A, to execute.
Task A attempts to acquire the semaphore held by Task C and gets blocked until Task C relinquishes the semaphore.
Task C is allowed to execute again, but gets suspended by some medium-priority Task B.

优先级翻转。正确使用信号量可以避免sched_lock()的问题。但是,请考虑以下示例:
1.某些低优先级任务(任务C)获取信号量以获得对受保护资源的独占访问权。
2.暂停任务C以允许执行某些高优先级任务(任务A)。
3.任务A尝试获取任务C持有的信号量并被阻塞,直到任务C放弃信号量。
4.允许任务C再次执行,但被某些中优先级任务B暂停。

At this point, the high-priority Task A cannot execute until Task B (and possibly other medium-priority tasks) completes and until Task C relinquishes the semaphore. In effect, the high-priority task, Task A behaves as though it were lower in priority than the low-priority task, Task C! This phenomenon is called priority inversion.

此时,高优先级任务A无法执行,直到任务B(可能还有其他中优先级任务)完成,直到任务C放弃信号量。 实际上,高优先级任务,任务A的行为就好像优先级低于低优先级任务,任务C! 这种现象称为优先级倒置。

Some operating systems avoid priority inversion by automatically increasing the priority of the low-priority Task C (the operable buzz-word for this behavior is priority inheritance). NuttX supports this behavior, but only if CONFIG_PRIORITY_INHERITANCE is defined in your OS configuration file. If CONFIG_PRIORITY_INHERITANCE is not defined, then it is left to the designer to provide implementations that will not suffer from priority inversion.

一些操作系统通过自动增加低优先级任务C的优先级来避免优先级反转(此行为的可操作的流行词是优先级继承)。NuttX支持这种行为,但前提是必须在OS配置文件中定义CONFIG_PRIORITY_INHERITANCE。如果CONFIG_PRIORITY_INHERITANCE没有定义,则应该由设计器提供不受优先级反转影响的实现。(注,这里说到的这个定义在> RTOS Features下)

Enable priority inheritance

as examples:

  • Implement all tasks that need the semaphore-managed resources at the same priority level,
  • Boost the priority of the low-priority task before the semaphore is acquired, or
  • Use sched_lock() in the low-priority task.

举个例子:

  • 以相同的优先级实现需要信号量管理资源的所有任务,
  • 在获取信号量之前提升低优先级任务的优先级,或者
  • 在低优先级任务中使用sched_lock()。

Priority Inheritance. As mentioned, NuttX does support priority inheritance provided that CONFIG_PRIORITY_INHERITANCE is defined in your OS configuration file. However, the implementation and configuration of the priority inheritance feature is sufficiently complex that more needs to be said. How can a feature that can be described by a single, simple sentence require such a complex implementation:

优先级继承。 如前所述,只要在OS配置文件中定义了CONFIG_PRIORITY_INHERITANCE,NuttX就支持优先级继承。 但是,优先级继承特性的实现和配置非常复杂,需要进一步说明。 如何通过单个简单句子描述的功能需要如此复杂的实现:

  • CONFIG_SEM_PREALLOCHOLDERS. First of all, in NuttX priority inheritance is implement on POSIX counting semaphores. The reason for this is that these semaphores are the most primitive waiting mechanism in NuttX; Most other waiting facilities are based on semaphores. So if priority inheritance is implemented for POSIX counting semaphores, then most NuttX waiting mechanisms will have this capability.
    Complexity arises because counting semaphores can have numerous holders of semaphore counts. Therefore, in order to implement priority inheritance across all holders, then internal data structures must be allocated to manage the various holders associated with a semaphore. The setting CONFIG_SEM_PREALLOCHOLDERS defines the maximum number of different threads (minus one per semaphore instance) that can take counts on a semaphore with priority inheritance support. This setting defines the size of a single pool of pre-allocated structures. It may be set to zero if priority inheritance is disabled OR if you are only using semaphores as mutexes (only one holder) OR if no more than two threads participate using a counting semaphore.
    The cost associated with setting CONFIG_SEM_PREALLOCHOLDERS is slightly increased code size and around 6-12 bytes times the value of CONFIG_SEM_PREALLOCHOLDERS.
  • CONFIG_SEM_PREALLOCHOLDERS. 首先,在NuttX中,优先级继承是在POSIX计数信号量上实现的。原因是这些信号量是NuttX中最原始的等待机制;大多数其他的等待设施都是基于信号量。因此,如果为POSIX计数信号量实现了优先级继承,那么大多数的NuttX等待机制将具有这种能力。

    复杂性的产生是因为计算信号量可以拥有许多信号量计数器。 因此,为了在所有持有者之间实现优先级继承,必须分配内部数据结构来管理与信号量相关联的各种持有者。 设置CONFIG_SEM_PREALLOCHOLDERS定义了可以对具有优先级继承支持的信号量进行计数的不同线程的最大数量(减去每个信号量实例一个)。 此设置定义单个预分配结构池的大小。 如果禁用优先级继承,或者如果仅将信号量用作互斥锁(仅一个持有者),或者如果使用计数信号量不超过两个线程参与,则可以将其设置为零。

    设置CONFIG_SEM_PREALLOCHOLDERS将会略微增加了代码大小,大约是6-12个字节的CONFIG_SEM_PREALLOCHOLDERS倍。

  • CONFIG_SEM_NNESTPRIO: In addition, there may be multiple threads of various priorities that need to wait for a count from the semaphore. These, the lower priority thread holding the semaphore may have to be boosted numerous time and, to make things more complex, will have to keep track of all of the boost priorities values in in order to correctly restore the priorities after a count has been handed out to the higher priority thread. The CONFIG_SEM_NNESTPRIO defines the size of an array, one array per active thread. This setting is the maximum number of higher priority threads (minus 1) than can be waiting for another thread to release a count on a semaphore. This value may be set to zero if no more than one thread is expected to wait for a semaphore.
    The cost associated with setting CONFIG_SEM_NNESTPRIO is slightly increased code size and (CONFIG_SEM_PREALLOCHOLDERS + 1) times the maximum number of active threads.
  • CONFIG_SEM_NNESTPRIO此外,可能有多个不同优先级的线程需要等待来自信号量的计数。这些持有信号量的优先级较低的线程可能需要多次提升,必须跟踪所有的优先级值,以便在计数完成后正确恢复优先级,结果使事情变得更加复杂。超出优先级的线程。 CONFIG_SEM_NNESTPRIO定义数组的大小,每个活动线程一个数组。 此设置是可等待另一个线程释放信号量计数的最高优先级线程数(减1)。 如果预计不超过一个线程等待信号量,则此值可以设置为零。
    设置CONFIG_SEM_NNESTPRIO将会略微增加了代码大小,具体为(CONFIG_SEM_PREALLOCHOLDERS + 1)乘以最大活动线程数。
  • Increased Susceptibility to Bad Thread Behavior. These various structures tie the semaphore implementation more tightly to the behavior of the implementation. For examples, if a thread executes while holding counts on a semaphore, or if a thread exits without call sem_destroy() then. Or what if the thread with the boosted priority re-prioritizes itself? The NuttX implement of priority inheritance attempts to handle all of these types of corner cases, but it is very likely that some are missed. The worst case result is that memory could by stranded within the priority inheritance logic.
  • 增加对不良线程行为的敏感性。 这些不同的结构将信号量实现与实现的行为更紧密地联系在一起。 例如,如果线程在对信号量持有计数时执行,或者如果线程退出而没有调用sem_destroy(),那么。 或者如果具有提升优先级的线程重新优先考虑自己呢? NuttX优先级继承的实现尝试处理所有这些类型的极端情况,但很可能会遗漏一些。 最糟糕的情况是,内存可能滞留在优先级继承逻辑中。

Locking versus Signaling Semaphores. Semaphores (and mutexes) may be used for many different purposes. One typical use of for mutual exclusion and locking of resources: In this usage, the thread that needs exclusive access to a resources takes the semaphore to get access to the resource. The same thread subsequently releases the seamphore count when it no longer needs exclusive access. Priority inheritance is intended just for this usage case.

In a different usage case, a semaphore may to be used to signal an event: One thread A waits on a semaphore for an event to occur. When the event occurs, another thread B will post the semaphore waking the waiting thread A. This is a completely different usage model; notice that in the mutual exclusion case, the same thread takes and posts the semaphore. In the signaling case, one thread takes the seamphore and a different thread posts the samphore. Priority inheritance should never be used in this signaling case. Subtle, strange behaviors may result.

When priority inheritance is enabled with CONFIG_PRIORITY_INHERITANCE, the default protocol for the semaphore will be to use priority inheritance. For signaling semaphores, priority inheritance must be explicitly disabled by calling sem_setprotocol with SEM_PRIO_NONE. For the case of pthread mutexes, pthread_mutexattr_setprotocol with PTHREAD_PRIO_NONE.

**锁与信号量。**信号量(和互斥量)可用于许多不同的目的。 互斥和锁定资源的一种典型用法:对资源进行独占访问的线程使用信号量来访问资源。 当不再需要独占访问时,线程会释放seamphore计数。 优先级继承仅适用于这种情况。

在不同的使用情况下,可以使用信号量来发信号通知:一个线程A在信号量上等待事件发生。 当事件发生时,另一个线程B将发布唤醒等待线程A的信号量。这是一个完全不同的使用模型; 请注意,在互斥情况下,相同的线程会获取并发布信号量。 在信号情况下,一个线程占用了seamphore,另一个线程发布了samphore。 在此信令情况下绝不应使用优先级继承。 可能会导致微妙,奇怪的行为。

使用CONFIG_PRIORITY_INHERITANCE启用优先级继承时,信号量的默认协议将使用优先级继承。 对于信令信号量,必须通过使用SEM_PRIO_NONE调用sem_setprotocol来显式禁用优先级继承。 对于pthread互斥锁的情况,pthread_mutexattr_setprotocolPTHREAD_PRIO_NONE

##2.5.1 sem_init

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

此函数初始化UN-NAMED信号量sem。在成功调用sem_init()之后,信号量可用于后续调用sem_wait()sem_post()sem_trywait()。信号量在摧毁之前仍然可用。
只有sem本身可用于执行同步。 未定义的情况下调用sem_wait()sem_trywait()sem_post()sem_destroy()时引用sem副本的结果。

##2.5.2 sem_destroy

#include <semaphore.h>
int sem_destroy(sem_t *

此函数用于销毁sem指示的未命名信号量。 只有使用sem_init()创建的信号量可以使用sem_destroy()销毁。 使用命名信号量调用sem_destroy()的效果是未定义的。 后续使用信号量sem的效果是未定义的,直到通过另一次调用sem_init()重新初始化sem。

破坏当前阻止其他任务的信号量的效果是不确定的。

##2.5.3 sem_open

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, …);

此函数在命名信号量和任务之间建立连接。 在使用信号量名称调用sem_open()之后,任务可以使用此调用返回的地址引用与name关联的信号量。 信号量可用于后续调用sem_wait()sem_trywait()sem_post()。 在通过成功调用sem_close()关闭信号量之前,信号量仍然可用。

如果一个任务使用相同的名称多次调用sem_open(),则返回相同的信号量地址(前提是没有调用sem_unlink())。

##2.5.4 sem_close

#include <semaphore.h>
int sem_close(sem_t *sem);

调用此函数以指示调用任务已完成指定的命名信号量sem。 sem_close()释放系统为此命名信号量分配的所有系统资源。

如果没有通过调用sem_unlink()删除信号量,则sem_close()对命名信号量没有影响。 但是,当命名的信号量完全取消链接时,当最后一个任务关闭时,信号量将消失。

必须注意避免删除另一个调用任务已锁定的信号量的风险。

##2.5.5 sem_unlink

#include <semaphore.h>
int sem_unlink(const char *name);

此函数将删除输入名称参数指定的信号量。 如果一个或多个任务在调用sem_unlink()时打开名称命名的信号量,则推迟信号量的销毁,直到调用sem_close()销毁所有引用。

##2.5.6 sem_wait

#include <semaphore.h>
int sem_wait(sem_t *sem);

此函数尝试锁定sem引用的信号量。 如果信号量已被另一个任务锁定,则在成功获取锁定或呼叫被信号中断之前,调用任务将不会返回。


写不动了。直接看代码吧。

void sem_test(void)
{
  pthread_t waiter_thread1 = (pthread_t)0;
  pthread_t waiter_thread2 = (pthread_t)0;
  pthread_t poster_thread = (pthread_t)0;
  
  struct sched_param sparam;
  int prio_min;
  int prio_max;
  int prio_mid;
  pthread_attr_t attr;
  int status;

  printf("sem_test: Initializing semaphore to 0\n");
  sem_init(&sem, 0, 0);

这里调用了函数sem_init(),如上文所将,这里初始化了变量sem,这个变量在定义后到现在没有发现赋值。看看NuttX是怎么做的。点进去,发现函数nxsem_init():里面进行赋值:

sem->semcount = (int16_t)value;

对应到本代码中,是将0传递进去了。所以,sem并不需要手动赋值,sem_init()会帮忙赋值。

  /* Start two waiter thread instances */

  printf("sem_test: Starting waiter thread 1\n");
  status = pthread_attr_init(&attr);
  if (status != OK)
    {
      printf("sem_test: pthread_attr_init failed, status=%d\n",  status);
    }

  prio_min = sched_get_priority_min(SCHED_FIFO);
  prio_max = sched_get_priority_max(SCHED_FIFO);
  prio_mid = (prio_min + prio_max) / 2;
  
  sparam.sched_priority = (prio_mid + prio_max) / 2;

这里取了中间值(255+1)/2=128


  status = pthread_attr_setschedparam(&attr,&sparam);

  if (status != OK)
    {
      printf("sem_test: ERROR: pthread_attr_setschedparam failed, status=%d\n",  status);
    }
  else
    {
      printf("sem_test: Set thread 1 priority to %d\n",  sparam.sched_priority);
    }

设置了线程的优先级。为(128+255)/2=191

  status = pthread_create(&waiter_thread1, &attr, waiter_func, (pthread_addr_t)1);
  if (status != 0)
    {
      printf("sem_test: ERROR: Thread 1 creation failed: %d\n",  status);
    }

创建了一个waiter_func()线程。

  printf("sem_test: Starting waiter thread 2\n");
  status = pthread_attr_init(&attr);
  if (status != 0)
    {
      printf("sem_test: ERROR: pthread_attr_init failed, status=%d\n",  status);
    }

  sparam.sched_priority = prio_mid;
  status = pthread_attr_setschedparam(&attr,&sparam);
  if (status != OK)
    {
      printf("sem_test: ERROR: pthread_attr_setschedparam failed, status=%d\n",  status);
    }
  else
    {
      printf("sem_test: Set thread 2 priority to %d\n",  sparam.sched_priority);
    }

128的优先级。

  status = pthread_create(&waiter_thread2, &attr, waiter_func, (pthread_addr_t)2);
  if (status != 0)
    {
      printf("sem_test: ERROR: Thread 2 creation failed: %d\n",  status);
    }

创建了第二个waiter_func()线程。

  printf("sem_test: Starting poster thread 3\n");
  status = pthread_attr_init(&attr);
  if (status != 0)
    {
      printf("sem_test: ERROR: pthread_attr_init failed, status=%d\n",  status);
    }

  sparam.sched_priority = (prio_min + prio_mid) / 2;
  status = pthread_attr_setschedparam(&attr,&sparam);
  if (status != OK)
    {
      printf("sem_test: pthread_attr_setschedparam failed, status=%d\n",  status);
    }
  else
    {
      printf("sem_test: Set thread 3 priority to %d\n",  sparam.sched_priority);
    }

线程3优先级为(1+128)/2=64

  status = pthread_create(&poster_thread, &attr, poster_func, (pthread_addr_t)3);
  if (status != 0)
    {
      printf("sem_test: ERROR: Thread 3 creation failed: %d\n",  status);
      printf("          Canceling waiter threads\n");

      pthread_cancel(waiter_thread1);
      pthread_cancel(waiter_thread2);
    }

  if (waiter_thread1 != (pthread_t)0)
    {
      pthread_join(waiter_thread1, NULL);
    }

  if (waiter_thread2 != (pthread_t)0)
    {
      pthread_join(waiter_thread2, NULL);
    }

  if (poster_thread != (pthread_t)0)
    {
      pthread_join(poster_thread, NULL);
    }
}

综合一下:

pthread pthread name priority
pthread1 waiter_func() 191
pthread2 waiter_func() 128
pthread3 poster_func() 64
Semaphores pthread1 pthread2 pthread3 0 start sem_wait() -1 pause start sem_wait() -2 pause start sem_post() -1 resume stop sem_post() 0 resume stop stop Semaphores pthread1 pthread2 pthread3

稍微花了点时间画了一个序列图。还算是能表现三个线程和信号量的变化过程。

这里,pthread1pthread2pthread3执行sem_post()后,是优先级较高的pthread1先运行了,这里做个实验,把pthread2改的比pthread1高一些。直接改成最大,编译,下载运行。NuttX运行ostest

waiter_func: Thread 2 waiting on semaphore
poster_func: Thread 3 started
poster_func: Thread 3 semaphore value = -2
poster_func: Thread 3 posting semaphore
waiter_func: Thread 2 awakened
poster_func: Thread 3 new semaphore value = -1
waiter_func: Thread 2 new semaphore value = -1
poster_func: Thread 3 semaphore value = -1
waiter_func: Thread 2 done
poster_func: Thread 3 posting semaphore
waiter_func: Thread 1 awakened
poster_func: Thread 3 new semaphore value = 0
waiter_func: Thread 1 new semaphore value = 0
poster_func: Thread 3 done
waiter_func: Thread 1 done

2线程优先于1线程运行。OK。

猜你喜欢

转载自blog.csdn.net/yt454287063/article/details/81230483