2015-11-14 3 views
1

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

Я знаю, что там, где когда-либо есть вероятность параллелизма в части кода, нам требуется блокировка. Но как мы решаем, что, если мы пропустим и проверим случаи, не поймаем их. Раньше я писал код для системных вызовов и файловых систем, где я никогда не заботился о блокировке.

ответ

0

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

В зависимости от данных, однако, могут быть ярлыки, которые безопасны и быстро. Если это простое целое число (а по целому я имею в виду собственный размер слова процессора, т. Е. Не 64-разрядный бит на 32-битном процессоре), вам может не понадобиться блокировать: если один поток пытается записать на целое число, а другое читает его в одно и то же время, читатель либо получит старое значение, либо новое значение, никогда не смешивая их. Если читателю все равно, что он получил старое значение, тогда нет необходимости в блокировке.

Если, однако, вы обновляете два целых числа вместе, и для читателя было бы плохо получить новое значение для одного и старого значения для другого, тогда вам понадобится блокировка. Другим примером является то, что поток увеличивает число. Обычно это включает чтение, добавление и запись. Если вы читаете старое значение, то другому удается читать, добавлять и записывать новое значение, тогда первый поток добавляет и записывает новое значение, оба полагают, что они увеличили эту переменную, но вместо того, чтобы увеличивать его дважды, это было только один раз увеличивается. Для этого требуется либо блокировка, либо использование примитива атомного инкремента, чтобы гарантировать, что цикл чтения/изменения/записи не может быть прерван. Есть также атомарные тестовые и заданные примитивы, поэтому вы можете прочитать значение, выполнить некоторую математику, затем попробуйте, чтобы записать его обратно, но запись только удалась, если она все еще содержит исходное значение. То есть, если другой поток изменил его с момента его прочтения, тестовый набор будет терпеть неудачу, тогда вы можете отбросить свое новое значение и начать с чтения значения, заданного другим потоком, и попытаться выполнить тестирование и -изложите его снова.

Указатели действительно являются целыми числами, поэтому, если вы настроили структуру данных, тогда сохраните указатель на нее, где другой поток может найти ее, вам не нужно блокировать, пока вы полностью настроили структуру до вы сохраняете его адрес в указателе. Другой поток, читающий указатель (он должен обязательно прочитать указатель только один раз, то есть путем его хранения в локальной переменной, а затем использовать только это, чтобы ссылаться на структуру с того времени) либо увидит новую структуру, либо старую один, но не промежуточное состояние. Если большинство потоков считывает структуру только через указатель, и все, кто хочет писать, делают это либо с помощью блокировки, либо с помощью атомного теста и набора указателя, этого достаточно. Каждый раз, когда вы хотите изменить какой-либо элемент структуры, вы должны скопировать его на новый, изменить новый, а затем обновить указатель. Это, по сути, работает механизм ядра RCU (чтение, копирование, обновление).

1

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

Любой объект (глобальная переменная, поле объекта структуры и т. Д.), Доступно одновременно, когда Доступ к одному из способов доступа к записи требует некоторой блокировки для доступа.

Но как мы решаем, что, если мы пропустим и проверим случаи, не поймаем их?

Хорошая практика является подходящим комментарием для каждого объявления поля переменной, структуры или структуры, которое требует блокировки для доступа. Любой, кто использует эту переменную, читает этот комментарий и пишет соответствующий код для доступа. Ядро ядра и модули имеют тенденцию следовать этой стратегии.

Что касается тестирования, то общее тестирование редко выявляет проблемы параллелизма из-за их низкой вероятности. При тестировании модулей ядра я бы посоветовал использовать Kernel Strider, который пытается доказать правильность параллельных доступов к памяти или RaceHound, что увеличивает вероятность одновременных проблем и проверяет их.

0

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

  1. Продолжительность, для которых ресурс будет занят (Использование ресурса) & тип замка
  2. Количество задач в очередь на конкретный ресурс (Load) & приоритета
  3. Вида связи, деля механизм, связанный с ресурсами
  4. условия возникновения ошибок, связанное с ресурсами

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

Этот процесс может помочь вам в определении недостающих сценариев/неизвестных, критических разделов, а также в определении узких мест.

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

  1. Helgrind - инструмент Valgrind для обнаружения ошибок синхронизации. Это может помочь в выявлении проблем с рассылками/синхронизацией данных из-за для неправильной блокировки, упорядочение блокировки, которое может вызвать блокировки, и также неверное использование API нитей POSIX, которое может иметь более поздние воздействия. http://valgrind.org/docs/manual/hg-manual.html
  2. Locksmith - Для определения общих ошибок блокировки, которые могут возникнуть во время работы , или которые могут вызвать взаимоблокировки.
  3. ThreadSanitizer - Для определения состояния гонок. Должен отображать все обращения & блокировки, задействованные для всех доступов.
  4. Sparse может помочь в перечислении блокировок, полученных и выпущенных функцией, а также идентификации таких проблем, как смешение указателей на адресное пространство пользователя и указатели на адресное пространство ядра.
  5. Lockdep - Для отладки замков
  6. iotop - Для определения текущего использования ввода/вывода с помощью процессов или потоков в системе путем мониторинга/вывод информационного использования вывода I ядро.
  7. LTTng - Для отслеживания условий гонки и прерывающих каскадов. (Преемник LTT - Объединение функций kprobes, tracepoint и perf)
  8. Ftrace - Внутренний трассировщик ядра Linux для анализа и отладки латентности и проблем, связанных с производительностью.
  9. lsof и fuser могут быть полезны при определении процессов, имеющих блокировку и вид замков.

Профилирование может помочь определить, где именно время расходуется ядром. Это можно сделать с помощью таких инструментов, как perf, Oprofile. strace может перехватывать/записывать системные вызовы, вызываемые процессом, а также сигналы, принимаемые процессом. Он должен показывать порядок событий и все пути возврата/возобновления вызовов.

Смежные вопросы