Обновить

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

Правильная картинка
Правильная картинка

Да, пока про рыбов могу только рассказать))) Лучше дотестирую и выкачу уже нормальный проект

По-моему, делать ОС, которая и для сервера и для десктопа так себе идея. Слишком разные требования.

Семейство убунты разве не этим же занимается? У меня и на десктопе, и на сервере и gh раннеры

Ну я на работе пишу под чисто серверную систему, а на десктопах у меня винда и линукс. Огромная разница в том, как реализовано на сервере и как на десктопе.

Описывать разницу будет слишком долго. Потому что все иначе.

Если интересно - можно почитать Ф.Солтис "Основы AS/400" Там много как общей, так и технической информации о внутренней организации системы.

Windows и Linux пытаются работать и на сервере, и на десктопе практически без изменения ядра — отсюда и компромиссы, и форки.

В OptimaOS ядро содержит только самый минимум (память, IPC, безопасность, планировщик с настройками). Всё остальное — сеть, файловые системы, GUI — вынесено в userspace и собирается в нужную конфигурацию через runtime-профили. Под сервер подкладывается один набор политик и сервисов, под десктоп — другой, под edge — третий. Ядро при этом одно и то же.

Получится ли обойти те противоречия, о которых вы говорите, — покажет время. Но архитектурно я заложил возможность настраивать поведение под задачу, а не пытаться сделать «одну систему, которая всем хороша».

Десктопные и серверные системы, все-таки, отличаются не только наличием GUI. У серверных систем совершенно другие требования по нагрузке - они работают у условиях огромного количества одновременно выполняющихся заданий и тут сразу встают вопросы изолированности заданий друг от друга так, чтобы даже самая кривая программа в одном задании не могла оказать влияние на все остальные. Не скажу за линукс, но винду я легко могу загнать в дикие лаги одной программой. Всю винду. Так, что даже завершить процесс будет сложно.

Второй момент - время переключения контекста заданий. В винде/линуксе это достаточно большие накладные расходы.

Третий момент - системное логирование, журналирование изменения объектов. На серверной ОС всем этим должна заниматься система (системные журналы, логи заданий...).

Четвертый момент - поддержка исключений на уровне системы, а не ЯП. Т.е. вы не должны ориентироваться на языковые исключения, а иметь в доступе простой механизм использования системных с возможностью проброса их на любой уровень стека вызовов по необходимости.

А то, что Вы описываете - тут скорее надо смотреть как реализованы микроядерные ОС. Например, QNX

так, чтобы даже самая кривая программа в одном задании не могла оказать влияние на все остальные

В общем случае, не очень понятно, что это значит. Я так понимаю, что "задача" примерно соответствует "потоку" (или же процессу)? Любому потоку отдаётся ограниченный таймслайс и занести в лаги он может исключительно при большом количестве потоков — но это уже административная проблема. Ну и если действивтельно появляется такое требование, то есть всякие ulimit/cgroups/job/etc — чтобы ограничить потребление ресурсов группы процессов.

Второй момент - время переключения контекста заданий. В винде/линуксе это достаточно большие накладные расходы.

Так эти накладные расходы вызваны наличием виртуальной памяти — то есть вышеупомянутой изоляции. Что здесь серверная ОС может сделать по-другому?

Третий момент - системное логирование, журналирование изменения объектов. На серверной ОС всем этим должна заниматься система (системные журналы, логи заданий...).

Вроде с этим все успешно справляются на уровне дистрибутивов. Тот же journald довольно неплохо реализует агрегацию логов.

Четвертый момент - поддержка исключений на уровне системы, а не ЯП. Т.е. вы не должны ориентироваться на языковые исключения, а иметь в доступе простой механизм использования системных с возможностью проброса их на любой уровень стека вызовов по необходимости.

Что здесь имеется ввиду и зачем это нужно? Ну, вот в той же вроде есть SEH — можно кидать исключения в одном ЯП а ловить в другом. Но не очень понятно, что это принципиально меняет (в любом случае стараются держать каждый процесс на одном ЯП)...

В общем случае, не очень понятно, что это значит. Я так понимаю, что "задача" примерно соответствует "потоку" (или же процессу)?

Это ближе к процессу. Но не совсем. Задание - job - это своего рода изолированный контейнер. Со своими настройками, определяемыми в job description (jobd - отдельный системный объект, они могут быть разные, при создании задания указывается подходящий для него jobd).

