понедельник, 20 декабря 2010 г.

GEOM: практические знания. Часть 2.

Продолжить начатую ранее тему устройства GEOM я хотел бы с таких понятий как tasting, orphanization и spoiling. Для пользователя системы наиболее интересным наверно покажется первый термин. Tasting - это процесс автоматической настройки geom объектов. Этот процесс инициируется подсистемой GEOM каждый раз, когда создаётся новый GEOM класс или провайдер. С точки зрения алгоритма работы эти два случая немного различаются, так tasting при создании нового провайдера выглядит примерно так:
  1. Ядро проходит в цикле по списку известных ему GEOM классов.
  2. Для каждого из них вызывается метод класса taste, который уже в зависимости от реализации класса может создать новый geom объект, либо посчитать этот новый провайдер неподходящим для использования классом.
В случае, когда создаётся новый класс, алгоритм такой:
  1. Ядро проходит в цикле по списку известных ему GEOM классов.
  2. Для каждого класса просматривается каждый существующий geom объект и все его провайдеры, которые передаются на проверку в метод taste для нового класса. А тот уже решает, подходит ему этот провайдер или нет.
Таким образом сделана возможность своего рода "plug-and-play". При загрузке модуля ядра, реализующего какой-то новый GEOM класс, у него имеется возможность автоматически найти нужный провайдер и начать выполнять свои действия.

Рассмотрим пример возможного сценария работы tasting при обнаружении жесткого диска:
  1. Драйвер жесткого диска создаёт geom объект ada0 класса DISK, тот в свою очередь создаёт провайдер ada0.
  2. Создание провайдера инициирует tasting. Последовательность может отличаться, но мы предположим такое развитие событий - вызывается метод taste класса DEV. Этот класс служит для создания файлов устройств в файловой системе devfs. Т.е. это его мы можем поблагодарить за создание /dev/ada0 (стоит заметить, что этот класс не создаёт своих провайдеров).
  3. На этом процесс tasting не заканчивается. Предположим, что в списке классов, которые знает ядро, следующим стоит класс PART. Ядро вызывает метод taste этого класса, он считывает секторы этого провайдера, в которых могут находиться таблицы разделов, и проверяет есть ли они там. Если есть, то создаются новые провайдеры, в соответствии с таблицей разделов. Это, в свою очередь, опять же инициирует tasting.
Многие GEOM классы (не все) хранят свои метаданные на диске, обычно для этого используется один последний сектор. Метод taste этих классов считывает нужный классу сектор провайдера и проверяет считанные данные, если он находит там метаданные класса, он предпринимает какие либо действия. Чтобы пользователь случайно не повредил метаданные, размер создаваемого классом провайдера обычно меньше на один сектор. Например, когда вы создаёте зеркало на основе класса MIRROR, то размер итогового mirror/gm0 будет на один сектор меньше, чем размер провайдеров, из которых он состоит. Повреждение, перезапись последнего сектора, изменение размера провайдера или размера сектора - всё это приводит к тому, что метод taste не обнаружит свои метаданные на провайдере, а значит и не будет создан geom объект этого класса. Поэтому, если вы задумали нечто такое - не удивляйтесь последствиям :)


Про tasting сказать вроде больше нечего, перейдём к orphanization. Это обратный tasting процесс, во время которого провайдеры уничтожаются. В переводе orphanization означает "осиротение" и это не просто так. Например, когда вы отключаете жесткий диск, драйвер диска посылает классу DISK команду на уничтожение. Тот, в свою очередь, инициирует процесс "осиротения" и все объекты geom, связанные с провайдером диска через консьюмеров "информируются" о том, что они стали "сиротами". Им передаётся код ошибки, который будет возвращён пользователю при любых попытка взаимодействия с осиротевшими провайдерами. Т.е. если вы попробуете записать данные на такой провайдер, то получите ошибку. Информирование выполняется через вызов базового для всех классов метода orphan, в реализации которого разработчик может предусмотреть действия, необходимые для завершения работы объекта. Ну и заканчивается этот процесс самоуничтожением всех осиротевших провайдеров и объектов. Если не углубляться в особенности реализации, то можно сказать, что "оно" всё делает "само". Само находит что ему нужно во время tasting, само уничтожается во время orphanization :)

Теперь рассмотрим spoiling. Этот механизм предназначен для защиты от устаревших метаданных. Стоит понимать, что защащются здесь не метаданные, а находящийся в памяти geom объект. Так как система оперирует с данными в памяти, важно чтобы они были синхронизированы с метаданными на диске.

Ранее я рассказывал о счётчиках доступа, которые по-совместительству служат для ограничения доступа. Этими же счётчиками подсистема GEOM пользуется для активации механизма spoiling. Когда кто-либо обращается к провайдеру, он явно или неявно вызывает функцию g_access, в которой указывается желаемый режим доступа. Например, вы записываете что-то на диск, для этого вам нужно получить "разрешение" на запись, при этом счётчик w увеличивается. Как только вы прекращаете запись - счётчик уменьшается. Переход состояния счётчика w из нуля в значение большее нуля вызывает механизм spoiling. При этом все консьюмеры этого провайдера предупреждаются о том, что кто-то открыл провайдера для записи. Никто не знает, что вы туда будете записывать, но если geom объекту это важно (там могут быть его метаданные), то он примет информацию к сведению. Обычно "принять к сведению" означает, что geom объект самоуничтожится (так же, как он это делает во время orphanization). Но стоит вам завершить запись, чтобы значение счётчика w перешло в ноль, будет иницирован процесс re-tasting по алгоритму, используемому при создании нового провайдера.

Таким образом срабатывает защита от устаревших метаданных. Если вы измените метаданные, или же просто произведёте запись не связанную с ними, то GEOM класс, использующий провайдера, заново обнаружит свои метаданные на нём и автоматически сконфигурируется. Если же вы удалите или повредите метаданные, то spoiling уничтожит geom объект, использующий провайдер. Жизненный пример на эту тему - gpart vs. bsdlabel:

# gpart show md0 md0s1
=>     9  204786  md0  MBR  (100M)
       9  204786    1  freebsd  (100M)

=>     0  204786  md0s1  BSD  (100M)
       0   51200      1  freebsd-ufs  (25M)
   51200   51200      2  freebsd-ufs  (25M)
  102400   51200      4  freebsd-ufs  (25M)
  153600   51186         - free -  (25M)

# bsdlabel -w md0s1
# gpart show md0 md0s1
=>     9  204786  md0  MBR  (100M)
       9  204786    1  freebsd  (100M)

=>     0  204786  md0s1  BSD  (100M)
       0      16         - free -  (8.0K)
      16  204770      1  !0  (100M)

В первом выводе gpart show есть два geom объекта класса PART. md0 - представляет таблицу разделов MBR, а md0s1 - является партицией MBR, провайдером объекта md0, и, в то же время, содержит разметку BSD. Выполнив bsdlabel -w мы выполнили запись на раздел md0s1, изменив метаданные объекта md0s1. При этом, в момент вызова команды bsdlabel она открыла провайдера md0s1 для записи, что инициировало spoiling - самоуничтожение объекта md0s1 и всех его партиций. После завершения записи на провайдере md0s1 снова были найдены метаданные разметки BSD, но на этот раз там один раздел.

Ещё один пример, эксплуатирующий эти особенности GEOM - это запуск механизма re-tasting в ручную. Хотя, честно говоря, мне он ниразу не понадобился, но в списках рассылки вчтречаются люди, которым это нужно. Делается это просто, нужно всего лишь "открыть" провайдер для записи и "закрыть" его, например так:

# true > /dev/ada2 

Это инициирует re-tasting и GEOM классы заново "обследуют" этот провайдер.


