Обновить

Комментарии 13

Ка кни странно дорого оони обходтся сугубо по прчиине борьбы со всякими аппартаными сайд-эффектами (meltdown и иже с ним). Сначала 20 лет оптимизировали переключение контекста и передачу данных, сделали всё супер-легковесным, а потом за три года всё похерили для борбы с утечакми данных.

Да, инструкция процессора syscall тяжелая, но эта единственная поддерживаемая на аппаратном уровне изоляция кода ядра от кода пользователя в режиме x86_64. Но тут возникает вопрос к разработчикам ядра Linux: А все ли в ядре нужно запихивать на высокий уровень привилегии?

Мне кажется, что на разработчиков Linux больше повлияло – не стать похожим на Windows, чем историческая преемственность от вызова прерывания int 80h.

Например, разработчики Windows с самого начала пошли по пути предоставления системных функций ядра в адресном пространстве пользователя, а в зависимости от необходимости функция самостоятельно решает переходить на высокий уровень привилегии или выполнять на уровне пользователя. Да, есть накладные расходы при создании каждого потока пользователя транслировать страницы кода и данных ядра в его адресном пространстве, но в дальнейшем это ускоряет работу пользовательского кода.

Разработчики Linux упростили себе работу и весь код ядра "засунули" на высокий уровень привилегии и каждый syscall производим переход в процесс ядра. Да, ускорили загрузку и создание процесса, но за то каждый вызов пользователем системного обработчика с помощью инструкции syscall – это тормоза…

Отсюда и "вечная" борьба разработчиков ядра Linux с копированием данных из разных адресных пространств (пользователя или ядра).

Отсюда, "любовь" к технологиям позволяющим работать на высоком уровне привилегии – eBPF, XDP, Kprobes и другие.

Теперь разработчики ядра Linux предоставили библиотеку vDSO, которая, как в Windows,  ядро автоматически отображает в адресное пространство всех процессов пользовательского пространства. Но тут разработчики ядра Linux посчитали унижением просто скопировать и сделать, как в Windows, и поэтому опять, как обычно, все сделали через …

Ну и немного на вентилятор... это обычная практика для разработчиков ядра Linux предоставить сначало что-то "генеальное" с их точки зрения, потом внедряют какие-то улучшения, которые вообще не совместимы с предыдущим, а потом через ...цать версий, бац... говорить все это было фигня, вот вам взамен всего COM-технология ... ну, это так к примеру

Расскажите зачем в 21 веке мы все ещё делим все по (мелким) файлам? Открытие файла требует системный вызов, вы компилируете проект с 10 тысячами файлов исходников? Оверхед файловой системы может превзойти время компиляции

Человеки - очень неприятные люди с маленьким контекстным окном. Приходится нарезать все помельче 🤷‍♂️

Не, ну некоторые технологии - это прям шаг вперед, например io_uring vs epoll для асинхронных приложений

Емнип, vDSO - это не библиотека, а механизм; виртуальная библиотека - linux_vdso.so.1, и оно действительно включает в себя функции, которым не нужно повышение привилегий. Syscall же всегда и в любой ОС считался "дорогой" операцией из-за переключения контекста и разработчики идут на множество ухищрений с указателями и хэндлами, zero-copy, общими буферами, io_uring, разделяемой памятью и т. д. и т. п. чтобы всячески избегать и копирования данных, и самого syscall.

Я что-то не понял насчёт "предоставления системных функций ядра в адресном пространстве пользователя". Поясните пожалуйста, разве процессы Windows не дёргают ntdll.dll, которое всё равно вызывает "ядерные" функции? Более того, вы вот ругаете линух, а их io_uring таки завезли в последних версиях винды, что подтверждает мои слова: разработчики любой ОС стремятся минимизировать кол-во сисколлов, и разработчики Linux, кажется, преуспели в этом плане. Если что сморозил не так - томатами не кидайте, я пока полный нуб в этом.