Все, что происходит в системе, происходит в каком-то задании. Задания могут быть интерактивные (любая терминальная сессия существует в своем интерактивном задании) или фоновые (batch). Находясь в терминальной сессии можно программу запустить как в рамках этого же задания (call с указанием имени программы и параметров), так и в новом фоновом задании (submit job - sbmjob с указанием имени программы, параметров и подходящего jobd).

Потоки (thread) могут существовать в рамках задания если они разрешены в соответствующем jobd.

Если в той же винде я могу "занавесить" всю систему просто загнав программу в бесконечный цикл, то здесь так не выйдет. Можно "занавесить" задание, но такое задание всегда можно принудительно завершить командой endjob из любого другого задания. Т.е. все, что происходит в задании, происходит только в этом задании и никак не влияет на остальные.

Временные объекты (если они нужны) можно располагать во временной библиотеке QTEMP которая для каждого задания своя - она создается при старте задания и уничтожается при его завершении (вместе со всем содержимым). Также при завершении задания освобождаются выделенные в рамках него ресурсы (на самом деле там еще сложнее - есть "группы активации" как подсистема задания и можно отдельно, не завершая всего задания, удалить группу активации, освободив все выделенные в ее рамках ресурсы).

Ну и если действивтельно появляется такое требование, то есть всякие ulimit/cgroups/job/etc — чтобы ограничить потребление ресурсов группы процессов.

Это все описывается в jobd с которым запускается задание.

Подробнее про задания

Так эти накладные расходы вызваны наличием виртуальной памяти — то есть вышеупомянутой изоляции. Что здесь серверная ОС может сделать по-другому?

На самом деле может. С использованием одноуровневой модели памяти накладные расходы на переключение контекста заданий стремятся к нулю.

Вроде с этим все успешно справляются на уровне дистрибутивов. Тот же journald довольно неплохо реализует агрегацию логов.

У нас все это на уровне системы. У каждого задания есть свой joblog в котором автоматически отражается все, что там происходило. Пока задание активно он доступен для просомтра или командой или SQL запросом (БД интегрирована в систему и SQL движок тоже)

SELECT ORDINAL_POSITION,
       MESSAGE_TYPE,
       MESSAGE_TIMESTAMP,
       FROM_LIBRARY,
       FROM_PROGRAM,
       MESSAGE_TEXT,
       MESSAGE_SECOND_LEVEL_TEXT
FROM TABLE(QSYS2.JOBLOG_INFO(JOB_NAME => '472499/*****/J181'));

(звездочками "запикано" имя пользователя от которого запущено задание).
После завершения задания joblog падает в спул (очередь вывода на принтер) - там его можно смотреть отдельными командами или загрузить на комп в виде txt или pdf

Что здесь имеется ввиду и зачем это нужно? Ну, вот в той же вроде есть SEH — можно кидать исключения в одном ЯП а ловить в другом. Но не очень понятно, что это принципиально меняет (в любом случае стараются держать каждый процесс на одном ЯП)...

А почему в одном ЯП? Кто такое ограничения наложил? У нас вот можно написать часть кода на одном ЯП, часть на другом а потом собрать все это в один программный объект. Это называется ILE - интегрированная языковая среда.

Кроме того, для нас нормальным является ситуация когда одна программа в рамках задания вызывает другую, та третью и т.п.

В нашем основном ЯП вообще без разницы что вызывать - процедуру в том же бинарнике, процедуру из сервисной программы (аналог динамической библиотеки) или внешнюю программу - все на уровне описания прототипа. Внешняя программа вызывается как обычная процедура, просто это позднее связывание.

Поэтому тут языковые исключения такое себе...

Системный механизам исключений базируется на понятии "структурированной ошибки". У ошибки есть 7-символьный код, есть текст (где можно разметить места для подставновки данных) и набор данных для подстановки. Все ошибки хранятся в специальных message files. При вызове исключения передается код ошибки + данные для подстановки.

Как пример - я добавляю в msgf KSMMSGF ошибку с кодом GFC0005 и текстом “Программа-обработчик сообщения &1 не найдена в *LIBL”. Далее в нужном месте, если случилось ошибка заполняю структуру

  when not CheckPGM(iPrcPgm);
    // Нельзя указать несуществующую программу-обработчик
    dsError.errCode    = 'GFC0005';
    dsError.errParmAll = iPrcPgm;

и потом бросаю исключение (аналог throw)

