| |
Copyright © 2000, 2001, 2002, 2003 The FreeBSD Documentation Project
Добро пожаловать в Руководство по Архитектуре FreeBSD. Этот документ находится в процессе написания и представляет собой результат работы множества людей. Многие секции еще не написаны, а некоторые из написанных требуют обновления. Если Вы хотите помочь этому проекту, напишите в Список рассылки Проекта Документации FreeBSD.
Последняя версия этого документа постоянно доступна с Всемирного Веб Сайта FreeBSD. Этот документ может также быть найден в множестве форматов с FTP Сервера FreeBSD или одного из множества зеркал.
FreeBSD is a registered trademark of Wind River Systems, Inc. This is expected to change soon.
UNIX is a registered trademark of The Open Group in the US and other countries.
Sun, Sun Microsystems, SunOS, Solaris, and Java are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
Apple and QuickTime are trademarks of Apple Computer, Inc., registered in the U.S. and other countries.
Macromedia and Flash are trademarks or registered trademarks of Macromedia, Inc. in the United States and/or other countries.
Microsoft, Windows, and Windows Media are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.
PartitionMagic is a registered trademark of PowerQuest Corporation in the United States and/or other countries.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the FreeBSD Project was aware of the trademark claim, the designations have been followed by the '™' symbol.
Распространение и использование исходных (SGML DocBook) и 'скомпилированных' форм (SGML, HTML, PDF, PostScript, RTF и прочих) с модификацией или без оной, разрешены при соблюдении следующих соглашений:
Распространяемые копии исходного кода (SGML DocBook) должны сохранять вышеупомянутые объявления copyright, этот список положений и сохранять следующий отказ от права в первых строках этого файла немодифицированным.
Распространяемые копии скомпилированных форм (преобразование в другие DTD, конвертированные в PDF, PostScript, RTF и другие форматы) должны повторять вышеупомянутые объявления copyright, этот список положений и и следующий отказ в документации и/или других материалах, поставляемых с дистрибьюцией.
Important: ЭТА ДОКУМЕНТАЦИЯ ПОСТАВЛЯЕТСЯ ПРОЕКТОМ ДОКУМЕНТАЦИИ FREEBSD "КАК ЕСТЬ" И ЛЮБЫЕ ЯВНЫЕ ИЛИ НЕЯВНЫЕ ГАРАНТИИ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ НЕЯВНЫМИ ГАРАНТИЯМИ, КОММЕРЧЕСКОЙ ЦЕННОСТИ И ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ ОТРИЦАЮТСЯ. НИ В КОЕМ СЛУЧАЕ РЕГЕНТЫ ИЛИ УЧАСТНИКИ НЕ ДОЛЖНЫ БЫТЬ ОТВЕТСТВЕННЫМИ ЗА ЛЮБОЙ ПРЯМОЙ, КОСВЕННЫЙ, СЛУЧАЙНЫЙ, СПЕЦИАЛЬНЫЙ, ОБРАЗЦОВЫЙ ИЛИ ПОСЛЕДУЮЩИЙ УЩЕРБЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ПОСТАВКОЙ ТОВАРОВ ЗАМЕНЫ ИЛИ УСЛУГ; ПОТЕРЮ ДАННЫХ ИЛИ ИХ НЕПРАВИЛЬНУЮ ПЕРЕДАЧУ ИЛИ ПОТЕРИ; ПРИОСТАНОВЛЕНИЕ БИЗНЕСА), И ТЕМ НЕ МЕНЕЕ ВЫЗВАННЫЕ И В ЛЮБОЙ ТЕОРИИ ОТВЕТСТВЕННОСТИ, НЕЗАВИСИМО ОТ КОНТРАКТНОЙ, СТРОГОЙ ОТВЕТСТВЕННОСТИ, ИЛИ ПРАВОНАРУШЕНИИ (ВКЛЮЧАЯ ХАЛАТНОСТЬ ИЛИ ИНЫМ СПОСОБОМ), ВОЗНИКШЕМ ЛЮБЫМ ПУТЕМ ПРИ ИСПОЛЬЗОВАНИИ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, ДАЖЕ ЕСЛИ БЫ БЫЛО СООБЩЕНО О ВОЗМОЖНОСТИ ТАКОГО УЩЕРБА.
Эта глава поддерживается проектом FreeBSD
SMP Next Generation Project <[email protected]>
.
Этот документ описывает механизм блокировки, используемый в ядре FreeBSD для обеспечения эффективной поддержки нескольких процессоров в ядре. Блокировку можно рассматривать с нескольких точек зрения. Структуры данных могут быть защищены с помощью блокировок mutex или lockmgr(9). Несколько переменных защищены просто в силу атомарности используемых для доступа к ним операций.
Мьютекс (mutex) - это просто блокировка, используемая для реализации гарантированной исключительности. В частности, в каждый момент времени мьютексом может владеть только один объект. Если какой-то объект хочет получить мьютекс, который уже кто-то занял, он должен дождаться момента его освобождения. В ядре FreeBSD владельцами мьютексов являются процессы.
Мьютексы могут быть затребованы рекурсивно, но предполагается, что они занимаются на короткое время. В частности, владельцу мьютекса нельзя выдерживать паузу. Если вам нужно выполнить блокировку на время паузы, используйте блокировку через lockmgr(9).
Каждый мьютекс имеет несколько представляющих интерес характеристик:
Имя переменной struct mtx в исходных текстах ядра.
Имя мьютекса, назначенное ему через mtx_init
. Это имя
выводится в сообщениях трассировки KTR и диагностических предупреждающих и ошибочных
сообщениях и используется для идентификации мьютексов в отладочном коде.
Тип мьютекса в терминах флагов MTX_*
. Значение каждого
флага связано с его смыслом так, как это описано в mutex(9).
MTX_DEF
Sleep-мьютекс
MTX_SPIN
Spin-мьютекс
MTX_COLD
Этот мьютекс инициализируется очень рано. Поэтому он должен быть объявлен через
функции MUTEX_DECLARE
, а флаг MTX_COLD
должен быть передан в функцию mtx_init
.
MTX_TOPHALF
Этот spin-мьютекс не запрещает прерывания.
MTX_NORECURSE
Этот мьютекс не разрешается блокировать рекурсивно.
Список структур данных или членов структур данных, которые защищает этот мьютекс. Для
членов структур данных имя будет в форме имя
структуры
.имя члена структуры
.
Функции, которые можно вызвать, если этот мьютекс занят.
Table 1-1. Список мьютексов
Имя переменной | Логическое имя | Тип | Защиты | Зависимые функции |
---|---|---|---|---|
sched_lock | ``sched lock'' | MTX_SPIN | MTX_COLD |
_gmonparam, cnt.v_swtch, cp_time, curpriority, mtx .mtx_blocked , mtx .mtx_contested , proc .p_contested , proc .p_blocked , proc .p_flag (P_PROFIL XXX, P_INMEM , P_SINTR , P_TIMEOUT , P_SWAPINREQ XXX, P_INMEN XXX),
proc .p_nice , proc .p_procq , proc .p_blocked , proc .p_estcpu , proc .p_nativepri , proc .p_priority , proc .p_usrpri , proc .p_rtprio , proc .p_rqindex , proc .p_stats->p_prof ,
proc .p_stats->p_ru ,
proc .p_stat , proc .p_cpticks proc .p_iticks , proc .p_uticks , proc .p_sticks , proc .p_swtime , proc .p_slptime , proc .p_runtime , proc .p_pctcpu , proc .p_oncpu , proc .p_asleep , proc .p_wchan , proc .p_wmesg , proc .p_slpq , proc .p_vmspace (XXX - в statclock ), pscnt, slpque, itqueuebits, itqueues, rtqueuebits, rtqueues, queuebits, queues, idqueuebits, idqueues, switchtime, |
setrunqueue , remrunqueue ,
mi_switch , chooseproc , schedclock , resetpriority , updatepri , maybe_resched , cpu_switch , cpu_throw |
vm86pcb_lock | ``vm86pcb lock'' | MTX_DEF | MTX_COLD |
vm86pcb | vm86_bioscall |
Giant | ``Giant'' | MTX_DEF | MTX_COLD |
nearly everything | lots |
callout_lock | ``callout lock'' | MTX_SPIN |
callfree, callwheel, nextsoftcheck, proc .p_itcallout , proc .p_slpcallout , softticks, ticks |
Блокировки, которые даются через интерфейс lockmgr(9), являются блокировками менеджера блокировок. Эти блокировки являются блокировками на чтение/запись и ими могут владеть процессы в состоянии ожидания.
Переменной, защищенной атомарно, является особая переменная, которая не защищается явной блокировкой. Вместо этого для доступа к данным переменных используются специальные атомарные операции, как описано в atomic(9). Лишь несколько переменных используются таким образом, хотя другие примитивы синхронизации, такие как мьютексы, реализованы с атомарно защищенными переменными.
astpending
mtx
.mtx_lock
Объекты ядра (Kernel Objects), или сокращенно Kobj, дают систему программирования на C для ядра, ориентированную на объекты. Таким образом, обрабатываемые данные включают описание того, как их обрабатывать. Это позволяет добавлять и удалять функции из интерфейса во время работы и без нарушения двоичной совместимости.
Набор данных - структура данных - распределение данных.
Операция - функция.
Один или более методов.
Стандартный набор из одного или более методов.
Kobj работает, создавая описания методов. Каждое описание содержит уникальный идентификатор, а также функцию по умолчанию. Адрес описания используется для однозначной идентификации метода внутри таблицы методов класса.
Класс строится созданием таблицы методов, связывающей одну или большее количество функций с описаниями методов. Перед использованием класс компилируется. При компиляции выделяется кэш и он связывается с классом. Каждому описанию метода в таблице методов класса ставится в соответствие уникальный идентификатор, если это уже не сделано при компиляции другого связанного класса. Для каждого используемого метода скриптом генерируется функция для проверки аргументов и автоматической отсылки к описанию метода для поиска. Генерируемая функция ищет метод при помощи уникального идентификатора, связанного с описание метода, используемого в качестве хэша, в кэше, связанном с классом объекта. Если метод не кэширован, то генерируемая функция будет использовать таблицу класса для поиска метода. Если метод найден, то используется соответствующая функция из класса; в противном случае используется функция по умолчанию, связанная с описанием метода.
Это описание можно изобразить следующим образом:
object->cache<->class
struct kobj_method
void kobj_class_compile(kobj_class_t cls); void kobj_class_compile_static(kobj_class_t cls, kobj_ops_t ops); void kobj_class_free(kobj_class_t cls); kobj_t kobj_create(kobj_class_t cls, struct malloc_type *mtype, int mflags); void kobj_init(kobj_t obj, kobj_class_t cls); void kobj_delete(kobj_t obj, struct malloc_type *mtype);
KOBJ_CLASS_FIELDS KOBJ_FIELDS DEFINE_CLASS(name, methods, size) KOBJMETHOD(NAME, FUNC)
Первый шаг при использовании Kobj заключается в создании интерфейса. Создание интерфейса включает создание шаблона, который может использоваться скриптом src/sys/kern/makeobjops.pl для генерации файла объявлений и кода для объявлений метода и функций поиска методов.
Внутри этого шаблона используются следующие ключевые слова: #include, INTERFACE, CODE, METHOD, STATICMETHOD и DEFAULT.
Директива #include и то, что следует далее, без изменений копируется в начало файла генерируемого кода.
Например:
#include <sys/foo.h>
Ключевое слово INTERFACE используется для определения имени интерфейса. Это имя объединяется с именем каждого метода в виде [имя интерфейса]_[имя метода]. Он имеет синтаксис INTERFACE [имя интерфейса];.
Например:
INTERFACE foo;
Ключевое слово CODE копирует свои аргументы без изменений в файл кода. Он имеет синтаксис CODE { [нечто] };
Например:
CODE { struct foo * foo_alloc_null(struct bar *) { return NULL; } };
Ключевое слово METHOD описывает метод. Его синтаксис METHOD [возвращаемый тип] [имя метода] { [объект [, аргументы]] };
For example:
METHOD int bar { struct object *; struct foo *; struct bar; };
Ключевое слово DEFAULT может следовать за ключевым словом METHOD. Оно расширяет ключевое слово METHOD добавлением к методу функции, используемой по умолчанию. Расширенный синтаксис имеет вид METHOD [возвращаемый тип] [имя метода] { [объект; [другие аргументы]] }DEFAULT [функция по умолчанию];
Например:
METHOD int bar { struct object *; struct foo *; int bar; } DEFAULT foo_hack;
Ключевое слово STATICMETHOD используется так же, как и METHOD, за исключением того, что данные kobj не располагаются в начале структуры объекта, поэтому приведение к типу kobj_t будет некорректным. Вместо этого STATICMETHOD опирается на данные Kobj, на которые ссылаются как 'ops'. Это также полезно для непосредственного вызова методов из таблицы методов класса.
Другие законченные примеры:
src/sys/kern/bus_if.m src/sys/kern/device_if.m
Второй шаг при использовании Kobj заключается в создании класса. Класс состоит из
имени, таблицы методов и размера объектов, если использовались механизмы обработки
объекта Kobj. Для создания класса воспользуйтесь макросом DEFINE_CLASS()
. Для создания таблицы методов создайте массив из
kobj_method_t, завершающийся NULL. Каждый элемент, не равный NULL, может быть создан при
помощи макроса KOBJMETHOD()
.
Например:
DEFINE_CLASS(fooclass, foomethods, sizeof(struct foodata)); kobj_method_t foomethods[] = { KOBJMETHOD(bar_doo, foo_doo), KOBJMETHOD(bar_foo, foo_foo), { NULL, NULL} };
Класс должен быть ``откомпилирован''. В зависимости от состояния системы в момент,
когда класс инициализирует статически выделяемый кэш, используется ``таблица ops''. Это
может быть достигнуто объявлением struct kobj_ops
и
использованием kobj_class_compile_static();
, в противном
случае должна быть использована функция kobj_class_compile()
.
Третий шаг в использовании Kobj включает и то, как определить объект. Процедуры
создания объекта Kobj предполагают, что данные Kobj располагаются в начале объекта. Если
это не подходит, то вам нужно распределить объект самостоятельно и затем воспользоваться
функцией kobj_init()
над его частью, относящейся к Kobj; в
противном случае вы можете использовать функцию kobj_create()
для выделения и инициализации части Kobk объекта
автоматически. kobj_init()
можно также использовать для
изменения класса, который использует объект.
Для интеграции Kobj в объект, вы должны использовать макрос KOBJ_FIELDS.
Например
struct foo_data { KOBJ_FIELDS; foo_foo; foo_bar; };
Последний шаг в использовании Kobj сводится просто к использованию сгенерированных функция для вызова требуемого метода из класса объекта. Это также просто, как использование имени интерфейса и имени метода с некоторыми модификациями. Имя интерфейса должно быть объединено с именем метода через символ '_' между ними, все в верхнем регистре.
К примеру, если имя интерфейса было foo, а метод назывался bar, то вызов будет таким:
[return value = ] FOO_BAR(object [, other parameters]);
Когда объект, распределенный через kobj_create()
, больше
не нужен, то над ним может быть выполнена функция kobj_delete()
, а когда класс больше не используется, то над ним
может быть выполнен вызов kobj_class_free()
.
Перевод на русский язык: Сергей Любка (<[email protected]>
)
На большинстве UNIX систем, пользователь root имеет неограниченные права. Это потенциально небезопасно. Если атакующий сумеет получить права root, любая функция системы будет под его контролем. FreeBSD имеет ряд параметров ядра, ограничивающих безраздельные права root для уменьшения возможного ущерба от атакующего. Например, одним из таких параметров является уровень защиты (secure level). Начиная с версии FreeBSD 4.0, другим таким параметром является jail(8). Jail (jail в переводе - тюрьма, заключение) делает chroot окружения, и накладывает определенные ограничения на процессы, которые в нем порождены. Например, jailed процесс (т.е. процесс, сделавший вызов jail) не может влиять на процессы вне jail, делать некоторые системные вызовы, или каким-либо образом повреждать другие части ОС.
Jail становится новой моделью защиты. Администраторы запускают потенциально уязвимые сервисы, такие как Apache, BIBD и sendmail, внутри jail; и если атакующий даже и получит права root внутри Jail, то это не приведет к краху всей системы. Эта статья концентрируется на внутренней реализации Jail и Jail NG, а также предложит некоторые улучшения к теперешней реализации. Если Вас интересует административная сторона установки Jail, я рекомендую просмотреть мою статью в Sys Admin Magazine за май 2001, под заглавием "Securing FreeBSD using Jail".
Jail состоит из двух частей: непривилегированной (user-space) программы, jail, и кода в ядре: системного вызова jail и соответствующих ограничений. Сначала я рассмотрю user-space утилиту jail, а затем то, как jail реализованa в ядре.
Исходный код утилиты jail находится в каталоге /usr/src/usr.sbin/jail, в файле jail.c. Утилита принимает следующие аргументы командной строки: путь к jail, имя хоста, ip адрес, и команду.
В файле jail.c первой вещью, которую я хотел бы отметить, это декларация важной структуры, struct jail j, которая включена из файла /usr/include/sys/jail.h.
Вот определение это структуры:
/usr/include/sys/jail.h: struct jail { u_int32_t version; char *path; char *hostname; u_int32_t ip_number; };
Как можно заметить, в ней есть поле для каждого из аргументов, передаваемого утилите jail, и они выставляются при запуске jail.
/usr/src/usr.sbin/jail.c j.version = 0; j.path = argv[1]; j.hostname = argv[2];
Одним из аргументов, передаваемых jail, есть ip адрес, по которому jail может быть доступен из сети. Jail переводит переданный ip адрес в big endian формат (network byte order), и сохраняет его в j (структуре jail).
/usr/src/usr.sbin/jail/jail.c: struct in.addr in; ... i = inet.aton(argv[3], &in); ... j.ip.number = ntohl(in.s.addr);
Функция inet_aton(3)
интерпретирует переданную ей строку как интернет адрес, и сохраняет его по переданному
указателю на структуру in_addr. Функция ntohl()
изменяет
порядок следования байтов на сетевой (big endian), и получившееся значение сохраняется в
поле ip адреса структуры jail.
Наконец, утилита jail выполняет системный вызов jail, чем ``заключает в тюрьму'' сама себя, и порождает дочерний процесс, который затем выполняет команду, указанную в командной строке jail, используя вызов execv(3):
/usr/src/sys/usr.sbin/jail/jail.c i = jail(&j); ... i = execv(argv[4], argv + 4);
Системному вызову jail, как видно из листинга, передается указатель на заполненную структуру jail. Теперь я собираюсь обсудить, как Jail реализован в ядре.
Заглянем в файл /usr/src/sys/kern/kern_jail.c. Это файл, в котором определены системный вызов jail, соответствующие параметры ядра (sysctls), и сетевые функции.
В файле kern_jail.c определены следующие параметры ядра:
/usr/src/sys/kern/kern_jail.c: int jail_set_hostname_allowed = 1; SYSCTL_INT(_jail, OID_AUTO, set_hostname_allowed, CTLFLAG_RW, &jail_set_hostname_allowed, 0, "Processes in jail can set their hostnames"); int jail_socket_unixiproute_only = 1; SYSCTL_INT(_jail, OID_AUTO, socket_unixiproute_only, CTLFLAG_RW, &jail_socket_unixiproute_only, 0, "Processes in jail are limited to creating UNIX/IPv4/route sockets only "); int jail_sysvipc_allowed = 0; SYSCTL_INT(_jail, OID_AUTO, sysvipc_allowed, CTLFLAG_RW, &jail_sysvipc_allowed, 0, "Processes in jail can use System V IPC primitives");
Каждый из этих параметров доступен пользователю через утилиту sysctl. В ядре эти параметры распознаются по уникальному имени; например, имя первого параметра будет jail.set.hostname.allowed.
Как и все другие системные вызовы, вызов jail(2) принимает два аргумента, struct proc *p и struct jail_args *uap. p - это указатель на структуру proc текущего процесса, а uap - это аргумент, переданный утилитой jail системному вызову jail(2):
/usr/src/sys/kern/kern_jail.c: int jail(p, uap) struct proc *p; struct jail_args /* { syscallarg(struct jail *) jail; } */ *uap;
Таким образом, uap->jail будет указывать на структуру
jail, переданную системному вызову. Далее, структура jail копируется в адресное
пространство ядра с помощью функции copyin()
, которая
принимает три аргумента: данные, которые необходимо скопировать (uap->jail), где их сохранить (j) и
размер копируемых данных. Структура jail, переданная как параметр системному вызову,
копируется в пространство ядра и сохраняется в другой структуре jail, j.
/usr/src/sys/kern/kern_jail.c: error = copyin(uap->jail, &j, sizeof j);
В файле jail.h определена другая важная структура - структура prison (pr). Эта структура используется исключительно ядром. Системный вызов jail(2) копирует все из структуры jail в структуру prison. Вот определение структуры prison:
/usr/include/sys/jail.h: struct prison { int pr_ref; char pr_host[MAXHOSTNAMELEN]; u_int32_t pr_ip; void *pr_linux; };
Далее, системный вызов jail() выделяет память для структуры prison и копирует данные:
/usr/src/sys/kern/kern_jail.c: MALLOC(pr, struct prison *, sizeof *pr , M_PRISON, M_WAITOK); bzero((caddr_t)pr, sizeof *pr); error = copyinstr(j.hostname, &pr->pr_host, sizeof pr->pr_host, 0); if (error) goto bail;
Наконец, jail() делает вызов chroot(2) для указанного пути. Вызову chroot() передаются два аргумента: первый - это p, который представляет вызвавший процесс, а второй - указатель на структуру chroot args. Структура chroot args содержит путь, который станет новым корнем. Путь, указанный в структуре jail, копируется в структуру chroot args и используется далее:
/usr/src/sys/kern/kern_jail.c: ca.path = j.path; error = chroot(p, &ca);
Следующие три строки кода очень важны, поскольку указывают, как ядро распознает jailed процесс. Каждый процесс в UNIX описывается своей структурой proc. Определение структуры proc можно посмотреть в /usr/include/sys/proc.h. К примеру, аргумент p, передаваемый в каждый системный вызов есть на самом деле указатель на эту структуру, как было указано выше. Структура proc содержит поля, описывающие владельца процесса (p_cred), лимиты ресурсов (p_limit), и так далее. В определении структуры есть также указатель на структуру prison (p_prison):
/usr/include/sys/proc.h: struct proc { ... struct prison *p_prison; ... };
В файле kern_jail.c, вызов jail() копирует структуру pr, заполненную в соответствии с информацией из структуры jail, в структуру p->p_prison. Затем делается побитовое OR p->p_flag и константы P_JAILED, что в дальнейшем даст возможность распознать этот процесс как jailed. Родительский процесс для любого дочернего процесса внутри jail есть программа jail. Когда дочерний процесс делает execve(), он наследует поля структуры proc от родительского, и, таким образом, также имеет в поле p->p_flag выставленный флаг P_JAILED, и заполненную структуру p->p_prison.
/usr/src/sys/kern/kern_jail.c p->p.prison = pr; p->p.flag --= P.JAILED;
Когда процесс порождает дочерний процесс, системный вызов fork(2) работает несколько иначе для процесса в jail. fork(2) принимает два аргумента - указателя на структуру proc, p1 и p2. Первый указатель, p1, указывает на структуру proc родительского процесса, а второй, p2 - дочернего. Структура, на которую указывает p2, не заполнена. После копирования всех необходимых данных между структурами, fork(2) проверяет, не заполнена ли структура p->p_prison у дочернего процесса, и если заполнена, то увеличивает счетчик pr.ref на единицу и выставляет флаг P_JAILED в поле p_flag:
/usr/src/sys/kern/kern_fork.c: if (p2->p_prison) { p2->p_prison->pr_ref++; p2->p_flag |= P_JAILED; }
Для реализации jail, во многих местах в коде ядра встречаются соответствующие проверки и ограничения. Как правило, большинство этих проверок сводятся к проверке, является ли данный процесс ``заключенным'' (jailed) и возврат кода ошибки, если да. К примеру:
if (p->p_prison) return EPERM;
Подсистема межпроцессного взаимодействия SysV основана на сообщениях. Процессы могут посылать сообщения друг другу с различной информацией. Для отсылки сообщений используются следующие функции: msgsys, msgctl, msgget, msgsend и msgrcv. Ранее я упоминал о том, что существует ряд параметров ядра, установка или сброс которых могут повлиять на поведение Jail. Одним из таких параметров является jail_sysvipc_allowed. На большинстве систем этот параметр установлен в 0. Если его установить в 1, это может свести на нет само использование Jail, так как привилегированный пользователь внутри jail сможет посылать сообщения процессам вне jail, и таким образом, влиять на них. Отличие между сигналом и сообщением SysV состоит в том, что сигнал можно рассматривать как сообщение с одним полем данных, а именно, номером сигнала, когда как сообщения SysV могут содержать большее количество полей данных:
/usr/src/sys/kern/sysv_msg.c:
msgget(3): msgget возвращает (и, возможно, создает) дескриптор очереди сообщений для дальнейшей отсылки/приема сообщений. Очередь сообщений, где хранятся сообщения, посланные процессами, располагается в пространстве ядра.
msgctl(3): Используя эту функцию, процесс может запросить текущий статус очереди сообщений по ее дескриптору.
msgsnd(3): msgsnd отсылает сообщение в очередь сообщений.
msgrcv(3): Процесс вызывает эту функцию для извлечения сообщения из очереди сообщений
Для каждого из этих системных вызовов, в коде присутствует следующая проверка:
/usr/src/sys/kern/sysv msg.c: if (!jail.sysvipc.allowed && p->p_prison != NULL) return (ENOSYS);
Системные вызовы, связанные с семафорами, позволяют различным процессам синхронизировать выполнения путем выполнения атомарных операций над специальными объектами - семафорами. Семафоры - один из способов блокировки ресурсов. Процесс, ждущий освобождения семафора, будет находиться в состоянии сна до тех пор, пока семафор не будет освобожден. Для процесса в jail недоступны следующие вызовы: semsys, semget, semctl и semop.
/usr/src/sys/kern/sysv_sem.c:
semctl(2)(id, num, cmd, arg): Semctl выполняет операцию cmd над семафором, указанном id.
semget(2)(key, nsems, flag): Semget создает массив семафоров для ключа key.
Key и flag имеют то же значение, что и для вызова msgget.
semop(2)(id, ops, num): Semop производит атомарные операции, указанные op, над семафорами, указанными id.
SysV IPC позволяет процессам совместно использовать память. Процессы могут совместно использовать части своих адресных пространств и таким образом непосредственно обращаться к памяти другого процесса. Для процесса в jail недоступны следующие вызовы: shmdt, shmat, oshmctl, shmctl, shmget, и shmsys.
shmctl(2)(id, cmd, buf): shmctl производит различные управляющие операции над блоком памяти, указанном id.
shmget(2)(key, size, flag): shmget возвращает или создает новый блок памяти размером в size байт.
shmat(2)(id, addr, flag): shmat присоединяет блок памяти, указанным id к адресному пространству процесса.
shmdt(2)(addr): shmdt detaches отсоединяет блок памяти, ранее присоединенный по адресу addr.
Внутри jail системный вызов socket(2) и связанные с ним низкоуровневые функции работают особым образом. Для того чтобы определить, разрешено ли создание того или иного сокета, socket(2) сначала проверяет, установлен ли параметр ядра jail.socket.unixiproute.only. Если установлен, то сокет можно создать только для доменов PF_LOCAL, PF_INET или PF_ROUTE. Иначе, возвращается ошибка:
/usr/src/sys/kern/uipc_socket.c: int socreate(dom, aso, type, proto, p) ... register struct protosw *prp; ... { if (p->p_prison && jail_socket_unixiproute_only && prp->pr_domain->dom_family != PR_LOCAL && prp->pr_domain->dom_family != PF_INET && prp->pr_domain->dom_family != PF_ROUTE) return (EPROTONOSUPPORT); ... }
Berkeley Packet Filter обеспечивает интерфейс к уровню канала данных. Функция bpfopen() открывает устройство Ethernet. Процессам внутри jail вызов этой функции запрещен:
/usr/src/sys/net/bpf.c: static int bpfopen(dev, flags, fmt, p) ... { if (p->p_prison) return (EPERM); ... }
Существует ряд распространенных сетевых протоколов, таких как TCP, UDP, IP, ICMP. IP и ICMP находятся на одном уроне стека: на сетевом. Для предотвращения привязки jailed процесса к определенному порту, предусмотрен ряд мер: процессу позволено сделать bind(), только если установлен параметр nam. nam - это указатель на структуру sockaddr, которая указывает на адрес, к которому привязать сервис. В функции pcbbind, sin - это указатель на структуру sockaddr.in, содержащую порт, адрес и домен сокета, для которого требуется привязка.
/usr/src/sys/kern/netinet/in_pcb.c: int in.pcbbind(int, nam, p) ... struct sockaddr *nam; struct proc *p; { ... struct sockaddr.in *sin; ... if (nam) { sin = (struct sockaddr.in *)nam; ... if (sin->sin_addr.s_addr != INADDR_ANY) if (prison.ip(p, 0, &sin->sin.addr.s_addr)) return (EINVAL); .... } ... }
Вы можете спросить, что делает функция prison_ip(). prison.ip принимает три аргумента: текущий процесс (указываемый p), флаги, и ip адрес. Она возвращает 1 если ip адрес принадлежит jail и 0, если нет. Как можно видеть из фрагмента кода, если ip адрес принадлежит jail, сокету не будет позволено привязаться к порту.
/usr/src/sys/kern/kern_jail.c: int prison_ip(struct proc *p, int flag, u_int32_t *ip) { u_int32_t tmp; if (!p->p_prison) return (0); if (flag) tmp = *ip; else tmp = ntohl (*ip); if (tmp == INADDR_ANY) { if (flag) *ip = p->p_prison->pr_ip; else *ip = htonl(p->p_prison->pr_ip); return (0); } if (p->p_prison->pr_ip != tmp) return (1); return (0); }
Процессам в jail не позволено привязывать сервисы к ip адресам, не принадлежащим jail. Это ограничение также есть в функции in_pcbbind:
/usr/src/sys/net inet/in_pcb.c if (nam) { ... lport = sin->sin.port; ... if (lport) { ... if (p && p->p_prison) prison = 1; if (prison && prison_ip(p, 0, &sin->sin_addr.s_addr)) return (EADDRNOTAVAIL);
Даже пользователь root внутри jail не может устанавливать специальные флаги для файла, такие как immutable, append, no unlink, если securelevel больше 0:
/usr/src/sys/ufs/ufs/ufs_vnops.c: int ufs.setattr(ap) ... { if ((cred->cr.uid == 0) && (p->prison == NULL)) { if ((ip->i_flags & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) && securelevel > 0) return (EPERM); }
Jail NG - это ``переписанная с нуля'' реализация Jail, ведомая Робертом Ватсоном (Robert Watson), коммиттером FreeBSD. Ряд новшеств включают в себя возможность добавления процессов в jail, усовершенствованный механизм управления, и возможность устанавливать параметры (sysctls) для каждой jail в отдельности. Например, одна jail может иметь sysvipc_permitted установленной, что позволит использовать SysV IPC, тогда как другая может иметь этот параметр сброшенным. Патчи для ядра доступны по адресу http://www.watson.org/~robert/jailng/.
Sysinit является общим механизмом для сортировки и диспетчеризации вызовов. В настоящее время FreeBSD использует его для динамической инициализации ядра. Sysinit позволяет переорганизовывать подсистемы ядра FreeBSD, а также добавлять, удалять и замещать их на этапе компоновки ядра, во время его загрузки или загрузки одного из его модулей, без необходимости редактировать статически организованный порядок инициализации и перекомпилировать ядро. Эта система позволяет также модулям ядра, которые сейчас называются KLD, компилироваться отдельно, компоноваться и инициализироваться во время загрузки, и к тому же загружаться позже, при уже работающей системе. Это достигается при помощи ``компоновщика ядра'' и ``компоновочных наборов''.
Техника компоновки, при которой компоновщик переносит статически объявленные данные посредством исходных файлов программы в один сплошной адресуемый блок данных.
Sysinit основан на возможности компоновщика брать статические данные, объявленные во многих местах, из исходного кода программы и группировать их вместе как один сплошной блок данных. Эта техника компоновки называется ``linker set'' (компоновочный набор). Sysinit использует два набора компоновки для работы с двумя наборами данных, содержащих все последовательности вызовов, функцию и указатель на данные для передачи этой функции.
Sysinit использует два приоритета при организации последовательности вызовов функций. Первый приоритет это идентификатор (ID) подсистемы, дающий общий порядок диспетчеризации функции через Sysinit. Текущие предопределенные ID перечислены в файле <sys/kernel.h> в списке sysinit_sub_id. Второй используемый приоритет является порядковым номером элемента в подсистеме. Текущие предопределенные порядковые номера элементов подсистемы находятся в <sys/kernel.h> с списке sysinit_elem_order.
На данный момент есть два применения Sysinit. Диспетчеризация функций при загрузке системы и модулей ядра, и диспетчеризация функций при закрытии системы и выгрузке модулей ядра.
<sys/kernel.h>
SYSINIT(uniquifier, subsystem, order, func, ident) SYSUNINIT(uniquifier, subsystem, order, func, ident)
Макрос SYSINIT() создает необходимые данные sysinit в начальном наборе данных Sysinit для того, чтобы Sysinit сортировал и диспетчеризировал функцию при запуске системы и загрузке модуля. SYSINIT() принимает уникальный идентификатор, который используется в Sysinit для идентификации данных диспетчеризации конкретной функции, порядковый номер подсистемы, порядковый номер элемента подсистемы, вызываемую функцию и данные для передачи в функцию. Все функции должны принимать в качестве параметра статический указатель.
Например:
#include <sys/kernel.h> void foo_null(void *unused) { foo_doo(); } SYSINIT(foo_null, SI_SUB_FOO, SI_ORDER_FOO, NULL); struct foo foo_voodoo = { FOO_VOODOO; } void foo_arg(void *vdata) { struct foo *foo = (struct foo *)vdata; foo_data(foo); } SYSINIT(foo_arg, SI_SUB_FOO, SI_ORDER_FOO, foo_voodoo);
Макрос SYSUNINIT() ведет себя похоже на макрос SYSINIT(), за исключением того, что он добавляет данные Sysinit к набору данных закрытия Sysinit.
Например:
#include <sys/kernel.h> void foo_cleanup(void *unused) { foo_kill(); } SYSUNINIT(foo_cleanup, SI_SUB_FOO, SI_ORDER_FOO, NULL); struct foo_stack foo_stack = { FOO_STACK_VOODOO; } void foo_flush(void *vdata) { } SYSUNINIT(foo_flush, SI_SUB_FOO, SI_ORDER_FOO, foo_stack);
Перевод на русский язык: Андрей Захватов (<[email protected]>
)
Материал предоставил Matthew Dillon <[email protected]>
. 6 февраля
1999
Физическая память управляется на уровне отдельных страниц посредством структур vm_page_t. Страницы физической памяти разделены на категории через помещение соответствующих им структур vm_page_t в одну из нескольких очередей страниц.
Страница может находиться в связанном, активном, неактивном, кэшированном или свободном состоянии. Кроме случая связанного состояния, страница обычно помещается в двойной связный список очереди, соответствующей состоянию страницы. Закрепленные страницы не помещаются ни в какую очередь.
Для реализации алгоритма подгонки страниц во FreeBSD в основном применяется очередь для кэшированных и свободных страниц. Каждое из этих состояний затрагивает несколько очередей, которые строятся согласно размерам кэшей L1 и L2 процессора. Когда требуется выделение новой страницы, FreeBSD пытается получить ту, что расположена в достаточной мере рядом с точки зрения кэшей L1 и L2 относительно объекта VM, для которого выделяется страница.
Кроме того, страница может удерживаться счетчиком ссылок или может быть заблокирована счетчиком занятости. Система VM также реализует состояние ``безусловной блокировки'' для страницы установкой бита PG_BUSY в поле флагов страницы.
Говоря общими словами, каждая из очередей страниц работает по принципу LRU. Первоначально страница обычно приводится в связанное или активное состояние. Если она связана, то страница обычно указана где-то в таблице страниц. Система VM отслеживает устаревание страницы, сканируя страницы в более активной очереди страниц (LRU) для того, чтобы переместить их в менее активную очередь страниц. Страницы, которые перемещены в кэш, остаются связанными с объектом VM, но являются кандидатами на немедленное повторное использование. Страницы в очереди свободных страниц действительно являются свободными. FreeBSD пытается минимизировать количество страниц в очереди свободных страниц, однако для обеспечения выделения страниц во время обработки прерываний должно поддерживаться некоторое минимальное количество действительно свободных страниц.
Если процесс пытается обратиться к странице, которой не существует в его таблице страниц, но она имеется в одной из очередей страниц (например, в очереди неактивных страниц или очереди кэша), возникает сравнительно легко обрабатываемая ошибка отсутствия страницы, что приводит к повторной активации страницы. Если страницы вообще нет в системной памяти, то процесс должен быть блокирован, пока страница не будет взята с диска.
FreeBSD динамически изменяет свои очереди страниц, и пытается поддерживать разумное соотношение страниц в различных очередях, а также пытается поддерживать разумную схему работы с чистыми и грязными страницами. Количество случающихся перемещений зависит от нагрузки на память системы. Это перемещение выполняется даемоном выгрузки страниц и затрагивает стирание грязных страниц (синхронизируя их с хранилищем), пометку страниц при активной работе с ними (обновляя их расположение в очередях LRU или перемещая их между очередями), постепенное перемещение страниц между очередями при нарушении баланса между очередями, и так далее. VM-система FreeBSD старается обработать достаточное количество ошибок доступа к странице для реактивации, чтобы определить реальную активность или простой страницы. Это приводит к повышению качества решений, принимаемых при стирании или выгрузке страницы в раздел подкачки.
Во FreeBSD реализована идея ``объекта VM'' общего вида. Объекты VM могут быть связаны с хранилищами различного типа--без долговременного хранения, с хранением в области подкачки, с хранением на физическом устройстве или с хранением в файле. Так как файловая система использует одни и те же объекты VM для управления внутренними данными, связанными с файлами, то в результате получается универсальный буферизирующий кэш.
Объекты VM могут затеняться. Это значит, что они могут выстраиваться один над другим. Например, вы можете иметь объект VM с хранением в области подкачки, который выстроен над VM-объектом с хранением в файле, для реализации операции mmap() типа MAP_PRIVATE. Такое построение также используется для реализации различных функций при совместном использовании, включая копирование при записи для разветвляющегося адресного пространства.
Должно быть отмечено, что vm_page_t может быть одновременно связана только с одним объектов VM. Затенение VM-объекта реализует эффективное совместное использование одной и той же страницы между несколькими экземплярами.
Объектам VM, хранящимся в vnode-узлах, таким, как объекты с хранением в файле, как правило, нужно поддерживать собственную информацию о чистоте/использованности независимо от предположении системы VM о чистоте/использованности. Например, когда система VM решает синхронизировать физическую страницу с ее хранилищем, то ей нужно помечать страницу как очищенную перед тем, как она действительно будет записана в хранилище. Кроме того, файловым системам нужно отображать части файла или метаданных файла в KVM для работы с ним.
Структуры, используемые для этого, известны как буферы файловой системы, struct buf, или bp. Когда файловой системе нужно произвести операцию с частью объекта VM, она обычно отображает часть объекта в struct buf и отображает страницы из struct buf в KVM. Таким же образом дисковый ввод/вывод обычно осуществляется отображением частей объектов в буферные структуры с последующим выполнением ввода/вывода этих структур. Низлежащие vm_page_t, как правило, на время ввода/вывода становятся занятыми, что удобно с точки зрения кода драйвера файловой системы, которому нужно будет работать с буферами файловой системы, а не прямо со страницами VM..
FreeBSD резервирует ограниченное число KVM для хранения отображений из struct bufs, но должно быть понятно, что эти KVM используются строго для хранения отображений и не ограничивают возможности по кэшированию данных. Кэширование физических данных является исключительно функцией vm_page_t, а не буферов файловой системы. Однако, так как буферы файловой системы являются заменителями ввода/вывода, они соответственно ограничивают количество выполняемых одновременно операций ввода/вывода. Так как обычно доступно несколько тысяч буферов файловой системы, то это, как правило, проблем не вызывает.
Во FreeBSD таблица расположения физических страниц отделена от системы VM. Все жесткие таблицы страниц для каждого процесса могут быть перестроены на лету и обычно являются временными. Специальные таблицы страниц, например, те, что управляют KVM, обычно выделяются предварительно и перманентно. Эти таблицы страниц не являются временными.
FreeBSD связывает группы объектов vm_object с диапазонами адресов в виртуальной памяти посредством структур vm_map_t и vm_entry_t. Таблицы страниц строятся непосредственно из иерархии vm_map_t/vm_entry_t/ vm_object_t. Вспомните, что я говорил о том, что физические страницы являются единственными, прямо связанными с vm_object. На самом деле это не совсем так. Объекты vm_page_t также связаны в таблицы страниц, с которыми они активно связаны. Один vm_page_t может быть связан в несколько карт pmaps, так называются таблицы страниц. Однако иерархическая связь хранит все ссылки так, что одна и та же страница в том же самом объекте ссылается на одну и ту же vm_page_t, что дает нам универсальный кэширующий буфер.
FreeBSD использует KVM для хранения различных структур ядра. Самым большим единственным объектом, хранящимся в KVM, является кэширующий буфер файловой системы. Это отображения, связанные с объектами struct buf.
В отличие от Linux, FreeBSD НЕ отображает всю физическую память в KVM. Это значит, что FreeBSD на 32-разрядных платформах может работать с конфигурациями памяти до 4ГБ. На самом деле, если бы аппаратный модуль управления памятью мог это делать, то на 32-разрядных платформах FreeBSD может теоретически работать с конфигурациями памяти до 8ТБ. Однако, так как большинство 32-разрядных платформ могут отображать только 4ГБ оперативной памяти, то этот вопрос остается теоретическим.
KVM управляется посредством нескольких методов. Основным механизмом, используемым для управления KVM, является zone allocator. Распределитель зоны берет кусок KVM и разделяет его на блоки памяти постоянного размера для того, чтобы выделить место под некоторый тип структуры. Вы можете воспользоваться командой vmstat -m для получения статистики текущего использования KVM до уровня зон.
Прилагались совместные усилия для того, чтобы сделать ядро FreeBSD самонастраивающимся. Обычно вам не нужно разбираться ни с чем, кроме параметров конфигурации ядра maxusers и NMBCLUSTERS. Это те параметры компиляции ядра, что указываются (обычно) в файле /usr/src/sys/i386/conf/CONFIG_FILE. Описание всех доступных параметров настройки ядра может быть найдено в файле /usr/src/sys/i386/conf/LINT.
В случае большой системы вам может понадобиться увеличить значение maxusers. Значения этого параметра, как правило, располагаются в диапазоне от 10 до 128. Заметьте, что слишком большое значение maxusers может привести к переполнению доступной KVM, что влечет за собой непредсказуемые результаты. Лучше задать некоторое разумное значение maxusers и добавить другие параметры, такие, как NMBCLUSTERS, для увеличения конкретных ресурсов.
Если ваша система будет интенсивно работать с сетью, вам может потребоваться увеличить значение NMBCLUSTERS. Обычно значения этого параметра находятся в пределах от 1024 до 4096.
Параметр NBUF традиционно использовался для масштабирования системы. Этот параметр определяет количество KVA, которое может использоваться системой для отображения буферов файловой системы для ввода/вывода. Заметьте, что этот параметр не имеет никакого отношения с единым кэшируемым буфером! Этот параметр динамически настраивается в ядрах версии 3.0-CURRENT и более поздних и не должен изменяться вручную. Мы рекомендуем вам НЕ пытаться задавать значение параметра NBUF. Позвольте сделать это системе. Слишком маленькое значение может привести к очень низкой эффективности операций с файловой системой, когда как слишком большое значение может истощить очереди страниц, так как слишком много страниц окажутся связанными.
По умолчанию ядра FreeBSD не оптимизированы. Вы можете задать флаги отладки и оптимизации при помощи директивы makeoptions в файле конфигурации ядра. Заметьте, что вы не должны использовать параметр -g, если не сможете использовать получающиеся при этом большие ядра (обычно превышающие размером 7МБ).
makeoptions DEBUG="-g" makeoptions COPTFLAGS="-O -pipe"
Утилита sysctl дает возможность изменить параметры ядра во время его работы. Как правило, вам не приходится работать ни с какими sysctl-переменными, особенно с теми, что относятся к VM.
Тонкая оптимизация VM и системы во время работы сравнительно проста и понятна. Сначала включите использование softupdates на ваших файловых системах UFS/FFS везде, где это возможно. В файле /usr/src/contrib/sys/softupdates/README находятся инструкции (и ограничения) по настройке этой функциональности.
Затем выделите достаточное количество пространства в области подкачки. Вы должны иметь на каждом физическом диске до четырех настроенных разделов подкачки, даже на ``рабочих'' дисках. Пространства в области подкачки должно быть не менее чем в два раза больше объема оперативной памяти, и даже больше, если памяти у вас не очень много. Вы должны также определять размер раздела подкачки, исходя из максимального объема оперативной памяти, который может быть на вашей машине, чтобы потом не выполнять разбиение диска повторно. Если вы хотите иметь возможность работы с дампом аварийного останова, то ваш первый раздел подкачки должен иметь объем, по крайней мере равный объему оперативной памяти, а в каталоге /var/crash должно быть достаточно свободного места для размещения дампа.
Подкачка поверх NFS прекрасно работает на системах версий -4.x и более поздних, но вы должны иметь в виду, что нагрузка от подкачки страниц ляжет на сервер NFS.
Эту главу написал &.murray; на основе множества источников, включая справочную
страницу intro(4), которую создал Jörg Wunsch <[email protected]>
.
Эта глава является кратким введением в процесс написания драйверов устройств для FreeBSD. В этом контексте термин устройство используется в основном для вещей, связанных с оборудованием, относящимся к системе, таких, как диски, печатающие устройства или графические дисплеи с клавиатурами. Драйвер устройства является программной компонентой операционной системы, управляющей некоторым устройством. Имеются также так называемые псевдо-устройства, в случае которых драйвер устройства эмулирует поведение устройства программно, без наличия какой-либо соответствующей аппаратуры. Драйверы устройств могут быть вкомпилированы в систему статически или могут загружаться по требованию при помощи механизма динамического компоновщика ядра `kld'.
Большинство устройств в Unix-подобной операционной системе доступны через файлы устройств (device-nodes), иногда также называемые специальными файлами. В иерархии файловой системы эти файлы обычно находятся в каталоге /dev. В версиях FreeBSD, более старых, чем 5.0-RELEASE, в которых поддержка devfs(5) не интегрирована в систему, каждый файл устройства должен создаваться статически и вне зависимости от наличия соответствующего драйвера устройства. Большинство файлов устройств в системе создаются при помощи команды MAKEDEV.
Драйверы устройств могут быть условно разделены на две категории; драйверы символьных и сетевых устройств.
Интерфейс kld позволяет системным администраторам динамически добавлять и убирать функциональность из работающей системы. Это позволяет разработчикам драйверов устройств загружать собственные изменения в работающее ядро без постоянных перезагрузок для тестирования изменений.
Для работы с интерфейсом kld используются следующие команды привилегированного режима:
kldload - загружает новый модуль ядра
kldunload - выгружает модуль ядра
kldstat - выводит список загруженных в данный момент модулей
Скелет модуля ядра
/* * KLD Skeleton * Inspired by Andrew Reiter's Daemonnews article */ #include <sys/types.h> #include <sys/module.h> #include <sys/systm.h> /* uprintf */ #include <sys/errno.h> #include <sys/param.h> /* defines used in kernel.h */ #include <sys/kernel.h> /* types used in module initialization */ /* * Load handler that deals with the loading and unloading of a KLD. */ static int skel_loader(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: /* kldload */ uprintf("Skeleton KLD loaded.\n"); break; case MOD_UNLOAD: uprintf("Skeleton KLD unloaded.\n"); break; default: err = EINVAL; break; } return(err); } /* Declare this module to the rest of the kernel */ static moduledata_t skel_mod = { "skel", skel_loader, NULL }; DECLARE_MODULE(skeleton, skel_mod, SI_SUB_KLD, SI_ORDER_ANY);
Во FreeBSD имеются заготовки для включения в make-файлы, которые вы можете использовать для быстрой компиляции собственных дополнений к ядру.
SRCS=skeleton.c KMOD=skeleton .include <bsd.kmod.mk>
Простой запуск команды make с этим make-файлом приведет к созданию файла skeleton.ko, который можно загрузить в вашу систему, набрав:
# kldload -v ./skeleton.ko
Unix дает некоторый общий набор системных вызовов для использования в пользовательских приложениях. Когда пользователь обращается к файлу устройства, высокие уровни ядра перенаправляют эти обращения к соответствующему драйверу устройства. Скрипт /dev/MAKEDEV создает большинство файлов устройств в вашей системе, однако если вы ведете разработку своего собственного драйвера, то может появиться необходимость в создании собственных файлов устройств при помощи команды mknod.
Для создания файла устройства команде mknod требуется указать четыре аргумента. Вы должны указать имя файла устройства, тип устройства, старшее число устройства и младшее число устройства.
Файловая система устройств, devfs, предоставляет доступ к пространству имен устройств ядра из глобального пространства имен файловой системы. Это устраняет потенциальную проблемы наличия драйвера без статического файла устройства или файла устройства без установленного драйвера устройства. Devfs все еще находится в разработке, однако она уже достаточно хорошо работает.
Драйвер символьного устройства передает данные непосредственно в или из процесса пользователя. Это самый распространенный тип драйвера устройства и в дереве исходных текстов имеется достаточно простых примеров таких драйверов.
В этом простом примере псевдо-устройство запоминает какие угодно значения, которые вы в него записываете, и затем может выдавать их назад при чтении из этого устройства. Приведены две версии, одна для FreeBSD 4.X, а другая для FreeBSD 5.X.
Example 10-1. Пример драйвера псевдо-устройства Echo для FreeBSD 4.X
/* * Simple `echo' pseudo-device KLD * * Murray Stokely */ #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #include <sys/types.h> #include <sys/module.h> #include <sys/systm.h> /* uprintf */ #include <sys/errno.h> #include <sys/param.h> /* defines used in kernel.h */ #include <sys/kernel.h> /* types used in module initialization */ #include <sys/conf.h> /* cdevsw struct */ #include <sys/uio.h> /* uio struct */ #include <sys/malloc.h> #define BUFFERSIZE 256 /* Function prototypes */ d_open_t echo_open; d_close_t echo_close; d_read_t echo_read; d_write_t echo_write; /* Character device entry points */ static struct cdevsw echo_cdevsw = { echo_open, echo_close, echo_read, echo_write, noioctl, nopoll, nommap, nostrategy, "echo", 33, /* reserved for lkms - /usr/src/sys/conf/majors */ nodump, nopsize, D_TTY, -1 }; struct s_echo { char msg[BUFFERSIZE]; int len; } t_echo; /* vars */ static dev_t sdev; static int len; static int count; static t_echo *echomsg; MALLOC_DECLARE(M_ECHOBUF); MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module"); /* * This function is called by the kld[un]load(2) system calls to * determine what actions to take when a module is loaded or unloaded. */ static int echo_loader(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: /* kldload */ sdev = make_dev(&echo_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "echo"); /* kmalloc memory for use by this driver */ MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK); printf("Echo device loaded.\n"); break; case MOD_UNLOAD: destroy_dev(sdev); FREE(echomsg,M_ECHOBUF); printf("Echo device unloaded.\n"); break; default: err = EINVAL; break; } return(err); } int echo_open(dev_t dev, int oflags, int devtype, struct proc *p) { int err = 0; uprintf("Opened device \"echo\" successfully.\n"); return(err); } int echo_close(dev_t dev, int fflag, int devtype, struct proc *p) { uprintf("Closing device \"echo.\"\n"); return(0); } /* * The read function just takes the buf that was saved via * echo_write() and returns it to userland for accessing. * uio(9) */ int echo_read(dev_t dev, struct uio *uio, int ioflag) { int err = 0; int amt; /* How big is this read operation? Either as big as the user wants, or as big as the remaining data */ amt = MIN(uio->uio_resid, (echomsg->len - uio->uio_offset > 0) ? echomsg->len - uio->uio_offset : 0); if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) { uprintf("uiomove failed!\n"); } return err; } /* * echo_write takes in a character string and saves it * to buf for later accessing. */ int echo_write(dev_t dev, struct uio *uio, int ioflag) { int err = 0; /* Copy the string in from user memory to kernel memory */ err = copyin(uio->uio_iov->iov_base, echomsg->msg, MIN(uio->uio_iov->iov_len,BUFFERSIZE)); /* Now we need to null terminate */ *(echomsg->msg + MIN(uio->uio_iov->iov_len,BUFFERSIZE)) = 0; /* Record the length */ echomsg->len = MIN(uio->uio_iov->iov_len,BUFFERSIZE); if (err != 0) { uprintf("Write failed: bad address!\n"); } count++; return(err); } DEV_MODULE(echo,echo_loader,NULL);
Example 10-2. Пример драйвера псевдо-устройства Echo для FreeBSD 5.X
/* * Simple `echo' pseudo-device KLD * * Murray Stokely * * Converted to 5.X by Sren (Xride) Straarup */ #include <sys/types.h> #include <sys/module.h> #include <sys/systm.h> /* uprintf */ #include <sys/errno.h> #include <sys/param.h> /* defines used in kernel.h */ #include <sys/kernel.h> /* types used in module initialization */ #include <sys/conf.h> /* cdevsw struct */ #include <sys/uio.h> /* uio struct */ #include <sys/malloc.h> #define BUFFERSIZE 256 #define CDEV_MAJOR 33 /* Function prototypes */ static d_open_t echo_open; static d_close_t echo_close; static d_read_t echo_read; static d_write_t echo_write; /* Character device entry points */ static struct cdevsw echo_cdevsw = { .d_open = echo_open, .d_close = echo_close, .d_maj = CDEV_MAJOR, .d_name = "echo", .d_read = echo_read, .d_write = echo_write }; typedef struct s_echo { char msg[BUFFERSIZE]; int len; } t_echo; /* vars */ static dev_t echo_dev; static int count; static t_echo *echomsg; MALLOC_DECLARE(M_ECHOBUF); MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module"); /* * This function is called by the kld[un]load(2) system calls to * determine what actions to take when a module is loaded or unloaded. */ static int echo_loader(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: /* kldload */ echo_dev = make_dev(&echo_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "echo"); /* kmalloc memory for use by this driver */ MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK); printf("Echo device loaded.\n"); break; case MOD_UNLOAD: destroy_dev(echo_dev); FREE(echomsg,M_ECHOBUF); printf("Echo device unloaded.\n"); break; default: err = EINVAL; break; } return(err); } static int echo_open(dev_t dev, int oflags, int devtype, struct thread *p) { int err = 0; uprintf("Opened device \"echo\" successfully.\n"); return(err); } static int echo_close(dev_t dev, int fflag, int devtype, struct thread *p) { uprintf("Closing device \"echo.\"\n"); return(0); } /* * The read function just takes the buf that was saved via * echo_write() and returns it to userland for accessing. * uio(9) */ static int echo_read(dev_t dev, struct uio *uio, int ioflag) { int err = 0; int amt; /* * How big is this read operation? Either as big as the user wants, * or as big as the remaining data */ amt = MIN(uio->uio_resid, (echomsg->len - uio->uio_offset > 0) ? echomsg->len - uio->uio_offset : 0); if ((err = uiomove(echomsg->msg + uio->uio_offset,amt,uio)) != 0) { uprintf("uiomove failed!\n"); } return(err); } /* * echo_write takes in a character string and saves it * to buf for later accessing. */ static int echo_write(dev_t dev, struct uio *uio, int ioflag) { int err = 0; /* Copy the string in from user memory to kernel memory */ err = copyin(uio->uio_iov->iov_base, echomsg->msg, MIN(uio->uio_iov->iov_len,BUFFERSIZE - 1)); /* Now we need to null terminate, then record the length */ *(echomsg->msg + MIN(uio->uio_iov->iov_len,BUFFERSIZE - 1)) = 0; echomsg->len = MIN(uio->uio_iov->iov_len,BUFFERSIZE); if (err != 0) { uprintf("Write failed: bad address!\n"); } count++; return(err); } DEV_MODULE(echo,echo_loader,NULL);
Для установки этого драйвера во FreeBSD 4.X сначала вам нужно создать файл устройства в вашей файловой системе по команде типа следующей:
# mknod /dev/echo c 33 0
Когда этот драйвер загружен, вы можете выполнять следующие действия:
# echo -n "Test Data" > /dev/echo # cat /dev/echo Test Data
Устройства, обслуживающие реальное оборудование, описываются в следующей главе.
Дополнительные источники информации
Учебник по программированию механизма динамического компоновщика ядра (KLD) - Daemonnews Октябрь 2000
Как писать драйверы ядра в парадигме NEWBUS - Daemonnews Июль 2000
Другие UNIX®-системы могут поддерживать со вторым типом дисковых устройств, так называемых устройств с блочной организацией. Блочные устройства являются дисковыми устройствами, для которых ядро организует кэширование. Такое кэширование делает блочные устройства практически бесполезными, или по крайней мере ненадёжными. Кэширование изменяет последовательность операций записи, лишая приложение возможности узнать реальное содержимое диска в любой момент времени. Это делает предсказуемое и надежное восстановление данных на диске (файловые системы, базы данных и прочее) после сбоя невозможным. Так как запись может быть отложенной, то нет способа сообщить приложению, при выполнении какой именно операции записи ядро встретилось с ошибкой, что таким образом осложняет проблему целостности данных. По этой причине серьёзные приложения не полагаются на блочные устройства, и, на самом деле практически во всех приложениях, которые работают с диском напрямую, имеется большая проблема выбора устройств с последовательным доступом (или ``raw''), которые должны использоваться. Из-за реализации отображения каждого диска (раздела) в два устройства с разными смыслами, которая усложняет соответствующий код ядра, во FreeBSD поддержка дисковых устройств с кэшированием была отброшена в процессе модернизации инфраструктуры I/O-операций с дисками.
В случае драйверов сетевых устройств файлы устройств для доступа к ним не используются. Их выбор основан на другом механизме, работающем в ядре, и не использующем вызов open(); об использование сетевых устройств в общем случае рассказано в описании системного вызова socket(2).
Почитайте справочную информацию о вызове ifnet(), устройстве loopback, почитайте драйверы Билла Пола (Bill Paul), и так далее..
Эта глава посвящена механизмам FreeBSD по написанию драйверов устройств, работающих на шине PCI.
Здесь находится информация о том, как код шины PCI проходит по неподключенным устройствам и распознает возможность загруженного драйвера kld выполнить подключение к какому-либо из них.
/* * Simple KLD to play with the PCI functions. * * Murray Stokely */ #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #include <sys/types.h> #include <sys/module.h> #include <sys/systm.h> /* uprintf */ #include <sys/errno.h> #include <sys/param.h> /* defines used in kernel.h */ #include <sys/kernel.h> /* types used in module initialization */ #include <sys/conf.h> /* cdevsw struct */ #include <sys/uio.h> /* uio struct */ #include <sys/malloc.h> #include <sys/bus.h> /* structs, prototypes for pci bus stuff */ #include <pci/pcivar.h> /* For get_pci macros! */ /* Function prototypes */ d_open_t mypci_open; d_close_t mypci_close; d_read_t mypci_read; d_write_t mypci_write; /* Character device entry points */ static struct cdevsw mypci_cdevsw = { mypci_open, mypci_close, mypci_read, mypci_write, noioctl, nopoll, nommap, nostrategy, "mypci", 36, /* reserved for lkms - /usr/src/sys/conf/majors */ nodump, nopsize, D_TTY, -1 }; /* vars */ static dev_t sdev; /* We're more interested in probe/attach than with open/close/read/write at this point */ int mypci_open(dev_t dev, int oflags, int devtype, struct proc *p) { int err = 0; uprintf("Opened device \"mypci\" successfully.\n"); return(err); } int mypci_close(dev_t dev, int fflag, int devtype, struct proc *p) { int err=0; uprintf("Closing device \"mypci.\"\n"); return(err); } int mypci_read(dev_t dev, struct uio *uio, int ioflag) { int err = 0; uprintf("mypci read!\n"); return err; } int mypci_write(dev_t dev, struct uio *uio, int ioflag) { int err = 0; uprintf("mypci write!\n"); return(err); } /* PCI Support Functions */ /* * Return identification string if this is device is ours. */ static int mypci_probe(device_t dev) { uprintf("MyPCI Probe\n" "Vendor ID : 0x%x\n" "Device ID : 0x%x\n",pci_get_vendor(dev),pci_get_device(dev)); if (pci_get_vendor(dev) == 0x11c1) { uprintf("We've got the Winmodem, probe successful!\n"); return 0; } return ENXIO; } /* Attach function is only called if the probe is successful */ static int mypci_attach(device_t dev) { uprintf("MyPCI Attach for : deviceID : 0x%x\n",pci_get_vendor(dev)); sdev = make_dev(&mypci_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "mypci"); uprintf("Mypci device loaded.\n"); return ENXIO; } /* Detach device. */ static int mypci_detach(device_t dev) { uprintf("Mypci detach!\n"); return 0; } /* Called during system shutdown after sync. */ static int mypci_shutdown(device_t dev) { uprintf("Mypci shutdown!\n"); return 0; } /* * Device suspend routine. */ static int mypci_suspend(device_t dev) { uprintf("Mypci suspend!\n"); return 0; } /* * Device resume routine. */ static int mypci_resume(device_t dev) { uprintf("Mypci resume!\n"); return 0; } static device_method_t mypci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mypci_probe), DEVMETHOD(device_attach, mypci_attach), DEVMETHOD(device_detach, mypci_detach), DEVMETHOD(device_shutdown, mypci_shutdown), DEVMETHOD(device_suspend, mypci_suspend), DEVMETHOD(device_resume, mypci_resume), { 0, 0 } }; static driver_t mypci_driver = { "mypci", mypci_methods, 0, /* sizeof(struct mypci_softc), */ }; static devclass_t mypci_devclass; DRIVER_MODULE(mypci, pci, mypci_driver, mypci_devclass, 0, 0);
Дополнительная информация
PCI System Architecture, Fourth Edition by Tom Shanley, et al.
Эту главу написал Nick Hibma <[email protected]>
. Изменения для
Руководства внес Murray Stokely <[email protected]>
.
Универсальная Последовательная Шина (Universal Serial Bus - USB) является новым способом подключения устройств к персональным компьютерам. Среди возможностей архитектуры шины имеется двунаправленный обмен данными и это было разработано в качестве ответа на то, что устройства становятся все более сложными и требуют большего взаимодействия с хостом. Поддержка USB включена во все современные наборы микросхем для PC и поэтому имеется во всех недавно выпущенных PC. Выпуск компанией Apple компьютера iMac только с USB стал большим знаком для производителей оборудования на создание USB-версий своих устройств. Спецификации будущие PC задают, что все устаревшие разъемы на PC должны быть заменены на один или несколько разъемов USB, что дает всеобщие возможности технологии plug and play. Поддержка оборудования USB имелась в начальном объеме в NetBSD и была разработана Леннартом Ангустссоном (Lennart Augustsson) для проекта NetBSD. Код был перенесен во FreeBSD и в настоящее время мы поддерживаем общий код. Для реализации подсистемы USB важны несколько возможностей USB.
Lennart Augustsson сделал большую часть работы в реализации поддержки USB для проекта NetBSD. Приносим много благодарностей за эту исключительную работу. большое спасибо также Арди (Ardy) и Дирку (Dirk) за их комментарии и выверку текста этого документа.
Устройства подключаются непосредственно к портам компьютера или к устройствам, которые называются разветвителями, что формирует структуру устройств, похожую на дерево.
Устройства могут подключаться и отключаться во время работы.
Устройства могут самостоятельно переходить в режим ожидания или получать сигналы на продолжение работы от ведущей системы
Так как устройства могут получать электропитание с шины, то программное обеспечение хоста отслеживает напряжение для каждого разветвителя.
Различное качество обслуживания, требуемое различными типами устройств, вместе с максимальным их количеством в 126 устройств, которые можно подключить к одной и той же шине, требует тщательного планирования передачи данных по общей шине для получения полной отдачи от общей пропускной способности в 12Мбит/c. (более 400Мбит/c для USB 2.0)
Устройства сложны и содержат легкодоступную информацию о самих себе
Разработка драйверов для подсистемы USB и устройств, к ней подключаемых, поддерживается разрабатываемыми спецификациями и спецификациями, которые будут разработаны. Эти спецификации общедоступны с домашних страниц USB. Apple сильно продвигает драйверы, основанные на стандартах, помещая в свободный доступ драйверы классов общего назначения для своей операционной системы MacOS и не рекомендует использование отдельных драйверов для каждого нового устройства. В этой главе делается попытка собрать информацию, достаточную для общего понимания текущей реализации набора драйверов USB для FreeBSD/NetBSD. Но все же рекомендуется дополнительно прочитать и соответствующие спецификации, упоминаемые ниже.
Поддержка USB во FreeBSD может быть разделена на три слоя. Самый нижний слой содержит драйвер контроллера хоста, который дает общий интерфейс к оборудованию и его возможностям по управлению. Он поддерживает инициализацию оборудования, управление передачами и обработку полных и/или прерванных передач. Любой драйвер контроллера хоста реализует виртуальный разветвитель, который дает независимый от оборудования доступ к регистрам, контролирующим корневые порты на задней панели машины.
Средний слой обрабатывает подключение и отключение устройства, основную инициализацию устройства, выбор драйвера, каналы связи и управление ресурсами. Этот слой услуг также управляет каналами по умолчанию и запросы устройств, передаваемых по этим каналам.
Верхний уровень содержит отдельные драйверы, поддерживающие специфичные (классы) устройств. Эти драйверы реализуют протокол, который используется в каналах, отличающихся от используемых по умолчанию. Они также реализуют дополнительную функциональность, которая делает устройство доступным другим частям ядра или пользовательской части. Они используют интерфейс драйвера USB (USBDI), используемый слоем сервисов.
Хост-контроллер (HC) управляет передачей пакетов по шине. Использовались кадры в 1 миллисекунду. В начале каждого кадра хост-контроллер генерирует пакет начала кадра (SOF - Start of Frame).
Пакет SOF используется для синхронизации начала кадра и отслеживания количества кадров. Пакеты передаются с каждым кадром, как от хоста к устройству (исходящие), так и от устройства к хосту (входящие). Передачи всегда инициируются хостом (запрошенные передачи). В силу этого может быть только один хост на шине USB. Каждая передача пакета имеет период статуса, в котором сторона, принимающая данные, может возвратить ACK (подтверждение приема), NAK (повтор), STALL (условная ошибка) или ничего (потерянный период данных, недоступное устройство или отсоединение). Раздел 8.5 Спецификации USB детально описывает пакеты. На шине USB могут произойти четыре различных типа передач: управляющая, основная, прерывание и изохронная. Типы передач и их характеристики описаны ниже (подраздел `Каналы').
Передачи больших объемов данных между устройством на шине USB и драйвером устройства делятся на множество пакетов хост-контроллером или драйвером HC.
Запросы устройства (управляющие передачи) к конечных точкам, используемым по умолчанию, являются специальными. Они состоят из двух или трех фаз: SETUP, DATA (oпциональная) и STATUS. пакет посылается устройству. Если есть фаза данных, то направление пакетов (или пакета) данных дается в настроечном пакете. Направление в фазе статуса противоположно направлению во время фазы данных. или IN если не было фазы данных. Оборудование хост-контроллера также дает регистры с текущим статусом корневых портов и изменений, которые случились с момента последнего сброса регистра изменения статуса. Доступ к этим регистрам дается через виртуализированных разветвитель, как и предполагается по спецификации USB [ 2]. Виртуальный разветвитель должен работать вместе с классом устройств-разветвителей, который описывается в 11 главе той спецификации. Он должен давать канал, используемый по умолчанию, через который запросы устройств могут ему посылаться. Он возвращает набор дескрипторов, стандартных и специфичных для класса разветвителя. Он должен также давать канал прерываний, который сообщает об изменениях, произошедших на его портах. На данный момент для хост-контроллеров существуют две спецификации: Universal Host Controller Interface (UHCI; Intel) и Open Host Controller Interface (OHCI; Compaq, Microsoft, National Semiconductor). Спецификация UHCI разработана для уменьшения аппаратной сложности, требуя от драйвера хост-контроллера поддержки полного распределения передач для каждого кадра. Контроллеры типа OHCI гораздо более независимы, и дают более абстрактный интерфейс, выполняя много работы самостоятельно.
Хост-контроллер UHCI отслеживает список кадров с 1024 указателями на структуры данных, соответствующих отдельному фрейму. Он понимает два различных типа данных: описатели передач (TD - transfer descriptor) и начала очереди (QH - queue heads). Каждый TD представляет пакет, связывающий от или в конечную точку устройства. QH имеют смысл для объединения TD (и QH) вместе.
Каждая передача состоит из одного или большего количества пакетов. Драйвер UHCI разделяет большие объемы передач на множество пакетов. Для каждой передачи, за исключением изохронных передач, формируется QH. Для каждого типа передачи эти QH объединяются в QH для этого типа. Изохронные передачи выполняются в первую очередь из-за фиксированных требований к устойчивости и непосредственно ссылается по указателю на список кадров. Последний изохронный TD ссылается на QH для передачи прерываний для этого кадра. Все QH для передач прерываний указывают на QH для управляющих передач, которые, в свою очередь, указывают на QH для основных передач. Следующая диаграмма дает графическое представление этого:
Это приводит к следующему сценарию, запускаемому в каждом кадре. После получения указателя на текущий кадр из списка кадров контроллер сначала выполняет TD для всех изохронных пакетов в этом кадре. Последний из этих TD ссылается на QH для передач прерываний для этого кадра. Хост-контроллер затем спускается от этого QH к QH для отдельных передач прерываний. После завершения работы этой очереди QH для прерванных передач будет отсылать контроллер на QH для всех управляющих передач. Он будет выполнять все под-очереди, здесь запланированные, за которыми следуют все передачи, поставленные в очередь в массовые QH. Для облегчения обработки законченных или завершившихся неудачно передач аппаратурой генерируется различные типы прерываний в конце каждого кадра. В последнем TD для передачи бит Interrupt-On Completion (прерывание при завершении) устанавливается драйвером HC для вызова прерывания после окончания передачи. Прерывание ошибки устанавливается, если TD достиг своего максимального количества ошибок. Если в TD установлен бит обнаружения короткого пакета, и передается пакет, размером меньшим, чем установлено, устанавливается это прерывание для оповещения драйвера контроллера о завершении передачи. Задачей драйвера хост-контроллера является нахождение того, какая передача была завершена или выдача ошибки. При вызове прерывания вспомогательная подпрограмма найдет все завершенные передачи и вызовет их подпрограммы.
Более детальное описание находится в спецификации на UHCI.
Программирование хост-контроллера OHCI гораздо проще. Контроллер полагает, что имеется набор конечных точек, и заботится о планировании приоритетов и порядке следования типов передач в кадре. Основной структурой данных, используемой хост-контроллером, является описатель конечной точки (Endpoint Descriptor - ED), которому назначается очередь описателей передач (Transfer Descriptors - TD). ED хранит максимальный размер пакета, разрешенный для конечной точки, а аппаратура контроллера выполняет разбиение на пакеты. Указатели на буферы данных обновляются после каждой передачи, и когда начальный и конечный указатели совпадут, TD отбрасывается в очередь выполненного. Четыре типа конечных точек имеют свои собственные очереди. Управляющие и обычные конечные точки ставятся каждая в свою собственную очередь. ED прерываний ставятся в очередь в дерево, с уровнем в дереве, задающим частоту, с которой они выполняются.
framelist interruptisochronous control bulk
Распределение, выполняемое хост-контроллером в каждом кадре, имеет следующий вид. Контроллер сначала выполняет очереди непериодичного управления и обычную очередь, до момента времени, устанавливаемого драйвером HC. Затем выполняются прерванные передачи для этого количества кадров, используя младшие пять бит номера кадра в качестве индекса в уровне 0 дерева прерываний ED. В конце этого дерева подключаются изохронные ED и они выполняются последовательно. Изохронные TD содержат номер первого кадра, с которого должна начаться передача. После выполнения всех периодических передач, снова обрабатываются управляющая и обычная очереди. Периодически вызывается подпрограмма обслуживания прерываний для обработки очереди выполненного и вызова соответствующих функций для каждой передачи и перепланирования изохронных конечных точек и прерываний.
Более детальное описание есть в спецификации OHCI. Средний уровень услуг дает доступ к устройству в смысле управления и отслеживает ресурсы, используемые различными драйверами и уровнями услуг. Уровень отвечает за следующие вопросы:
Информация о настройке устройства
Каналы коммуникаций с устройством
Распознавание, подключение и отключение от устройства.
Каждое устройство имеет различные уровни детализации информации о настройке. Каждое устройство имеет одну или несколько конфигураций, одна из которых выбирается во время инициализации/подключения. При выборе конфигурации определяются требования к электропитанию и пропускной способности. Каждой конфигурации может соответствовать несколько интерфейсов. Интерфейс устройства является набором конечных точек. К примеру, колонки USB могут иметь интерфейс для звуковых данных (Audio Class) и интерфейс для ручек, кнопок, шкал (HID Class). Все интерфейсы в конфигурации активны в одно и то же время и могут быть подключены к различным драйверам. Каждый интерфейс может иметь альтернативы, дающие различное качество вспомогательных параметров. К примеру, видеокамеры, используемые для задания различных размеров кадров и количества кадров в секунду.
Внутри каждого интерфейса может быть задано 0 или большее количество конечных точек. Конечные точки являются однонаправленными точками доступа для коммуникации с устройством. Они предоставляют буферы для временного хранения получаемых и передаваемых от устройства данных. Каждая конечная точка в конфигурации имеет уникальный адрес, номер конечной точки плюс ее направление. Конечная точка, используемая по умолчанию, конечная точка 0, не является частью никакого интерфейса и доступна во всех конфигурациях. Она управляется уровнями услуг и драйверам устройств непосредственно не доступна.
Level 0 Level 1 Level 2 Slot 0
Slot 3 Slot 2 Slot 1
(Показаны только 4 из 32 слотов)
Такая иерархически организованная информация о конфигурации описана в устройстве стандартным набором дескрипторов (обратитесь к разделу 9.6 спецификации USB [ 2]). Они могут быть получены через запрос на получение дескриптора (Get Descriptor Request). Слой услуг кэширует эти дескрипторы для избежания ненужных передач по шине USB. Доступ к дескрипторам дается через вызовы функций.
Дескрипторы устройства: Общая информация об устройстве, такая, как Производитель, Наименование продукта и Номер версии, поддерживаемый класс устройств, подкласс и протокол, если это имеет смысл, максимальный размер пакета для конечной точки, используемой по умолчанию, и так далее.
Дескрипторы конфигурации: Количество интерфейсов в этой конфигурации, поддержка функциональность приостанова и продолжения работы, а также требования по электропитанию.
Дескрипторы интерфейса: класс интерфейса, подкласс и протокол, если он уместен, количество альтернативных настроек для интерфейса и число конечных точек.
Дескрипторы конечной точки: Адрес конечной точки, ее направление и тип, максимальный поддерживаемый размер пакета и частота посылки запросов, если конечная точка имеет тип прерывание. Для конечной точки, используемой по умолчанию (конечная точка 0) дескриптора не существует и он никогда не считается как дескриптор интерфейса.
Строковые дескрипторы строк: В других дескрипторах для некоторых полей даются строковые индексы. Они могут использоваться для получения описательных строк, возможно, на нескольких языках.
Определения класса могут добавлять свои собственные типы дескрипторов, которые доступны через запрос на получение дескриптора.
Конвейерное общение с конечными точками устройства происходит через так называемые каналы. Драйверы отдают передачи к конечным точкам в канал и дают callback-функцию для вызова при окончании или сбое передачи (асинхронные передачи) или ожидают окончания (синхронные передачи). Передачи в конечную точку ставятся в очередь в канале. Передача может быть завершена, либо произойти с ошибкой либо остановиться по таймауту (если он был задан). Для передач существуют два типа таймаутов. Таймауты могут возникнуть из-за задержек на шине USB (миллисекунды). Эти задержки видятся как сбои и могут появиться по причине отключения устройства. Второй тип задержки реализован в программном обеспечении и появляется, когда передача не завершена в течение заданного периода времени (секунды). Они появляются, когда устройство отвечает отрицательно (NAK) на запросы передачи пакетов. Причиной этого является неготовность устройства к приему данных, переполнение или заполнение буфера или ошибки протокола.
Если передача по каналу превышает максимальный размер пакета, указанный в соответствующем дескрипторе конечной точки, то хост-контроллер (OHCI) или драйвер HC (UHCI) будет разбивать передачу на пакеты максимального размера, с последним пакетом, возможно, меньшим, чем максимальный размер пакета.
Иногда для устройства не является проблемой возвратить меньше данных, чем запрошено. К примеру, запрос на передачу от модема может запросить 200 байт данных, но модем сейчас имеет только 5 байт. Драйвер может задать признак короткого пакета (SPD). Это позволяет хост-контроллеру принять пакет, даже если количество передаваемых данных меньше, чем запрошено. Этот флаг имеет смысл только для входящих передач, так как объем пересылаемых устройству данных всегда известен заранее. Если в устройстве во время передачи произошла неисправимая ошибка, то канал теряется. Перед тем, как любые дополнительные данные будут подтверждены или переданы, драйверу нужно выяснить причину потери и сбросить условие потери конечной точки при помощи посылки запроса на очистку конечной точки и сброса устройства по каналу, используемому по умолчанию. Конечная точка, используемая по умолчанию, никогда не должна теряться.
Имеются четыре различных типа конечных точек и соответствующих каналов: - управляющий канал / канал, используемый по умолчанию: Имеется один управляющий канал на устройство, подключенный к конечной точке, используемой по умолчанию (конечная точка 0). Канал содержит запросы устройства и соответствующие данные. Различие между передачами по каналу, используемому по умолчанию и другими каналами заключается в том, что протокол для передач описан в спецификации USB [ 2]. Эти запросы используются для сброса и настройки устройства. Базовый набор команд, который должен поддерживаться всеми устройствами, дан в главе 9 спецификации USB [ 2]. Команды, поддерживаемые в этом канале, могут быть расширены спецификацией класса устройств для поддержки дополнительной функциональности.
Канал передачи потока: Это USB-эквивалент для среды передачи без обработки.
Канал прерываний: Хост посылает запрос на передачу данных устройству, и если в устройстве нет данных для передачи, она посылает NAK на пакет данных. Передачи прерываний планируются на частоте, задаваемой при создании канала.
Изохронный канал: Такие каналы предназначены для изохронных данных, к примеру, потоки аудио или видео, с фиксированной устойчивостью, но не с гарантированной доставкой. В текущей реализации имеются некоторая поддержка для каналов этого типа. Пакеты в управлении, данные и передачи прерываний выполняются повторно, если при передаче возникает ошибка или если устройство отвечает на подтверждение отрицательно (NAK), например, из-за нехватки места в буфере для хранения входящих данных. Изохронные пакеты, однако, не повторяются в случае сбоя при доставке или получения NAK пакета, так как это может нарушить ограничения по времени.
Доступность необходимой пропускной способности вычисляется во время создания канала. Передачи планируются в кадрах продолжительностью 1 миллисекунда. Выделение пропускной способности внутри кадра описано в спецификациях USB, раздел 5.6 [ 2]. Изохронным передачам и передачам прерываний разрешено использовать до 90% пропускной способности кадра. Пакеты для управления и обычных передач планируются после всех изохронных пакетов и пакетов прерываний и будут занимать всю оставшуюся пропускную способность.
Дополнительная информация о планировании передач и корректировке пропускной способности может быть найдена в главе 5 спецификаций USB [ 2], разделе 1.3 спецификации UHCI [ 3] и разделе 3.4.2 спецификации OHCI [4].
После оповещения от разветвителя о том, что было подключено новое устройство, уровень услуг включает порт, предоставляя устройству напряжение в 100 mA. В этот момент устройство находится в своем состоянии по умолчанию и слушает на адресе устройства 0. Уровень услуг будут продолжать запрашивать различные дескрипторы через канал по умолчанию. После этого он пошлет запрос установки адреса для перемещения устройства от адреса устройства, используемого по умолчанию (адрес 0). Несколько драйверов устройств могут поддерживать устройство. К примеру, драйвер модема может поддерживать ISDN TA через интерфейс, совместимый с AT. Драйвер для этой конкретной модели адаптера ISDN может, однако, дать улучшенную поддержку для этого этого устройства. Для обеспечения такой гибкости процедура распознания возвращает приоритеты, показывающие их уровень поддержки. Поддержка конкретной версии продукта дает драйверу общего вида наименьший приоритет. Может также быть, что несколько драйверов могут подключаться к одному устройству, если есть несколько интерфейсов в одной конфигурации. Каждому драйверу достаточно поддерживать только подмножество интерфейсов.
При распознании драйвера для только что подключенного устройства сначала проверяются драйверы для специфичных устройств. Если они не найдены, то код распознания проходит по всем поддерживаемым конфигурациям то тех пор, пока драйвер не подключит некоторую конфигурацию. Для поддержки устройств с несколькими драйверами на разных интерфейсах процедура распознания проходит по всем интерфейсам в конфигурации, которые еще не были заняты драйвером. Конфигурации, которые превысили ограничения на электропитание для хаба, игнорируются. Во время подключения драйвер должен инициализировать устройство в нормальное состояние, но не переинициализировать его, так как это приведет к отключению устройства от шины и перестартовать процесс распознания для него. Во избежание избыточного потребления пропускной способности во время подключения не нужно захватывать канал прерываний, но выделение канала должно быть отложено до открытия файла и реального использования данных. Когда файл закрывается, то канал должен быть снова закрыт, даже если устройство продолжает оставаться подключенным.
Драйвер устройства должен ожидать получение ошибок во время любого обмена данными с устройством. Архитектура USB поддерживает и поощряет отключение устройств в любой момент времени. При исчезновении устройства драйверы должны работать корректно.
Более того, устройство, которое было отключено и подключено повторно, не будет подключено повторно на тот же самый экземпляр устройства. Это может измениться в будущем, когда большее количество устройств будет поддерживать серийные номера (обратитесь к информации о дескрипторе устройства) или, другими словами, означает разработку идентификации для устройства.
Отключение устройства сообщается хабом в пакете прерываний, передаваемом драйвером хаба. Информация изменения статуса указывает, на каком порту обнаружено изменение. Вызывается метод отключения устройства для всех драйверов устройств для устройства, подключенного на этом порту, и очищаются структуры. Если статус порта указывает на то, что в данный момент устройство подключено на этом порту, то начинается процедура обнаружения и подключения устройства. Сброс устройства вызовет последовательность отключений-подключений на хабе и будет отработан так, как описано выше.
Протокол, используемый в каналах, отличающихся от канала, используемого по умолчанию, в спецификации USB не определен. Информация об этом может быть найдена в различных источниках. Самым надежным является раздел разработчиков на домашних страницах USB [ 1]. На этих страницах можно найти все возрастающее количество спецификаций классов устройств. Эти спецификации задают то, как должно выглядеть соответствующее стандарту устройство с точки зрения драйвера, базовую функциональность, которую оно должно предоставлять и протокол, который используется в коммуникационных каналах. Спецификация USB [ 2] включает описание класса концентратора USB. Спецификация класса для Устройств взаимодействия с человеком (HID - Human Interface Devices) был создан для работы с клавиатурами, планшетами, устройствами чтения бар-кода, кнопок, ручек, переключателей и так далее. Третьим примером является спецификация класса для устройств хранения информации. Полный список классов устройств можно найти в разделе разработчиков на домашних страницах USB [ 1].
Однако для многих устройств информация о протоколе еще не была опубликована. Информация об используемом протоколе может быть получена от компании, создавшей устройство. Некоторые компании будут требовать от вас подписания Соглашения о Неразглашении (NDA - Non-Disclosure Agreement) до того, как даст вам спецификации. Это в большинстве случаев исключает создание драйвера с открытым кодом.
Другим хорошим источником информации являются исходные коды драйверов Linux, потому что некоторое количество компаний начали предоставлять драйверы своих устройств для Linux. Всегда полезно связаться с авторами этих драйверов для выяснения их источника информации.
Пример: Устройства взаимодействия с человеком. На спецификации для этих устройств, таких, как клавиатуры, мыши, планшеты, кнопки, пульты и прочее, имеются ссылки в спецификациях других классов устройств, и они используются во многих устройствах.
К примеру, аудио-колонки дают конечные точки для цифровых и аналоговых преобразователей и, возможно, дополнительный канал для микрофона. Они также дают конечную точку HID в отдельном интерфейсе для кнопок и ручек на передней панели устройства. То же самое имеет место для класса управления монитором. Понятно, как строить поддержку для этих интерфейсов через имеющиеся пользовательские библиотеки и библиотеки ядра вместе с драйвером класса HID или драйвера общего вида. Другим устройством, которое может выступать примером для интерфейсов внутри одной конфигурации, управляемыми различными драйверами устройств, является дешевая клавиатура со встроенным устаревшим портом мыши. Во избежание повышения стоимости за счет включения оборудования для концентратора USB в устройство, производители объединяют данные мыши, получаемые с порта PS/2 на задней стенке клавиатуры и нажатия клавиш от клавиатуры, в два отдельных интерфейса одной и той же конфигурации. Драйверы мыши и клавиатуры каждый подключается к соответствующему интерфейсу и организуют каналы к двум независимым конечным точкам.
Пример: Загрузка прошивок. Многие уже разработанные устройства опираются на процессор общего назначения с дополнительным ядром USB. Так как разработка драйверов и прошивок для устройств USB все еще является новым делом, многие устройства после подключения требуют загрузки прошивок.
При этом выполняется следующая процедура. Устройство идентифицирует себя при помощи идентификаторов производителя и продукта. Первый драйвер распознает и подключает его, после чего загружает прошивку в устройство. После этого устройство программно сбрасывает себя и драйвер отключается. После короткой задержки устройство объявляет о своем присутствии на шине. Устройство меняет свои идентификаторы производителя/продукта/версии для отражения того факта, что в нем уже загружена прошивка, и соответственно второй драйвер будет распознавать и подключаться к устройству.
Примером такого типа устройств является плата ввода/вывода ActiveWire, построенная на наборе микросхем EZ-USB. Для этого набора имеется загрузчик прошивок общего вида. Прошивка, загруженная в адаптер ActiveWire, изменяет номер версии. Затем выполняется программный сброс микросхемы EZ-USB в части USB для отключения от шины USB и повторного подключения.
Пример: Устройства хранения данных. Поддержка устройств хранения данных главным образом строится на основе существующих протоколов. Накопитель Iomega USB Zipdrive основан на SCSI-версии их устройства. Команды SCSI и сообщения о состоянии объединяются в блоки и передаются по каналам передачи данных к устройству и от устройства, эмулируя контроллер SCSI по соединению USB. Команды ATAPI и UFI поддерживаются подобным же образом.
Спецификация на устройства хранения поддерживает 2 различных типа объединения блоков управления. Первоначальная попытка была основана на посылке команды и состояния через канал, используемый по умолчанию, и использовании основных передач для данных, перемещаемых между хостом и устройством. На основании опыта работы второй подход был разработан на основе объединения блоков команд и статуса и посылке их по основному каналу к и от конечной точки. Спецификация явно задает, что случается, когда и что нужно сделать в случае условия возникновения ошибки. Самой большой трудностью при написании драйверов для таких устройств является обеспечить соответствие протокола USB в существующую поддержку для устройств хранения. CAM дает способы для этого ясным образом. ATAPI менее прост, так как исторически интерфейс IDE никогда не имел много различных представлений.
Поддержка дискет USB от Y-E Data опять же менее понятно, так как был разработан новый набор команд.
Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
По вопросам связанными с FreeBSD, прочитайте документацию прежде чем писать в <[email protected]>.
По вопросам связанным с этой документацией, пишите <[email protected]>.
По вопросам связанным с русским переводом документации, пишите <[email protected]>.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |