Глава 18. Сложные темы

18.1. Как можно узнать больше о внутреннем устройстве FreeBSD?
18.2. Как можно оказать помощь проекту FreeBSD?
18.3. Что такое снапшоты и релизы?
18.4. Как самому сделать релиз?
18.5. По команде make world были переустановлены все программы.
18.6. Почему cvsup.FreeBSD.org не является одной DNS-записью для нескольких машин для распределения нагрузки между несколькими CVSup-серверами?
18.7. Можно ли работать с -CURRENT при ограниченном доступе в Internet?
18.8. Как вы разделяете дистрибутив на файлы по 1392 Кбайт?
18.9. Я написал некоторое добавление к ядру, кому его послать?
18.10. Как распознаются и инициализируются адаптеры ISA Plug N Play?
18.11. Мне нужно старшее число для написанного мною драйвера устройства.
18.12. Альтернативный метод размещения каталогов
18.13. Что делать при аварийном останове системы?
18.14. Перестала работать функция dlsym() для исполняемых файлов ELF!
18.15. Как я могу увеличить или уменьшить адресное пространство ядра в архитектуре i386?

18.1. Как можно узнать больше о внутреннем устройстве FreeBSD?

В настоящее время существует только одна книга по внутреннему устройству ОС, посвящённая FreeBSD, а именно ''The Design and Implementation of the FreeBSD Operating System'' Маршалла Кёрка МакКузика (Marshall Kirk McKusick) и Джорджа В. Невилле-Нейла (George V. Neville-Neil), ISBN 0-201-70245-2. В ней рассматривается FreeBSD версии 5.X.

Кроме того, большинство общих знаний о UNIX® непосредственно применимо к FreeBSD.

Список относящихся к делу книг можно найти в разделе Руководства Библиография по внутреннему устройству операционной системы.

18.2. Как можно оказать помощь проекту FreeBSD?

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

18.3. Что такое снапшоты и релизы?

В Хранилище CVS сейчас находятся четыре активно/полуактивно развивающихся ветки FreeBSD. (Более ранние ветки изменяются очень редко, именно поэтому в разработке только четыре активных ветки):

  • RELENG_7, также известная как 7-STABLE

  • RELENG_8, также известная как 8-STABLE

  • RELENG_9, также известная как 9-STABLE

  • HEAD, также известная как -CURRENT и 10-CURRENT

HEAD - это не реальный тэг ветки, в отличие от остальных; это просто символьная константа для обозначения ''текущего, не ветвящегося, находящегося в разработке дерева'', то есть -CURRENT.

На данный момент -CURRENT является находящимся в разработке деревом 10.X; ветка 9-STABLE, RELENG_9, отделилась от -CURRENT в сентябре 2011 года года, а ветка 8-STABLE, RELENG_8, отделилась от -CURRENT в августе 2009 года.

18.4. Как самому сделать релиз?

Пожалуйста, обратитесь к статье о процессе выпуска релизов.

18.5. По команде make world были переустановлены все программы.

Да, так и должно быть; как говорит название этой команды, make world выполняет построение всех системных файлов с нуля, так что в итоге можете быть уверены, что получите чистую рабочую систему (вот почему это занимает столько времени).

Если в момент запуска команд make world или make install определена переменная окружения DESTDIR, то вновь создаваемые файлы будут помещены в дерево каталогов. идентичное существующему, с корнем, располагающимся в ${DESTDIR}. Однако некоторые случайные комбинации модификаций совместно используемых библиотек и версий компилируемых программ при исполнении команды make world, могут этому помешать.

18.6. Почему cvsup.FreeBSD.org не является одной DNS-записью для нескольких машин для распределения нагрузки между несколькими CVSup-серверами?

Хотя зеркала CVSup обновляются с основного CVSup-сервера каждый час, это обновление может происходить в любой момент в течение часа. Это значит, что некоторые серверы будут иметь более новый код, чем остальные, хотя разница между версиями кода не превышает часа. Если бы для серверов cvsup.FreeBSD.org использовалась одна DNS-запись, то простое перенаправление пользователей на случайно выбранный сервер CVSup, при условии, что CVSup будет запускаться два раза подряд, может привести к сгрузке кода, более старого, чем тот, что присутствует в системе.

