Как стать автором
Обновить
70
0.3
Иван Савватеев @SIISII

Микроконтроллеры, цифровая электроника, ОС…

Отправить сообщение

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

Вообще, в классическом Паскале объектов не было -- были только записи (record), которые технически ничем от классических сишных структур не отличаются. Объекты же появились в объектно-ориентированном расширении Паскаля от Борланд, в версии 5, если склероз не изменяет.

Но по существу вопроса согласен: делать ООП на C, используя обычные структуры, вместо того чтобы взять C++ -- ну, это такое.

Ну, ручные аналоги виртуальных функций в коде на чистом C используют постоянно (скажем, всякие там библиотеки периферии "от производителя", будь то STM, Microchip или кто ещё, набиты структурами, содержащими указатели на функции, и ты должен правильно заполнить такие структуры и передавать их вызываемому библиотечному коду). Почему бы не использовать язык, где всё это делается автоматически?

Ну, я вот под МК пишу на C++, и от неча делать как-то обошёлся вообще без всей стандартной библиотеки (не считая заголовков, нужных для типов вроде std::uint32_t). Абсолютно весь необходимый код библиотеки времени выполнения написал сам на ассемблере, ориентируясь на ругань компоновщика -- причём этого кода оказалось очень мало (грубо говоря, memcpy и ещё несколько подобных функций). Когда добавил статические экземпляры классов, потребовалось дописать вызовы их конструкторов перед вызовом main и вызовы деструкторов после возврата (последнее, понятное дело, фикция в данном случае). В общем, ничего реально тяжеловесного компилятору не требуется. Вот библиотекам -- там да, очень много что может быть, но это уже дело программиста -- использовать стандартные библиотеки или писать своё.

Это, конечно, freestanding в терминологии стандарта, но и ядро ОС -- тоже не прикладная программа и, прямо скажем, принципиальных отличий от прошивки микроконтроллера не имеет (не считая объёма и сложности -- но не "взаимоотношений" со средствами разработки и с аппаратурой), и там тоже не можно, а нужно использовать именно freestanding.

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

dynamic_cast нужен в весьма специфичных случаях, а применительно к ядру -- реально никогда. Вместо обычного new можно использовать new (std::nothrow) -- и он будет возвращать nullptr при отсутствии памяти, а не кидаться исключениями.

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

C++ ровно столь же тяжеловесен, как чистый C. Если ты сам не напишешь тонну бреда, компилятор из воздуха это бред не сгенерирует. В частности, использование обычных классов с обычным наследованием будет порождать технически тот же код, что компилятор наклепает, если ты будешь использовать таблицы с адресами функций и простые структуры, вручную имитируя ООП.

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

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

Кстати говоря, в модульности ядра ничего особо нового нет. Скажем, в RSX-11 -- бабке Винды, появившейся ещё в первой половине 1970-х -- драйверы могли (и обычно делались) загружаемыми, и можно было по мере необходимости их загружать/выгружать. Ну а в OS/360 (ещё чуть ли не на 10 лет раньше) вообще 80% ядра загружалась в память по мере необходимости, причём пользователь мог писать собственные модули (и работало сие без всякой виртуальной памяти, поддержки которой тогда на уровне железа ещё не было).

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

Почти всем можно пользоваться, просто голову нужно держать включённой -- а у ЛТ она как раз напрочь отключена, поэтому дичь и несёт. В частности, никто не притащит в ядро STL, Boost и прочее, если ты не притащишь это сам -- ну так не используй те средства, которые в коде ядра неуместны, в чём проблема-то?

Что же до прикладных программистов, то написание системного кода было для них сродни чёрной магии и 40 лет назад, причём совершенно независимо от используемого языка: мало того что ты остаёшься один на один с железом, так ещё и должен соблюдать изрядное количество неочевидных правил, чтобы что-нибудь не развалить из-за нарушения порядка использования внутриядерных структур данных и т.п. (на прикладном-то уровне такие проблемы возникают редко и на 95% разруливаются через ОС).

В Винде изрядная часть драйверов тоже являются частью ядра, и долгое время 95% BSODов происходило именно из-за их кривизны. Когда мелкомягкие догадались вытащить "тихоходные" драйверы или их части в пространство пользователя, многие такие проблемы ушли.

Правда, в Винде не ломают постоянно внутриядерные интерфейсы, поэтому корректно написанный под какую-нить Вынь2000 драйвер соберётся и будет работать и в 11. Есть исключения, главное из которых -- видеодрова, поскольку при переходе от 2003 на Вислу полностью поменяли драйверную модель для видюх, но практически всё остальное вполне себе переносимо.

