Powered By Blogger

Thursday, November 29, 2007

Toshiba Satellite A100-811: ещё немного о Bluetooth. Удалённый доступ.

Итак, мы настроили устройство Bluetooth-адаптера. Что дальше? Ну, для начала, если Ваш телефон умеет GPRS и у Вас есть подписка, можно настроить удалённый доступ через GPRS-модем, в роли которого будет выступать Ваш телефон, оснащённый Bluetooth. В принципе, всё, о чём пойдёт речь ниже - вполне применимо и к настройке доступа с помощью GPRS + IrDA. Разве что во втором случае в качестве модема будет использоваться устройство ircomm, а не знакомый нам уже rfcomm.

Самый простой способ ИМХО, это настроить соединение в какой-нибудь звонилке, а ля kppp. Хотя, можно сделать это и с помощью скриптов ppp. Описание второго способа попадалось мне довольно часто, поэтому я думаю, у желающих и без меня будет масса возможностей настроить доступ с помощью скриптов. Я приведу пример, но подробно останавливаться на деталях не буду. С kppp и того проще. Моя цель лишь указать на некоторые вещи, которые мешают установить работоспособное соединение и которые не всегда очевидны для начинающих пользователей.

Будем исходить их того, что Bluetooth у Вас уже настроен, как рассказывалось ранее и есть конфиг для устройства, эмулирующего последовательную линию - rfcomm (для IrDA это будет устройство ircomm). На настройках телефона останавливаться не будем. Они в любом случае не универсальны и будут зависеть от Вашего оператора, которого и надо будет терроризировать на предмет информации по этому поводу :)

Первое, что мы сделаем, это убедимся, что телефон предоставляет услугу удалённого доступа (для Bluetooth это профиль DUN, для IrDA всё немного проще, ибо у IrDA устройств нет профилей, как это имеет место с Bluetooth). При включенном Bluetooth-адаптере запускаем:

root@devel0:/home/f0x# hcitool scan
Scanning ...
        00:0E:07:DC:F8:3E       Red Quick Fox
root@devel0:/home/f0x# hcitool inq
Inquiring ...
        00:0E:07:DC:F8:3E       clock offset: 0x71ce    class: 0x520204
root@devel0:/home/f0x# sdptool browse
Inquiring ...
Browsing 00:0E:07:DC:F8:3E ...
Service Name: Dial-up Networking
Service RecHandle: 0x10000
Service Class ID List:
  "Dialup Networking" (0x1103)
  "Generic Networking" (0x1201)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 1
Profile Descriptor List:
  "Dialup Networking" (0x1103)
    Version: 0x0100

Service Name: Voice gateway
Service RecHandle: 0x10002
Service Class ID List:
  "Headset Audio Gateway" (0x1112)
  "Generic Audio" (0x1203)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 3
Profile Descriptor List:
  "Headset" (0x1108)
    Version: 0x0100

Service Name: Serial Port 1
Service RecHandle: 0x10003
Service Class ID List:
  "Serial Port" (0x1101)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 4

Service Name: Serial Port 2
Service RecHandle: 0x10004
Service Class ID List:
  "Serial Port" (0x1101)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 5

Service Name: OBEX Object Push
Service RecHandle: 0x10005
Service Class ID List:
  "OBEX Object Push" (0x1105)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 10
  "OBEX" (0x0008)
Profile Descriptor List:
  "OBEX Object Push" (0x1105)
    Version: 0x0100

Service Name: IrMC Synchronization
Service RecHandle: 0x10006
Service Class ID List:
  "IrMC Sync" (0x1104)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 11
  "OBEX" (0x0008)
Profile Descriptor List:
  "IrMC Sync" (0x1104)
    Version: 0x0100

Service Name: HF Voice gateway
Service RecHandle: 0x10007
Service Class ID List:
  "Handfree Audio Gateway" (0x111f)
  "Generic Audio" (0x1203)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 6
Profile Descriptor List:
  "Handsfree" (0x111e)
    Version: 0x0100

Service Name: OBEX Basic Imaging
Service RecHandle: 0x1000b
Service Class ID List:
  "Imaging Responder" (0x111b)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 15
  "OBEX" (0x0008)
Profile Descriptor List:
  "Imaging" (0x111a)
    Version: 0x0100

Service Name: OBEX File Transfer
Service RecHandle: 0x1000f
Service Class ID List:
  "OBEX File Transfer" (0x1106)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 7
  "OBEX" (0x0008)
Profile Descriptor List:
  "OBEX File Transfer" (0x1106)
    Version: 0x0100

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

