Содержание статьи
Введение
Для начала немного статистики. За 2014 год:
- Вышло шесть мажорных версий ядра (с 3.13 по 3.18).
- В общей сложности было выпущено 74 691 патчсет (в среднем по 12 449 на каждую мажорную версию).
- В среднем в ядро за 2014 год внесли свой вклад 1495 людей.
- Первые места по количеству изменений стабильно занимают Intel и Red Hat.
Посмотрим, что же нового появилось за этот год.
Сетевая подсистема
Самым явным нововведением в сетевой подсистеме выглядит, пожалуй, приходящий на замену iptables nftables. Дело в том, что iptables слишком линеен, и это ограничивает его гибкость. Разумеется, существуют обертки вокруг него (такие, например, как не раз и не два упоминавшийся мной Shorewall), но они выполняются в пользовательском пространстве, что при наличии десятков тысяч подключений создает немалый overhead.
Nftables представляет собой нечто аналогичное BPF: утилита nft компилирует правила в байт‑код и передает его в ядро. Это позволяет не только значительно улучшить гибкость, но и уменьшить количество кода ядра. Так, отпадает надобность в реализации расширений режима ядра для поддержки всякого вновь появляющегося протокола. В качестве же базовых «кирпичиков» выступают элементы старого доброго Netfilter, такие как хуки.
Синтаксис утилиты nft, однако, отличается от синтаксиса старых утилит и представляет собой иерархический язык, более подходящий для описания правил, нежели линейный стиль iptables. Парсер для этого языка сгенерирован с помощью BISON.
Сам же BPF (встроенный в ядро) обзавелся новым JIT-компилятором, минимальным отладчиком и теперь делится на два набора инструкций: «классический» и «внутренний». Последний работает быстрее за счет того, что больше совместим с реальным машинным кодом. «Внутренняя» реализация BPF предназначена исключительно для работы в режиме ядра — код из «классической» транслируется в нее перед первым запуском. Есть также планы по использованию BPF в других подсистемах, не только в сетевой. В частности, seccomp-bpf уже его использует.
Усовершенствовали также и ipset: теперь он поддерживает network namespaces, что позволяет использовать его в LXC. Добавлена и поддержка комментариев и еще некоторые полезные мелочи.
Внутренности сетевой подсистемы тоже претерпели некоторые изменения. Так, были внесены изменения, позволяющие отправлять мелкие пакеты сразу группами (при соответствующей поддержке драйвера и сетевой карты); это позволяет разгрузить сетевой стек и даже на обычном компьютере достигнуть скорости передачи в 40 Гбит/с.
Помимо этого, был реализован механизм FOU — foo over UDP, позволяющий организовывать всяческие туннели поверх UDP. Это может понадобиться для некоторых специфических случаев; например, отдельные коммутаторы и сетевые карты предоставляют механизмы быстрой обработки пакетов только для UDP.
Был также реализован и протокол Geneve, позволяющий инкапсулировать в пакеты абстракции, необходимые для виртуализации сети, — для облачных нужд (маршрутизация сегментов, к примеру). Существовавшие до этого протоколы (VXLAN и NVGRE) оказались слишком заточены под конкретные требования.
Файловые системы и блочные устройства
В подсистеме блочных устройств и файловых систем тоже достаточно много изменений. В частности, был реализован новый механизм, который обеспечивает более быструю работу с SSD-накопителями. Это достигается путем двухуровневой модели очередей: очереди первого уровня распределяются по процессорам/ядрам, а очереди второго управляют обращениями к накопителям; таким образом, количество процессоров может теперь влиять на подсистему ввода/вывода достаточно сильно.
Исследования показали, что новый механизм теперь может обеспечить миллионы запросов ввода/вывода в секунду, — сравни со старым, пиковая производительность которого была всего лишь около 800 тысяч IO-запросов в секунду. Конечно, во времена жестких дисков производительности старого механизма хватало с лихвой, однако с появлением SSD-накопителей его недостатки стали очевидны.
В подсистеме bcache, которая использует SSD-накопители в качестве кеша к обычным HDD, переписан сборщик мусора — теперь он инкрементальный. Это позволило свести к минимуму задержки при очистке кеша. В модуле же dm-cache, выполняющем практически те же функции, добавлен режим сквозного проброса, который применяется, когда неизвестно, насколько содержимое кеша актуально. В данном режиме все операции чтения используют жесткий диск напрямую, а запись происходит на накопитель, заданный для кеширования.
В FUSE реализован writeback-кеш, что делает данную подсистему производительнее в ситуациях с интенсивной записью.
Ускорена работа с файловой системой SquashFS, используемой, как правило, в Live CD. Реализована, к примеру, распаковка в кеш страниц напрямую, что позволяет избежать лишних накладных расходов и существенно увеличивает скорость — некоторые тесты показывают чуть ли не шестикратное увеличение.
В BTRFS наконец появилась возможность подмонтировать отдельные подтома как в режиме RO-, так и RW. Кроме того, добавлена опция --commit, позволяющая указывать интервал коммитов на диск и некоторые другие полезности.
XFS теперь поддерживает опцию O_TMPFILE, введенную в системный вызов open() в сентябре 2013-го и служащую для создания безымянных временных файлов, что позволяет снизить риск возникновения соответствующих уязвимостей. Во‑вторых, был добавлен B+ tree индекс для свободных инодов — с целью ускорения их выделения.
Появилась полноценная поддержка OverlayFS — «двухслойной» файловой системы, нижний слой которой обычно находится на read-only-носителях, а на верхний можно спокойно писать, не беспокоясь о том, что на самом деле это уже совсем другая файловая система. Подобные ФС полезны, например, в усовершенствованных прошивках для роутеров, таких как OpenWRT, где нижним слоем является та же SquashFS, а верхним — либо выделенная часть флеш‑памяти, либо же USB-накопитель. Отличие от аналогов — все файловые операции после открытия файла производятся напрямую с соответствующим «слоем», что, во‑первых, упрощает реализацию, а во‑вторых, увеличивает быстродействие.
В zRam, технологии, позволяющей создавать сжатые RAM-диски (что может быть крайне полезным для размещения на них свопа — поскольку память намного быстрее HDD, разница в скоростях между реальной памятью и сжатой областью подкачки практически неощутима, а при хорошем раскладе страницы памяти сжимаются в 2–3 раза) и ставшей стабильной, появилась возможность указывать лимит памяти. Кроме того, была добавлена поддержка алгоритма LZ4, обеспечивающего в некоторых случаях большую скорость и степень сжатия, нежели используемый до этого LZO.
А в /proc/partitions появилось наконец отображение RAM-дисков.
Виртуализация
В данной отрасли, пожалуй, самое значимое изменение — поддержка режима PVH в XEN. Данный режим является компромиссом между аппаратной и паравиртуализацией. Прослойка эмулируемого железа отсутствует как таковая, в то же время управление памятью и привилегированные инструкции выполняются в самом ядре гостевой ОС. Разделение происходит с помощью технологий процессора. Изменения также коснулись и той части сетевой подсистемы, которая относится к XEN, — теперь драйверы виртуальных сетевых интерфейсов поддерживают множественные очереди.
Кроме того, реализован проброс SCSI-устройств в паравиртуальное окружение, что позволяет совершать с данными устройствами специфические операции, которые недоступны при использовании API более высокого уровня, например перемотку ленты.
Появилась возможность использовать EFI в случае загрузки в качестве dom0. Поскольку те части памяти и прочие связанные вещи, которые относятся к EFI, находятся в распоряжении гипервизора, ядро стандартным образом доступ к ним получить не может. Соответственно, используются вызовы гипервизора — когда ядро в dom0 загружается и определяет, что система у нас EFI-совместимая, с помощью данных вызовов заполняется искусственная структура данных EFI.
Добавлено устройство KVM-VFIO, позволяющее гипервизору KVM обращаться к драйверам устройств, которые написаны с использованием фреймворка VFIO и работают, соответственно, в User mode. KVM теперь также поддерживает вложенную виртуализацию MPX — Memory Protection Extensions, технологии, используемой для защиты от атак на разыменование указателя, базирующуюся на новых особенностях процессоров Intel.
Безопасность
Много за прошедший год появилось и усовершенствований в плане безопасности. Прежде всего отметим, что контексты в SELinux теперь могут назначаться и для файлов в RAMFS. Также в политике SELinux теперь можно включать опцию always_check_network, которая всегда интерпретирует метки SELinux для пакетов включенными, даже если не задано ни одно правило Netfilter и не стоит ни одна фактическая метка.
Усовершенствованы функции, относящиеся к PRNG, в частности вместо taus88 в функции prandom32() используется алгоритм taus113, который обеспечивает период в 2113. Также вместо примешивания (с помощью XOR) результатов работы аппаратного генератора случайных чисел к итоговому буферу данные результаты добавляются в пул энтропии на ранних стадиях.
Добавлена технология KASLR — рандомизация адресного пространства ядра. По идее, это обеспечивает защиту ядра против атак на переполнение стека, однако на практике, по исследованиям некоторых хакеров ядра, защита достаточно легко обходится.
Ядро теперь поддерживает компиляцию с новой опцией GCC4.9 — -fstack-protector-strong. Базовая идея всех подобных методов заключается в том, чтобы положить в стек некоторую случайную величину сразу после того, как в нем окажется указатель на возвращаемое значение функции. Перед возвратом из функции эта величина проверяется, и, если она изменилась, код прекращает работу. Использование подобных проверок имеет один побочный эффект — они отнимают немалое время. Для пары функций это заметно не будет, но вот в случае с тысячами и десятками тысяч функций подобное поведение может крайне негативно сказаться на производительности.
Таким образом, перед разработчиками встает вопрос: какие именно функции нуждаются в подобной проверке? Один из наборов опций GCC как раз‑таки и определяет какие. Опция -fstack-protector-all защищает все функции, вне зависимости от возвращаемого значения. Эта опция самая параноидальная и самая медленная. Опция же -fstack-protector действует только на те функции, которые кладут в стек более чем восьмибайтовый массив char’ов. Последнее, конечно, покрывает большинство опасных мест. Большинство, но не все. Наконец, опция -fstack-protector-strong, в дополнение к защите, добавляемой предыдущей опцией, защищает все локальные массивы независимо от их типа — даже в случае, если они находятся в структурах или объединениях, — и также защищает еще несколько уязвимых мест. Данный вариант защиты покрывает примерно 20% функций ядра, что считается очень хорошим показателем — защита, добавляемая опцией -fstack-protector, покрывала всего 2%. Также в ядро включена защита стека и данных модулей еще на самых ранних стадиях их загрузки — аж до разбора параметров.
Механизм seccomp-bpf стал поддерживать JIT-компиляцию BPF-фильтров. Seccomp-bpf — механизм, позволяющий задавать ограничения на системные вызовы. Отличие от LSM заключается в том, что большинство реализаций последнего защищает приложения, навязывая им внешние ограничения. Seccomp-bpf же, хоть и позволяет поступать аналогично, больше подходит для ограничений, зашитых в код самого приложения. При его написании задается список разрешенных системных вызовов и аргументов для них и поведение при некорректном сисколле. Данная функциональность позволяет весьма гибко задавать правила и при должном применении может сильно затруднить жизнь атакующему. Иное дело, что ограничения все же лучше создавать не тем же самым людям, что пишут приложение, нуждающееся в защите.
Внесены изменения в SMACK — в частности, добавлен «разрешительный» режим, предназначенный для отладки правил. SMACK представляет собой еще одну систему принудительного контроля доступа, отличающуюся от аналогов простотой. Все, что можно конфигурировать, конфигурируется через псевдофайловую систему. Обычные текстовые правила записываются в определенные файлы и объектам проставляются метки. Поддерживаются ограничения как для файлов, так и для сетевых подключений.
Была также реализована поддержка NFC Secure Elements API, что, таким образом, при наличии usermode-инструментов обеспечит возможность совершения финансовых транзакций с использованием данного протокола.
Разное
В планировщике процессов появилась новая политика — SCHED_DEADLINE, которая обеспечивает алгоритм планирования EDF (Earliest Deadline First). Данный алгоритм реализует идею выбора той задачи из очереди ожидающих процессов, которая наиболее близка к дедлайну. Это может понадобиться в системах жесткого реального времени, когда процессу нужно гарантированное выполнение в любом случае, невзирая на общее количество процессов. Ранее, до существования данной политики, планировщик не мог гарантировать нужное время выполнения задачи в некотором интервале из‑за накладных расходов на переключение, связанных с общим количеством выполняющихся процессов.
Подсистема uprobes (которую, в частности, используют новейшие версии SystemTap) обзавелась поддержкой извлечения данных из стека и памяти процесса; также теперь она поддерживает обработку таких типов аргументов, как битовые поля и смещения в файлах.
На системах с UEFI появилась возможность загружать 64-разрядное ядро напрямую из 32-разрядного EFI (да‑да, некоторые EFI-прошивки запускаются в 32-разрядном режиме), что ранее было невозможно из‑за проблем с вызовом EFI-функций.
Добавлен Power Capping Framework, предоставляющий доступ через sysfs к унифицированному интерфейсу управления ограничениями питания. В нем поддерживаются так называемые зоны питания, представляющие различные части системы, которые могут управляться и мониториться с помощью методов ограничения питания. Каждая «зона питания» содержит набор атрибутов и механизмов управления, которые влияют на ограничения питания. «Зоны питания» могут быть организованы иерархически в соответствии с реальным положением дел в данной системе (например, диск — дисковая подсистема — материнская плата), что позволяет применять ограничения питания к набору устройств, а если необходима более точная настройка, то и к нужному устройству.
Ускорено возобновление работы после «засыпания» за счет иного алгоритма работы с жестким диском — ранее драйвер ATA-порта блокировал всю работу, пока жесткий диск не «проснется», что, таким образом, блокировало работу ядра. Сейчас же все команды, посылаемые драйверу, по заветам небезызвестного Шарикова становятся в очередь и выполняются асинхронно.
От sysfs отпочковалась kernfs — теперь sysfs является подмножеством последней. Также планируется на ее основе создавать новые псевдофайловые системы — такие как cgroupfs.
Появилась унифицированная иерархия cgroup. Раньше могло создаваться множество иерархий (одна для CPU, другая для blkio и так далее) и процесс мог находиться одновременно в нескольких. Это увеличивало гибкость, но приводило к излишним затратам ресурсов и создавало трудности при взаимодействии обработчиков различных иерархий.
Начата работа над сборкой ядра с помощью clang. Проблема заключается в том, что оно использует множество GCC-специфичных особенностей, в частности массивы переменной длины, которые, впрочем, уже заменены на эквивалент, созданный с помощью макроса SHASH_DESC_ON_STACK() и совместимый со стандартом C99.
Добавлена поддержка пятой версии GCC.
Было реализовано несколько новых системных вызовов:
- getrandom(), предоставляющий альтернативный метод генерации случайных чисел. Это может понадобиться в тех ситуациях, когда исчерпаны все файловые дескрипторы и открыть файлы устройств не получается. Раньше некоторые библиотеки в этой ситуации переключались на менее безопасный алгоритм генерации;
- renameat2(), позволяющий переименовать файлы один в другой. Это позволяет, например, произвести атомарную операцию переименования симлинков в дереве каталогов;
- bpf(), реализующий доступ к функциям eBPF — расширенного BPF, о котором писалось выше. Этот системный вызов крайне многогранен и объединяет множество операций, которые допустимо производить с использованием новой подсистемы; для упрощения работы используются функции‑обертки. Конечно, этот системный вызов достаточно опасен: загрузка в ядро непроверенного кода может повлечь за собой фатальные последствия. Именно поэтому большую часть кода данного патчсета занимает верификатор, который следит за внутренними регистрами и в том числе предотвращает чтение неинициализированных регистров. Сам же системный вызов требует капабилити CAP_SYS_ADMIN и запрещает выполнение кода, выпущенного не под GPL-лицензией;
- kexec_file_load(), предотвращающий загрузку неподписанных ядер, что необходимо для совместимости с UEFI Secure Boot;
- seccomp(), добавляющий (вместе с дополнительным патчсетом) возможность фильтровать системные вызовы не только у процессов, но также и у потоков — ранее это было невозможно.
Усовершенствован алгоритм определения размера рабочего набора, что необходимо для указания того, какие данные помещать в своп.
Наконец, предприняты шаги для решения проблемы 2038 года, связанной с переполнением 32-битного типа данных time_t — счетчика секунд от начала UNIX-эпохи.
kdbus
В ядре, возможно, скоро появится новый механизм IPC — kdbus. Он основан на тех же принципах, что и D-Bus, но имеет следующие преимущества:
- более высокая производительность за счет меньшего переключения контекстов;
- повышенная безопасность из‑за отсутствия влияния user-mode-процессов на содержимое системной шины и возможности использовать LSM для задания политик ограничения доступа;
- возможность использования данного механизма на ранних этапах загрузки.
Заключение
В ядро Linux добавляются все новые и новые возможности. С одной стороны, это свидетельствует о том, что над ним плотно работают. С другой же... объем кода ядра уже сейчас пугающе огромен. Да, большую часть функций можно отключить при сборке, да, ядро модульное, но, помилуйте, зачем в ядре интерпретатор байт‑кода или сборщик мусора в блочной подсистеме? Код, работающий в привилегированном режиме (который, напомню, в современных системах имеет доступ практически ко всему железу и памяти), в идеале должен выполнять исключительно задачи планирования процессорного времени и распределения памяти. Код ядра Linux, хоть и работает в привилегированном режиме, этим условиям даже с большой натяжкой не удовлетворяет.
Возникает закономерный вопрос: к чему все это приведет? Пока что видно лишь три пути:
- Разработчики упрутся в какую‑либо фундаментальную проблему, связанную с архитектурой ядра.
- Разработчики возьмутся за ум и перепишут ядро целиком с учетом вышесказанного.
- Вместо аппаратного разделения процессов будет использоваться программное. На ум сразу приходят Singularity от Microsoft Research и JNode — ОС на Java.
Третий вариант пока что выглядит наиболее реальным, даже несмотря на кучу проблем, которые его реализация может потянуть за собой. Посмотрим, что будет дальше.