C 在队列的关键部分使用二进制信号量而不是互斥来实现互斥有什么好处吗?

C 在队列的关键部分使用二进制信号量而不是互斥来实现互斥有什么好处吗?,c,multithreading,linux-kernel,mutex,semaphore,C,Multithreading,Linux Kernel,Mutex,Semaphore,对于一个OS类,我目前必须在linux内核中创建一个线程安全队列,使用syscalls与之交互 现在对于关键部分,我的直觉是我想在mutex.h标题中使用mutex\u lock和mutex\u unlock函数。但是,有人告诉我,我可以使用一个二进制信号量,在semaphore.h头中使用down\u interruptable和up,这样会更好 我已经通读了:从中,我了解到互斥锁的主要优点是它强制实现所有权的强度,而信号量的优点是,由于它不强制实现所有权,所以可以将其用作两个(多个?)不同线

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

现在对于关键部分,我的直觉是我想在
mutex.h
标题中使用
mutex\u lock
mutex\u unlock
函数。但是,有人告诉我,我可以使用一个二进制信号量,在
semaphore.h
头中使用
down\u interruptable
up
,这样会更好

我已经通读了:从中,我了解到互斥锁的主要优点是它强制实现所有权的强度,而信号量的优点是,由于它不强制实现所有权,所以可以将其用作两个(多个?)不同线程之间的同步机制

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

down()
/* critical */
up()
就像我会做的一样

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


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

#定义消息的最大大小512
typedef结构列表\标题列表\节点;
/*创建消息结构*/
类型定义结构{
大小;
列表节点;
字符数据[消息最大大小];
}信息;
/*创建带有虚拟头的链表队列*/
结构{
大小;
列出节点头;
}my_q={0,LIST_HEAD_INIT(my_q.HEAD)};
/*
将新项目添加到队列尾部。
@数据:指向要添加到列表的数据的指针
@len:数据的大小
*/
ASMLINK long sys_enqueue(const void___用户*数据,long len){
长res=0;
消息*msg=0;
如果(len<0)返回EINVAL;
如果(len>MESSAGE_MAX_SIZE)返回E2BIG;
msg=kmalloc(sizeof(Message),GFP_内核);
如果(msg==0)返回ENOMEM;
res=从用户处复制(消息->数据,数据,len);
如果(res!=0)返回默认值;
/*启动临界段*/
我的q.size++;
列表\u添加\u尾部(&msg->node,&my\u q.head);
/*端部临界截面*/
返回0;
}

在缺乏实证证据的情况下,我会引用《Linux内核开发》一书中的内容

它(即互斥)的行为类似于计数为1的信号量,但它具有 更简单的界面、更高效的性能和更多 对其使用的限制

此外,有许多约束适用于互斥体,但不适用于信号量。持有互斥对象时,进程之类的东西无法退出。此外,如果启用了
CONFIG\u DEBUG\u MUTEXES
kernel选项,则通过调试检查确保应用于mutexe的所有约束


因此,除非有充分的理由不使用互斥锁,否则互斥锁应该是第一选择。

默认的锁定原语是自旋锁。只有当您需要在持有锁的同时睡眠时,互斥才有意义,而在前面的代码示例中,您肯定没有这样做

#define MESSAGE_MAX_SIZE 512

typedef struct list_head list_node;
为什么?

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

/* 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; 
为什么要初始化这些her,为什么要将指针设置为0而不是NULL

  if (len < 0) return EINVAL;
为什么不sizeof(*msg)

这漏味精

  /* Start Critical Section */

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

  /* End Critical Section   */

  return 0;
}

您的队列是否需要递归锁定或必须处理优先级反转?不需要优先级反转,我非常确定我也不需要递归锁定。那么,我确实认为这取决于性能。当涉及到性能时,最好的做法是分析这两个方面,看看会发生什么。我的直觉是,如果它们的性能大致相同,我会坚持使用互斥,但其他人肯定会有更好的答案。@MichaelDorgan好的,非常感谢!我真的是个新手,不确定我是否错过了什么。我知道你的评论不一定足以作为答案,但我希望你能给出一个详细说明优先级反转和递归锁定将如何改变事情的答案,我会接受。如果今天没有其他人发布更好的答案,我会接受。递归锁定的要点是互斥锁支持立即锁定,并且“通常”可以处理优先级反转,而简单的构造“通常”不支持。基本上,它会变成函数/方法上的RTFM。我也会这么说。这不是一个“个人观点”,而是符合现有的linux内核编码准则,您没有明显的理由违反这些准则。关于你的实际问题,你应该怎么做在前两句话中解释了。祝你好运。如果我表现得不屑一顾,我很抱歉。我个人的观点是指风格,因为它只是我自己的项目,我没有对内核做出贡献,所以我觉得它不重要。至于list_head->list_节点typedef,这是因为list_head对我来说是一个非常不直观的名称。至于像有符号变量这样的东西,这些是我被分配用来处理的签名,所以我保留了它们。至于内存泄漏,我确实在一个出列函数中调用了kfree(),我没有在这里发布这个函数。我更喜欢0而不是NULL,因为它们是相同的东西。一个问题:为什么节点应该是消息结构中的第一个?我也删除了我的下一票。我想我只是对未经请求的代码审查感到恼火,并希望您能详细说明您编写的第一行内容,例如何时实际上需要睡眠而不是仅仅使用自旋锁。如果从用户复制失败,您将泄漏分配的内存。锁定通常是一个相当微妙的主题,你走错了方向(或者说,这里很奇怪)。既然你有了更好的关键词(spinlock),你就知道该用谷歌搜索什么了,或者最好问问谁在教训你。格式化就是要把事情做对——当你为给定的代码库编写代码时,你必须遵守其中的约定
  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;
}