Service Name: Dial-up Networking
Service RecHandle: 0x10000
Service Class ID List:
  "Dialup Networking" (0x1103)
  "Generic Networking" (0x1201)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 1
Profile Descriptor List:
  "Dialup Networking" (0x1103)
    Version: 0x0100
Это наш профиль DUN (Dial-Up Networking).

Чобы создать соединение РРР, в /etc/ppp/peers создайте файл с именем новой точки. В этот файл мы свалим параметры для нового соединения. Скорее всего (во всяком случае, так обстоит дело с моим оператором), нам придётся отключить всякое сжатие. Должно получиться что-то вроде этого:

/dev/rfcomm0 # [ устройство, эмулирующее последовательною линию RS-232 по Bluetooth ]
 115200
 connect "/usr/sbin/chat /etc/chatscripts/gprs"

 noauth 
 defaultroute 
 lock 
 debug   # [ вставляем на всякий случай, пока всё не будет обкатано ]
 novjccomp
 nopcomp
 noaccomp
 nodeflate
 novj
 noccp
 nobsdcomp
 default-asyncmap
 ipcp-accept-local
 ipcp-accept-remote
 lcp-echo-failure 100
 lcp-echo-interval 30
 usepeerdns
 user <user_name>
Здесь user_name - имя пользователя, под которым Вы будете стучаться на РРР-сервер Вашего оператора. Как показывает практика, у некототорых операторов есть определённые ограничения на работу с PPP при доступе по GPRS. Это касается прежде всего поддержки сжатия (специально наматывают траф? :( ). Отключаем, всё что можно:
  1. novjccomp, novj - отключить сжатие TCP/IP по Ван Якобсону.
  2. nobsdcomp - отключить сжатие пакетов по схеме BSD.
  3. nodeflate - отключить сжатие Deflation.
  4. noaccomp - отключить сжатие адресов/управляющих пакетов.
  5. nopcomp - отключить согласование сжатия между удалённой точкой и нашей машиной.
  6. noccp - отключить протокол управления сжатием.
Далее, часто встречается ситуация, когда удалённая точка по каким-либо причинам не в состоянии корректно обеспечить управление линией (на уровне LCP - Link Control Protocol - протокол управления линией). Как правило, это выражается в том, что клиентская машина посылает LCP-запросы, чтобы выяснить состояние линии, но не получает отклика от сервера. В подобном случае содинение разрывается по инициативе клиента, т.к. pppd исходит из того, что линия неработоспособна. Насколько это хорошо, сказать сложно. Но изменить поведение демона, отключив ожидание откликов не представляется возможным. Я более склонен возложить вину на оператора, нежели на демон pppd, ибо ИМХО это вполне в силах оператора обеспечить корректную работу ppp-сервера.

Работа LCP в частности управляется параметрами lcp-echo-failure и lcp-echo-interval (на самом деле, параметров LCP, конечно, немного больше). Первый (lcp-echo-failure) задаёт максимальное количество попыток, по истечении которого считается, что произошёл сбой линии. Второй параметр (lcp-echo-interval) определяет временной интервал между посылками LCP-запросов, на которые удалённый сервер должен прислать своё эхо. Время устанавливается в секундах. В нашем случае параметры имеют значения 100 и 30 соответственно, т.е. если ни на один из посланных 100 запросов не был получен отклик, при том, что запросы отправляются с периодичностью в 30 с, то линия считается мёртвой и демон разрывает соединение. При этом в лог сыплется что-то вроде этого (при включении debug):

Nov  3 23:55:57 devel0 pppd[19993]: pppd 2.4.4 started by f0x, uid 1000
Nov  3 23:55:57 devel0 pppd[19993]: Using interface ppp0
Nov  3 23:55:57 devel0 pppd[19993]: Connect: ppp0 <--> /dev/rfcomm0
Nov  3 23:55:57 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 0
Nov  3 23:55:57 devel0 pppd[19993]: PAP authentication succeeded
Nov  3 23:55:58 devel0 pppd[19993]: local  IP address 10.20.85.219
Nov  3 23:55:58 devel0 pppd[19993]: remote IP address 212.98.170.50
Nov  3 23:55:58 devel0 pppd[19993]: primary   DNS address 77.74.32.66
Nov  3 23:55:58 devel0 pppd[19993]: secondary DNS address 77.74.32.11
Nov  3 23:56:27 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 1
Nov  3 23:56:57 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 2
Nov  3 23:57:27 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 3
...
Nov  4 00:43:29 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 95
Nov  4 00:43:59 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 96
Nov  4 00:44:29 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 97
Nov  4 00:44:59 devel0 pppd[19993]: LCP: Rcvd Code-Reject for code 9, id 98
Nov  4 00:45:59 devel0 pppd[19993]: No response to 100 echo-requests

lock - традиционный параметр, предписывающий pppd создавать файл блокировки и т.о. обеспечивает исключительный доступ к соединению. defaultroute - установить новый маршрут к PPP-шлюзу, полученный при установке соединения, маршрутом по умолчанию. usepeerdns - использовать DNS удалённой точки, позволяет не заботиться о поиске собственного DNS :) noauth - не требовать от удалённой точки авторизации. ipcp-accept-local и ipcp-accept-remote - параметры, определяющие, будут ли приняты адреса удалённой точки и нашей машины по инициативе удалённого сервера, даже если локально адреса уже были заданы. Так в общих чертах обстоит дело с конфигурированием соединения с удалённой точкой - РРР-сервером.