Ну, смотрите, например, возьмем функцию выделения памяти из кучи процесса. Что в Windows и в Linux выделение памяти выравнивается по 4-кбайтной границе – это связано с механизмом работы виртуальной памяти процессора и обойти его никак. Так вот в Windows, вызов функции HeapAlloc не всегда заканчивается обращением к ядру, например, если размер нового блока укладывается в размер границы ранее выделенной страницы; в свою очередь, функция вызывает ядро, если требуется добавить новые физические страницы. А что в Linux, вы вызывает sys_brk, то есть вы всегда проваливаетесь в ядро, даже если нужно сдвинуть указатель внутри уже выделенной страницы.

В Windows функции, представленные в динамических библиотеках NTxxx.DLL – это не системные вызовы, в понимании Linux, это функции низкого уровня, и бизнес-логика этих функции все равно выполняется на пользовательском уровне привилегии, и только конкретные обращения к драйверам устройств выполняются на уровне ядра.

В Linux вся бизнес-логика – полностью и без остатка засунута в ядро. Поэтому в Linux без лишнего копирования данных из пространств пользователя и ядра, никуда не денешься. Технологии типа io_uring и раздельная память – это тот же самый syscall (вы создаете и управляете ими через вызовы syscall).

Надо понимать, что всякие технологии Linux, позволяющие пользовательским программам работать на уровне ядра – это тоже костыли. Это не развитие, это попытка обойти свое же ограничение.

Появление vDSO (пускай пока там 4 функции для x86_64) – это признак осмысления, что концепция развития операционной системы Linux, которая была принята в начале 90-х, подходит конец. И со временем мы увидим, что количество системных функции, которые будут доступны через vDSO будут расти.

А что в Linux, вы вызывает sys_brk...

Странно. Я вот сейчас читаю справку Glibc MallocInternals. Там написано немного другое - например, что память выделяется из tcache, привязанного к потоку, куда складываются свободные чанки, и только если размер запрашиваемой памяти слишком велик или свободных чанков нет, происходит обращение к ядру. Т. е. имеем логику выделения ОЗУ, схожую с Windows.

Что касается костылей... eBPF позволяет влёгкую, не заморачиваясь с написанием модулей или перекомпиляцией ядра, накладывать патчи и добавлять функционал, что есть гуд. vDSO (как и аналогичная структура в Windows) предназначен для получения данных в режиме "только для чтения", этим он жёстко ограничен и навряд ли там появится что-то новое, потому что всё остальное уже требует перехода в Ring 0 со всеми вытекающими. Не понимаю только, почему вы обвиняете Linux, когда как на самом деле причина в переключении контекста и жёстком разделении Ring 3 и Ring 0 в архитектуре x86_64. Все ухищрения, что я перечислил, являются попыткой "размыть границу" между ними - избежать копирования данных и syscall.

P. S. пока общался с вами, узнал что MS не только io_uring перетащило, но и работает над аналогом eBPF в Windows NT. Вы всё ещё настаиваете на том, что концепции развития Linux приходит конец?

Мы столкнулись с похожей проблемой при оптимизации voice AI pipeline. Latency на самом syscall казался смешным (микросекунды), но когда профилировали весь путь, оказалось, что context switch сжирал в разы больше времени из-за cache miss и pipeline flush.

Тема очистки TLB не раскрыта.

Вот я гляжу на ваш пример и вижу в нем безусловную загрузку CR3. И вспоминается мне, что в x86 это вызывает безусловную очистку всего TLB (буфера трансляции виртуальных адресов, по крайней мере так было раньше). А очистка TLB - это больно, и было это больно уже в давние времена (начиная с ЕС ЭВМ Ряд 2 она же IBM System/370), задолго до всяких x86: получение физического адреса страницы виртуальной памяти без TLB требует нескольких обращений к оперативной памяти - а это в нынешние времена может быть очень дорого. Оно и тогда было дорого когда цикл памяти от цикла процессора не отличался на порядки, а уж сейчас...

А вы этот момент не отразили в статье никак.

Годная статья для того, чтобы познакомиться с темой 👍 Считаю, что middle+ разработчики должны про такое знать. На практике на собесах в основном плавают по этой теме.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации