реализация функции спин-блокировки в xv6, чтобы иметь возможность использовать API-интерфейсы на уровне пользователя.

В xv6 есть файл spinlock.c для создания спин-блокировки для использования ядром. Но мне нужно реализовать API-интерфейсы спин-блокировки для использования на уровне пользователя. Например, я реализую sp_create() для создания спин-блокировки на уровне пользователя. Или вызовите sp_acquire(int id), чтобы получить блокировку, и т. д. Для этого я должен создать системные вызовы и поместить фактическую реализацию в ядро. xv6 имеет функцию спин-блокировки, но только для использования на уровне ядра.

Я думал о создании системных вызовов, которые на самом деле вызывают соответствующие функции в spinlock.c для создания блокировки, ее получения, освобождения и т. д. Но это не работает из-за некоторых проблем с отключением прерываний.

Я копирую ниже код, который я написал до сих пор:

//system call for lock_take():
int  l_take(int lockid) {

  struct proc *curproc = myproc();

  //process will take lock
  ..
    acquire(&LL.arraylockList[lockid].spnLock);  
  ..
  return 0; 
}

Проблема, с которой я сталкиваюсь, заключается в том, что это дает мне ошибку о панике: запланированные блокировки, я думаю, это потому, что в коде accept() есть pushcli().

void
acquire(struct spinlock *lk)
{
    pushcli(); // disable interrupts to avoid deadlock.
    if (holding(lk)) panic("acquire");

    // The xchg is atomic.
    while (xchg(&lk->locked, 1) != 0)
        ;

    // Tell the C compiler and the processor to not move loads or stores
    // past this point, to ensure that the critical section's memory
    // references happen after the lock is acquired.
    __sync_synchronize();

    // Record info about lock acquisition for debugging.
    lk->cpu = mycpu();
    getcallerpcs(&lk, lk->pcs);
}

Затем я скопировал код в новую функцию Acquis2() и использовал ее в своем системном вызове, где функция pushcli() закомментирована:

acquire2(struct spinlock *lk)
{
        //    pushcli(); // disable interrupts to avoid deadlock.
    if (holding(lk)) panic("acquire");

    // The xchg is atomic.
    while (xchg(&lk->locked, 1) != 0) {
      ;
    }

    // Tell the C compiler and the processor to not move loads or stores
    // past this point, to ensure that the critical section's memory
    // references happen after the lock is acquired.
    __sync_synchronize();

    // Record info about lock acquisition for debugging.
    lk->cpu = mycpu();
    getcallerpcs(&lk, lk->pcs);
}

Однако затем сообщение об ошибке меняется на это: panic: mycpu() вызывается с включенными прерываниями.

Оказывается при отключении прерывания не допускаются. Таким образом, pushcli() и popcli() не следует использовать. Затем мне нужно выяснить, как запустить mycpu() атомарно. Его реализация такова:

// Must be called with interrupts disabled to avoid the caller being rescheduled
// between reading lapicid and running through the loop.
struct cpu *
mycpu(void)
{
    int apicid, i;

    if (readeflags() & FL_IF) panic("mycpu called with interrupts enabled\n");

    apicid = lapicid();
    // APIC IDs are not guaranteed to be contiguous. Maybe we should have
    // a reverse map, or reserve a register to store &cpus[i].
    for (i = 0; i < ncpu; ++i) {
        if (cpus[i].apicid == apicid) return &cpus[i];
    }
    panic("unknown apicid\n");
}

Цикл for и строка над ним должны выполняться атомарно. Как мне это сделать?


person aky    schedule 25.11.2019    source источник


Ответы (1)


Работая в обратном порядке (начиная с конца ваших вопросов)...

Как мне это сделать?

Если вы должны это сделать; вы бы начали с поиска альтернативного способа, который не включает цикл.

Вероятно, есть много возможных альтернатив (при условии, что 80x86; использование «локальной структуры ЦП, найденной через. GS или FS каким-либо образом, использование другого TSS для каждого ЦП и использование его для поиска номера ЦП, использование инструкции RDTSCP после того, как убедитесь, что она поддерживается и настроена для костюм, кража / перепрофилирование регистра, такого как DR3, использование пейджинга для создания «конкретной зоны ЦП», ...).

Цикл for и строка над ним должны выполняться атомарно.

Почему? Предполагая, что таблица создается во время загрузки и никогда не изменяется после этого (и что нет поддержки ЦП с горячей заменой); или предполагая, что таблица изменяется только таким образом, чтобы гарантировать ее правильность, когда ЦП необходимо найти себя (признавая, что ЦП, который был переведен в автономный режим, не может пытаться найти себя); нет причин (я вижу), что цикл должен быть атомарным.

Оказывается при отключении прерывания не допускаются.

Примечание. Это неправильно и должно быть «Оказывается, прерывания ДОЛЖНЫ быть отключены».

Это потому, что ОС использует «отключить IRQ» для отключения/отсрочки переключения задач. Это нехорошее или необходимое требование (ядро может иметь свой собственный флаг/переменную где-то еще, чтобы отключить/отложить переключение задач без отключения IRQ; и 2 разных типа спин-блокировок, одна из которых используется кодом, который может выполняться обработчиками IRQ). который отключает оба и тот, который никогда не используется кодом, который может выполняться обработчиками IRQ, который только отключает/откладывает задачу и никогда не отключает IRQ). Конечно, это потребует серьезных изменений в ядре.