Далее, в случае с настройкой соединения с помощью скриптов идёт скрипт чата, определяющий реакцию на состояния линии ("занято", "звонок", "удалённый модем положил трубку" и т.п.). Здесь же прописывается номер, который набирает модем. В общих чертах скрипт чата выглядит так:

ECHO ON 
 ABORT '\nBUSY\r' 
 ABORT '\nERROR\r' 
 ABORT '\nNO ANSWER\r' 
 ABORT '\nNO CARRIER\r' 
 ABORT '\nNO DIALTONE\r' 
 ABORT '\nRINGING\r\n\r\nRINGING\r' 
 '' \rAT 
 TIMEOUT 12 
 OK ATH 
 OK ATE1 
 OK AT+CGDCONT=1,"IP","internet-address" 
 OK ATD*99*1# 
 TIMEOUT 22 
 CONNECT
Кладём этот файл в /etc/chatscripts/ под именем, ну, хотя бы gprs (за более подробными сведениями обращайтесь к man-страницам). Далее, в файле /etc/ppp/pap-secrets прописываем имя пользователя и пароль:
user_name * password
где user_name и password - соответственно имя пользователя и пароль, под которыми Вы ходите на сервер оператора. В общих чертах, всё. Теперь можно устанавливать соединение командой pppd call peer-name, где peer-name - имя точки доступа в /etc/ppp/peers.

Настройка доступа с помощью kppp отличается лишь тем, что параметры конфигурации для pppd и прочее нужно прописывать не в файлах ppp, а в диалогах kppp. В качестве устройства модема при конфигурировании устройства в kppp указывайте /dev/ircomm0 (или другое число вместо нуля в зависимости от того, как у вас настроена подсистема ircomm).

Sunday, November 11, 2007

Toshiba Satellite A100-811: Core 2 Duo и частота ядер после возврата из suspend-to-RAM (solved).

Довольно долгое время у меня на ноуте под управлением Ubuntu 7.04 наблюдался такой странный глюк. Во время работы от аккумулятора при засыпании ноута в память (suspend-to-RAM) и по возвращении из сна одно ядро процессора работало на пониженной частоте, как и положено в режиме экономии, а другое - почему-то начинало работать на полных оборотах. На просторах интернета я нашёл следующее решение: накатать скрипт следующего содержания:

#!/bin/sh

SYS_DIR=/sys/devices/system/cpu
POLICY=powersave

# ondemand

for CPU in `ls $SYS_DIR`
do
    echo -n "$POLICY" > $SYS_DIR/$CPU/cpufreq/scaling_governor
done
и положить его под именем <priority>-<name> в папку /etc/acpi/resume.d. Здесь priority - это число, определяющее порядок выполнения скрипта. Как и в директориях rc?.d файлы обрабатываются демоном acpid в алфавитном порядке и чтобы задать приоритет, необходимо приписать соответствующий префикс. Пусть в нашем случае это будет 10-core2duo-freq-fix.sh

Не понадобилось много времени, чтобы заметить, что данный способ не работает. Долго и упорно я пытался найти решение, копаясь в спецификации ACPI, в частности, производя поиски на предмет генерируемых событий. Много чего нового узнал, но того, что надо, так и не нашёл. Я не мог понять, почему в моей системе не генерируется событие по выходу из сна - resume... И не мог понять, отдаётся ли такое событие в принципе и предусмотрено ли оно самим ACPI. И хотя ответ на этот вопрос сразу найден не был, решение задачи оказалось гораздо проще и лежало оно в немного другой области. Собственно, решение мне подсказали на форуме и было даже немного обидно, до того всё оказалось очевидным :)
Чтобы понять все "что, как и почему" и вникнуть в суть, давайте немного пройдёмся по матчасти.

ACPI
Во времена оные всё было иначе - солнышко светило ярче, трава была зеленее и забористее, деревья большие, а для управления питанием использовался APM - интерфейс Advanced Power Management. APM - не что иное, как "расширение" BIOS'а по управлению питанием. Или говоря иначе, в системах с APM было устройство /dev/apm, на котором можно было сделать ioctl(), а остальное уже выполнялось железом. APM работал не идеально, но работал. Кто-то пришёл к выводу, что это не есть хорошо и так появился ACPI - Advanced Configuration and Power Interface. В отличие от APM, ACPI возлагает часть работы на операционную систему. Поэтому, строго говоря, если с APM всё было достаточно просто, т.к. со стороны ОС требовалась лишь поддержка интерфейса APM и вся работа выполнялась уже вне ядра ОС аппаратным обеспечением, то для нормальной работы энергосбережения через ACPI необходима корректная реализация этого самого ACPI в ОС, что несколько усложняет вещи. Но с другой стороны у ACPI есть преимущество перед APM. Если APM - это функция железа, то ACPI - это функция во многом ОС. Что легче заменить в случае неправильной работы? Железо или ядро ОС? Не будем углубляться в тонкости работы обоих интерфейсов. Сосредоточимся на ACPI. Как было упомянуто, тут ядро выполняет гораздо больше работы, чем в случае с APM. В частности, это позволяет переопределить поведение системы в некоторых случаях, где это было сложно или невозможно с APM. Работа ACPI управляется во многом событиями. Допустим такой пример: пользователь нажимает кнопку "power", это генерирует прерывание, которое перехватывается встроенным контроллером. Реакцией на прерывание становится выставление флагов. Таким образом, ОС уведомляется о том, что что-то произошло. Далее, ОС смотрит, что должно произойти дальше согласно DSDT и передаёт сообщение пользовательскому процессу в виде события. В роли процесса обычно выступает демон acpid, который читает файл /proc/acpi/event. Далее, acpid выполняет какие-то предопределённые конфигурацией действия. В общих чертах схема такова.

Suspend, как он есть.
Спецификация ACPI определяет набор так называемых состояний (глобальных - Gx и сна - Sx). suspend-2-ram - одно из S-состояний системы. В случае с переходом в suspend дела могут обстоять несколько иначе, чем было описано выше. В частности, если пользователь нажимает какую-то кнопку в диалоге выхода из системы, то процесс идёт в обратном направлении. Сам по себе процесс засыпания на этот раз инициируется пользовательским процессом - всё тем же acpid, допустим. Он выполняет некие действия, предусмотренные конфигурацией. Пусть была нажата кнопка перехода в состояние suspend-2-ram, происходит приблизительно следующее:

  1. acpid поучает сообщение через unix-сокет. Как гласит файл /etc/acpi/events/sleepbtn, обработчиком события является скрипт /etc/acpi/sleep.sh:
    # /etc/acpi/events/sleepbtn
    # Called when the user presses the sleep button
    # [ Здесь-то мы и определяем обработчик события ]
    
    event=button[ /]sleep
    action=/etc/acpi/sleep.sh
  2. Далее управление передаётся скрипту /etc/acpi/sleep.sh. Мы не будем рассматривать его здесь очень детально, скажем лишь, что этот скрипт вполне может умыть руки и передать управление gnome-power-manager'у или klaptopdaemon'у например, если кто-то из них запущен и не определено форсирование выполнения обработчика ACPI - /etc/acpi/sleep.sh. В противном случае, если демоны Gnome или KDE не работают или необходимо форсировать выполнение /etc/acpi/sleep.sh, далее по очереди вызывается скрипт prepare.sh. Он подготавливает пользовательское окружение к переходу в S-состояние. Всё, что делает этот скрипт - выполняет скрипты, сложенные в директории /etc/acpi/suspend.d:
    #!/bin/bash
    
    for SCRIPT in /etc/acpi/suspend.d/*.sh; do
      . $SCRIPT
    done
  3. Далее идёт кое-какая рутина. И, наконец, самое интересное, в конце... То, чего я не заметил и то, что оказалось так очевидно. Вызывается скрипт /etc/acpi/resume.sh, который тянет в свою очередь всё, что лежит в директории /etc/acpi/resume.d. Знакомое имя? :)
  4. acpid просит ядро заснуть. И наконец, очередь доходит до ядра, которое выполняет всю чёрную работу - останавливает и замораживает процессы и переводит железо в S-состояние. Теперь мы спим.

Хотя, на самом деле, есть событие ACPI, которое генерируется при пробуждении (Wakeup) - WAK, но как мы увидели, обработчика такого события у acpid нет. Всё сделано гораздо проще. Выполнение скрипта приостанавливается как раз в той точке, где в специальный файл записывается значение, определяющее тип сна - echo -n $ACPI_SLEEP_MODE >/sys/power/state. При пробуждении нам не нужны никакие обработчики, потому что скрипт продолжает выполняться со следующей строки и вполне логично доходит до вызова скриптов, что находятся в директории /etc/acpi/resume.d. Довольно красиво и просто. Итак, мы ответили на два вопроса - "что?" и "как?". Остался ещё один - почему не работает наш скрипт, помещённый в /etc/acpi/resume.d? Нет, конечно же он работает, если запускать его вручную. Но это не наш метод. Ответ на самом деле уже был дан. Осталось придать ему более чёткую формулировку.

KDE, Gnome и HAL
HAL - Hardware Abstraction Layer - предоставляет ещё один метод абстракции от железа и управления питанием. В десктоп-менеджерах, Gnome и KDE часто работают свои менеджеры питания. У меня, в частности, работает утилитка из kde-guidance-powermanager. Она не пользуется услугами acpid, используя вместо него HAL. HAL использует и gnome-power-manager. Дальше всё просто. Можно считать, решение задачи найдено. Мы уже знаем, где находятся скрипты HAL: /usr/lib/hal/scripts/linux. Здесь нам нужен скрипт hal-system-power-suspend-linux. Выполняется он по такому же принципу, как и скрипт-обработчик ACPI - т.е., доходит до определённой точки, усыпляет систему и продолжает выполнение далее по пробуждении. Вот этим-то мы и воспользуемся, добавив в конец скрипта такие строки:

for x in /proc/acpi/ac_adapter/*; do
    grep -q off-line $x/state

    if [ $? = 0 ] && [ x$1 != xstop ]; then
        /etc/acpi/resume.d/10-dualcore-freq.sh
    fi
done
Здесь мы проверяем, работает ли ноутбук от аккумулятора. Если мы работаем от сети, то ничего делать не надо. Профиль производительности cpufreq как был performance, так и остаётся. В противном случае, если мы работаем на аккумуляторе, сбиваем частоту ядер записью профиля powersave в /sys/devices/system/cpu/*/cpufreq/scaling_governor. Выравниваем частоту обоих ядер, чтобы не ошибиться :), так как ничего страшного в этом нет и искать ядро-спринтер нет нужды. Теперь всё должно работать. Мысленно поздравьте себя ;)


