Операционная система, позволяющая задействовать все возможности компьютера, резко отличается от специализированного программного обеспечения огромным числом т. н. «вариантов использования» (use cases) и обширнейшими возможностями тонкой настройки для решения задач конкретного пользователя наилучшим способом. Достаточно сравнить какую-нибудь игровую приставку (например, PlayStation2) под управлением собственной операционной системы и её же под управлением Linux. Вычислительная и мультимедийная мощность такого копьютера весьма высока (известно, что именно компьютерные игры определяют сейчас ресурсопотребление персонального компьютера). Однако способы управления одной и другой системами настолько различны, что неподготовленный человек просто теряется при виде возможностей Linux: где? на какие кнопки нажимать? А кнопок-то и нет...
Можно попытаться описать операционную систему как большой и сложный универсальный инструмент решения любых задач. Предполагается, что пользователь, прочтя документацию, в которой описывается как работает система и как применять её в различных ситуациях, сможет решать и свои задачи. Правда, для этого ему придётся прочесть большую часть документации по системе (в том числе и технической) и перепрограммировать некоторые части системы сообразно своим нуждам. На такой подвиг способны немногие, времени это займёт немало, да и вероятность ошибки (которая тем выше, чем сложнее средства управления системой) при таком подходе недопустимо велика. Сами утилиты или службы Linux, каждую из которых можно «окинуть взором» и понять, что и как она умеет, и чего в ней не хватает, разрабатываются именно теми из пользователей, у которых хватает времени, знаний и навыков на такое полное освоение (см. лекцию Политика свободного лицензирования. История Linux: от ядра к дистрибутивам). Вывод: пользователь — не разработчик, ему всё-таки важнее быстро и качественно решить задачу, чем долго и качественно улучшать инструмент решения.
Можно пойти обратным путём: попытаться предусмотреть все основные способы использования операционной системы на всех основных пользовательских задачах, и на каждый такой способ создать (запрограммировать) отдельную часть, управляемую «кнопкой» или утилитой. Эту часть обычно называют «решением» (solution), и в документации обычно пишут, что должно быть «на входе» системы, и что получается «на выходе» после применения решения. Если пользователь не умеет сам поставить задачу, или делает это в неопределённой форме («хочу, чтобы был текст», «хочу, чтобы играла музыка»), этот способ работает на ура: та же игровая приставка — это отличное решение крайне неопределённой задачи «хочу без толку потратить время». Однако, стоит пользователю захотеть чего-то конкретного, начинаются трудности. Трудности могут быстро стать непреодолимыми, как только для этого «конкретного» не окажется готового решения: внутренняя структура систем, ориентированных на «решения», столь сложна и столь плохо документирована, что сделать что-либо вручную, скорее всего, не удастся. Вывод: пользователь, понимающий суть собственных задач, — не «клиент», он должен иметь возможность быстро и качественно решать задачи самостоятельно, а не выбирать то из готовых «решений», которое нанесёт меньше вреда.
Что же нужно идеальному — достаточно подготовленному, чтобы действовать самостоятельно, и достаточно занятому, чтобы не переделывать системы — пользователю? По-видимому, механизм, с помощью которого можно сформулировать и придать операционной системе все требуемые свойства, имея возможность описывать решение задачи по крайней мере на том же уровне конкретности, на котором было поставлено её условие. Большая часть других, не нужных для решения собственных задач пользователя, свойств должны быть «стандартными» и не требовать его вмешательства.
Профиль системы
Так возникает идея разделить систему на два подмножества: профиль и реализацию. Всё, что не потребует вмешательства пользователя, необходимо запрограммировать и использовать в готовом виде в качестве составных частей реализации. В Linux этому соответствуют программы и подпрограммы: ядро, модули, демоны, утилиты; используемые ими библиотеки и прочие разделяемые файлы и т. п. Реализация — это монолитная, неизменяемая часть системы, устроенная по типу «решений» основных задач, только задачи эти, как правило, не совпадают с задачами пользователей, а только помогают решать их, являясь как бы строительным материалом, деталями и инструментами сборки «больших» решений.
Всё, чего может коснуться рука человека, из реализации переносится в профиль системы. Профиль задаёт поведение реализации на данных пользователя, и должен быть устроен так, чтобы пользователь мог его беспрепятственно изменять, если понадобится. С одной стороны, это может быть вариант «высокоуровневого программирования», когда пользователь описывает алгоритм решения и структуру используемых данных на некотором высокоуровневом языке (специализированном или общем, например, на shell). С другой стороны задание свойств может превращаться в указание модификаторов поведения, когда пользователь просто перечисляет необходимые параметры работы программы, которые изменяют её заранее известную, но достаточно общую функциональность.
Таким образом система полностью описывается в виде набора необходимых компонентов реализации, активизированных (запущенных) с определёнными профилями, вкупе с текущим состоянием каждого компонента. Поскольку компонент реализации не может изменяться, а его текущее состояние, наоборот, меняется постоянно и не управляется пользователем, можно считать, что систему задаёт её профиль. Это означает, что для того, чтобы продублировать работу ситемы на другом компьютере, достаточно установить там стандартную реализацию и перенестипрофиль (обычно занимающий несравненно меньше места) и пользовательское наполнение. Наполнение (файлы пользователей, содерживое www-страниц и т. п.) может занимать много места, но оно входит в понятие «задача прользователя», поэтому забывать о нём нельзя.
- профиль
- Изменяемая часть системы, задающая её поведение во время работы.
Как проще всего создать профиль, если не всей системы, то хотя бы её компонента (программы)? Один из вариантов такой: снабдить программу функцией «сохранить настройки», тогда можно будет эту программу запустить, любым способом добиться её работоспособности, а после зафиксировать достигнутое состояние с помощью этой функции. При этом по началу совершенно неважно, как выглядят эти настройки: программа-то заработала, значит, цель достигнута (проницательный Мефодий немедленно заметил, что название функции «сохранить настройки» как-то подозрительно похоже на название кнопки).
Зачастую для того, чтобы собрать более или менее отвечающий требованиям пользователя профиль, задействуется больше ресурсов, чем для работы самой программы. Утилита автоматической настройки выглядит эдаким шаманом, или кудесником, который, задав всего несколько вопросов человеку, невесть как приводит систему в работоспособное состояние. Такая подсистема и называется-то «wizard», причём в русском переводе её отчего-то стесняются называть «шаманом», а величают уважительно «мастером». Вот пример поведения обычного шамана-настройщика (пакет wvdial
, заведующий модемным подключением к Internet):
[root@localhost root]# wvdialconf
Usage: wvdialconf <configfile-name>
(create/update a wvdial.conf file automatically)
[root@localhost root]# wvdialconf .wvdialrc
Scanning your serial ports for a modem.
Port Scan<*1>: Scanning ttyS4 first, /dev/modem is a link to it.
. . .
ttyS4<*1>: Modem Identifier: ATI -- Xircom CardBus 10/100+Modem 56 (Revision 2.40)
. . .
ttyS4<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
ircomm0<*1>: ATQ0 V1 E1 -- failed at 9600 and 19200 baud.
. . .
ircomm9<*1>: ATQ0 V1 E1 -- failed at 9600 and 19200 baud.
Port Scan<*1>: LT0
. . .
ttyS0<*1>: ATQ0 V1 E1 -- and failed too at 115200, giving up.
. . .
ttyS1<*1>: ATQ0 V1 E1 -- and failed too at 115200, giving up.
Port Scan<*1>: S2 S3 S5 S6 S7 S8 S10
. . .
Port Scan<*1>: USB11 USB12 USB13 USB14 USB15
Found a modem on /dev/ttyS4, using link /dev/modem in config.
Modem configuration written to .wvdialrc.
ttyS4<Info>: Speed 115200; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"
Пример 1. Кудесник wvdialconf
Даже ни о каких наводящих вопросах речи не зашло! Программа проверила более полусотни устройств, не модемы ли они, но нашла всего одно — /dev/ttyS4
. Его настройки определились автоматически (и хорошо, потому что Мефодий не знает, что такое «ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0»). Профиль (а wvdialrc
создаёт именно профиль) лежит теперь в файле .wvdialrc
, так что программа wvdial
начнёт работать с модемом, нуждаясь только в пользовательских настройках (входное имя, пароль и т. п.).
Яркий пример того, как элементы реализации связываются профилем в единую подсистему для решения определённой задачи — командная строка и сецнарии командного интерпретатора. Здесь утилиты играют роль элементов реализации, их параметры — роль «настроечной» части профиля, а способ их объединения в сценарий — «программируемой» части профиля. Скажем, команда find /etc -type f 2> /dev/null | xargs -n1 file | cut -d: -f2 | sort | uniq -c
задействует шесть утилит системы: командную оболочку, find
, xargs
, cut
, sort
и uniq
, причём четыре из них запускаются с изменённым профилем.
Эта команда определяет, файлы какого типа и в каком количестве содержатся в каталоге /etc
.
Командная оболочка дополнительно программируется для создания конвейера между командами.
Конфигурационный файл
Задание профиля с помощью командной строки — метод далеко не всегда удобный. Даже при работе с самой командной строкой используется окружение для сохранения настроек, чтобы не задавать их всякий раз и для всякой команды. Что уж говорить о сложных системных службах, свойства которых должны сохраняться не от сеанса к сеансу, а постоянно (в том числе при перезагрузке системы). Вывод прост: профиль необходимо держать в файле, вроде того, что создаётся по команде «сохранить настройки».
Однако сам подход к хранению профиля в файле, при котором пользователь не может изменять этот файл напрямую, а пользуется «умными» конфигураторами, удобен только в случаях, когда настроек много, а цена ошибки невелика (например, при настройке внешнего вида рабочего стола). В общем случае довольно сложно задать поведение системы на основании описания (часто неявного) свойств того, что эта система должна получать в результате. Иными словами, из описания того, что должно получиться, далеко не всегда можно автоматически сделать вывод, как оно должно получаться.
- конфигурационный файл
- Текстовый файл, содержащий настройки какой-нибудь части системы (утилиты, демона и т. п.). Как правило, считывается ею при запуске. Типичный для Linux способ организации профиля.
Одним словом, если есть конфигурационный файл, то должны быть и средства редактирования этого файла. Учитывая то, что в Linux есть высокоразвитая система хранения и переработки (как ручной, так и автоматической) данных в текстовом виде, изобретать какой-то новый формат не лучше, чем изобретать новый велосипед. Тем более, что именно текст, разделённый на строки и слова, лучше всего подходит тогда, когда есть чёткое деление профиля на объекты управления и их свойства (например, настройки какого-нибудь демона и значения этих настроек). Вдобавок именно со структурированными текстами отменно управляются текстовые редакторы Linux: Vi, Emacs и др.
methody@localhost:~ $ cat .vimrc
so $VIMRUNTIME/vimrc_example.vim
" Some mappings
map <F2> :wall!^M
map! <F2> ^O:wall!^M
" Tune up
set shiftwidth=2 tabstop=8 history=200 viminfo='50
set showmode showmatch showcmd ruler modeline
set autoindent ignorecase smartcase
set nohlsearch noincsearch
set dir=/var/tmp
set wildmode=list:longest,full
set wildmenu
" Colouring
syntax on
colorscheme desert
Пример 2. Настройки редактора vim
Вот как выглядит конфигурационный файл для Vi, написанный Мефодием на основе взятого у Гуревича. Легко заметить, что файл состоит из команд режима командной строки Vi с комментариями (в отличие от большинства утилит Linux, в Vi комментарии начинаются на «"
»). Символы «^O
» и «^M
» — это именно соответствующие управляющие символы (вставленные в текстовый файл с помощью «^V
», см. лекцию Текстовые редакторы). Такой конфигурационный файл легко понимать и изменять.
Как уже было замечено, набор переменных окружения составляет особенный профиль, к которому чувствительны все запускаемые программы — в этом его достоинство. Задаются переменные окружения обычно в командном сценарии, который тоже можно рассматривать как конфигурационный файл). Например, во многих дистрибутивах используется конфигурационный файл .i18n
для настройки языковых особенностей клавиатуры, языка вывода сообщений и т. п.:
methody@localhost:~ $ cat .i18n
LANG=ru_RU.KOI8-R
LANGUAGE=ru_RU.KOI8-R
SYSFONTACM=koi8-r
SYSFONT=UniCyr_8x16
DICTIONARY=russian
MPAGE="-CKOI8-R"
export DICTIONARY MPAGE
Пример 3. Файл настройки языковых особенностей
Однако хранить настройки специфической программы (не нужные всем остальным) в окружении — не самое удачное решение: синтаксис, задающий переменную окружения, слишком прост (имя_переменной = значение
), а самих переменных становится слишком много, поэтому при просмотре трудно выделить, какая из них к какой группе настроек относится. Если пытаться упаковать все настройки в значение одной переменной, это значение окажется трудночитаемым, и всё преимущество текстового формата сойдёт на нет. Например, стандартный конфигурационный файл утилиты ls
(точнее, только её цветовых предпочтений) — /etc/DIR_COLORS
(его можно подменить личным файлом ~/.dir_colors
) занимает около ста строк вместе с комментариями. Команда ls
использует не этот файл, а создаваемую утилитой dircolors
переменную LS_COLORS
, значене которой — 600-символьная строка безо всяких комментариев.
Если профиль слишком велик, держать его в одном конфигурационном файле — значит, доставлять пользователю сомнительное удовольствие разбирать этот файл целиком даже при необходимости внести минимальное изменение. Методов борьбы с неудобочитаемостью несколько. Во-первых, уже известный по лекции Этапы загрузки системы механизм «. d»: файл разделяется на несколько независимых друг от друга так, что редактировать приходится только один из файлов, а программа во время самонастройки считывает все.
Другой способ опирается на то, что изменения, которые пользователь вносит в профиль, как правило существенно меньше объёма всего профиля. Поэтому может быть выгодно хранить все настройки по умолчанию в каком-нибудь файле, менять который вообще не надо, а файл пользовательских настроек использовать как бы «поверх», изменяя профиль в соотвтетствии с ними после того, как выставлен профиль по умолчанию. Дополнительная выгода такого способа — в том, что пользователь всегда может подглядеть в «большой» файл, чтобы узнать как оформляется та или иная настройка. Например, утилита updfstab
, которая изменяет содержимое /etc/fstab
при появлении или удалении съёмного дискового носителя (например, лазерного диска), считывает данные из конфигурационного файла /etc/updfstab.conf
. Сам этот файл состоит из единственной строки: include /etc/updfstab.conf.default
, что приводит к чтению файла с настройками по умолчанию, где задан способ работы со многими съёмными устройствами системы. Если администратору нужно как-то изменить поведение updfstab
в отношении определённого устройства, он копирует соответствующую группу настроек из updfstab.conf.default
в updfstab.conf
после строчки include
... и исправляет их. То, что эти группы настроек читаются дважды, не играет особой роли: чтение коротких файлов выполняется быстро.
Наконец, третий способ сделать конфигурационный файл удобочитаемым — это секционирование профиля, когда все настройки разбиваются на группы, каждой группе даётся собственное имя, и синтаксис конфигурационного файла проектируется так, чтобы границы групп хорошо различались при просмотре. В сущности, этот способ — предок схемы «. d», где группе соответствует отдельный файл, однако нередки ситуации, когда разбиение на файлы неудобно (например, если группы не полностью независимы, поэтому может понадобиться редактировать их сразу и несколько). Конфигурационный файл утилиты дозвона wvdial
, например, секционируется по адресату (провайдеру) плюс отдельная секция «по умолчанию». Сами секции отделяются друг от друга заголовками, заключёнными в квадратные скобки:
root@localhost:~> cat .wvdialrc
[Dialer Defaults]
Modem = /dev/modem
Baud = 115200
Init1 = ATZ
Init2 = ATQ0 L0 M4 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Auto DNS = on
Modem Type = Analog Modem
[Dialer hotspace]
Phone = 0123456
Username = fireman
Password = Fire!Fire!
TOnline = true
[Dialer warlock]
Phone = 0246813
Username = cop-120
Password = gimmethegun
Force Address=10.0.0.120
Пример 4. Секционированный конфигурационный файл
Утилита wvdial
обладает высокоразвитым искусственным интеллектом: она самостоятельно догадывается, какой именно тип идентификации используется на сервере. Например, «с той стороны» может оказаться терминал Linux, которому требуется сначала ввести обыкновенное входное имя и пароль, затем надо получить командную строку, затем запустить на сервере сетевой демон pppd
, и только затем запустить pppd
на собственной машине. Другой вариант: pppd
на сервере уже запущен, а настройки «Username» и «Password» означают идентификационную информацию протокола CHAP, используемого pppd
. Обо всём этом и о многом другом wvdial
умеет догадываться, как wvdialconf
умел определять, какое же из устройств — модем.
Однако на любой искуственный интеллект найдется непостижимая ему жизненная ситуация. На одном из серверов (секция «Dialer hotspace») тоже стоит программа с зачатками искуственного интеллекта, которая тоже пытается определить, каким способом хочет идентифицироваться позвонивший. Оттого эти два кудесника, созвонившись, всё ждут, пока кто-нибудь не проявит себя... Помогает настройка TOnline
, которая заставляет wvdial
немедленно использовать протокол ppp
, на что сервер, подумавши «ах, ppp
!», с облегчением запускает pppd
. Остаётся вопросом: почему эта полезная настройка никак не отражена в документации (её нашёл в исходных текстах программы Гуревич)? Не потому ли, что пара wvdialconf
-wvdial
не по Linux-овски стремится всё делать за пользователя, а стало быть, пользовательская документация для разработчиков этой программы — не главное?..
Идею чтения настроек по умолчанию можно развить чуть дальше. Оказывается, бывает удобно, когда описание настройки помещено не в руководство, а непосредственно в конфигурационный файл в виде комментариев. Тогда при изменении этой настройки пользователь сразу видит, что она из себя представляет, при этом отпадает необходимость сначала находить строчку в файле, а потом искать её же в руководстве. Такой способ оформления называется самодокументированием профиля и часто используется. Например, файл /etc/man.conf
, управляющий работой команды man
, оформлен в самодокументированном стиле:
methody@localhost:~ $ cat /etc/man.conf
. . .
# NOCACHE keeps man from creating cache pages ("cat pages")
# (generally one enables/disable cat page creation by creating/deleting
# the directory they would live in - man never does mkdir)
#
# NOCACHE
# The command "man -a xyzzy" will show all man pages for xyzzy.
# When CMP is defined man will try to avoid showing the same
# text twice. (But compressed pages compare unequal.)
#
CMP /usr/bin/cmp -s
. . .
Пример 5. Самодокументированный конфигурационный файл
Мефодий, может быть, и не понял бы сразу, зачем команде man
использовать утилиту cmp
, однако в поясняющем комментарии написано: когда нужно показать несколько руководств разом, предварительно они сравниваются, и показываются только несовпадающие.
Если пойти ещё дальше, то можно создать несколько различных файлов с примерами настроек, чтобы пользователь мог взять один из них и довести до нужного ему состояния. Именно такую — демонстрационную — настройку Мефодий и включил в качестве настройки по умолчанию в свой .vimrc
(в первой строке). Кстати, на самом деле профиль Vi весьма сложен, но большинство его настроек по умолчанию находятся в различных файлах дерева каталогов /usr/share/vim
— эдакая «схема. d/. d», где файлы профиля, соответствующие подгруппам настроек, лежат в подкаталогах, соответствующих группам. Включение определённого настроечного файла может происходить неявно: например, строчка colorscheme desert
из .vimrc
приводит к чтению /usr/share/vim/colors/desert.vim
.
Конфигурационные файлы могут иметь довольно замысловатый синтаксис, если соответствуют сложным структурам данных (таковы, например, настройка irc-клиента irssi
) или содержать в себе дополнительные средства самодокументиования (например, файл настройки текстового www-броузера lynx
не просто хорошо документирован, но и размечен теми же средствами, какие используются в самом броузере для представления HTML).
Изменение конфигурационных файлов
Как правило, конфигурационный файл считывается программой при запуске, отражая, таким образом, её состояние на момент старта. Изменения настроек работающей программы в конфигурационном файле, как правило, не отражаются. Тому есть несколько причин: не стоит превращать файл, изредка редактируемый пользователем, в файл, изменение которого происходит постоянно; не стоит держать конфигурационный файл всегда открытым; тяжело, изменяя файл автоматически, не испортить структуру комментариев (интерпретируемых не машиной, а пользователем) и т. д. Впрочем, многие утилиты, особенно использующие графическую среду, могут записывать настройки в файл по окончании работы. Большинство конфигурационных файлов весьма удобно редактировать вручную, с помощью Vi или Emacs (для файлов более или менее похожих используется общая подсветка синтаксиса, а для наиболее популярных есть и собственные варианты подсветки).
В /etc
хранятся настройки системных служб, в том числе настройки по умолчанию, настройки по умолчанию пользовательских утилит, профили командных интерпретаторов, а также настройки, используемые в процессе загрузки системы (например, modules.conf
). Там же располагаются и стартовые сценарии, о которых рассказано в лекции Этапы загрузки системы. Чего не стоит искать в /etc
, так это разнообразных примеров настройки той или иной службы. Считается, что пример — это часть документации, и их стоит помещать, например, в /usr/share/doc/название_службы/examples
.
Файлы, имеющие отношение к процессу досистемной загрузки, обычно лежат не там, а в /boot
, это стоит иметь в виду, так как /boot/grub/menu.lst
— тоже часть профиля системы, хотя и довольно специфическая. В профиль системы входит содержимое каталогов etc
из т. н. «песочниц», расположенных обычно в /var/lib
.
Смысл термина «песочница» вот в чём. В Linux есть замечательный системный вызов chroot()
и использующая его утилита chroot
, формат командной строки которой chroot каталог команда
. Эта утилита запускает команду, изменив окружение таким образом, что та считает каталог
— корневым. Соответственно, все подкаталоги каталога
кажутся команде
каталогами первого уровня вложенности, и т. д. Если необходимо во что бы то ни стало ограничить область действия некоторой утилиты (например, по причине её небезопасности), можно запускать её с помощью chroot
. Тогда, даже имея права суперпользователя, эта утилита получит доступ только к каталогу
и его подкаталогам, а /etc
и прочие важные части системы окажутся в неприкосновенности. Сам каталог
как раз и играет роль «песочницы», в которую утилиту «пустили поиграть», позволяя вытворять что угодно. Часто бывает, что в песочнице есть и свой каталог etc
, содержащий необходимые для запуска утилиты (или системной службы) настройки. Вот этот-то etc
из «песочницы» также входит в список каталогов, хранящих профиль системы.
В /etc
могут находиться не только файлы, но и подкаталоги (особенно в стиле «. d») и целые поддеревья каталогов. Например, в некоторых дистрибутивах Linux используется подкаталог /etc/sysconfig
. Этот каталог создаётся и заполняется файлами при установке системы или при запуске специального «конфигуратора» — программы-кудесника, задающей наводящие вопросы. Некоторые стартовые сценарии, использующие полученные настройки, также лежат в этом каталоге или его подкаталогах. Если в системе есть каталог /etc/sysconfig
, там должны оказаться настройки, относящиеся не к самим службам или утилитам, а к способу их запуска при загрузке, а также языковые и сетевые настройки, тип мыши и т. д.