18.7. Можно ли работать с -CURRENT при ограниченном доступе в Internet?

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

18.8. Как вы разделяете дистрибутив на файлы по 1392 Кбайт?

Команда split(1) в современных BSD-системах имеет опцию -b, позволяющую разрезать файлы на части с точностью до байта.

Вот пример из файла /usr/src/release/Makefile.

ZIPNSPLIT=              gzip --no-name -9 -c | split -b 1392k -

18.9. Я написал некоторое добавление к ядру, кому его послать?

Обратитесь к статье о том, как помочь проекту FreeBSD, чтобы выяснить, как это сделать.

И спасибо Вам за Ваши усилия!

18.10. Как распознаются и инициализируются адаптеры ISA Plug N Play?

От: Фрэнка Дурды IV (Frank Durda IV)

Если рассматривать на самом низком уровне, то существует несколько портов ввода/вывода, в которые должны выводить информацию все адаптеры PnP, когда компьютер пытается выполнить запрос о наличии установленных адаптеров. Так что, когда запускается процедура определения адаптеров PnP, она выполняет запрос о наличии каких-либо адаптеров PnP, а все такие адаптеры выдают свой номер модели при чтении того же порта ввода/вывода, поэтому процедура определения получит ответ на свой запрос, состоящий из логически наложенных номеров моделей, интерпретируемый как ''да''. В этом ответе по крайней мере один бит будет установлен в единицу. Затем код определения адаптеров может ''выключать'' адаптеры с ID (назначаемыми Microsoft®/Intel®), большими, чем X. Потом следует попытка определить, остались ли ещё адаптеры, отвечающие на запрос. Если ответ 0, то адаптеров с ID, большими чем X, нет. После этого делается попытка определить наличие адаптеров с номерами, меньшими чем X. В заключение происходит запрос на выключение адаптерам, большим чем X - (limit / 4). Запрос повторяется. Применив этот метод полудвоичного поиска границ расположения ID достаточное количество раз, код идентификации найдёт все адаптеры PnP, установленные в данной машине за число итераций, гораздо меньшее, чем может занять перебор 264 возможных вариантов ID.

ID представляет собой два 32-разрядные числа (отсюда число 264) + 8 бит контрольной суммы. Первые 32 бита являются идентификатором производителя. Они никогда не сообщаются, однако часто бывает, что различные типы адаптеров от одного и того же производителя имеют различные 32-битные значения идентификатора производителя. Необходимость в 32 разрядах только для задания производителя адаптера выглядит несколько излишним.

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

Группы по 32 бита не могут быть нулевыми. Это позволяет при логическом объединении OR их номеров получать ненулевое значение во время начального поиска адаптеров.

Как только система определила ID всех адаптеров, она активизирует каждый адаптер, по одному за раз (через те же порты ввода/вывода), и определяет, какие ресурсы требуются данному адаптеру, какие возможные прерывания доступны и т.д. Сканирование и сбор информации происходит по всем адаптерам.

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

Затем устройства PnP опрашиваются ещё раз для назначения им портов ввода/вывода, DMA, IRQ и адресов отображаемой памяти. Теперь устройства должны иметь именно такие настройки и они должны оставаться такими до следующей перезагрузки, хотя нигде не сказано, что вы не можете их менять, когда захотите.

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

Microsoft использовала для PnP некоторые порты статуса первого принтера, по их логике, не существует адаптеров, использующих эти адреса для ввода/вывода. Я обнаружил один такой адаптер принтера от IBM, который декодирует запись в порт статуса в момент начального опроса устройств PnP, на что Microsoft ответила ''хулиган''. Так что они выполняют запись в порт статуса принтера для установки адресов, вдобавок используют этот адрес + 0x800, и ещё один порт ввода/вывода используют для чтения, который может располагаться где угодно в диапазоне между 0x200 и 0x3ff.

18.11. Мне нужно старшее число для написанного мною драйвера устройства.

В версиях FreeBSD, вышедших после февраля 2003 года, есть механизм динамического и автоматического выделения старших номеров для драйверов устройств во время выполнения (смотрите devfs(5)), поэтому в этом нет необходимости.