При написании частично использовалась статья http://www.advogato.org/. Большое спасибо аффтару.

udev в GNU/Linux

Содержание

* 1 udev - реализация devfs на уровне пользователя
* 2 Вступление
* 3 /dev
* 4 Проблемы. Какому файлу в /dev соответствует какое устройство?
* 5 Недостаточно номеров
* 6 объём статического /dev слишком велик
* 7 devfs
* 8 Цели, стоящие перед udev
* 9 namedev
* 10 libsysfs
* 11 udev

udev - реализация devfs на уровне пользователя

Вступление

Начиная с ядра версии 2.5 все физические и виртуальные устройства в системе видны пользователю через файловую систему sysfs, которая представляет все устройства ввиде иерархической древовидной структуры. Когда в системе появляется новое устройство или удаляется старое, hotplug уведомляет об этом окружение пользователя. Используя два этих момента, можно создать пользовательскую реализацию динамической файловой системы в /dev, которая предоставляла бы более гибкие правила именования устройств, нежели это было в devfs. В этом документе обсуждается udev - программа, которая заменяет devfs без потерь для функциональности системы. Более того, udev создаёт в /dev файлы только для тех устройств, которые присутствуют на данный момент в системе. udev предоставляет и новые возможности, которых не было в devfs:

1. постоянно закреплённые за устройствами имена, которые не зависят от того, какое положение они занимают в дереве устройств.
2. уведомление внешних по отношению к ядру программ, если устройство было заменено.
3. гибкие правила именования устройств.
4. динамическое использование нижних и верхних номеров устройств.
5. переход правил именования устройств из ядра в пространство пользователя.

Этот документ объясняет, почему программа, выполняемая в режиме пользователя предпочтительнее чем devfs, которая работает в режиме ядра. Далее также будет объяснено, как работает udev, как создавать для неё расширения (разные схемы именования устройств).

/dev

На каждом Linux-box'е /dev - это директория, в которой находятся файлы устройств, известных системе. Файл устройства - это интерфейс между пользовательскими приложениями и аппаратурой, механизм, предоставляемый ядром для доступа к физическим устройствам системы. Например устройство /dev/hda, как правило, представляет первый (master) IDE-накопитель на первичном канале. Имени hda соответствуют два числа - верхний и нижний номер устройства. Эти номера используются ядром для того, чтобы узнать, к какому физическому устройству необходимо обратиться (да, имена устройств существуют лишь для отвода глаз, реально же используются номера). Обычно каждому типу устройств отводится свой диапазон номеров. На данный момент есть ряд устройств одного типа с разными именами, но одинаковыми номерами (правда, их не так уж и много). Присвоение имён и номеров разным устройствам было проведено LANANA - The Linux Assigned Names And Numbers Authority. Список всех устройств можно найти в http://www.lanana.org/docs/device-list/devices.txt или в директории с исходными кодами ядра в Documentation/devices.txt. По мере того как в ядре появляется поддержка новых устройств, им необходимо выделать новые диапазоны номеров, с тем чтобы к ним можно было получить доступ через /dev. Одна из возможностей преодолеть дефицит номеров в статической /dev - организовать её как файловую систему (devfs). В ядрах серии 2.4 и более ранних диапазоны доступных номеров были 1-255 для нижней и 1-255 для верхней части.

Проблемы. Какому файлу в /dev соответствует какое устройство?

Когда ядро обнаруживает устройство, оно присваивает ему нижний/верхний номер, соответствующий типу устройства. Допустим, при загрузке ядро обнаружило USB-принтер. Оно присвоило этому устройству номера major:180 minor:0, в /dev этому устройству будет соответствовать файл /dev/usb/lp0. Второй USB-принтер получает номера 180:1 и к нему можно обращаться как к /dev/usb/lp1. Но если пользователь изменит топологию USB-устройств, скажем добавив USB-хаб? Тогда при следующей загрузке порядок обнаружения ядром принтеров может измениться. Принтер, на который раньше ссылались как на /dev/usb/lp1 превратиться в /dev/usb/lp0 и наоборот. Это справедливо и для устройств, которые могут быть подключены или удалены во время работы компьютера. Сказанное касается шин PCI hotplug, IEEE 394, USB, CardBus. Появление sysfs в ядрах 2.5 позволяет узнать какое именно устройство данного класса "скрывается" за данным minor-номером. Для системы с двумя подключенными USB-принтерами sysfs sys/class/usb может выглядеть следующим образом

/sys/class/usb

|--  lp0
|      |--dev
|      |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1:0
|      +--driver -> ../../../bus/usb/drivers/usblp
+--  lp1
       |--dev
       |--device -> ../../../devices/pci0/00:0d.0/usb3/3-1/3-1:0
       +--driver -> ../../../bus/usb/drivers/usblp


$ cat /sys/class/usb/lp0/device/serial HXOLL0012202323480 $cat /sys/class/usb/lp1/device/serial W09090207101241330

Симлинки lp0/device и lp1/device ссылаются на директории, содержащие дополнительную информацию об устройствах. Таким образом можно выяснить производителя устройства и его (устройства) уникальный (надеемся) серийный номер. Как мы выяснили, принтеру lp0 соответствует S/N HXOLL0012202323480, а lp1 - W09090207101241330. Если поменять порядок подключения принтеров или кинуть их через хаб, поменяются и их имена (но не серийные номера, конечно!). Вот что у нас получится:

/sys/class/usb

|--  lp0
|      |--dev
|      |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1.1/1-1.1:0
|      +--driver -> ../../../bus/usb/drivers/usblp
+--  lp1
       |--dev
       |--device -> ../../../devices/pci0/00:09.0/usb1/1-1/1-1.4/1-1.4:0
       +--driver -> ../../../bus/usb/drivers/usblp


$ cat /sys/class/usb/lp0/device/serial W09090207101241330 $cat /sys/class/usb/lp1/device/serial HXOLL0012202323480

Теперь принтеру lp0 соответствует S/N W09090207101241330, а lp1 - HXOLL0012202323480. sysfs позволяет с лёгкостью узнать, какому именно устройству соответствует какой файл. Это достаточно мощный механизм сопоставления физическим устройствам файлов устройств в /dev. Однако пользователя не интересует то, что lp0 и lp1 поменялись местами и что не мешало бы внести изменения в некоторые конфигурационные файлы. Пользователь просто хочет печатать на том принтере, на котором ему нужно, не задумываясь, к какому порту подключено USB-устройство и не подключено ил оно вообще через хаб.

Недостаточно номеров

