Конкуренция и управление ей

Предыдущая  Содержание  Следующая V*D*V

В современной системе Linux существует множество источников конкуренции и, следовательно, возможны состояния гонок. Работают множество процессов в пользовательском пространстве и они могут получить доступ к вашему коду удивительной комбинацией способов. SMP системы могут выполнять ваш код одновременно на нескольких процессорах. Код ядра является вытесняемым; код вашего драйвера может лишиться процессора в любой момент, а процесс, который его заменит, также мог бы выполняться в вашем драйвере. Прерывания устройства являются асинхронными событиями, которые могут вызвать одновременное выполнение вашего кода. Ядро также обеспечивает различные механизмы для задержанного выполнения кода, такие как очереди задач (workqueues), микрозадачи (tasklets) и таймеры (timers), которые могут привести к запуску вашего кода в любое время способами, не связанными с тем, что делает текущий процесс. В современном мире "горячей" замены ваше устройство может просто исчезнуть в то время, как вы находитесь в середине работы с ним.

 

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

 

Состояния гонок возникают в результате совместного доступа к ресурсам. Когда два потока исполнения (* Для целей применения в этой главе "поток" исполнения является любым описанием выполняющегося кода. Каждый процесс является, очевидно, потоком исполнения, а так же обработчик прерываний или другой код, который выполняется в ответ на асинхронные события ядра.) имеют причину поработать с одними и теми же структурами данных (или аппаратными ресурсами), всегда существует потенциал для смешения. Поэтому первым эмпирическим правилом, которое надо иметь в виду, когда вы разрабатываете свой драйвер, является избегание общих ресурсов, когда это возможно. Если нет одновременного доступа, не может быть никаких состояний гонок. Так что тщательно написанный код ядра должен иметь минимум общих ресурсов. Наиболее очевидное применение этой идеи заключается в отказе от использования глобальных переменных. Если вы помещаете ресурс в место, где его могут найти более одного потока исполнения, для этого должны быть веские основания.

 

Однако, неоспоримый факт, что такой обмен требуется часто. Ресурсы оборудования, которые по своей природе общие, а также программные ресурсы зачастую должны быть доступны более чем одному потоку. Имейте в виду также, что глобальные переменные далеко не единственный способ совместного использования данных; в любой момент ваш код передаёт указатель на другую часть ядра, это потенциально создаёт новую ситуацию доступа к общим данным. Общие ресурсы - это факт жизни.

 

Вот жёсткое правило разделения ресурсов: в любое время, когда ресурс аппаратного или программного обеспечения разделяется не одним потоком исполнения и существует вероятность того, что один поток может столкнуться с несоотвествющим видом этого ресурса, вы должны управлять доступом к этому ресурсу явно. В приведенном выше примере для scull мнение о ситуации процесса Б несостоятельно; незнание, что процесс А уже выделил память для (общего) устройства, он выполняет своё собственное выделение и перезаписывает работу А. В этом случае мы должны контролировать доступ к структуре данных scull. Мы должны организовать вещи так, чтобы код либо видел память, которая была выделена, или знал, что память не выделена или будет выделена кем-то ещё. Обычная техника для управления доступом, названная блокировкой или взаимным исключением, гарантирует, что в любое время общим ресурсом может манипулировать только один поток исполнения. Оставшаяся большая часть этой главы будет посвящена блокировке.

 

Прежде всего, однако, мы должны кратко рассмотреть одно важное правило. Когда код ядра создаёт объект, который будет использоваться совместно с любой другой частью ядра, такой объект должен продолжать своё существование (и нормально функционировать), пока не станет известно, что никаких внешних ссылок на него больше не существует . В момент, когда scull делает свои устройства доступными, он должен быть готов для обработки запросов на эти устройства. И scull должен продолжать быть в состоянии обрабатывать запросы на свои устройства, пока не узнает, что ссылок (таких, как открытые файлы в пространстве пользователя) на эти устройства больше не существует. Из этого правила следуют два требования: объект не может быть сделан доступным для ядра, пока он не находится в состоянии для правильного функционирования, и ссылки на такие объекты должны отслеживаться. В большинстве случаев вы обнаружите, что подсчёт ссылок для вас делает ядро, но всегда есть исключения.

 

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

Предыдущая  Содержание  Следующая