18.12. Альтернативный метод размещения каталогов

В ответ на вопрос о других методах размещения каталогов могу сказать, что используемая в настоящее схема не претерпела изменений с того момента, как я реализовал её в 1983 году. Эти соглашения были разработаны для оригинальной файловой системы FFS, и я никогда их не пересматривал. Эта схема хорошо работает, позволяя избежать переполнения групп дорожек. Как некоторые из вас заметили, она работает плохо при поиске. Большинство файловых систем создаются из архивов, которые были созданы с глубиной первого поиска (aka ftw). Это приводит к тому, что их каталоги размещаются на нескольких группах дорожек, создавая наихудший случай для последующего поиска глубиной один. Если бы было известно общее количество каталогов, которые должны быть созданы, выходом было бы создание каталогов в количестве (total / fs_ncg) на каждую группу дорожек. Очевидно, что пришлось бы придумать некоторую эвристику для определения этого числа. Даже при использовании небольшого фиксированного количества, скажем 10, ситуация улучшилась бы на порядок. Чтобы различать операции восстановления от обычных операций (для которых текущий алгоритм, вероятно, является более разумным), вы могли бы использовать объединение в кластеры объёмом до 10, если они помещаются в 10-секундном окне. Как бы то ни было, я думаю, что это большое поле для экспериментов.

Kirk McKusick , сентябрь 1998 года

18.13. Что делать при аварийном останове системы?

Вот типичная паника ядра:

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x40
fault code              = supervisor read, page not present
instruction pointer     = 0x8:0xf014a7e5
stack pointer           = 0x10:0xf4ed6f24
frame pointer           = 0x10:0xf4ed6f28
code segment            = base 0x0, limit 0xfffff, type 0x1b
                        = DPL 0, pres 1, def32 1, gran 1
processor eflags        = interrupt enabled, resume, IOPL = 0
current process         = 80 (mount)
interrupt mask          =
trap number             = 12
panic: page fault

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

Вот что вы должны сделать:

  1. Запишите значение указателя инструкций. Заметьте, что часть 0x8: в этом случае не важна: нам нужна часть 0xf0xxxxxx.

  2. Когда система перезагрузится, сделайте следующее:

    % nm -n kernel.that.caused.the.panic | grep f0xxxxxx
    

    где f0xxxxxx — это значение указателя инструкций. Однако неприятность заключается в том, что вы не получите точного соответствия, так как в таблице имен ядра для точек входа в функции даны адреса на начало функций, а указатель инструкций будет указывать куда-то внутрь её тела. Если вы не получили точного соответствия, опустите последнюю цифру в значении указателя инструкций и попробуйте снова, то есть:

    % nm -n kernel.that.caused.the.panic | grep f0xxxxx
    

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

Тем не менее, лучшим способом выяснить причину, вызвавшую аварийный останов, является получение аварийного дампа системы, а затем использование kgdb(1) для получения трассировки вызовов в этом дампе.

В любом случае, метод таков:

  1. Убедитесь в том, что в файле конфигурации ядра имеется следующая строка (/usr/src/sys/arch/conf/MYKERNEL):

    makeoptions     DEBUG=-g          # Build kernel with gdb(1) debug symbols
    
  2. Перейдите в каталог /usr/src:

    # cd /usr/src
    
  3. Скомпилируйте ядро:

    # make buildkernel KERNCONF=MYKERNEL
    
  4. Дождитесь завершения компиляции.

  5. # make installkernel KERNCONF=MYKERNEL
    
  6. Выполните перезагрузку.

Замечание: Если вы не используете переменную make KERNCONF, то будет собрано и установлено ядро GENERIC.

В процессе выполнения команды make(1) будут построены два ядра, /usr/obj/usr/src/sys/MYKERNEL/kernel и /usr/obj/usr/src/sys/MYKERNEL/kernel.debug. kernel будет установлен как /boot/kernel/kernel, тогда как kernel.debug может быть использован в качестве источника отладочных символов для kgdb(1).