Major- и minor-части номера устройства занимают по 8 бит (т.е., как было уже упомянуто 1-255 для верхней и столько же для нижней части). На данный момент для символьных и блочных устройств осталось не так уж и много major-номеров. Символьные и блочные устройства могут использовать один и тот же major-номер, ядро относит их к разным типам, поэтому конфликтов не возникает. Казалось бы, ещё море свободных номеров. Но на некоторых системах может использоваться, скажем, 4000 дисков, а 8 бит нижней части номера для этого уже явно не достаточно. Для каждого диска ядро резервирует 16 младших номеров, т.к. на диске может быть до 16 разделов. 4000 дисков требуют 64000 (!) файлов-устройств (не хило, не так ли, господа?). Но для того чтобы получить к ним доступ, есть лищь 255 номеров. Конечно и 8 битовая схема сработала бы, если распределить все диски по всему диапазону от 1 до 65535, но ведь большинство старших номеров уже зарезервировано. Дисковая подсистема не может просто оттяпать кусок из диапазона старших номеров. Если же это произошло, необходимо уведомить окружение пользователя, что отныне часть старших номеров переназначена под использование подсистемой SCSI. Из-за этого ограничения многие настаивают на увеличении диапазонов старших и младших номеров. Не стоит забывать и о том, что 4000 дисков это ещё не предел, на некоторых системах есть необходимость подключить одновременно 10000 накопителей. Похоже, что увеличение разрядности номера устройства могло бы произойти в ядрах серии 2.6, но по прежнему проблемой остаётся уведомление пользовательского окружения, какие major- и minor-номера заняты, а какие нет. Даже если диапазон верхних половин номеров будет увеличен, необходимо определить, какому устройству соответствует какой участок этого диапазона. Это требует наличия некоего органа, который будет заниматься распределением блоков номеров и стандартизированием имён файлов-устройств. Кроме того, неисключено, что в будущем и этот ресурс будет исчерпан. Если бы ядро автоматически распределяло диапазоны старших и младших номеров между устройствами, которые представлены в данный момент в системе, необходимость в таком органе отпала бы и верхний предел номеров, вероятно, не будет превышен. Идея с динамическим распределением номеров наталкивается на одно "но": окружение пользователя по прежнему не имеет понятия, какое оборудование подключено к машине и какие номера соответствуют каким устройствам. Пока ещё лишь немногие подсистемы ядра умеют динамически распределять номера устройств. Так например с успехом поступает подсистема USB2Serial (ещё со времён ядер серии 2.2). И вновь старая проблема, неведение пользователя. Было бы бесчеловечно заставлять его просматривать логи ядра, чтобы выяснить, какому устройству соответствуют определённые номера... sysfs, появившаяся в ветке 2.5, позволяет легко выяснить то, что нам так нужно.

Объём статического /dev слишком велик

Далеко не все устройства, для которых есть файлы в /dev рально присутствуют в системе. Некоторые могут быть просто не подключены на данный момент, а некоторых в ней никогда и не было. /dev как правило генерируется при установке ОС на жёстский диск и содержит файлы для всех устройств, известных на данный момент. На машине с установленным Red Hat 9 около 18000 файлов устройств. Иногда это сбивает с толку пользователей, которые пытаются определить, какие устройства реально присутствуют в системе. Поэтому в некоторых Unix-like ОС и дистрибутивах Linux управление /dev отдаётся на откуп ядру, которое знает, какие устройства подключены и которое создаёт в памяти devfs-систему. Такая схема стала достаточно популярной.

devfs

devfs используется в некоторых Unix-like ОС. Её реализация есть и в Linux. Однако в Linux devfs по-прежнему не решает многих проблем. devfs содержит лишь те устройства, которые представлены в системе и таким образом решает проблему с диким объёмом /dev. Но имена файлов в devfs не соответствуют именам, утверждённым LANANA. По этой причине полный переход со статического /dev на динамическую devfs проблематичен. Необходимы кардинальные изменения в файлах конфигурации. Создатели devfs попытались решить эту проблему, введя дополнительный слой совместимости (devfsd-демон), чтобы эмулировать /dev. Однако при том, что совместимость достигнута, правила именования устройств находятся в ведении ядра. Первый IDE-накопитель на первичном канале будет называться /dev/hda или /dev/ide/hd/c0b0t0u0 (лично у меня - /dev/ide/host0/bus0/target0/lun0/disc) и тут ничего не сделаешь. Вообще разработчики ядра не любят выносить правила именования устройств за пределы ядра. Но правила присвоения имён должны быть убраны из ядра. Это позволит разработчикам драйверов не заострять внимание на схемах именования. Короче, ядро не должно волновать то, как пользователь называет устройство. В случае с devfs это невозможно. devfs не позволяет распределять номера устройств динамически, в ней используются номера определённые LANANA. Хотя devfs можно модифицировать, чтобы она позволяла использовать динамические номера, никто до сих пор этого не сделал. Все данные (база данных, имена) devfs хранит в памяти ядра, которая не выгружается на диск. При больших количествах подключенных устройств (те же 4000 дисков) затраты памяти становятся весьма существенными.