// Если заполнили сообщение об ошибке - послылаем его
if dsError <> *blanks;
  snd-msg *escape %msg(dsError.errCode: 'KSMMSGF': dsError.errParmAll) %target('GFCLMTCMP': 2);
endif;

Тут еще момент - мне нужно послать его на два уровня стека выше потому что в стеке вызова

Вызывающая программа
PEP для вызываемой программы
Вызываемая программа

для этого есть механизм %target позволяющий указывать куда должно пойти исключение.

В итоге

И это будет записано в joblog автоматически (т.е. вся история зафиксирована).

Елси нужен механизм try/catch, в нашем ЯП (в котором своих языковых исключений нет) делаем так:

monitor;
  ...
on-excp 'GFC0005';
  ...
on-error;
 ...
endmon;

есть блок для обработки конкретных ошибок, есть блок для обработки всех остальных. monitor - try, on-excp/on-error - catch.

В С/С++ механизм перехвата чуть иной, но тоже есть

#pragma exception_handler(UsrQExeptHandler, 0, _C1_ALL, _C2_ALL, _CTLA_HANDLE_NO_MSG)
    pSP = rslvsp(_Usrq, pQueItem->UsrQName, pQueItem->UsrQLib, _AUTH_ALL);
#pragma disable_handler

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

Плюс сам по себе механизм структурированной ошибки широко используем и без исключений. Просто для возврата ошибок.

Парни спасибо за обсуждения. Внес в проект доработки

A. Job-объект над процессами

Сейчас в scheduler.rs есть процессы/потоки. Не хватает формального контейнера задания:

  • JobDescriptor — политика ресурсов, приоритет, allowed capabilities

  • При завершении job → автоматически освобождаются все ресурсы (сейчас это не гарантировано)

  • Временное пространство, изолированное per-job (аналог QTEMP)

B. Activation groups в capability.rs

Группы ресурсов внутри задания с возможностью освободить группу без завершения задания. Это уже близко к capability scopes, но нет явного lifetime-контейнера.

C. Per-task audit log в audit.rs

Сейчас audit — глобальный. Нужна привязка событий к job ID, чтобы можно было: query_audit(job_id: JobId) -> Vec Это также упростит отладку в linux-compat (сейчас сложно трассировать что делало конкретное задание).

D. Structured system exceptions для cross-ABI

Актуально для linux-compat + будущих ЯП. Идея: ошибки как системные объекты (код + данные), а не Rust Result. Это позволит linux-compat выбрасывать ошибки которые правильно интерпретируются на уровне ядра, не через errno.

Прошу прощения за ИИшный текст в ответе, но ИИ агенты уже практически стандарт в разработке (два шага вперед один назад)))

Про группы активации можно тут почитать

Не для копирования 1:1, просто понимания что это такое и как работает. Штука интересная и полезная есть уметь ими правильно пользоваться (я вот сейчас уже без них, наверное, страдал бы коли пришлось бы что-то писать под другие платформы), могу подробнее рассказать, но это скорее в личку уже, наверное...

Комментарий большой, поэтому отвечу по частям.

Находясь в терминальной сессии можно программу запустить как в рамках этого же задания

А, ну тогда это скорее аналог cgroup или Job (в вин).

Если в той же винде я могу "занавесить" всю систему просто загнав программу в бесконечный цикл, то здесь так не выйдет. Можно "занавесить" задание, но такое задание всегда можно принудительно завершить командой endjob из любого другого задания. Т.е. все, что происходит в задании, происходит только в этом задании и никак не влияет на остальные.

Ну, это скорее особенность винды, т.к. она пытается притворился, что программы умирают кооперативно. В том же линуксе спокойно можно убить любой процесс не дожидаясь ничего. Но различия этих подходов никак не связаны с десктопной vs серверной ОС — это просто разные дизайн решения, с разными трейдоффами на тему cancelation (каноничная проблема: если задание отправило сетевой пакет на перегрузку реактора, то крайне не хочется убивать это задание, пока оно не отправит пакет на отмену...).

Аналогично про временные ресурсы: в любой адекватной ОС хочется иметь механизмы аллокации временных ресурсов(файлов и прочего), которые ОС сама подчистит после завершения процесса/задачи. Поэтому опять же не про серверную ОС.

На самом деле может. С использованием одноуровневой модели памяти накладные расходы на переключение контекста заданий стремятся к нулю.

Ну так это решение на уровне железа! Можно пойти дальше и взять тот же cheri и там тоже всё будет быстро :)

В общем и в целом, пока что все софтовые различия, которые Вы описываете имеют вполне себе неплохие аналоги в тех же Win/Lin.