Обратите внимание, что для кода, который вы опубликовали, весьма вероятно, что получение спин-блокировки приведет к отключению всех IRQ, а затем снятие спин-блокировки приведет к повторному включению IRQ. Это означает (если вы разрешите пользовательскому пространству использовать код ядра через системный вызов), пользовательское пространство может получить любую спин-блокировку, а затем вечно использовать 100% процессорного времени (никогда не освобождая спин-блокировку). Другими словами, то, что вы планируете сделать, создаст массовую уязвимость системы безопасности типа «отказ в обслуживании».

Я думал о создании системных вызовов, которые на самом деле вызывают соответствующие функции в spinlock.c для создания блокировки, ее получения, освобождения и т. д.

Это причина всех ваших проблем.

Вместо создания системного вызова просто создайте код спин-блокировки для пользовательского пространства. Вы можете сделать это, скопировав (с помощью «копировать и вставить») функции ядра непосредственно в ваш код пользовательского пространства (или, может быть, в библиотеку) и удалив все ненужные вещи, чтобы получить что-то вроде:

my_acquire(int *lk)
{
    // The xchg is atomic.
    while (xchg(lk, 1) != 0) {
      ;
    }

    // Tell the C compiler and the processor to not move loads or stores
    // past this point, to ensure that the critical section's memory
    // references happen after the lock is acquired.
    __sync_synchronize();
}

Примечание. Чтобы это работало, вам также необходимо скопировать код для xchg() и __sync_synchronize() в пространство пользователя; но это не должно быть проблемой.

Однако; Я должен отметить, что код в ядре не очень хорош. Обычно вы хотите использовать подход «тестировать, затем атомарно протестировать и установить», чтобы (в условиях конкуренции) вы не делали ничего атомарного (а просто выполняли тестирование в цикле); и (для 80x86) вы хотите использовать инструкцию pause в цикле, чтобы улучшить «скорость выхода из цикла» и повысить скорость другого логического процессора в ядре (для гиперпоточности); и (для ядра) вы не хотите отключать IRQ при тестировании и хотите отключать IRQ только для «атомарного теста и установки», чтобы избежать повреждения «задержки IRQ» без причины.

Реализация спин-блокировки на уровне пользователя

Это также, вероятно, будет плохой идеей в целом.

Проблема в том, что (если это не массовая катастрофа с безопасностью) может произойти переключение задач, пока вы держите блокировку; заставляя все другие задачи (например, потенциально много ЦП) тратить все свои временные интервалы на вращение без какой-либо надежды получить блокировку (поскольку блокировка уже получена задачей, которая не запущена и не будет выполняться, потому что задачи заняты тратой процессорного времени) вместо этого крутится впустую).

Решение этой проблемы (которое не является серьезной проблемой безопасности) — мьютексы/семафоры. В частности, решение состоит в том, чтобы иметь возможность сказать планировщику ядра «не давать этой задаче процессорное время, пока она не сможет получить необходимый мьютекс/семафор». Конечно, это можно реализовать таким образом, что «счастливый случай» (когда вообще нет разногласий) обрабатывается исключительно в пользовательском пространстве, а ядро ​​(и накладные расходы на системный вызов и т. д.) задействованы только в том случае, если/когда это действительно необходимо. .

person Brendan    schedule 25.11.2019
comment
Спасибо, но я отредактировал вопрос. Ранее, когда я говорил о функциональности спин-блокировки на уровне пользователя, мне все еще приходится делать это через системные вызовы. Это требование. Реализация будет в ядре, пользовательский уровень будет выполнять системные вызовы для ее использования. Пожалуйста, смотрите исправленную версию. Спасибо - person aky; 25.11.2019
comment
@aky: Это было бы, по сути, тем же самым - либо массовая катастрофа с безопасностью (из-за того, что IRQ остаются отключенными, пока блокировка удерживается), либо огромная проблема с производительностью (из-за разрешения переключения задач, когда блокировка удерживается); с теми же вариантами реализации (например, заменить mycpu() на что-то лучшее, или решить, что он не должен быть атомарным, или создать версию кода получения/освобождения спин-блокировки, в которой не нужно использовать mycpu() или..). - person Brendan; 26.11.2019
comment
@aky: обратите внимание, что если вы разрешите переключение задач, пока удерживается блокировка (пространства пользователя) (что является наименее плохим вариантом); тогда вам все равно, какой ЦП использует задача (после нескольких переключений задачи задача, удерживающая блокировку, может работать на другом ЦП) и, следовательно, не захотите использовать mycpu() (но, возможно, захотите использовать идентификатор потока задачи вместо этого, если вы предполагаете, что одна и та же задача, получающая блокировку во второй раз, является ошибкой, а не совершенно законной и преднамеренной вещью, такой как ожидание другой задачи, чтобы снять блокировку, которую я получил). - person Brendan; 26.11.2019
comment
Спасибо, это было здорово. - person aky; 01.12.2019