在队列的关键部分使用二进制信号量而不是互斥锁有啥优势吗?

Posted

技术标签:

【中文标题】在队列的关键部分使用二进制信号量而不是互斥锁有啥优势吗?【英文标题】:Are there any advantages to using a binary semaphore instead of a mutex for mutual exclusion in a critical section of a queue?在队列的关键部分使用二进制信号量而不是互斥锁有什么优势吗? 【发布时间】:2017-03-10 13:34:44 【问题描述】:

对于一个 OS 类,我目前必须在 linux 内核中创建一个线程安全队列,使用系统调用与之交互。

现在对于关键部分,我的直觉是我想在mutex.h 标头中使用mutex_lockmutex_unlock 函数。但是,有人告诉我,我可以在semaphore.h 标头中使用带有down_interruptibleup 的二进制信号量,这样会更好。

我通读了Difference between binary semaphore and mutex:从中,我了解到互斥锁的主要优点是它强制所有权的强度,而信号量的优点是因为它不强制所有权,你可以使用它作为两个(多个?)不同线程之间的同步机制。

我的问题是二进制信号量的优点是什么,如果您以与互斥锁完全相同的方式使用它。如果我写的更明确:

down()
/* critical */
up()

和我做的一样

mutex_lock()
/* critical */
mutex_unlock()

是否有一些性能优势,因为它不如互斥锁安全?我错过了什么吗?


如果你想要更多上下文,我想把它做成线程安全的一小段代码(这是我的第一个 C 项目):

#define MESSAGE_MAX_SIZE 512

typedef struct list_head list_node;

/* Create message struct */
typedef struct 
  size_t size;
  list_node node;
  char data[MESSAGE_MAX_SIZE];
 Message;

/* Create the linked list queue with dummy head */
struct 
  size_t size;
  list_node head;
 my_q =  0, LIST_HEAD_INIT(my_q.head) ;

/*
  Adds a new item to the tail of the queue. 

  @data: pointer to data to add to list
  @len: size of the data
*/
asmlinkage long sys_enqueue(const void __user *data, long len) 
  long res = 0;
  Message *msg = 0; 

  if (len < 0) return EINVAL;
  if (len > MESSAGE_MAX_SIZE) return E2BIG;

  msg = kmalloc(sizeof(Message), GFP_KERNEL);
  if (msg == 0) return ENOMEM;

  res = copy_from_user(msg->data, data, len);
  if (res != 0) return EFAULT;

  /* Start Critical Section */

  my_q.size++;
  list_add_tail(&msg->node, &my_q.head);

  /* End Critical Section   */

  return 0;

【问题讨论】:

您的队列是否需要递归锁定或必须处理优先级反转? 没有优先级反转,我很确定我也不需要递归锁定。 那么在这一点上,我确实认为它归结为性能。当它是性能时,最好的办法是分析两者并看看会发生什么。我的直觉是,如果它们的性能大致相同,我会坚持使用互斥体,但其他人肯定会有更好的答案。 @MichaelDorgan 好的,非常感谢!我对此真的很陌生,不确定我是否只是错过了一些东西。我知道您的评论不一定足以回答,但如果您发布一个详细说明优先级反转和递归锁定如何改变事情的答案,我会很高兴,我会接受。 如果没有其他人发布更好的东西,我会在今天晚些时候发布。递归锁定的要点是互斥锁立即支持它,并且还可以“通常”处理优先级反转,而更简单的构造“通常”不能。基本上,它就变成了 RTFM 在功能/方法上的确定。 【参考方案1】:

在没有经验证据的情况下,我会引用Linux Kernel Development一书

它(即互斥体)的行为类似于计数为 1 的信号量,但它有一个 更简单的界面,更高效的性能,以及额外的 对其使用的限制。

此外,有许多约束适用于互斥体,但不适用于信号量。持有互斥锁时,进程之类的东西不能退出。此外,如果启用了CONFIG_DEBUG_MUTEXES 内核选项,那么适用于互斥体的所有约束都会通过调试检查来确保。