А вот в Линухе попробуй возьми драйвер, написанный для 2-й версии, и заставь его собраться для 5-й. (Поэтому часто во всяких там встраиваемых системах и используют жутко древние версии линуховых ядер: драйверы только под них, переписывать -- то ещё удовольствие, даже если документация на железо имеется...)

Стандарт Це++ много чего не предусматривает, зато предусматривает кучу всякого бреда, особенно в плане UB, хотя 95% этих UB вполне себе DB на любой платформе.

Хм... так бюджетного или игрового? :)

Плюс, там были короткие команды для доступа к нулевой странице памяти (0000-00FF), которые работали быстрей обычных, длинных. Плюс, типичное применение машинок на процах такого уровня -- выполнение всего одной задачи, а значит, вся память -- её, повторная входимость обычно не нужна и т.д. и т.п. Так что вызовы подпрограмм нередко выполнялись по "гибридной модели", так сказать: сам вызов технически использовал стек для адреса возврата (команды JSR и RTS), а параметры нередко лежали в предопределённых ячейках памяти.

Лично я предпочитаю иметь модель примерно как в ARM: где я сам могу либо традиционным "стековым" образом вызывать подпрограммы, либо сделать, как в Системе 360, либо некое промежуточное решение. В общем, когда есть гибкость. Правда, она востребована, только если пишешь на ассемблере -- а сейчас это редкость.

Не было. Там самый что ни на есть обычный стек, только объём всего 256 байт.

Система инструкции ARM - это старая система (1983)

Но она новее и мэйнфреймов (анонс в 1964 году, продажи -- с 1965), и 8086/88, из коего выросла IA-32 (1977-й, если память не изменяет).

В настоящих современных архитектурах производиться разделение пользовательского стека и стека для хранения обратных вызовов и состояний

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

Резюмируя классическим образом, "если какая-то неприятность может случиться, она случается" (с)

Насчёт бесстекового способа вставлю свои пять копеек.

  1. Исторически в OS/360 каждое динамическое выделение памяти производилось путём обращения к супервизору (грубо говоря, к ядру ОС): ассемблерная программа, желающая получить память, использовала макрокоманду GETMAIN ("main" -- от названия "основная память", main storage, использовавшейся и использующейся поныне для обозначения ОЗУ в IBMовских мэйнфреймах), ну а эта макрокоманда разворачивалась в загрузку параметров (в первом приближении -- требуемого объёма памяти) в регистры процессора и выдачу команды SVC, приводящей к прерыванию по вызову супервизора. Сейчас такой способ кажется диким: прерывание на современных процессорах -- очень долгий процесс по сравнению с простым выполнением команд, но тогда, в середине 1960-х и в 1970-х, особой разницы во времени выполнения не было.

  2. Такая достаточно современная архитектура, как ARM, хотя и имеет стек, позволяет реализовать вызов подпрограмм без его использования: команда BL, вызывающая подпрограмму, сохраняет адрес возврата в регистре LR, а не записывает его в стек, как это делает, скажем, CALL на IA-32 (x86). Соответственно, потенциально можно реализовать ту же схему вызова, что и в IBMовских мэйнфреймах. Правда, в ARM адрес возврата заносится всегда в один и тот же регистр, а в мэйнфреймах можно использовать любой из 16 регистров общего назначения, но это уже технические детали.

Дык и производство самого 6502 продолжается :)

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

Ну и, по большому счёту, наша Рапира -- это, по сути, Паскаль на русском, насколько помню. Её я видел, но не ковырял: первым у меня был Бейсик на Агате, вторым -- встроенный в него же ассемблер 6502, ну а третьим стала СМ-4 и затем СМ-1420 с Фортраном, Паскалем и ассемблером -- ну и пошло-поехало; после этого на агатах я вёл школьный кружок, сам ещё будучи школьником, но серьёзно на них ничего не делал: у меня ж был доступ к настоящей машине :)

У Агата-7 очень гибко с памятью было. Минимальный объём -- 32 килобайта на материнской плате, но и на ней могло быть больше, и в разъёмы расширения ставились платы допОЗУ и псевдоПЗУ. У нас в школе было не меньше 64 Кбайт в сумме, но, кажется, больше -- но сейчас уже и не вспомню точно.

А вот видеорежимы -- да, с Эплом-2 совсем никак не совместимы до появления Агата-9.

Информация

В рейтинге
2 407-й
Откуда
Солнечногорск, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Embedded Software Engineer
Lead