Основная причина, по которой raw-устройства отсутствуют [в Linux], заключается в том, что лично я считаю, что raw-устройства — это глупость.
Линус Торвальдс, linux-kernel mailing list, 1996
В Unix-подобных ОС с самого начала была реализована введенная Bell Labs концепция «Everything is a file», или «Все есть файл».
Файлами являются даже параметры конфигурации устройств и настройки отдельных соединений. Такие файлы хранятся в специальных файловых системах, например DevFS и ProcFS. Файлы в них — это не отображение записей реальной файловой системы, которая хранит их на постоянном носителе. Это точки входа, записывая в которые можно обращаться либо к ядру, либо к драйверам устройств, или точки входа, читая из которых можно получать данные с устройств.
Специальные файловые системы также называются виртуальными, синтетическими или псевдофайловыми.
Обычно этот вариант применяется в скриптах, особенно в скриптах оболочки, например Bash. Но иногда возникает желание использовать такой способ в приложениях. Однако в программировании на языках, подобных C++, этот способ взаимодействия используется редко по ряду причин:
• Данные файловые системы различаются между ОС, то есть переносимость таких настроек плохая даже между Unix-подобными системами. Мало того, различия могут быть даже между разными дистрибутивами Linux, потому что содержимое данных файловых систем (ФС) не стандартизовано или стандартизовано недостаточно.
• В ОС семейства Windows /proc и /sys, очевидно, нет.
• Такой способ доступа к параметрам добавляет еще один слой абстракции, что создает дополнительные накладные расходы, а значит, не слишком эффективно.
• Для большинства языков такой подход не является естественным, и запись в файл для установки параметра устройства воспринимается как нечто чужеродное.
Впрочем, некоторые приложения активно используют данные возможности. Например, Linux Netstat из пакета net-tools работает, читая /proc. Хотя на смену Netstat приходит утилита SS, которая этого не делает.
Записи в файлы специальных ФС приложения должны избегать — в основном потому, что приложение не должно конфигурировать систему. Этим должен заниматься системный администратор, который лучше знает, как настроить ОС в каждом конкретном случае. И если он явно не запрашивает конфигурацию, обычно ее делать нельзя.
Сценарии, в которых необходимо конфигурировать что-либо самостоятельно, существуют, но их достаточно мало:
• Приложение для оптимизации конфигурации системы по определенным критериям. Это может быть повышение безопасности, пропускной способности и т.п. Очевидно, что основная задача такого приложения — настройка сетевого стека системы.
• Система «под ключ». Например, программно-аппаратный комплекс. Но даже в этом случае нежелательно, чтобы конфигурированием занималось основное приложение. Вынесите это в установщик или в скрипт настройки. Либо сделайте прошивку с предопределенными настройками.
• Приложение, которое не может работать без выполнения специальных действий. Например, менеджер виртуальных машин, который строит между ними сеть на базе TUN- и TAP-устройств. В этом случае ему просто необходимо добавлять и удалять такие устройства.
В остальных случаях права администратора и необходимость вносить изменения на уровне операционной системы обычно не требуются.
В этой главе мы рассмотрим назначение специальных ФС, например DevFS или ProcFS. Все они описаны в стандарте UNIX Filesystem Hierarchy Standard. Его полезно изучить, чтобы понимать назначение различных каталогов, точек монтирования и файловых систем в Unix-подобных ОС, включая Linux. Однако стоит также учитывать, что нестандартизованные ФС тоже существуют.
Внимание! Данная книга не посвящена сетевому администрированию. Поэтому не стоит ожидать в ней подробного рассмотрения указанных файловых систем. Их возможности будут упомянуты исключительно в контексте разработки сетевых приложений.
Файловая система устройств DevFS — виртуальная файловая система, которая содержит псевдофайлы устройств, что дает процессам в пользовательском пространстве доступ к устройствам и некоторым параметрам работающего ядра ОС.
Работа со всеми ФС начинается с монтирования ФС.
Монтирование — это процесс, выполнение которого делает файлы и каталоги на устройстве хранения данных доступными для пользователей через файловую систему.
Любая примонтированная ФС с типом devtmpfs создает устройства автоматически и не требует явного создания через вызов mknod(), хотя может понадобиться ее дополнительная настройка через udev.
Все POSIX-совместимые ОС содержат каталог /dev, в который монтируется данная файловая система:
dev on /dev type devtmpfs
(rw,nosuid,relatime,size=8086776k,nr_inodes=2021694,mode=755,inode64)
В современных ОС все устройства, подключенные к системе и опознанные соответствующим драйвером, представлены в DevFS, кроме сетевых. Сетевые интерфейсы существуют в своем собственном пространстве имен и экспортируют набор операций, отличный от операций для большинства устройств.
Основная тому причина — историческая. Многие устройства ведут себя как файлы, поэтому концепция «Все есть файл» должна была породить красивую, чистую, поддающуюся сопровождению парадигму операций.
Сокеты и, соответственно, управление сетевыми устройствами были реализованы позже в Калифорнийском университете в Беркли. Разработчикам из Беркли нужно было добавить интернет-функции, которые в концепцию символьных и блочных устройств вписывались не очень хорошо.
За исключением сетевых блочных устройств, для которых существуют специальные приложения.
Другая причина в том, что сетевые приложения более сложны, чем приложения для доступа к файлам, а сетевые адаптеры сложнее многих устройств:
• Они работают не с байтами, а с пакетами и дейтаграммами.
• Хотя обмен данными, как правило, осуществляется с помощью вызовов, аналогичных чтению и записи, для установления соединения требуется больше информации, чем просто имя файла. Например, прослушивание TCP-соединений состоит из двух шагов: один должен выполняться, когда сервер начинает прослушивание, а другой — каждый раз, когда подключается клиент.
• «Обычный API», реализующий чтение и запись, может быть реализован, но, вероятно, не будет удобен. Каждый вызов записи будет отправлять пакет, а каждый вызов чтения будет его получать. Если же буфер слишком мал для пакета, этот пакет будет потерян.
• Большинство сетевых приложений не работают с конкретными сетевыми устройствами, они работают на более высоком уровне, который предоставляют транспортные протоколы. Например, веб-браузер устанавливает TCP-соединение к веб-серверу. Через какое устройство будет идти соединение, не важно. Это должен настраивать администратор для всех приложений.
Таким образом, сетевые интерфейсы могут существовать как устройства, предоставляющие только ioctl, что и реализовано в некоторых вариантах Unix.
Возможно, для цели обмена данными были бы полезны, например, устройства для сетевых протоколов высокого уровня.
Однако, как уже говорилось, существует ОС Plan 9, в которой все является файлом. В Plan 9 операции для сетевых устройств, подобные fcntl() либо ioctl(), доступны путем чтения и записи в специальные файлы, аналогичные файлам устройств.
Для Linux применимо решение Glendix, доступное на , — проект, который предлагает порт виртуальной файловой системы /net от Plan9.
Версии ядра Linux до 1.0 создавали сетевые устройства в /dev для целей управления. Скрипт MAKEDEV 1994 года поддерживает /dev/ne[0-3] — NE2000 и другие сетевые адаптеры.
Развитие DevFS
Существовало несколько вариантов поддержки устройств в Unix-подобных ОС и в Linux в частности. Один из первых — скрипт MAKEDEV, который через вызов утилиты mknod(), работающей поверх одноименного системного вызова, создавал большой набор файлов устройств.
Например, чтобы создать управляющее TUN/TAP устройство, нужно вызвать:
mknod /dev/net/tun c 10 200
Будет создан файл символьного устройства с major-номером 10 и minor-номером 200.
Скрипт не проверял, существуют ли реально эти устройства. Если пользователь обращался к несуществующему устройству, система просто выдавала ошибку. Если устройство добавлялось или удалялось, требовалось запустить скрипт MAKEDEV вручную.
В то время /dev был лишь подкаталогом корневой файловой системы и созданные файлы устройств существовали там всегда. Драйвер устройства выбирался на основе его major-номера.
В Linux такой подход использовался до версии ядра 2.4. Ненужные файлы забивали /dev, путали пользователя, создавали проблемы с безопасностью, а major-номеров, которых было всего 255, не хватало. Кроме того, Linux становилась все более «пользовательской» системой, и нужно было поддержать динамическое извлечение и добавление устройств.
Проблему решила DevFS, но не та, которая используется в современных ядрах, а ее первый вариант. Она позволила драйверу создавать устройство через вызов devfs_register. С ее приходом в /dev перестали создаваться несуществующие устройства, а драйверы смогли устанавливать права на создаваемые ими файлы устройств. Был реализован демон devfsd. Этот демон создавал символические ссылки на файлы устройств, однако сами файлы не трогал.
Такой подход, решив существующие проблемы, создал новые:
• Пользователь не мог выбирать название файла устройства, основываясь, например, на его позиции на шине или его идентификаторе.
• В результате устройства не имели предсказуемых имен: имена зависели от последовательности их подключения и еще некоторых факторов.
• Все существующие имена приходилось хранить в невыгружаемой памяти ядра.
• Пользовательские скрипты при загрузке должны были ожидать заполнения иерархии и как-то синхронизироваться.
• Необходимость явных вызовов devfs_register из всех драйверов не была удачным решением. В ядре и в драйверах накапливался код, отвечающий за devfs и требующий поддержки.
Эти проблемы начали решать с версии ядра 2.6. Первым шагом была реализация SysFS. В ней стали отображаться поддерживаемые драйверами устройства. Общий код создавал структуру каталогов с настройками устройств на основе структур данных в драйвере.
Следующим шагом была реализация демона udevd, который, используя SysFS и набор правил конфигурации, формировал актуальную иерархию устройств в /dev.
Udevd ожидал сообщений, передаваемых от драйверов, через сокеты NETLINK_KOBJECT_UEVENT и подсистему NETLINK, когда драйвер сообщал об изменении состояния устройства, создавал, удалял или менял нужную запись.
В /dev монтировался обычный TmpFS.
Постепенно udevd разросся, был интегрирован в SystemD и стал требовать для своей работы большое число пользовательских утилит и библиотек.
Это было неприемлемо для встраиваемых систем и аварийных режимов системы, в которых доступен только Initramfs, поэтому в современной Linux реализован гибридный подход:
• В /dev монтируется DevTmpFS, которая сразу отображает все перечисленные драйверами устройства.
• Если демон udevd меняет какой-либо из файлов, демон становится ответственным за него и управляет им сам. Так же, как он бы это делал с обычным TmpFS.
Это дает возможность встраиваемым устройствам работать без udevd и systemd, не ломая существующий udevd, плюс ко всему ускоряет загрузку. А код для регистрации устройств реализован в ограниченном количестве мест.
Также существует достаточно старый интерфейс Transport Layer Interface, или TLI, который мы упоминали в главе 1. В нем для того, чтобы получить сокет соответствующего семейства протоколов, необходимо было открыть специальный файл: /dev/ip, /dev/tcp или /dev/udp.
Другой класс устройств, которые обычно не имеют записей в /dev в Linux, — видеоадаптеры. Хотя в некоторых других вариантах Unix они есть. В принципе, простые видеоадаптеры могут быть представлены как устройства кадрового буфера, то есть блочные устройства, в которых блок представляет цвет каждого пикселя.
Ускоренные видеоадаптеры можно представить как символьные устройства, на которые приложения посылают команды.
Однако недостатком интерфейса такого устройства является его медлительность: отображающее приложение, на практике — X-сервер, должно было бы выполнять вызовы ядра при каждом отображении чего-либо. Вместо этого X-сервер в основном пишет напрямую в память видеоадаптера, что значительно быстрее.
Примерно та же проблема возникает при работе с высокоскоростными сетевыми адаптерами.
В Linux тоже есть устройства /dev/tcp и /dev/udp, в большинстве ядер они отключены. Рассмотрим пример использования такого устройства.
Сначала устройство необходимо создать:
➭ sudo mknod /dev/tcp c 30 36
После этого его можно использовать в скриптах:
#!/bin/bash
TCP_HOST=google.com
TCP_PORT=80
# Открыть файл на ввод и вывод, связав его с дескриптором 3,
# и перезапустить скрипт.
exec 3<>"/dev/tcp/${TCP_HOST}/${TCP_PORT}"
# Отправить запрос в дескриптор.
echo -e "HEAD / HTTP/1.0\r\nHost: ${TCP_HOST}\r\nConnection: close\r\n\r\n" \
>&3
# Прочитать ответ из дескриптора.
cat <&3
В результате получим ответ:
➭ src/book01/ch13/shell/dev-tcp.sh
HTTP/1.0 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Content-Security-Policy-Report-Only: object-src 'none'; ... https://csp.withgoogle.com/csp/gws/other-hp
Date: Tue, 25 Jun 2024 04:46:38 GMT
Expires: Thu, 25 Jul 2024 04:46:38 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Получается, что для сети файловый интерфейс вполне применим. Но в том же Bash легко обойтись и без него: в нем хватает сетевых утилит. Например, в некоторых современных дистрибутивах запрос GET выполнить очень просто. Для этого достаточно набрать в консоли:
➭ GET google.com
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ru"><head><meta content
...
Видим, что сервер вернул HTML-страницу. Аналогично выполняются POST, HEAD и некоторые другие HTTP-запросы. В данном случае GET и прочие — это утилиты из пакета libwww-perl или perl-libwww — в разных дистрибутивах он называется по-разному.
В скриптах чаще всего используется URL. Большинству разработчиков и администраторов этот вариант понятнее, чем использование сетевого файлового интерфейса, и он гораздо лучше переносим.
В языках, где имеется возможность пользоваться сокетным интерфейсом, использовать вместо него файловый, который не является переносимым, бессмысленно.
Также очевидно, что теперь, когда интерфейс устоялся, никакого смысла добавлять сетевые устройства в /dev или переписывать работающий код нет. Однако это не означает, что сетевые устройства не могут находиться в /dev/: любой конкретный драйвер может создать узел устройства для того, чтобы пользователь мог с ним взаимодействовать через ioctl(). Например, существует каталог /dev/net, в котором находятся устройства для управления TAP/TUN-интерфейсами, PPP и сетевые блочные устройства:
➭ ls -l /dev/net/
crw-rw-rw- 1 root root tun
Устройство tun, показанное выше, может быть использовано для создания новых интерфейсов.
Делается это примерно следующим образом:
...
// Тип параметра для ioctl.
ifreq ifr = {};
// Устройство открывается для того, чтобы выполнить ioctl над дескриптором.
int dev_fd = open("/dev/net/tun", O_RDWR);
if (-1 == dev_fd)
{
perror("Opening /dev/net/tun");
}
// Тип, имя, флаги создаваемого виртуального интерфейса.
ifr.ifr_flags = IFF_TUN;
std::copy_if(dev_name.cbegin(), dev_name.cend(), ifr.ifr_name,
[](char c) { return c != '\0'; });
// Выполнить ioctl над дескриптором устройства:
if (-1 == ioctl(dev_fd, TUNSETIFF, &ifr))
{
perror("TUNSETIFF ioctl");
close(dev_fd);
}
...
О TUN/TAP мы более подробно будем говорить при описании перехвата данных в главе 23.
FDescFS — это специальная файловая система FreeBSD, которая монтируется в /dev/fd и предоставляет доступ к файловым дескрипторам каждого процесса. Ее альтернатива в Linux — каталог /proc/self/fd, который может быть полезен для работы с дескрипторами процесса.
Содержимое FDescFS отображается как список нумерованных файлов, соответствующих открытым файлам процесса, читающего смонтированный каталог. Файлы имеют шаблон имени типа /dev/fd/n, где n — число, которое ссылается на файловые дескрипторы. Например, /dev/fd/0.
Рис. 13.1. Схема каталогов файловой системы ProcFS
ProcFS — это виртуальная файловая система, которая используется для представления информации о работающих процессах и параметрах структур данных ядра через файлы. Основные ее каталоги показаны на рис. 13.1.
Обычно монтируется в каталог /proc:
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
В первых версиях Unix-программы узнавали о запущенных процессах в системе напрямую, читая структуры из памяти ядра. Например, первые версии команды ps читали псевдофайл /dev/mem и разбирали необработанные данные, но выставлять системные данные непосредственно в пользовательское пространство через /dev/mem достаточно небезопасно.
Со временем для этой цели появились системные вызовы, но создавать новые системные вызовы каждый раз, когда требуется экспортировать новые атрибуты процесса, накладно.
ProcFS, которая появилась в Unix System V, содержала информацию о каждом запущенном в данный момент процессе, которую можно было получить, используя схему /proc/$PID/stuff. Интерфейсы и структуры каталогов могли оставаться прежними, даже если структуры данных в ядре изменялись.
К процессу, из которого делается запрос к /proc, можно обратиться через симлинк self:
➭ ls -l /proc/self
lrwxrwxrwx 1 root root 0 мая 22 00:57 /proc/self -> 991517
Или через его PID, который можно получить, вызвав функцию getpid() либо использовав специальную переменную с именем $$ в оболочке.
Linux расширил содержимое /proc, добавив информацию о состоянии ядра и настройках.
Внимание! ProcFS не стандартизован! В некоторых старых BSD-системах он вообще отсутствует.
В /proc содержатся информационные файлы, доступные только для чтения, и файлы, доступные для записи, которые могут изменять состояние ядра. С точки зрения сетевого программирования нас прежде всего интересует каталог /proc/self/net или /proc/net, который является ссылкой на self:
➭ ls -ld /proc/net
lrwxrwxrwx 1 root root 8 июл 29 21:39 /proc/net -> self/net
Каждый каталог и виртуальный файл в этом каталоге описывает аспекты сетевой конфигурации выполняющегося процесса. Например, через него можно получить все интерфейсы, включая те, которые не имеют адресов или флага IFF_RUNNING, а также их полную статистику, такую как количество принятых и отправленных пакетов, количество ошибок, коллизий и прочее:
➭ cat /proc/net/dev
Inter-| Receive | Transmit
face | bytes packets errs drop fifo frame compressed multicast| bytes
lo: 9809906 98481 0 0 0 0 0 0 9809906
eno2: 0 0 0 0 0 0 0 0 0
wlo1: 17673001 155075 0 0 0 0 0 0 43285148
IPv6-адреса можно прочитать из /proc/net/if_inet6:
➭ cat /proc/net/if_inet6
fe800000000000006072680ebbd12345 03 40 20 80 wlo1
00000000000000000000000000000001 01 80 10 80 lo
fe80000000000000004207fffe912345 04 40 20 80 docker0
Из-за того что данный каталог относится к процессу, но отражает настройки всей системы, большая часть файлов будет недоступна для записи.
Для записи доступен pktgen — генератор пакетов, о котором мы поговорим далее.
Посмотрим, что вообще может содержать /proc:
• net/arp — выводит список ARP-таблиц ядра. Этот файл полезен для получения ассоциации аппаратных адресов с IP-адресами.
• net/atm/ — файлы в этом каталоге содержат настройки и статистику ATM — асинхронного режима передачи. Этот каталог в основном используется, например, для ADSL-подключений.
• net/dev — показанный выше список различных сетевых устройств и статистика приема и передачи данных.
• net/dev_mcast — список многоадресных групп, которые прослушивает каждое из устройств.
• net/igmp — список многоадресных IP-адресов, к которым присоединилась эта система.
• net/ip_mr_cache — список кэшей многоадресной маршрутизации.
• net/ip_mr_vif — список многоадресных виртуальных интерфейсов.
• net/netfilter/nfnetlink_queue — информация об очередях Netfilter, если он включен.
• net/netstat — подробный список сетевой статистики: тайм-ауты TCP, отправленные и полученные SYN-cookie и т.п.
• net/psched — глобальные параметры планировщика пакетов.
• net/rarp — содержит базу данных RARP, если RARP был включен при сборке ядра.
• net/raw — необработанная статистика устройства.
• net/route — таблица маршрутизации ядра.
• net/rt_cache — текущий кэш маршрутизации.
• net/snmp — список данных SNMP для используемых сетевых протоколов.
• net/sockstat — общая статистика по сокетам.
• net/ip_tables_names — список используемых типов IPTables. Содержит одно или несколько из следующих значений: filter, mangle или nat. Существует только в случае, если загружен iptables.
• net/tcp — подробная информация о TCP-сокетах.
• net/udp — подробная информация о UDP-сокетах.
• net/unix — список используемых сокетов домена UNIX.
• net/wireless — статистика беспроводных интерфейсов.
Конечно, этот список неполный. Параметров намного больше. А модули ядра, поддерживающие различные сетевые протоколы, могут создавать в данном каталоге новые файлы и подкаталоги.
В старых версиях ядра Linux ioctl() для получения маршрутов не было. Поэтому в старой программе route из пакета net-tools файлы /proc/net/route, /proc/net/ipv6_route, /proc/net/rt_cache использовались для просмотра маршрутов.
Другие протоколы использовали аналогичные файлы, например /proc/net/x25/route или /proc/net/atalk_route.
Каталог /proc/sys/ содержит подкаталоги с файлами, управляющими настройками работающего ядра. В большинство файлов данного каталога разрешена запись. Системный администратор через них может включать и выключать функциональные блоки ядра.
Внимание! Изменение настроек системы с помощью файлов в каталоге /proc/sys может сделать ядро нестабильным, что потребует перезагрузки системы. Убедитесь в корректности параметров, прежде чем пытаться изменять какое-либо значение в /proc/sys.
Для нас в этом каталоге интересен подкаталог /proc/sys/net, в котором содержатся настройки сети уровня системы. Например, значение по умолчанию для описанной ранее опции IPV6_V6ONLY определяется содержимым файла /proc/sys/net/ipv6/bindv6only:
# cat /proc/sys/net/ipv6/bindv6only
0
# echo 1 > /proc/sys/net/ipv6/bindv6only
# cat /proc/sys/net/ipv6/bindv6only
1
# echo 0 > /proc/sys/net/ipv6/bindv6only
# cat /proc/sys/net/ipv6/bindv6only
0
Максимально допустимый размер вспомогательного буфера для каждого сокета можно установить с помощью /proc/sys/net/core/optmem_max:
# cat /proc/sys/net/core/optmem_max
20480
# echo 20485 > /proc/sys/net/core/optmem_max
# cat /proc/sys/net/core/optmem_max
20485
Состав каталогов определяется набором модулей и опций времени компиляции ядра.
Некоторые файлы конфигурации в /proc/sys/ содержат более одного значения.
Чтобы правильно установить им новые значения, поместите между ними пробел:
echo "4 2 50" > /proc/sys/kernel/acct.
Каталог /proc/sys/net/core/ содержит файлы для настройки взаимодействия между ядром и сетевыми уровнями разных протоколов.
Наиболее важные из этих файлов:
• message_burst — устанавливает лимит времени в десятых долях секунды для записи нового предупреждающего сообщения из сетевого кода в журнал ядра. Этот параметр используется для смягчения атак типа «отказ в обслуживании». Значение по умолчанию — 50, то есть сообщения будут записываться не чаще одного за 5 секунд.
• message_cost — устанавливает стоимость каждого предупреждающего сообщения. Чем выше значение параметра, тем больше вероятность того, что предупреждающее сообщение будет проигнорировано. Этот параметр тоже используется для защиты от DoS-атак. Значение по умолчанию — 5.
• netdev_max_backlog — устанавливает максимальное количество пакетов, разрешенных в очереди, когда определенный интерфейс получает пакеты быстрее, чем ядро может их обработать. Значение по умолчанию для этого параметра — 300.
• optmem_max — максимальный размер вспомогательного буфера, разрешенный для каждого сокета.
• rmem_default — размер буфера приема сокета по умолчанию в байтах.
• rmem_max — максимальный размер буфера приема сокета в байтах.
• wmem_default — размер буфера передачи сокета по умолчанию в байтах.
• wmem_max — максимальный размер буфера передачи сокета в байтах.
Первые два параметра требуются для того, чтобы предотвратить заполнение примонтированной файловой системы журналами.
Каталоги /proc/sys/net/ipv4/ и /proc/sys/net/ipv6/ содержат дополнительные сетевые настройки протоколов IPv4 и IPv6 соответственно.
Рассмотрим несколько файлов из каталога /proc/sys/net/ipv4/:
• icmp_destunreach_rate, icmp_echoreply_rate, icmp_paramrob_rate и icmp_timeexeed_rate — максимальная скорость отправки пакетов ICMP на хосты при определенных условиях. Значение 0 выключает задержку.
• icmp_echo_ignore_all и icmp_echo_ignore_broadcasts — ядро будет игнорировать пакеты ICMP ECHO, то есть ping-запросы от каждого хоста или только те, которые исходят с широковещательных и многоадресных адресов соответственно, если значение 1 параметра запрещает ядру отвечать на ping-запросы.
• ip_default_ttl — TTL, время жизни по умолчанию, ограничивающее количество промежуточных узлов, которые может пройти данный пакет, прежде чем достигнет пункта назначения или будет отброшен. Увеличение этого значения может снизить производительность системы.
• ip_forward — разрешить интерфейсам в системе пересылать пакеты друг другу. По умолчанию для этого файла установлено значение 0. Запись в этот файл значения 1 включает пересылку.
• ip_local_port_range — диапазон портов, которые будут использоваться TCP или UDP для эфемерных портов. Первое число указывает младший порт, а второе — старший порт. Любые системы, которым требуется больше портов, чем по умолчанию от 1024 до 4999, должны использовать диапазон от 32 768 до 61 000.
• tcp_fin_timeout — значение по умолчанию для опции TCP_LINGER2.
• tcp_retries1 — количество разрешенных повторных передач для ответа на входящее соединение. По умолчанию 3.
• tcp_retries2 — количество разрешенных повторных передач TCP-пакетов. По умолчанию 15.
• tcp_syn_retries — ограничение на количество повторных передач SYN пакета при установке соединения.
В каталогах /proc/sys/net/ipv4/ и /proc/sys/net/ipv6/ существует ряд других каталогов, каждый из которых охватывает отдельный аспект сетевого стека:
• conf/ позволяет настраивать каждый системный интерфейс отдельно:
• conf/default/ — настройки по умолчанию для несконфигурированных устройств.
• conf/all/ — настройки, переопределяющие все специальные конфигурации.
• neigh/ — настройки для связи с хостами, напрямую подключенными к системе, то есть соседями по сети. Также содержит различные настройки для систем, удаленных не более чем на один переход.
• route/ — настройки, отвечающие за маршрутизацию. В отличие от conf/ и neigh/ данный каталог применим к маршрутизации через любые интерфейсы в системе. Многие из этих параметров, например max_size, max_delay и min_delay, относятся к управлению размером кэша маршрутизации.
Обратите внимание, что списки параметров конфигурации интерфейсов для IPv4 и IPv6 различаются:
/proc/sys/net/ipv4/conf/eno2 | /proc/sys/net/ipv6/conf/eno2 |
accept_local accept_redirects accept_source_route arp_accept arp_announce arp_filter arp_ignore arp_notify bc_forwarding bootp_relay disable_policy disable_xfrm drop_gratuitous_arp drop_unicast_in_l2_multicast force_igmp_version forwarding igmpv2_unsolicited_report_interval igmpv3_unsolicited_report_interval ignore_routes_with_linkdown log_martians mc_forwarding medium_id | accept_dad accept_ra accept_ra_defrtr accept_ra_from_local accept_ra_min_hop_limit accept_ra_mtu accept_ra_pinfo accept_ra_rt_info_max_plen accept_ra_rt_info_min_plen accept_ra_rtr_pref accept_redirects accept_source_route addr_gen_mode autoconf dad_transmits disable_ipv6 disable_policy drop_unicast_in_l2_multicast drop_unsolicited_na enhanced_dad force_mld_version force_tllao |
promote_secondaries proxy_arp proxy_arp_pvlan route_localnet rp_filter secure_redirects send_redirects shared_media src_valid_mark tag | forwarding hop_limit ignore_routes_with_linkdown ioam6_enabled ioam6_id ioam6_id_wide keep_addr_on_down max_addresses max_desync_factor mc_forwarding mldv1_unsolicited_report_interval mldv2_unsolicited_report_interval mtu ndisc_notify ndisc_tclass optimistic_dad proxy_ndp ra_defrtr_metric regen_max_retry router_probe_interval router_solicitation_delay router_solicitation_interval router_solicitation_max_interval router_solicitations rpl_seg_enabled seg6_enabled seg6_require_hmac stable_secret suppress_frag_ndisc temp_prefered_lft temp_valid_lft use_oif_addrs_only use_optimistic use_tempaddr |
Например, разрешение на пересылку дейтаграмм через параметр forwarding на конкретном адаптере можно настроить отдельно для IPv4 и IPv6.
Для IPv6 также существует каталог /proc/sys/net/ipv6/icmp, в котором хранятся настройки ICMP6-протокола. Для IPv4 они разбросаны по файлам в каталоге /proc/sys/net/ipv4.
Для сокетов типа AF_UNIX настройки хранятся в нескольких каталогах:
• /proc/sys/net/unix/ — общие настройки сокета, например максимальный размер дейтаграммы, max_dgram_len.
• /proc/sys/fs — в этом каталоге настройки каналов начинаются с префикса pipe-. Например /proc/sys/fs/pipe-max-size — предел, до которого непривилегированный процесс может подстроить емкость буфера канала.
Дополнительную информацию о каталогах и значениях конфигурационных файлов в ProcFS возможно найти в man 7 ip, man 5 proc, man 5 procfs и в документации по ядру: filesystems/proc.txt, networking/ip-sysctl.txt.
SysFS — псевдофайловая система, которая обеспечивает получение системной информации и унифицированный способ доступа к параметрам оборудования, файловых систем, модулей, атрибутам драйверов и настройкам ядра из пользовательского пространства.
Также позволяет дистрибутивам Linux управлять событиями подключения устройств через соответствующие демоны, такие как udev.
Структура драйверов в ядре автоматически создает каталоги в SysFS, показанные на рис. 13.2, при регистрации драйверов, на основе типа драйвера и значений в их структурах данных.
Рис. 13.2. Схема каталогов и файловой системы SysFS и связанных структур
Все драйверы определенного типа будут иметь одни и те же элементы, доступные через SysFS.
Информация об инициализированных устройствах сгруппирована по разным критериям: типам устройств, шинам, протоколам и т.п.
В разных каталогах существуют символические ссылки на различные устройства.
SysFS монтируется в каталог /sys:
sys on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
Данная ФС была реализована с целью выделения параметров устройств из ProcFS, которая исторически предназначалась для получения доступа к дереву процессов и должна выполнять именно эту задачу. Но когда в Linux начали добавлять в ProcFS параметры ядра, никакого стандарта еще не существовало. Поэтому /proc вскоре превратился в свалку атрибутов устройств, распределенным по разным подкаталогам.
Начиная с ядра Linux версии 2.6, параметры ядра, связанные с драйверами устройств и файловыми системами, были вынесены в каталог /sys.
Таким образом, SysFS — это попытка разработчиков ядра придать структуру настройкам. В идеале вся информация, не относящаяся к процессам, должна быть вынесена из /proc в /sys, но для совместимости в /proc сохранилось много разных настроек. Поэтому нередко есть два способа внести изменения в работающее ядро:
• Старый способ — через /proc.
• Новый способ — через /sys, который предпочтительно использовать в новом коде.
Подытожим отличия между двумя ФС:
• SysFS содержит более подробную информацию об устройствах. Положение узлов представляет иерархию устройств по подсистемам. Для каждого объекта в модели драйвера создается каталог.
• SysFS более строго организована, чем /proc, так как начиналась с более строгого дизайна, и ей не требуется поддерживать устаревшую функциональность, как это делается в ProcFS. Каждый файл в SysFS содержит значение одного атрибута.
• ProcFS допускает произвольные файловые операции, SysFS более ограниченна. Записи ProcFS получают структуру file_operations, которая содержит указатели на функции, запускаемые файловыми системными вызовами. Например: open(), read(), mmap() и т.д., и с ними можно выполнять произвольные действия. В SysFS для работы с атрибутами используются методы show() и store().
Поверх иерархии SysFS монтируется несколько других файловых систем, которые будут кратко описаны ниже.
Содержит список подкаталогов, каждый из которых представляет тип физической шины, поддерживаемой ядром: USB, PCI, SCSI и т.д.
Каждый тип шины содержит подкаталоги devices и drivers:
• devices — список устройств, обнаруженных в настоящее время или привязанных к данному типу шины. Каждый файл в каталоге является символической ссылкой на файл устройства в подкаталогах /sys/devices.
• drivers — содержит каталоги, описывающие драйверы устройства, зарегистрированные в диспетчере шины. Его файлы — атрибуты, которые показывают текущую конфигурацию драйвера, параметры, которые можно изменить, и символические ссылки, указывающие на каталог физического устройства, к которому привязан драйвер.
Содержит глобальную иерархию устройств с информацией для каждого физического и виртуального устройства, которое обнаружено ядром.
Основной каталог, на который ссылаются подкаталоги в каталогах классификаторов:
• /sys/block — драйверы блочных устройств. Символические ссылки в этом каталоге — имена устройств, например dm-0, loop1, nvme0n1 и т.д.
• /sys/class — классификация по типу устройства, зарегистрированному в ядре. Класс устройства описывает функциональный тип устройства. Каждый каталог класса содержит подкаталоги символических ссылок на зарегистрированные в этом классе устройства.
• /sys/dev — классификация по типу и значениям major, minor устройства. Внутри содержит каталоги block и char, в которых устройства представлены как символические ссылки вида major:minor.
Посмотрим на каталог /sys/class/net/, в котором представлена конфигурация и статистика различных сетевых устройств:
➭ ls -l /sys/class/net
docker0 -> ../../devices/virtual/net/docker0
eno2 -> ../../devices/pci0000:00/0000:00:1f.6/net/eno2
lo -> ../../devices/virtual/net/lo
tun0 -> ../../devices/virtual/net/tun0
wlo1 -> ../../devices/pci0000:00/0000:00:14.3/net/wlo1
Подкаталоги — устройства, а файлы подкаталогов — их параметры:
➭ ls -l /sys/class/net/eno2/
-r--r--r-- 1 root root addr_assign_type
-r--r--r-- 1 root root address
-r--r--r-- 1 root root addr_len
-r--r--r-- 1 root root broadcast
-rw-r--r-- 1 root root carrier
-r--r--r-- 1 root root carrier_changes
-r--r--r-- 1 root root carrier_down_count
-r--r--r-- 1 root root carrier_up_count
lrwxrwxrwx 1 root root device -> ../../../0000:00:1f.6
-r--r--r-- 1 root root dev_id
-r--r--r-- 1 root root dev_port
-r--r--r-- 1 root root dormant
-r--r--r-- 1 root root duplex
-rw-r--r-- 1 root root flags
-rw-r--r-- 1 root root gro_flush_timeout
-rw-r--r-- 1 root root ifalias
-r--r--r-- 1 root root ifindex
-r--r--r-- 1 root root iflink
-r--r--r-- 1 root root link_mode
-rw-r--r-- 1 root root mtu
-r--r--r-- 1 root root name_assign_type
-rw-r--r-- 1 root root napi_defer_hard_irqs
-rw-r--r-- 1 root root netdev_group
-r--r--r-- 1 root root operstate
-r--r--r-- 1 root root phys_port_id
-r--r--r-- 1 root root phys_port_name
-r--r--r-- 1 root root phys_switch_id
drwxr-xr-x 2 root root power
-rw-r--r-- 1 root root proto_down
drwxr-xr-x 4 root root queues
-r--r--r-- 1 root root speed
drwxr-xr-x 2 root root statistics
lrwxrwxrwx 1 root root subsystem -> ../../../../../class/net
-r--r--r-- 1 root root testing
-rw-r--r-- 1 root root threaded
-rw-r--r-- 1 root root tx_queue_len
-r--r--r-- 1 root root type
-rw-r--r-- 1 root root uevent
➭ cat /sys/class/net/eno2/mtu
1500
Видно, например, что, используя SysFS, можно получить и установить MTU сетевого адаптера.
Эту же настройку возможно посмотреть и через ProcFS, используя каталог /proc/sys/net/ipv6/conf/eno2/mtu. Но установить ее через ProcFS нельзя, только через SysFS.
Каталог прошивки содержит интерфейсы для просмотра и управления прошивкой для конкретной платформы. В /sys/firmware/efi, например, содержатся конфигурационные данные UEFI.
Содержит подкаталоги, именованные по типам используемых в работающей системе файловых систем, внутри которых обычно содержатся каталоги с именами разделов, где используются эти ФС.
Каталоги содержат настройки для каждой из файловых систем, а также общие настройки. Там же содержатся каталоги для FUSE, контрольных групп BPF и т.д.
Подкаталоги, которые представляют каждый загруженный модуль ядра. Каждый из каталогов модуля содержит общую информацию о модуле, такую как количество ссылок для выгружаемых модулей, параметры модуля, переданные ему при загрузке, параметры, определяемые модулем, и прочее.
Файлы и подкаталоги с информацией о работающем ядре: подсистема управления памятью, контрольные группы и т.п.
Содержит точки монтирования файловых систем TraceFS и DebugFS.
Управление питанием всей системы и устройств.
DebugFS — специальная файловая система, разработанная для целей отладки. Она дает возможность сделать информацию ядра доступной в пользовательском пространстве.
В отличие от ProcFS, которая предназначена для получения информации о процессах, или SysFS, которая организована по строгим правилам с одним значением для каждого файла, для DebugFS правила организации отсутствуют. Разработчики драйвера могут поместить туда любую информацию, которую захотят.
Обычно монтируется в /sys/kernel/debug:
debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime)
Из данных, содержащихся в этой ФС, прикладной разработчик может получить массу информации о драйверах, которую не показывают другие средства. Например, данные от драйвера Wi-Fi-адаптера:
# ls -1 /sys/kernel/debug/ieee80211/phy0/
aql_enable
aql_threshold
aql_txq_limit
aqm
force_tx_status
fragmentation_threshold
ht40allow_map
hw_conf
hwflags
iwlwifi -> ../../iwlwifi/0000:00:14.3
keys
long_retry_limit
misc
netdev:p2p-dev-wlo1
netdev:wlo1
power
queues
rate_ctrl_alg
reset
rts_threshold
short_retry_limit
statistics
total_ps_buffered
user_power
wep_iv
Можно получить практически любые сведения, включая ключи или данные подключенной точки доступа. Например, мощность передатчика Wi-Fi или аппаратные флаги, предоставляемые адаптером, если драйвер зарегистрировал все эти параметры:
# cat /sys/kernel/debug/ieee80211/phy0/netdev\:wlo1/txpower
22
# cat /sys/kernel/debug/ieee80211/phy0/hwflags
SIGNAL_DBM
SPECTRUM_MGMT
AMPDU_AGGREGATION
SUPPORTS_PS
SUPPORTS_DYNAMIC_PS
MFP_CAPABLE
WANT_MONITOR_VIF
SUPPORT_FAST_XMIT
REPORTS_TX_ACK_STATUS
CONNECTION_MONITOR
AP_LINK_PS
TIMING_BEACON_ONLY
CHANCTX_STA_CSA
SUPPORTS_CLONED_SKBS
SINGLE_SCAN_ON_ALL_BANDS
TDLS_WIDER_BW
SUPPORTS_AMSDU_IN_AMPDU
NEEDS_UNIQUE_STA_ADDR
SUPPORTS_REORDERING_BUFFER
USES_RSS
TX_AMSDU
TX_FRAG_LIST
DEAUTH_NEED_MGD_TX_PREP
BUFF_MMPDU_TXQ
SUPPORTS_VHT_EXT_NSS_BW
STA_MMPDU_TXQ
Таким образом, ядро может выводить через DebugFS «закрытые» сведения или даже управлять некоторыми внутренними настройками. Поэтому доступ к DebugFS может получить только администратор.
Вот пример принудительного опустошения буфера передачи устройства:
# echo 0 > /sys/kernel/debug/iwlwifi/0000\:00\:14.3/iwlmvm/tx_flush
Через DebugFS можно осуществлять прямое взаимодействие с устройством, но для каждого драйвера имеется свой набор параметров, который не стандартизован.
TraceFS предназначена для упрощения доступа из пространства пользователя к данным трассировки ядра Linux.
В большинстве дистрибутивов ФС монтируется в каталог /sys/kernel/tracing, но может быть смонтирована в /sys/kernel/debug/tracing, причем одновременно:
tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)
tracefs on /sys/kernel/debug/tracing type tracefs (rw,...,relatime)
Это одна и та же ФС, просто смонтирована два раза в разные точки.
Она применяется, когда DebugFS не подходит из соображений безопасности. TraceFS позволяет администратору использовать только интерфейс трассировки, без открытия доступа к другим возможностям DebugFS, а также обеспечить поддержку создания буферов трассировки через системные вызовы mkdir и rmdir, что DebugFS не поддерживает.
TraceFS сохраняет обратную совместимость со старыми приложениями, эмулируя поведение DebugFS.
BPFFS — псевдофайловая система, которая позволяет создавать файлы, ссылающиеся на объекты Berkley Packet Filter.
Существует два варианта BPF:
• cBPF, или «классический BPF» — пакетный фильтр, использующий язык фильтрации пакетов, как в tcpdump.
• eBPF — виртуальная машина, которая может исполнять скрипты в пространстве ядра ОС, то есть это не пакетный фильтр.
В ядре Linux реализован именно eBPF. Он совместим с cBPF и транслирует его байт-код в представление eBPF прямо в ядре. Программы eBPF либо интерпретируются, либо JIT-компилируются.
Наличие такой виртуальной машины позволяет расширять возможности ядра без загрузки модулей, перезагрузки и тому подобных операций.
Встроенный верификатор выполняет статический анализ кода и отклоняет сбойные и плохо влияющие на работу ядра программы. Например, будут отклонены программы с циклами for/while без условий выхода или программы, разыменовывающие указатели без проверки.
Код BPF запускается, как правило, в случае наступления определенного события и выполняется до своего завершения, что похоже на обработчик прерывания.
eBPF используется в обработке сетевого трафика, при трассировке и для выполнения функций безопасности.
Код eBPF программ может использовать некоторые функции ядра для выполнения некоторых системных вызовов и получения доступа к структурам ядра.
Файловая система BPF обычно монтируется в /sys/fs/bpf:
bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)
Объекты BPF создаются пользователем при помощи команд системного вызова bpf:
• BPF_PROG_LOAD — проверить и загрузить BPF-программу.
• BPF_MAP_CREATE — создать BPF-карту. Карта — это структурированная область памяти, которую программы BPF могут использовать совместно.
Вызов делает программа-загрузчик, показанная на рис. 13.3. Верификатор — статический анализатор, работающий в пространстве ядра, проверяет, корректна ли загружаемая программа и не может ли она нарушить работу системы.
Рис. 13.3. Файловая система BPF
Каждый файловый объект, доступный в пространстве пользователя, увеличивает счетчик ссылок объекта BPF в ядре.
Поэтому когда файлы для взаимодействия созданы, программа-загрузчик может завершить работу, а созданные ею объекты продолжат существовать.
Например, если пользователю требуется собирать статистику с BPF длительное время, он может не держать постоянно запущенный демон, а создать специальный файл в BPFFS и читать его время от времени.
Рассматривать BPF в данной книге мы не будем по соображениям объема, хотя eBPF — очень успешная разработка в Linux и в разном виде она была перенесена в другие операционные системы, например ОС Windows, где похожий инструмент называется WFP.
ConfigFS была добавлена в ядро Linux в 2005 году, чтобы обеспечить гибкую настройку модулей пользователем без введения новых ioctl() и системных вызовов. Монтируется в /sys/kernel/config. Предназначается для создания и уничтожения объектов ядра из пространства пользователя. По сути, эта ФС в чем-то обратна SysFS, которая используется для просмотра объектов и управления ими. SysFS нежелательно использовать для создания объектов, ибо ее архитектура этого не предполагала.
Объект ядра создается через вызов mkdir и будет создан, если какой-либо модуль ядра поддерживает создание такого объекта. Удаляется объект через rmdir. Созданные «каталоги» содержат файлы-атрибуты, в которые отображается состояние модуля.
Обычно данная ФС пустая. Ее использует небольшое число модулей, например модуль netconsole.
Если посмотреть на список всех смонтированных файловых систем, он будет несколько больше показанного командой mount:
➭ cat /proc/filesystems
nodev sysfs
nodev tmpfs
nodev bdev
nodev proc
nodev cgroup
nodev cgroup2
nodev cpuset
nodev devtmpfs
nodev binfmt_misc
nodev configfs
nodev debugfs
nodev tracefs
nodev securityfs
nodev sockfs
nodev bpf
nodev pipefs
nodev ramfs
nodev hugetlbfs
nodev devpts
nodev autofs
nodev efivarfs
nodev mqueue
nodev binder
nodev pstore
ext3
ext2
ext4
fuseblk
nodev fuse
nodev fusectl
vfat
squashfs
nodev overlay
В списке файловых систем можно заметить такие, как bdev, cpuset, securityfs, sockfs, pipefs, mqueue, binder, pstore и подобные. Это служебные файловые системы.
Например, cpuset нужна для того, чтобы связывать наборы процессоров с задачами, mqueue — для поддержки очередей POSIX, и т.д. Мы не будем описывать их все и коснемся только SockFS.
Для записи данных в сокеты Unix-подобных ОС можно использовать системный вызов write()и системный вызов read() — для их чтения. В Linux для этого используется подсистема VFS — виртуальная файловая система. Это модульная инфраструктура, позволяющая регистрировать модули обработки различных файловых систем.
В коде ядра, в файле net/socket.c, содержится функция sock_init(), которая вызывается при инициализации ОС. В функции регистрируется инфраструктура SysFS для управления сокетами, а затем регистрируется и монтируется специальная файловая система SockFS, описанная структурой sock_fs_type:
static struct file_system_type sock_fs_type =
{
.name = "sockfs",
.init_fs_context = sockfs_init_fs_context,
.kill_sb = kill_anon_super,
};
Причем монтируется она без явной точки монтирования.
Системный вызов __sys_socket, рассмотренный в главе 7, выполняет еще несколько операций, которые мы не описывали, что в итоге приводит к сопоставлению с новым сокетом открытого файла, хранящегося в памяти на SockFS.
Вызов делает следующее:
1. Приводит к вызову функции __sock_create(), в которой вызывается функция sock_alloc(), создающая inode сокета в SockFS через вызов alloc_inode().
2. Вызывает функцию sock_map_fd(), которая выполняет следующие действия:
a) вызывает alloc_file_pseudo() для создания нового файла и привязки его к inode. По сути, это вызов alloc_file(). В качестве обработчиков файловых операций передается структура socket_file_ops, которая перенаправляет файловые вызовы на вызовы сокета;
б) вызывает для этого файла stream_open(), что позволяет работать с ним как с открытым файловым потоком;
в) вызывает fd_install(), чтобы установить в таблице дескрипторов указатель на файл.
В итоге, например, запись через write() в дескриптор сокета приводит к записи в псевдофайл и вызову __sock_sendmsg(), который является обработчиком записи.
Таким образом, эта файловая система является неким «слоем совместимости», обеспечивающим возможность работы с сокетами через файловый API. Примерно для тех же задач используется PipeFS, но уже для работы с каналами с помощью файлового API.
SockFS можно даже монтировать:
➭ mkdir sfs
➭ sudo mount -t sockfs sockfs sfs
➭ ls sfs
ls: невозможно открыть 'sfs': это не каталог
➭ sudo umount sfs
Но она не поддерживает листинг, и польза от ее монтирования будет невелика.
Использование служебной файловой системы для поддержки файловых вызовов на дескрипторе, который файлом не является, не отличительная особенность Linux. Даже в ОС Windows есть похожее средство — файловая система IFS, которая позволяет использовать подмножество файлового API для чтения из сокетов и записи в них.
Каталог /run содержит настройки работающих приложений и некоторую информацию о работающей системе с момента последней загрузки, например файл utmp с зарегистрированными в системе пользователями. Также в этом каталоге хранит свои настройки демон udev. Настройки ядра ОС данная ФС не содержит.
Обычно в этот каталог монтируется TmpFS, хранящая в оперативной памяти информацию, требуемую на раннем этапе загрузки:
run on /run type tmpfs (rw,nosuid,nodev,relatime,mode=755,inode64)
Каталог /run доступен на запись в процессе загрузки, и его используют системные приложения.
В /run не всегда монтируется tmpfs. Если данный каталог создан в ФС, расположенной в постоянном хранилище, например на диске, файлы в этом каталоге должны быть либо удалены, либо усечены системой в начале процесса загрузки.
ФС будет очищена в случае перезагрузки, но если этого не происходит, файлы в /run не удаляются автоматически при остановке процесса, поэтому сам процесс должен удалить все им созданное, то есть выполнить надлежащую очистку.
Каталог /var/run является символической ссылкой на /run и существует исторически.
На данный момент /var/run считается неправильным расположением и сохраняется для поддержки устаревшего программного обеспечения.
Правильный каталог — /run.
Каталог /tmp доступен на запись всем пользователям. В этом каталоге может сохранять файлы любое приложение.
В случае перезагрузки /tmp будет очищен; кроме того, периодически система может удалять в нем неиспользуемые файлы.
В большинстве современных ОС TmpFS монтируется в /tmp, как и в /run:
tmpfs on /tmp type tmpfs
(rw,nosuid,nodev,size=8100948k,nr_inodes=1048576,inode64)
Внимание! Поскольку /run и /tmp обычно хранятся в памяти, в них желательно не записывать большие файлы, так как память может переполниться. И если для /tmp обычно устанавливается ограничение на максимальный размер, то для /run — нет, то есть он будет расти до исчерпания всей свободной памяти. После этого часть ФС будет сброшена в раздел подкачки, если он подключен, что снизит производительность работы системы в целом.
Если вы пишете системное приложение, храните временные данные в /run/<каталог_приложения>, в ином случае пользуйтесь для хранения /tmp. Всегда при выходе из процесса удаляйте из него все созданные им файлы.
Функцию sysctl() и системный вызов можно встретить в старых версиях Linux. В ядре Linux 5.5 системный вызов sysctl был удален. Поддержка GLibC была удалена в версии 2.32.
sysctl() отвечает за чтение и запись параметров ядра:
#include <unistd.h>
#include <linux/sysctl.h>
int _sysctl(__sysctl_args *args);
Сейчас вместо него рекомендуется использовать /proc/sys. Если вы раньше применяли sysctl(), это тот случай, когда для новых версий ОС придется использовать специальные ФС.
Функция sysconf() нужна для получения параметров системы в процессе выполнения:
#include <unistd.h>
long sysconf(int name);
Параметр name — имя получаемой опции.
Функция позволяет вернуть такие параметры, как максимальное количество файлов, которое может открыть процесс, размер страницы памяти, максимальное количество символов в имени системного пользователя и т.п. Нас же она интересует прежде всего потому, что дает возможность получить максимальное количество буферов, которое может быть использовано такими функциями, как sendmsg(), то есть количество элементов массива msg_ioc в структуре msghdr. Для этого используется опция _SC_IOV_MAX, и в общем случае получение максимального количества буферов выглядит так:
extern "C"
{
// Определить макрос, чтобы включить объявление данной функции.
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE
#endif
#include <limits.h>
#include <unistd.h>
}
int main()
{
// Получить значение параметра.
auto buf_count = sysconf(_SC_IOV_MAX);
if (-1 == buf_count)
{
// Не удалось получить через sysconf().
perror("sysconf");
#if defined(IOV_MAX)
// Установим константу.
buf_count = IOV_MAX;
std::cout
<< "Buffers count got via IOV_MAX\n";
#endif
}
else
{
std::cout
<< "Buffers count got via sysconf()\n";
}
#if defined(IOV_MAX)
std::cout
<< "IOV_MAX = " << IOV_MAX << "\n";
#endif
std::cout
<< "Buffers count = "
<< buf_count << std::endl;
return EXIT_SUCCESS;
}
По умолчанию в Linux выделяется 1024 буфера:
➭ build/bin/b01-ch12-sysconf
Buffers count got via sysconf()
IOV_MAX = 1024
Buffers count = 1024
Функция sysconf() предоставляет еще одну полезную возможность — получение размера страницы, что используется, например, в libmnl для установки размера буфера:
#define MNL_SOCKET_BUFFER_SIZE \
(sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L)
Изменять параметры конфигурации ОС можно, используя специальные файловые системы, запись и чтение файлов которых вызывает обращение к ядру и драйверам. Файлы подобных систем не содержатся на физических носителях, и поэтому их еще называют виртуальными или псевдофайловыми.
Обычно систему через них настраивают системные администраторы. Не рекомендуется вносить такие изменения в «обычных» приложениях, не предназначенных для настройки ОС.
Существует несколько типов виртуальных файловых систем:
• DevFS — содержит псевдофайлы устройств, что дает процессам в пользовательском пространстве доступ к устройствам и некоторым параметрам работающего ядра ОС. Все POSIX-совместимые ОС содержат каталог /dev, в который монтируется данная файловая система. Почти все устройства, которые подключены к системе и опознаны соответствующим драйвером, представлены в DevFS.
• ProcFS — используется для представления информации о работающих процессах и параметрах структур данных ядра через файлы. Обычно монтируется в каталог /proc.
• SysFS и ConfigFS — обеспечивают получение системной информации и единый унифицированный способ доступа к параметрам оборудования, файловых систем, модулей, атрибутам драйверов и настройкам ядра из пользовательского пространства. SysFS монтируется в каталог /sys. Данные ФС были реализованы с целью выделения параметров устройств из ProcFS. Есть два способа внести изменения в работающее ядро: старый способ — через /proc — и новый способ — через /sys или через /sys/kernel/config. Новый способ предпочтительнее.
• DebugFS и TraceFS — используются для целей отладки. Они позволяют разработчикам ядра сделать его информацию доступной в пользовательском пространстве. Обычно монтируется в /sys/kernel/debug.
• BPFFS — позволяет взаимодействовать с Berkley Packet Filter. Файловая система BPF обычно монтируется в /sys/fs/bpf.
Все эти специальные файловые системы играют важную роль в операционных системах Linux, предоставляя доступ к различным системным ресурсам и информации. Они упрощают работу с устройствами, мониторинг процессов и состояния системы, отладку ядра и программ, а также настройку и трассировку системы. Это делает их неотъемлемой частью разработки и администрирования Linux-систем.
Помимо них, приложению доступны каталоги, в которых оно может сохранять данные, необходимые для работы: в /tmp хранятся временные данные, удаляемые между перезагрузками, а в /var более постоянные данные, такие как, например, кэши.
Чтобы не обращаться из приложения к SysFS напрямую, в Linux использовалась функция sysctl(), но в более новых версиях ОС эта функция удалена. На эту функцию чем-то похожа функция sysconf(), возвращающая некоторые параметры системы. В частности, вызвав ее, можно получить максимальное количество буферов, которое способна использовать функция sendmsg(). Эта функция обращается к кэшированным значениям, а не к операционной системе напрямую через системный вызов. Это делает ее довольно быстрой и удобной.
1. Что подразумевается под фразой «Все есть файл» в контексте Unix?
2. Почему в сетевых приложениях на C++ редко используется способ взаимодействия через специальные файлы?
3. Может ли веб-сервер заниматься настройкой системы и почему?
4. Что хранится в DevFS?
5. Почему сетевые устройства обычно не представлены в DevFS?
6. Какие файлы находятся в /proc/sys/?
7. Что может произойти в случае неправильной записи в файлы /proc? А в файлы /dev?
8. Где в /proc находятся сетевые настройки?
9. В каких случаях приложение может заниматься настройкой сетевого стека операционной системы?
10. Различаются ли параметры конфигурации одного и того же интерфейса для IPv4 и IPv6? Почему?
11. Для чего используется SysFS в Linux?
12. В чем разница между SysFS и ProcFS?
13. Где в /sys находятся настройки сетевых адаптеров?
14. Для чего нужны DebugFS и TraceFS? В чем их отличие?
15. Что такое BPFFS? Чем BPFFS может быть полезна разработчику сетевых приложений?
16. Для чего используется ConfigFS?
17. Для чего нужна SockFS?
18. Какие другие специальные файловые системы поддерживаются в Linux кроме перечисленных выше?
19. Какую информацию приложение может хранить в каталоге /tmp?
20. Какую информацию приложение может хранить в каталоге /var? Где именно в его иерархии?
21. Зачем нужна функция sysctl()?
22. Чем может быть полезна функция sysconf()?
23. Напишите программу, которой в качестве ключа сообщается название интерфейса и она только с помощью обращения к /proc получает и выводит MTU этого интерфейса.