所以,除非有充分的理由不使用互斥锁,否则它应该是首选。

【讨论】:

我也会这样做的。【参考方案2】:

默认的锁定原语是自旋锁。互斥锁仅在您需要在持有锁时睡觉时才有意义,而在上述代码示例中您绝对不需要。

#define MESSAGE_MAX_SIZE 512

typedef struct list_head list_node;

为什么?

/* Create message struct */
typedef struct 
  size_t size;
  list_node node;
  char data[MESSAGE_MAX_SIZE];
 Message;

奇怪的顺序,节点指针应该是第一个或最后一个。

/* Create the linked list queue with dummy head */
struct 
  size_t size;
  list_node head;
 my_q =  0, LIST_HEAD_INIT(my_q.head) ;

/*
  Adds a new item to the tail of the queue. 

  @data: pointer to data to add to list
  @len: size of the data
*/
asmlinkage long sys_enqueue(const void __user *data, long len) 

大括号应该在下一行。为什么是有符号类型的长度?

  long res = 0;
  Message *msg = 0; 

为什么要初始化这些她,为什么要将指针设置为 0 而不是 NULL?

  if (len < 0) return EINVAL;

return 语句应该在下一行。另请注意,如果类型以无符号开头,则此条件不相关。

  if (len > MESSAGE_MAX_SIZE) return E2BIG;

  msg = kmalloc(sizeof(Message), GFP_KERNEL);

为什么不 sizeof(*msg)

  if (msg == 0) return ENOMEM;

  res = copy_from_user(msg->data, data, len);
  if (res != 0) return EFAULT;

这会泄露味精。

  /* Start Critical Section */

  my_q.size++;
  list_add_tail(&msg->node, &my_q.head);

  /* End Critical Section   */

  return 0;

【讨论】:

这不是“个人意见”,而是符合现有的 linux 内核编码准则,您无缘无故地违反了这些准则。关于你的实际问题你应该做什么在前两句话中解释了。祝你好运。 对不起,如果我表现得不屑一顾。就个人观点而言,我的意思是风格,因为这只是我自己的项目,我没有为内核做出贡献,所以我觉得这并不重要。至于 list_head -> list_node typedef 这是因为 list_head 对我来说是一个不直观的名字。至于签名变量之类的东西,这些是我被分配使用的签名,所以我保留了它们。至于内存泄漏,我确实在一个未在此处发布的出列函数中调用了 kfree()。我更喜欢 0 到 NULL 因为它们是同一件事。一个问题:为什么节点应该在消息结构中排在第一位? 我也删除了我的反对票。我想我只是对不请自来的代码审查感到恼火,并希望您在编写的第一行中详细说明,例如何时真正需要睡眠而不是自旋锁。 如果 copy_from_user 失败,您将泄漏分配的内存。一般来说,锁定是一个相当微妙的主题,你走错了方向(或者更确切地说,这里很奇怪)。既然您有了更好的关键字(自旋锁),您就知道该用谷歌搜索什么,或者更好地询问正在给您讲课的人。格式化是为了正确地做事——当你为给定的代码库编写代码时,你要遵守所述代码库中的约定。 确实必须这样做,但是现在您通过列表指针将缓冲区与其长度分开,这没有意义。由于您在读取时将对象出列,因此您可以先将其从列表中删除,然后您可以复制到用户空间而无需持有任何锁,因为该对象对其他人不可见。

以上是关于在队列的关键部分使用二进制信号量而不是互斥锁有啥优势吗?的主要内容,如果未能解决你的问题,请参考以下文章

关键部分与计数信号量有什么关系?

使用互斥锁和条件变量而不是信号量在 c++14 中打印从 1 到 10 的数字?

信号量与互斥锁的区别

FreeRTOS 信号量

FreeRTOS高级篇5---FreeRTOS队列分析

Freertos-事件标志组,消息队列,信号量,二值信号量,互斥信号量