Являются ли системные вызовы потокобезопасными?

Я написал модуль ядра, это драйвер символьного устройства, что-то вроде этой ссылки. Драйвер имеет внутреннюю структуру, например:

struct {
    str[500];
}channelData;

static channelData chData[4];

Итак, у меня есть многопоточное приложение, которое использует этот драйвер, в настоящее время я использую этот драйвер следующим образом:

typedef struct
{
        int channelId;
        int len;
        char arg[100];
} driverArgs;

class DevDrv{
    static void STR_READ(int channelId, char *data);
    static void STR_SEND(int channelId, char *data,int len);
};

void DevDrv::STR_READ(int channelId, char *data)
{
    driverArgs arg= {-1,0, {0}};
    arg.channelId = channelId;
    ioctl(mfilehandler,IOCTL_STR_READ,&arg);
    memcpy(data,arg.arg,arg.len)

}
void DevDrv::STR_SEND(int channelId, char *data,int len)
{
    driverArgs arg= {-1,0, {0}};
    arg.channelId = channelId;
    arg.len=len;
    memcpy(arg.arg,data,len);
    ioctl(mfilehandler,IOCTL_STR_SEND,&arg);
}

Итак, вопрос в том, вызывают ли 4 потока в моем приложении эти функции для чтения или записи в свой собственный ChannelId, например, для чтения или записи в драйвер:

thread1:
   DevDrv::STR_READ(0,&localdst);
thread2:
   DevDrv::STR_READ(1,&localdst);
thread3:
   DevDrv::STR_READ(2,&localdst);
thread4:
   DevDrv::STR_READ(3,&localdst);

есть дата-гонка или что-то в этом роде?

1 ответ

Твой channelData не гарантируется, что структура будет выровнена по кешу, поэтому, если вы явно не синхронизируете chData, вы по-прежнему подвержены гонкам за данные.

Вот набросок гонки:

  1. Системный вызов хочет прочитать канал 2 на ЦП 0.
  2. CPU 0 выбирает все строки кеша, содержащие канал 2, что означает:
    • Все байты в канале 2
    • Несколько байтов от конца канала 1
    • Несколько байтов от начала канала 3
  3. Чтение идет как обычно.
  4. CPU 1 записывает 500 байтов в канал 1.
  5. Системный вызов хочет прочитать 500 байтов в канал 1 на ЦП 0.
  6. CPU 0 извлекает из канала 1 все байты, которые ранее не извлекались.
    • Несколько байтов с конца канала 1 повторно не выбираются

В этом сценарии эти несколько байтов устарели на ЦП 0, потому что они были перезаписаны ЦП 1, а ЦП 0 не знал об этом.

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

Теперь во многих случаях системный вызов вызывает барьер памяти, НО это не гарантируется.

Ваша программа в пользовательском пространстве в порядке, и символьное устройство - это канонический способ связи с модулем ядра, но ваш модуль ядра должен правильно синхронизироваться. Даже пример в вашей ссылке кажется очень вводным и делает такие вещи, какDevice_Open++ без использования атомики.

Другие вопросы по тегам