Как определить, ответил ли поток на звонок? - PullRequest
1 голос
/ 29 апреля 2019

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

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

Я построил этот минимальный пример:

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>

cnd_t wakeup;
mtx_t mutex;

enum request {
  REQUEST_NOTHING,
  REQUEST_PING,
  REQUEST_TERMINATION
} request;

int daemon(void *arg) {
  (void)arg;

  int retval;

  retval = mtx_lock(&mutex);
  assert(retval == thrd_success);

  for(;;) {
    request = REQUEST_NOTHING;

    retval = cnd_wait(&wakeup, &mutex);
    assert(retval == thrd_success);

    switch(request) {
      case REQUEST_NOTHING:
        break;

      case REQUEST_PING:
        puts("pong.");
        break;

      case REQUEST_TERMINATION:
        retval = mtx_unlock(&mutex);
        assert(retval == thrd_success);
        return 0;

      default:
        assert(false);
    }
  }
}

void send(enum request req) {
  int retval;

  retval = mtx_lock(&mutex);
  assert(retval == thrd_success);

  request = req;

  retval = mtx_unlock(&mutex);
  assert(retval == thrd_success);

  // TODO race condition: worker thread my not be listening yet

  retval = cnd_signal(&wakeup);
  assert(retval == thrd_success);
}

int main() {
  int retval;

  retval = mtx_init(&mutex, mtx_plain);
  assert(retval == thrd_success);

  retval = cnd_init(&wakeup);
  assert(retval == thrd_success);

  thrd_t thread;
  retval = thrd_create(&thread, daemon, NULL);
  assert(retval == thrd_success);

  puts("ping.");
  send(REQUEST_PING);

  // TODO wait for the worker thread to complete

  send(REQUEST_TERMINATION);
  retval = thrd_join(thread, NULL);
  assert(retval == thrd_success);

  cnd_destroy(&wakeup);
  mtx_destroy(&mutex);
}

В настоящее время я не реализовал привилегии, но у меня уже достаточно проблем:

  1. cnd_signal не указывает, был ли сигнал кем-либо принят:

    Разблокирует один поток, который в данный момент ожидает переменную условия, на которую указывает cond. Если никакие потоки не заблокированы, ничего не делает и возвращает thrd_success.
    Источник: cppreference.com

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

  2. Основной поток не ожидает завершения рабочего потока. В этом примере это не проблема, поскольку (1) основной поток должен дождаться получения блокировки перед отправкой следующего запроса, а (2) поток может быть просто присоединен перед выходом.

    В моей настоящей программе мне нужно подождать. Очевидным решением было бы ввести еще одну пару cnd_t и mtx_t, позволяющую рабочему потоку пробуждать основной поток. Но это кажется слишком сложным для такой простой задачи.

Я не смог найти много ресурсов, демонстрирующих использование библиотеки потоков C11 и, возможно, все вместе на неправильном пути. Буду признателен за отзыв и, возможно, решение проблем, описанных выше.

Ответы [ 2 ]

1 голос
/ 02 мая 2019

В вашем случае вам не нужно постоянно сохранять привилегии root в вашем процессе.Вам не нужно root для чтения с устройства ввода.Разрешения проверяются только при его открытии.

После открытия устройства ваш процесс может отказаться от привилегий root и продолжить свою работу, читая дескриптор открытого файла как непривилегированный пользователь, поэтому вам не нужен отдельный процесс или поток.

Отмена расширенных привилегий как можно раньше является стандартной процедурой для реализации доступа к специальным ресурсам при минимизации риска для безопасности.

См. Также https://www.oreilly.com/library/view/secure-programming-cookbook/0596003943/ch01s03.html

Могут быть и другиеслучаи, когда вам нужно постоянно сохранять особые привилегии.В таком случае ваша идея отделить задачи, которые требуют повышенных привилегий от других задач, хороша, но вам придется использовать отдельный процесс вместо потока.

0 голосов
/ 02 мая 2019

Отказ от ответственности: Хотя моя проблема уже была решена @dunes, и я узнал, что разрешения обрабатываются на основе процесса (спасибо @solomon), я нашел в бешенстве оставить этот вопрос без ответа, который разрешаетсамо состояние гонки.


Как оказалось, я просто неправильно понял, как эти переменные cnd_t должны использоваться.Хитрость заключается в том, чтобы заблокировать мьютекс до запуска рабочего потока и заставить его разблокировать мьютекс, когда он ожидает сигнала.При сигнале нити блокировка получается до , сигнализируя нить и не освобождается до тех пор, пока запрос не будет отправлен и сигнал не исчезнет.

Эта программа больше не имеет состояния гонки:

#include <assert.h>
#include <threads.h>

mtx_t mx_wakeup;
cnd_t cd_wakeup;

enum request {
  REQ_NOTHING,
  REQ_TERMINATE
} request;

int daemon(void *arg) {
  (void)arg;

  for(;;) {
    request = REQ_NOTHING;

    int retval = cnd_wait(&cd_wakeup, &mx_wakeup);
    assert(retval == thrd_success);

    if(request == REQ_TERMINATE) {
      return 0;
    }
  }
}

void send(enum request request_) {
  int retval;

  // The worker thread will unlock the mutex implicitly when
  // waiting for a signal, block until that happens.
  retval = mtx_lock(&mx_wakeup);
  assert(retval == thrd_success);

  request = request_;

  retval = cnd_signal(&cd_wakeup);
  assert(retval == thrd_success);

  // The worker thread needs to lock the mutex before waking up,
  // this ensures that it doesn't before receiving the signal.
  retval = mtx_unlock(&mx_wakeup);
  assert(retval == thrd_success);
}

int main() {
  int retval;

  retval = mtx_init(&mx_wakeup, mtx_plain);
  assert(retval == thrd_success);

  retval = cnd_init(&cd_wakeup);
  assert(retval == thrd_success);

  // The mutex will be unlocked by the worker thread when listening.
  retval = mtx_lock(&mx_wakeup);
  assert(retval == thrd_success);

  thrd_t thread;
  retval = thrd_create(&thread, daemon, NULL);
  assert(retval == thrd_success);

  send(REQ_TERMINATE);

  retval = thrd_join(thread, NULL);
  assert(retval == thrd_success);

  cnd_destroy(&cd_wakeup);
  mtx_destroy(&mx_wakeup);
}

Ожидание рабочего потока просто добавляет другую переменную условия.Только код завершения должен быть принят для освобождения мьютекса, иначе главный поток будет ждать блокировки вечно.

#include <assert.h>
#include <threads.h>

mtx_t mx_wakeup;
cnd_t cd_wakeup, cd_complete;

enum request {
  REQ_NOTHING,
  REQ_TERMINATE
} request;

int daemon(void *arg) {
  (void)arg;
  int retval;

  for(;;) {
    request = REQ_NOTHING;

    retval = cnd_wait(&cd_wakeup, &mx_wakeup);
    assert(retval == thrd_success);

    // The request can be processed here.

    // Inform the main thread that the request was completed. The main
    // thread can choose to wait or not.
    retval = cnd_signal(&cd_complete);
    assert(retval == thrd_success);

    // Termination is different because the mutex wouldn't be released
    // by the next `cnd_wait`, and must happend after the signal was send.
    if(request == REQ_TERMINATE) {
      retval = mtx_unlock(&mx_wakeup);
      assert(retval == thrd_success);
      return 0;
    }
  }
}

void send(enum request request_) {
  int retval;

  retval = mtx_lock(&mx_wakeup);
  assert(retval == thrd_success);

  request = request_;

  retval = cnd_signal(&cd_wakeup);
  assert(retval == thrd_success);

  // This unlocks the mutex thus allowing the worker thread to process the
  // request, thus the mutex can be reused here.
  retval = cnd_wait(&cd_complete, &mx_wakeup);
  assert(retval == thrd_success);

  retval = mtx_unlock(&mx_wakeup);
  assert(retval == thrd_success);
}

int main() {
  int retval;

  retval = mtx_init(&mx_wakeup, mtx_plain);
  assert(retval == thrd_success);

  retval = cnd_init(&cd_wakeup);
  assert(retval == thrd_success);

  // Remember to initialize the new conditional variable.
  retval = cnd_init(&cd_complete);
  assert(retval == thrd_success);

  retval = mtx_lock(&mx_wakeup);
  assert(retval == thrd_success);

  thrd_t thread;
  retval = thrd_create(&thread, daemon, NULL);
  assert(retval == thrd_success);

  send(REQ_TERMINATE);

  retval = thrd_join(thread, NULL);
  assert(retval == thrd_success);

  cnd_destroy(&cd_wakeup);
  mtx_destroy(&mx_wakeup);
}
...