На этом пока всё. На счёт продолжения не уверен, но, возможно, будет ещё одна заметка.

    пятница, 10 декабря 2010 г.

    GEOM: практические знания. Часть 1.

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

    Начнём с определений. Что такое GEOM класс? Классом он называется в связи с похожестью на классы в Си++. Т.е. это некоторая структура, содержащая в себе методы-функции которые реализуют возможности этого класса. Набор методов стандартный для всех классов. Грубо говоря, разработка какого-то нового класса - это новая реализация этих методов.

    GEOM класс может иметь объекты (а может и не иметь). Если проводить аналогии с классами Си++, то это переменные этого класса. Например, есть GEOM класс MIRROR, он предоставляет возможность создавать зеркала. Когда вы загружаете модуль ядра, либо он у вас вкомпилирован туда, то ядро "знает" о существовании такого класса. Но когда вы создаёте зеркало, вы создаёте объект этого класса.

    Каждый объект geom может иметь "провайдера" (provider), даже несколько. Провайдер, это в общем-то то, ради чего GEOM и придуман. Они предоставляют пользователю услугу реализуемую GEOM классом. Тот же GEOM класс MIRROR предоставляет нам провайдера, например mirror/gm0, работая с которым мы получаем "услугу", реализующую программное зеркало.

    Другим важным понятием является консьюмер (consumer). Консьюмер - это то, через что geom объект получает доступ к провайдерам других объектов geom. Пример: у вас есть два жестких диска. Кстати говоря, драйверы дисков создают geom объекты класса DISK. Вы можете выполнить команду "geom DISK list", чтобы просмотреть информацию об объектах класса DISK:

     > geom DISK list
    Geom name: ada0
    Providers:
    1. Name: ada0
       Mediasize: 41110142976 (38G)
       Sectorsize: 512
       Mode: r0w0e0
       fwsectors: 63
       fwheads: 16
    
    Geom name: ada1
    Providers:
    1. Name: ada1
       Mediasize: 163928604672 (153G)
       Sectorsize: 512
       Mode: r2w2e5
       fwsectors: 63
       fwheads: 16
    
    Как видно, у меня тут два диска ada0 и ada1. Они у меня разные да и заняты данными, так что портить для демонстрации я их не буду. Создам два идентичных объекта класса MD:

    # mdconfig -s 100m
    md0
    # mdconfig -s 100m
    md1
    # geom MD list
    Geom name: md0
    Providers:
    1. Name: md0
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r0w0e0
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 0
    
    Geom name: md1
    Providers:
    1. Name: md1
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r0w0e0
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 1
    Как видно из вывода команд, у нас есть два объекта md0 и md1, у каждого из них по одному провайдеру, соответственно md0 и md1. Теперь, при создании geom объекта класса MIRROR его консьюмеры подключатся к провайдерам объектов md0 и md1, и будет создан провайдер mirror/gm0:

    # gmirror label gm0 md0 md1
    # geom MIRROR list
    Geom name: gm0
    State: COMPLETE
    Components: 2
    Balance: load
    Slice: 4096
    Flags: NONE
    GenID: 0
    SyncID: 1
    ID: 7231867
    Providers:
    1. Name: mirror/gm0
       Mediasize: 104857088 (100M)
       Sectorsize: 512
       Mode: r0w0e0
    Consumers:
    1. Name: md0   Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       State: ACTIVE
       Priority: 0
       Flags: NONE
       GenID: 0
       SyncID: 1
       ID: 2647920667
    2. Name: md1
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       State: ACTIVE
       Priority: 1
       Flags: NONE
       GenID: 0
       SyncID: 1
       ID: 4248479672
    
    Думаю этого должно быть достаточно для понимания, если недостаточно, попробуйте прочитать ещё раз :)

    Глядя на вывод команды geom <КЛАСС> list можно увидеть много полезной информации, для каждого класса "полезность" определил разработчик, поэтому у каких-то классов больше, у других - меньше, у третьих вообще ничего нет...

    Я бы хотел обратить внимание на строку содержащую слово "Mode:" и набор букв с цифрами. Она есть у всех провайдеров и консьюмеров и показывает режим доступа, который осуществляется по отношению к ним на данный момент. В первом выводе "geom MD list" режим доступа к провайдерам отображается как "r0w0e0". Это означает, что никто не пытается с них читать (r = 0), никто не пытается на них записывать (w = 0) и никто не пытается получить эксклюзивный доступ к ним (e = 0).

    Эти счётчики служат не только для отображения информации о текущем состоянии провайдеров и консьюмеров. Они так же являются ограничителями доступа. Например, если кто-то уже получил эксклюзивный доступ к провайдеру, то любой другой желающий получить доступ к нему будет "обрадован" сообщением об ошибке EPERM - "Operation not permitted". Наверное, многие из вас сталкивались с этим сообщением при попытке записать загрузочный код на провайдер, который содержит смонтированные разделы. Именно эта "защита от прострела ноги" отключается переменной "kern.geom.debugflags=16".

    Если выполнить команду "geom MD list" сейчас, после создания зеркала на них, то мы увидим, что у провайдеров изменился режим:

    # geom MD list
    Geom name: md0
    Providers:
    1. Name: md0
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 0
    
    Geom name: md1
    Providers:
    1. Name: md1
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 1
    
    Это постарались консьюмеры объекта mirror/gm0. Т.е. теперь, эти провайдеры защищены от случайной записи с вашей стороны, а значит и объект mirror/gm0 не будет неприятно удивлён испорченными данными. Поэтому использование "kern.geom.debugflags=16" является плохой практикой.

    ... Продолжение следует ...