То есть я пока не вижу какого-то функционала, которого нет в условном Win именно по причине, что это "десктопная ОС". И чтобы при этом, данный функционал сделал десктопный опыт хуже.

Я понимаю, что AS/400 сделана достаточно интересно и в ней есть много интересных/хороших решений. Но пока что я не вижу чем эти решения "принципиально серверные".

Но различия этих подходов никак не связаны с десктопной vs серверной ОС

Очень даже связаны. Если кривой процесс может завалить всю ОС - такая ОС для сервера не годится.

В общем и в целом, пока что все софтовые различия, которые Вы описываете имеют вполне себе неплохие аналоги в тех же Win/Lin.

Я вам могу перечислить 100500 софтовых различий, которые вообще аналогов не имеют :-) Начиная с интегрированной в ОС БД (включая SQL движок) и далее и далее...

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

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

Но пока что я не вижу чем эти решения "принципиально серверные"

Сама организация системы подразумевает работу в условиях очень больших нагрузок. Т.е. она специально для этого делалась. От модели памяти и управления заданиями и до интегрированной БД.

Очень даже связаны. Если кривой процесс может завалить всю ОС - такая ОС для сервера не годится.

В винде бесконечный цикл не валит ОС, а сьедает доступную память. И винда ждет, пока она освободится для выполнения еще чего-то. В lin один процесс все памятт забрать разве не может?

Может, но за ним рано или поздно придёт OOM-killer.

А почему в одном ЯП? Кто такое ограничения наложил?

Там квантор всеобщности, очевидно. Т.е. можно кидать в одном и ловить в другом, а можно наоборот, кидать во второй и ловить в первом (или вообще в третьем).

В целом, кроссязыковые исключения уже очень давно поддерживается много кем. Там есть разные виды синтаксиса, но в Ваших примерах та же проблема присутствует.

Другое дело, что моделей обработки ошибок есть много и разных. И они, к сожалению, не очень совместимы между собой. Например в тех же го, или расте в принципе не принято кидать исключения — и если бы кто-то захотел бы интегрировать их в AS/400, то Ваш ILE бы перестал быть таким красивым...

Вы кажется смешиваете фреймворк/платформу для разработки и серверные ОС. Понятное дело, что хорошая платформа для разработки должна поддерживать единую систему обработки ошибок (и не важно, десктоп/сервер/консоль). Но таковых очень много. Банально, Вы можете даже скрестить C++ и джаву без особой боли (если принять модель ошибок явы...).

Например в тех же го, или расте в принципе не принято кидать исключения — и если бы кто-то захотел бы интегрировать их в AS/400, то Ваш ILE бы перестал быть таким красивым...

В RPG (наш основной язык) вообще нет языковых исключений. Поэтому пользуемся системными.

Другое дело что исключения - это достаточно тяжелый механизм (даже языковые). Поэтому не надо ими злоупотреблять особенно в высоконагруженных системах. Мы стараемся все-таки использовать механизм возврата структурированной ошибки через параметр, а не бросать ее в виде исключения. За исключением случаев, когда иначе никак.

В целом, кроссязыковые исключения уже очень давно поддерживается много кем. Там есть разные виды синтаксиса, но в Ваших примерах та же проблема присутствует.

На самом деле там нет проблемы. Разный синтаксис перехвата, но это не проблема. А сама структура там задана системой и в конечном итоге делается это через системные API.

Apple так делал, вполне нормальный вариант при микроядерной архитектуре.

Странное желание… В то время, как Rust и C++ работают над тем, чтобы как можно больше можно было перенести в compile-time вы разворачиваетесь в обратном направлении и для специализированных систем предлагаете решать, что им нужно в runtime… Зачем? Чтобы энтузиасты смогли запустить Doom на еще одном устройстве?

Согласен, тренд в C++ и Rust — это максимальный перенос в compile-time. Но здесь важно разделить что мы выносим в runtime и почему.

В OptimaOS в runtime вынесена не логика работы драйверов или ядра — она вся скомпилирована и статически верифицирована. В runtime вынесены решения о конфигурации, которые заранее неизвестны. Эти решения невозможно принять в compile-time, если мы не хотим превратиться во фрагментированный зоопарк форков — ровно ту проблему, которую я пытаюсь решить.

