Существуют ли какие-либо преимущества для использования двоичного семафора вместо мьютекса для взаимного исключения в критическом разделе очереди?

Для класса ОС в настоящее время мне нужно создать потокобезопасную очередь в ядре linux, с которой один взаимодействует с использованием системных вызовов.

Теперь для критических разделов мое чувство кишки состоит в том, что я хотел бы использовать функции mutex_lock и mutex_unlock в заголовке mutex.h. Однако мне сказали, что вместо этого можно использовать двоичный семафор с down_interruptible и up в заголовке semaphore.h и что было бы лучше.

Я прочитал Разницу между двоичным семафором и мьютексом. Из этого я понимаю, что основным преимуществом мьютекса является то, насколько сильно он обеспечивает право собственности, и что преимущество семафора заключается в том, что, поскольку он не обеспечивает права собственности, вы можете использовать его в качестве механизма синхронизации между двумя (несколькими?) различными потоками.

Мой вопрос в том, каковы преимущества двоичного семафора, если вы используете его точно так же, как и мьютекс. Явным образом, если бы я писал:

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;
}

Ответ 1

В отсутствие эмпирических данных я бы привел цитату из книги Linux Kernel Development

Это (т.е. мьютекс) ведет себя подобно семафору со счетом одного, но имеет более простой интерфейс, более эффективную производительность и дополнительные ограничения на его использование.

Кроме того, существует множество ограничений, которые применяются к мьютексам, но не к семафорам. Такие вещи, как процесс, не могут выйти, удерживая мьютекс. Более того, если опция CONFIG_DEBUG_MUTEXES kernel включена, то все ограничения, применяемые к мьютексам, гарантируются путем отладки чеков.

Итак, если нет веской причины не использовать мьютекс, это должен быть первый выбор.

Ответ 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;

Странный порядок, указатель 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; 

Почему вы инициализируете их и почему вы устанавливаете указатель на 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;
}