Цели, стоящие перед udev

В свете упомянутых проблем началась работа на проектом udev. Цели этого проекта следующие:

1 переход в пространство пользователя
2 создание динамического /dev
3 предоставление пользователю возможности, самому формировать и назначать имена файлов-устройств
4 создание пользовательского API для доступа к информации об устройствах, подключённых к системе

Переход в режим пользователя возможен благодаря тем фактам, что hotplug генерирует события при добавлении или удалении устройств, а с помощью sysfs получить информацию об этих устройствах не сложно. Остальные цели позволяют разбить проект на три подсистемы:

1 namedev - работа с именами устройств
2 libsysfs - библиотека доступа к информации в sysfs
3 udev - динамический аналог /dev

namedev

Ввиду необходимости разных схем именования, эта часть проекта была выделена в отдельную подсистему. Это было сделано чтобы отграничить, собственно udev от правил назначения имён и обеспечить поддержку подключаемых схем другими группами разработчиков. Подсистема именования, namedev, - это стандартный интерфейс, который udev использует, чтобы присвоить имя данному устройству. В первых релизах проекта namedev пока компонуется в один исполняемый файл с udev. Вместе с этими релизами идёт и простая схема именования, основанная на спецификации LANANA. Она скорее всего подойдёт большинству пользователей Linux. namedev позволяет присвоить устройству имя исходя из пяти признаков:

1 метка или серийный номер
2 номер шины
3 номер устройства на шине
4 другое (подстановочное) имя
5 имя устройства в ядре

Первый признак - метка или серийный номер, проверяется при подключении устройства. Этот признак интерпретируется в зависимости от типа устройства. В случае SCSI - это UUID, для USB - серийный номер, для прочих блочных устройств - метка файловой системы. Второй признак - номер шины. Номер шины на большинстве систем меняется редко. Это может произойти при апгрейде BIOS или при подключении hotplug-контроллера. В кажды момент времени номер шины постоянен и уникален. Трерий признак - топология шины. Это может оказаться полезным в случае с USB-шиной. Подстановочное имя используется вместо имени устройства в ядре. И, наконец, если ни один из 4 предыдущих признаков не прошёл, то используется имя устройства в ядре. Это имя совпадает с именем, которое использовалось бы на системе без devfs или udev со статическим /dev. Вот пример конфига, который позволяет назначить устройствам имена, отличные от тех, которые использует ядро:

   1. USB Epson printer to be called lp_epson

LABEL, BUS="usb", serial="HXOLL0012202323480", NAME="lp_epson"

   1. USB HP printer to be called lp_hp

LABEL, BUS="usb", serial=@W09090207101241330", NAME="lp_hp"

   1. sound card with PCI bus id 00:0b.0 to be the first

NUMBER, BUS="pci", id="00:0b.0", NAME="dsp"

   1. sound card with PCI bus id 00:07.1 to be the second

NUMBER, BUS="pci", id="00:0b.0", NAME="dsp1"
libsysfs

libsysfs предоставляет интерфейс для доступа к данным на sysfs. Собственно libsysfs может быть использована не только в проекте udev. Подсистема присвоения имён udev должна запрашивать из sysfs довольно много информации о подключённом устройстве. Было бы глупо дублировать код доступа к sysfs в каждом проекте, который её использует. Поэтому libsysfs была выделена в отдельную подсистему.

udev

Чтобы приводить в действие правила именования устройств udev пользутся услугами namedev и libsysfs. udev вызывается каждый раз, когда ядро обращается к hotplug. Это происходит благодаря ссылке на udev в /etc/hotplug.d/default. Ядро сообщает hotplug массу интересной информации: что за устройство было добавлено (USB, PCI или ещё что-нибудь), а может оно было удалено, а не добавлено, где в sysfs находятся данные об устройстве, вызвавшем событие. Всё это передаётся в udev, который обращается в свою очередь к namedev, чтобы выяснить, какое имя присвоить устройству или просто удалить имя отключенного устройства (если action=remove). Т.о. содержимое /dev динамически изменяется на протяжении работы системы.

По материалам Linux Symposium Версия 2. Публикуется с небольшими изменениями.

Перевод (c) by Ваш покорный слуга, red f0x

ПОСЕТИТЕЛИ

free counters