Зоопарк форков потому и появляется, что форк – это просто compile-time решение для конфигурации конкретного устройства. Все равно, что feature-флаг в Rust или дефайн в С++ – только живет не в общей базе кода, а у конкретного производителя (в общей зачем ему быть, если железо, для которого он нужен, есть только у конкретной компании?). Зачем там грузить что-то в runtime, если это всегда будет одно и то же?

А Redox OS не закрывает необходимый функционал? Я жду выхода финальной версии, мне кажется очень иниересный и перспективный проект

Да, практически у нас проект близко к Redox, но он планирует отдельные издания системы (Redox Server, Redox Desktop), а у нас одно ядро и поверх политики (которые можно менять "на лету"). А так да проект очень интересный

Без совместимости с NTAPI или *nix (то есть теми самыми 30-летними ядрами и всем их техдолгом) вы теряете драйвера, прикладное ПО, программистов и пользователей. Имитировать их сисколлы программно, делать обёртки и прослойки к вашему ядру - это долго и дорого (плюс накладные расходы на все операции), что не есть хорошо.

Может проще было сразу пилить *nix-like ядро, а не городить Linux ABI/API поверх? Меньше слоёв абстракции - лучше, или нет?

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

Redox OS, кстати, идёт похожим путём — они тоже реализуют совместимость с Linux. И у них это работает.

Для совместимости с существующим ПО в архитектуру с самого начала заложен linux-compat — не как надстройка или эмуляция, а как слой, транслирующий Linux syscall’ы в нативный optima_syscall_v0.

построить одно безопасное ядро с минимальным TCB, а совместимость с экосистемой обеспечить через прослойку, которая будет достаточно тонкой, чтобы не убивать производительность, но достаточно полной

Идейно схоже с Fuchia OS и Zircon?

слой, транслирующий Linux syscall’ы в нативный optima_syscall_v0

В этом-то и проблема: надо будет не просто транслировать сисколлы, а имитировать баги, специфическое поведение, поддерживать workaround'ы разработчиков вокруг всего этого. По итогу, вы придёте к подобию линухового ядра, под которым лежит ваше ядро, только в отличие от Linux, у вас будет меньше разработчиков. MS от первой версии WSL не просто так отказалась, хотя вам с вашим ядром проще будет это реализовать.

Настоящий линукс тоже, как не удивительно, реализует Linux ABI/API поверх внутренней инфраструктуры ядра, Которая не тождественна публичному API…

Просто сил и удачи!

То есть это будет микроядро полностью (memory-safe + unsafe) на Rust и с бинарной совместимостью с Linux и в т. ч. с линуксовыми драйверами? А какая лицензия планируется для кода в репозитории после публикации?

Бинарная совместимость с Linux не с ядром, а с пользовательским пространством Linux. Т.е. это userspace ABI, а не драйверная модель Linux. По лицензии — пока склоняюсь к MIT + Apache 2.0, окончательного решения не принял.

Понял, спасибо за подробности и удачи в развитии проекта, как и многие, я закинул статью в закладки. А downvote за что? :(

Это не я. Поставил плюс, но сравнялось к нулю)

Честно говоря, не уверен что стоит стремится к такой совместимостью именно с Линуксом. По моему скромному мнению, достаточно POSIX совместимости.

Fucshia делает не тоже самое?

Подход похож, но есть различия

я конечно вообще не спец в этом направлении. Но недавно поразмышляв о современных ОС, лично я пришел к выводу, что системы слишком архаичны, и несут за собой большой груз технического долга и множество попыток адаптации всего и вся. Что в свою очередь сталкивается с современными тенденциями и будущего данного направления.
Желаю успехов в данном проекте, надеюсь всё получится, на крайний случай, как минимум будет протестированная теория, которая либо окажется успешной, либо нет, либо вообще даст новое видение на всё это. Я пока больше теоретик только, и то начинающий, очень интересно будущее данного проекта. И в целом мне близки ваши мысли судя по вашим другим постам и идеям, относительно того же мессенджера

Изоляция процессов от памяти у вас через MPU/MMU ?
Уверены, что только профилем без подгрузки бинарного модуля получится обеспечить виртуальную память там где она есть и поддержать MPU там где её нет и который может быть вообще опционален, ведь трансляция адреса протекает во всю семантику внутренних структур ?

Видели TockOS (https://www.tockos.org/) как они решили изоляцию капсулами ?

Да, вы правы текущей изоляции недостаточно. Внесли этот момент в план доработки. Благодарю за идею.

Давайте угадаем, что это за LLM.

✅ Я 100% sure что это ChatGPT

🙂

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

Публикации