- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF107:Погружаясь в ядро
Материал из Linuxformat.
- Hardcore Linux Проверь себя на крутом проекте для продвинутых пользователей
Содержание |
Ядро: о сущности модулей
- Ядро – незаменимая часть ПО в центре вашего компьютера, но что оно, собственно, делает? Д-р Крис Браун вскроет его и заглянет внутрь.
Мой верный Оксфордский словарь определяет ядро как «мягкую, съедобную часть ореха», но есть и второе значение: «Центральная или очень важная часть чего-либо». (Кстати, первое определение породило термин ‘Оболочка’, что на языке Linux означает командный интерпретатор.) Для тех, кто имеет смутное представление, что именно делает ядро, приведем немножко теории.
Ядро (kernel) – это кусочек ПО, который, грубо говоря, представляет собой слой между оборудованием и приложениями, запущенными на компьютере. Если придерживаться строгой терминологии, ‘Linux’ означает только ядро – программу, которую Линус Торвальдс написал в начале 90-х годов. Все остальные части, которые вы можете найти в составе дистрибутива Linux – оболочка Bash, графическое окружение KDE, web-браузеры, X-сервер, Tux Racer и многое другое – это всего лишь приложения, запускаемые поверх ядра Linux, и они, подчеркнем это, не являются частью самой операционной системы. Чтобы оценить долю ядра, скажем, что свежеустановленный RHEL5 занимает около 2,5 ГБ на жестком диске (понятно, что точное число зависит от вашего выбора пакетов). Из них само ядро со всеми модулями занимает 47 МБ, или около 2 %.
Внутри ядра
Но что все-таки делает ядро? Диаграмма на рис. 1 показывает это. Ядро предоставляет приложениям свои службы, которые можно запустить через множество специальных точек входа – системных вызовов. С точки зрения программиста, это выглядит как простой вызов функции, хотя на самом деле системный вызов влечет за собой явное переключение режима процессора из пространства пользователя (user space) в пространство ядра (kernel space). Все вместе системные вызовы образуют «виртуальную машину Linux», которую можно представить как абстракцию нижележащего аппаратного обеспечения.
Одна из самых понятных абстракций, предоставляемых ядром, это файловая система. Для примера приведем маленькую программу (написанную на C), которая открывает файл и копирует его содержимое в стандартный вывод:
#include <fcntl.h> int main() { int fd, count; char buf[1000]; fd=open(“mydata”, O_RDONLY); count = read(fd, buf, 1000); write(1, buf, count); close(fd); }
Здесь вы видите пример четырех системных вызовов – open, read, write и close. Не вдавайтесь в детали синтаксиса: сейчас это не главное. Главное вот что: через эти (и некоторые другие) системные вызовы ядро Linux предоставляет нам иллюзию «файла» – ряда байтов с данными, который имеет имя – и защищает вас от нижележащих деталей: дорожек, секторов и головок со списком свободных блоков, которые вам бы пришлось учитывать при непосредственном доступе к оборудованию. Вот что мы подразумеваем под абстракцией.
Как видно из рис. 1, ядро выполняет сложную работу по созданию такой абстракции, когда сама файловая система может быть сохранена во множестве различных форматов, на локальных устройствах хранения, таких как жесткие диски, CD или USB-брелки – а то и на удаленной системе через сетевые протоколы вроде NFS или CIFS. Может даже иметься еще и дополнительный слой отображения устройств для поддержки логических томов или RAID. Слой виртуальной файловой системы (VFS) внутри ядра представляет эти нижележащие формы устройств хранения как коллекцию файлов внутри единой иерархической файловой системы.
За кулисами
Файловая система – одна из самых понятных абстракций, предоставляемых ядром. Но некоторые функции не видимы так явно. Например, ядро отвечает за планирование процессов. В любой момент времени существуют различные процессы, ожидающие запуска. Планировщик ядра предоставляет каждому часть процессорного времени, и если смотреть в течение нескольких секунд, у вас складывается иллюзия работы нескольких программ одновременно. Вот небольшая программа на С:
#include <stdlib.h> main() { if (fork()) { write(1, “Parent\n”, 7); wait(0); exit(0); } else { write(1, “Child\n”, 6); exit(0); } }
Она создает новый процесс; и исходный процесс (родительский), и новые процессы (дочерние) пишут сообщение в стандартный вывод и завершаются. Снова, забудьте про синтаксис. Просто отметьте, что системные вызовы fork(), exit() и wait() отвечают за создание, завершение и синхронизацию процессов соответственно. Эти элегантно-простые вызовы скрывают стоящую за ними сложность управления и планирования процессов.
Еще более скрытые, даже от программистов, функции ядра – это управление памятью. Каждый запускаемый процесс уверен, что имеет доступное только ему одному адресное пространство (допустимый набор адресов памяти). На самом деле он разделяет ОЗУ компьютера с множеством других процессов, и если памяти в системе не хватает, то часть его адресного пространства переедет на диск в область подкачки [swap]. Другой аспект управления памятью – защита адресного пространства одного процесса от доступа других – необходимое условие для сохранения целостности многозадачной операционной системы [оно реализуется аппаратно, – прим. ред.].
Ядро также осуществляет работу с сетевыми протоколами, такими как IP, TCP и UDP, предоставляющими соединение компьютер-компьютер и процесс-процесс через сеть. Снова вернемся к иллюзиям. TCP создает иллюзию постоянного соединения между двумя процессами – как кусок кабеля, соединяющий два телефона – хотя на самом деле никакого постоянного соединения не существует. Заметим также, что конкретные прикладные протоколы, такие как FTP, DNS или HTTP, применяемые в пользовательских программах, не являются частью ядра.
Linux (как и его «предок» Unix) имеет хорошую репутацию по части безопасности. Она достигается тем, что ядро отслеживает ID пользователя и группы для каждого запущенного процесса и использует их для ответа «да/нет» при каждом обращении процесса к ресурсу (например, открытию файла для записи), проверяя права доступа. Именно эта модель контроля доступа обеспечивает основную безопасность Linux-систем.
Наконец (сразу приношу извинения программистам, написавшим те части ядра, которые я не перечислил), ядро сопровождается большой коллекцией модулей, способных общаться с аппаратными устройствами на языке низкого уровня: считать сектор с диска, получить пакет с сетевой карты и так далее. Их также иногда называют драйверами устройств.
Модульное ядро
Теперь, получив представление о том, что делает ядро, давайте кратко рассмотрим его физическую организацию. Ранние версии ядра Linux были монолитными – это когда все части и кусочки были статически собраны в один (довольно большой) исполняемый файл. Современные ядра Linux, напротив, модульные: большая часть их функций содержится в модулях, динамически загружаемых в ядро. Это позволяет сохранять размер основной части ядра небольшим и делает возможным подключение и замену модулей в запущенном ядре без перезагрузки.
Основная часть ядра помещается в память во время загрузки системы, из файла в директории /boot с именем вида vmlinuz-ВЕРСИЯ_ЯДРА, где ВЕРСИЯ_ЯДРА – это, сами понимаете, номер версии текущего ядра. (Чтобы узнать, какая версия ядра используется у вас, выполните команду uname -r.) Модули ядра находятся в директории /lib/modules/ВЕРСИЯ_ЯДРА. Все эти кусочки были скопированы на место при установке ядра.
Управляем модулями
Как правило, Linux разбирается со своими модулями без нашей помощи, но на случай необходимости есть команды для обследования и управления модулями вручную. Например, чтобы найти, какие модули в настоящее время загружены в ядро, используйте lsmod. Вот пример его вывода:
# lsmod pcspkr 4224 0 hci_usb 18204 2 psmouse 38920 0 bluetooth 55908 7 rfcomm,l2cap,hci_usb yenta_socket 27532 5 rsrc_nonstatic 14080 1 yenta_socket isofs 36284 0
Поля этого вывода содержат имя модуля, его размер, счетчик его использования и список зависимых от него модулей. Счетчик использования важен для защиты модуля от выгрузки при его активности. Linux может удалить модуль, только когда его счетчик использования равен нулю.
Модули можно загружать и выгружать вручную, используя modprobe. (На самом деле, работа выполняется двумя низкоуровневыми командами, insmod и rmmod, но modbrobe проще в использовании, потому что автоматически разрешает зависимости модулей.) Например, вывод lsmod на нашей машине показал загруженный модуль с именем isofs, счетчик использования которого равен нулю и от него не зависят другие модули. (isofs – это модуль поддержки файловой системы ISO, используемой на CD). Ядро с удовольствием избавится от такого модуля при помощи команды:
# modprobe -r isofs
'Isofs больше не показывается в выводе lsmod, и, что ценно, ядро стало потреблять на 36284 байта меньше памяти. Если вы вставите CD диск и он смонтируется автоматически, ядро также автоматически подгрузит модуль isofs и увеличит счетчик его использования до 1. Попытка удалить модуль вам на сей раз не удастся, так как он используется:
# modprobe -r isofs FATAL: Module isofs is in use.
lsmod просто показывает модули, загруженные в данный момент; а вот modprobe -l выведет список всех доступных модулей, то есть все модули, находящиеся в /lib/modules/ВЕРСИЯ_ЯДРА; будьте готовы к длинному перечню.
В реальности, вам обычно незачем загружать модули вручную с помощью modprobe, но если придется, можно заодно передавать в модуль параметры, через командную строку modprobe. Например, так:
# modprobe usbcore blinkenlights=1
Термин blinkenlights («мигалка») отнюдь не выдуман – это реальный параметр модуля usbcore.
Сложность здесь заключается в знании, какие параметры имеет модуль. Можете использовать звонок другу или помощь зала, но лучше обратиться к команде modinfo: она выводит список различной информации о модуле.
Вот пример информации, выводимой для модуля snd-hda-intel (в сокращении):
# modinfo snd-hda-intel filename: /lib/modules/2.6.20-16-generic/kernel/sound/pci/ hda/snd-hda-intel.ko description: Intel HDA driver license: GPL srcversion: A3552B2DF3A932D88FFC00C alias: pci:v000010DEd0000055Dsv*sd*bc*sc*i* alias: pci:v000010DEd0000055Csv*sd*bc*sc*i* depends: snd-pcm,snd-page-alloc,snd-hda-codec,snd vermagic: 2.6.20-16-generic SMP mod_unload 586 parm: index:Index value for Intel HD audio interface. (int) parm: id:ID string for Intel HD audio interface. (charp) parm: model:Use the given board model. (charp) parm: position_fix:Fix DMA pointer (0 = auto, 1 = none, 2 = POSBUF 3 = FIFO size). (int) parm: probe_mask:Bitmask to probe codecs (default = -1). (int) parm: single_cmd:Use single command to communicate with codecs (for debugging only). (bool) parm: enable_msi:Enable Message Signaled Interrupt (MSI) (int) parm: enable:bool
Интересующие нас строки начинаются с parm: – они показывают параметры, принимаемые модулем. Их описание, мягко выражаясь, сжатое. Для охоты за дальнейшей информацией установите исходные коды ядра, затем найдите директорию, имеющую имя вроде /usr/src/ВЕРСИЯ_ЯДРА/Documentation. В ней содержится множество интересных вещей: например, файл /usr/src/ВЕРСИЯ_ЯДРА/Documentation/sound/alsa/ALSA-Configuration.txt описывает параметры, распознаваемые многими звуковыми модулями ALSA. Файл /usr/src/ВЕРСИЯ_ЯДРА/Documentation/kernel-parameters.txt тоже полезен.
Пример необходимости передачи параметров в модуль недавно обсуждался на одном из форумов Ubuntu (см. https://help.ubuntu.com/community/HdaIntelSoundHowto): говорилось, что модуль snd-hda-intel нуждается в небольшой помощи для правильного управления звуковым оборудованием и иногда «зависает» во время загрузки при старте системы. Часть исправления заключалась в применении к модулю опции probe_mask=1. Итак, если вы загружаете модуль вручную, напечатайте:
# modprobe snd-hda-intel probe_mask=1
А лучше поместить эту строку в файл /etc/modprobe.conf, следующим образом:
options snd-hda-intel probe_mask=1
Она велит modprobe включать опцию probe_mask=1 при каждой загрузке модуля snd-hda-intel. Некоторые существующие дистрибутивы Linux разбивают эту информацию по множеству файлов директории /etc/modprobe.d, а не сосредоточивают ее всю в modprobe.conf.
Файловая система /proc
Ядро Linux также экспонирует множество информации через файловую систему /proc. Чтобы получить понятие о /proc, мы должны расширить наше представление о файлах и рассматривать их не как хранилище информации на жестком диске, CD или карте памяти, а как любую информацию, к которой можно получить доступ через стандартные системные вызовы, типа открыть/читать/записать/закрыть, рассмотренные нами ранее, или через обычные программы вроде cat или less. «Файл» в /proc полностью является плодом воображения ядра и предоставляет возможность просмотра данных множества внутренних структур последнего.
Фактически, многие справочные утилиты Linux представляют в человеко-читаемой форме информацию, найденную в файлах /proc. Например, в /proc/modules содержится список текущих загруженных модулей, странным образом напоминающий вывод lsmod. Аналогично, /proc/meminfo содержит прорву деталей о текущем состояии виртуальной памяти системы, а утилиты типа vmstat и top предоставят часть этой информации в (чуть) более внятном формате. Другой пример – содержимое /proc/net/arp, которое показывает текущее состояние кэша ARP; в командной строке arp -a покажет похожую информацию.
Особо интересны «файлы» в /proc/sys. Например установки в /proc/sys/net/ipv4/ip_forward говорят ядру, должен ли компьютер пересылать адресованные не ему IP-датаграммы – т.е. выполнять функцию шлюза. В данном случае, ядро сообщает, что эта возможность выключена:
# cat /proc/sys/net/ipv4/ip_forward 0
А еще интереснее будет новость, что можно также и писать в эти файлы. Продолжая наш пример:
# echo 1 > /proc/sys/net/ipv4/ip_forward
включит IP-переадресацию в запущенном ядре.
Вместо использования cat или echo для исследования и модификации настроек в /proc/sys можете использовать команду sysctl.
# sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 0
эквивалентно
# cat /proc/sys/net/ipv4/ip_forward 0
тогда как
# sysctl -w net.ipv4.ip_forward=1 net.ipv4.ip_forward = 1
аналогично
# echo 1 > /proc/sys/net/ipv4/ip_forward
Заметим, что пути, применяемые вами в sysctl, используют для разделения компонентов точку (.) вместо привычного правого слэша (/), и что этот путь вычисляется относительно /proc/sys.
Помните, что настройки, изменяемые таким образом, будут в силе только на текущий запуск ядра – они не сохранятся при перезагрузке. Чтобы сделать их постоянными, поместите их в файл /etc/sysctl.conf. При загрузке системы sysctl должна будет автоматически применить любые настройки, найденные ею в этом файле.
Строка в /etc/sysctl.conf будет выглядеть так:
net.ipv4.ip_forward=1
Тюнинг производительности
Записываемые параметры в /proc/sys породили целую субкультуру тюнинга производительности Linux. Лично я считаю это излишним, но вот вам несколько примеров на пробу.
Инструкция по установке Oracle 10g (http://www.oracle.com/technology/obe/obe10gdb/install/linuxpreinst/linuxpreinst.htm) просит вас указать несколько параметров, включая
kernel.shmmax=2147483648
устанавливающий максимально возможный разделяемый сегмент памяти в 2 ГБ. (Разделяемая память представляет собой механизм межпроцессной коммуникации, когда сегмент памяти является видимым в адресных пространствах множества процессов.)
Руководство IBM ‘Redpaper’ по настройке производительности и тюнингу Linux (http://www.redbooks.ibm.com/abstracts/redp4285.html) всячески советует применять параметры в /proc/sys, например:
vm.swappiness=100
Этот параметр, очевидно, управляет интенсивностью сброса страниц памяти на диск.
Некоторые параметры можно подправить для усиления безопасности. Cайт Боба Кромвеля (http://cromwell-intl.com/security/security- stack-hardening.html) содержит несколько хороших примеров, включая такой:
net.ipv4.icmp_echo_ignore_broadcasts=1
Он предписывает ядру не отвечать на широковещательные ICMP-запросы (ping’и), делая вашу сеть менее чувствительной к одной из DoS-атак (отказ в обслуживани), известной также как Smurf-атака.
В другом примере,
net.ipv4.conf.all.rp_filter=1
ядру велено усилить уровень проверки, также называемый входной или выходной фильтрацией. Суть этого заключается в отбраковке пакетов, если IP-адрес отправителя или получателя в заголовке пакета не имеют смысла при его рассмотрении в свете использованного физического интерфейса.
А есть ли документация по всем этим параметрам ? Да – команда
# sysctl -a
покажет вам их имена и текущие значения. Это длинный список, но по нему не догадаешься, что параметры делают. Есть ли другие источники? Оказывается, издательство O’Reilly издало книгу, написанную Оливье Доделем [Olivier Daudel], под названием «/proc et /sys». Oui, mes amis, она на французском, и русского перевода нет. Другая полезная ссылка – Red Hat Enterprise Linux Reference Guide, руководство, содержащее главы о рассматриваемом объекте. Вы можете скачать его с http://www.redhat.com/docs/manuals/enterprise. Полезной книгой о ядре Linux является Ядро Linux. 3-е издание Бовета и Чезати (O’Reilly/BHV), но сразу предупредим, что она о внутренностях ядра, и будет полезна скорее будущим разработчикам и студентам компьютерных специальностей, чем системным администраторам.
Также возможно сконфигурировать и собрать собственное ядро. Для этого прочтите великолепный учебник Нейла Ботвика в LXF89 или Linux Kernel in a Nutshell Грега Кроа-Хартмана [Greg Kroah-Hartman] издательства O’Reilly, заголовок которой содержит очаровательную, но, по-видимому, непредумышленную игру слов [по-англ. in a nutshell буквально значит «в скорлупе ореха», а в переносном смысле – «вкратце», – прим. ред.]. Но, конечно, чтобы собирать ядро, надо быть крепким орешком. LXF
Стоит ли трудов тюнинг производительности?
Первым автомобилем моего отца был Wolseley 1500 с номером 49 RNU – уж и не знаю, как я упомнил эти подробности. Так или иначе, отец любил эксперименты и тратил часы на мелкие доводки вроде угла зажигания или качества смешения топлива. Время от времени он откручивал свечи и корректировал зазоры. Выкрутив свечи, он подавал топливо в цилиндры, как часть некого таинственного процесса внутреннего орошения. После этого выхлоп превращался в убедительные густые облака черного дыма.
Но вот беда: у него не было никаких объективных способов измерить, к чему привели его улучшения. Он скрупулезно вел записи о купленном бензине и километраже, рассчитывая расход топлива до нескольких десятичных знаков, и у него был специальный холм, куда он поднимался на третьей передаче, чтобы «посмотреть, как пошло», но все это нельзя было назвать научной методикой.
Многие системные администраторы Linux находятся в аналогичном положении. Они знают, что есть множество параметров, которые можно изменить и повысить производительность, но слабо представляют, что делает большинство из них, и не имеют хорошего способа для измерения производительности. Итак, мой вам совет: если вы не знаете, что вы делаете, и/или нет способа измерить результат, оставьте эти параметры в покое!
Достаем исходники ядра
Чтобы добыть исходные коды ядра, не надо быть суперхакером. Вы можете просто установить пакет с ними, предоставляемый вашим дистрибутивом, так же, как устанавливаете другие пакеты.
Например, в моем Ubuntu я запускал команду:
# apt-get install linux-source
Альтернатива – попытаться зайти на http://www.kernel.org и загрузить архив с исходными текстами (он может называться как-то вроде linux-2.6.25.6.tar.bz2) в /tmp. Распакуйте его в /usr/src:
# cd /usr/src # tar xjf /tmp/linux-2.6.24.2.tar.bz2
В обоих случаях вы в итоге получите исходные коды ядра в поддиректории /usr/src с именем, содержащим номер версии; обычно что то вроде /usr/src/linux-source-2.6.25.