Чтобы включить сброс аварийного дампа, вам нужно отредактировать файл /etc/rc.conf так, чтобы устройство dumpdev указывало на раздел подкачки (или имело значение AUTO). В этом случае скрипты rc(8) будут вызывать команду dumpon(8) для создания аварийных дампов. Также вы можете запустить команду dumpon(8) вручную. После аварийной остановки аварийный дамп может быть получен с помощью программы savecore(8) если значение переменной dumpdev было установлено в /etc/rc.conf, скрипты rc(8) запустят savecore(8) автоматически и поместят аварийный дамп в каталог /var/crash.

Замечание: Аварийные дампы FreeBSD обычно имеют размер, равный физическому объёму оперативной памяти вашей машины. Так что если у вас установлено 512 Мбайт ОЗУ, вы получите дамп размером 512 Мбайт. Поэтому вы должны удостовериться, что в каталоге /var/crash достаточно места для хранения дампа. Либо вы можете вручную запустить savecore(8) и создать аварийный дамп в другом каталоге, где достаточно места. Размер аварийного дампа можно уменьшить, указав в конфигурации ядра options MAXMEM=N, где N — значение в Кбайт для объёма памяти, которое будет использоваться ядром. Например, если у вас 1 Гбайт ОЗУ, вы можете ограничить использование памяти ядром 128 Мбайтами, так что размер аварийного дампа будет равен 128 Мбайт, а не 1 Гбайт.

Как только вы получили аварийный дамп, вы можете выполнить трассировку вызовов с помощью kgdb(1) таким образом:

% kgdb /usr/obj/usr/src/sys/MYKERNEL/kernel.debug /var/crash/vmcore.0
(kgdb) backtrace

Заметьте, что при этом может быть выведено несколько экранов информации; в идеале вы должны использовать script(1) для их перехвата. При использовании необработанного образа ядра со всей отладочной информацией может быть найдена конкретная строка исходного текста ядра, при достижении которой случилась аварийная остановка. Для выяснения последовательности событий, приведших к аварийному останову, обычно читается трассировка стека снизу вверх. Вы можете также использовать kgdb(1) для вывода значений различных переменных или структур, чтобы выяснить состояние системы во время аварии.

Подсказка: Теперь, если вы в самом деле душевнобольной и у вас есть второй компьютер, то вы можете настроить kgdb(1) для удалённой отладки, так, что сможете использовать kgdb(1) на одном компьютере, чтобы отладить ядро на другом, включая использование точек останова, пошагового прохода по коду ядра, всё как с обычной прикладной программой.

Замечание: Если у вас включена поддержка DDB и ядро переходит в режим отладки, вы можете намеренно вызвать аварийный останов (и создание аварийного дампа), набрав panic в командной строке ddb. Этот процесс может снова вызвать отладчик. В этом случае наберите continue и процесс будет завершён созданием аварийного дампа.

18.14. Перестала работать функция dlsym() для исполняемых файлов ELF!

По умолчанию при работе с форматом ELF символы, определённые в исполняемом файле, не доступны динамическому загрузчику. Поэтому при вызове функции dlsym(), которая осуществляет поиск по дескриптору, полученному после вызова dlopen(NULL, flags), желаемый результат достигнут не будет.

Если вы хотите осуществить поиск в выполнимом файле процесса с помощью функции dlsym(), вам нужно скомпоновать выполнимый файл с опцией --export-dynamic компоновщика ELF (ld(1)).

18.15. Как я могу увеличить или уменьшить адресное пространство ядра в архитектуре i386?

По умолчанию размер адресного пространства ядра для i386 равен 1 Гбайт (2 Гбайт для PAE). Если вы используете FreeBSD в качестве сервера с интенсивной сетевой нагрузкой (скажем, большой FTP или HTTP сервер) или хотите использовать ZFS, то вы можете обнаружить, что этого недостаточно.

Чтобы увеличить доступное пространство, добавьте следующую строку в файл конфигурации ядра и пересоберите его:

options KVA_PAGES=N

Чтобы получить нужное значение для N, разделите желаемый размер адресного пространства (в мегабайтах) на четыре. (Например, для 2 Гбайт это будет 512.)

Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

По вопросам, связанным с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам, связанным с русским переводом документации, пишите в рассылку <frdp@FreeBSD.org.ua>.
Информация по подписке на эту рассылку находится на сайте проекта перевода.