Pull to refresh
112
0
Григорий Речистов @Atakua

Send message
Более того, если описание технологии/устройства/изобретения опубликовано самим её создателем до подачи/получения патента, то это становится доказательством prior art.
По классификации исключительных ситуаций позволю себе процитировать самого же себя. Может быть, простыня ниже кому-то поможет разобраться в зоопарке терминологии.

Классификация исключений

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

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

Синхронные с повторением текущей инструкции (промах, fault) — событие,
связанное причинно-следственно с выполнением текущей инструкции и
обусловленное "неготовностью" среды исполнения к её
успешному завершению. Примеры таких ситуаций: отсутствие физической
страницы памяти с необходимыми данными; неготовность сопроцессора
выполнять работу, т.к. он требует дополнительной инициализации. В этих
случаях обработчик ситуации, находящийся в операционной системе, может
модифицировать среду исполнения так, что завершение инструкции станет
возможным, например, загрузить нужную страницу или включить сопроцессор.
Дальнейшее возвращение на тот же PC с перезапуском инструкции
позволит устранить проблему прозрачно для пользовательского приложения.

Синхронные без повторения текущей инструкции (исключения, exception).
Как и в предыдущем случае, событие порождено текущей инструкцией. Однако
его обработка не подразумевает её повтора, так как причина события
неустранима и не связана со средой исполнения, но связана только с самой
операцией и операндами. Примеры: инструкция целочисленного деления на
регистр, содержащий ноль, всегда будет давать ошибку; инструкция,
запрещённая к выполнению в текущем уровне привилегий, не может быть на
нём исполнена никогда. Чаще всего (но не всегда!) исключения обозначают
ошибку в программе. Управление после возврата из обработчика будет
передано в место, не связанное с PC того кода, где произошло событие.

Ловушки (trap) также синхронны. При этом они обозначают явное
"желание" программы быть прерванной и передать управление
в определённую область кода — обработчик вызова. Примером является
инструкция SYSCALL, вызывающая системные функции операционной системы,
такие как работа с файлами, создание новых процессов и т.п. Другой
пример — команда, предназначенная устройству-сопроцессору, физически
отсутствующему в системе, однако ОС умеет её эмулировать и таким образом
способна вернуть правильный результат прозрачно для пользовательской
задачи. С точки зрения прикладного ПО ловушка — это инструкция,
семантика которой определяется не спецификацией ЦПУ, а используемой
операционной системой и средой исполнения.

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

Асинхронные прерывания (interrupt). В отличие от всех ранее
рассмотренных событий, они вызваны причинами, внешними по отношению к
текущему контексту исполнения, и означают некоторое состояние внешней
среды, требующее внеочередной обработки. Примеры: жёсткий диск готов
передать новую порцию данных, ранее запрошенную независимым процессом;
температурный датчик сигнализирует о превышении измеряемой температуры
порогового значения; таймер сообщил о прошествии запрограммированного в
него интервала. Прерывание никак не связано с опкодом, адресом или
аргументами инструкций — оно могло произойти чуть раньше или позже, а
могло и вовсе не произойти. Однако игнорировать его в общем случае
нельзя. Часто недопустимо откладывать вызов обработчика во времени
дольше, чем на некоторый краткий период времени.

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

Аварийная ситуация (machine check, abort). Самое редкое и при этом
наиболее серьёзное событие, означающее обнаруженный сбой в аппаратуре.
Как и обычное асинхронное прерывание, оно не связано с работой текущей
программы, но вызвано внешними обстоятельствами. Отличает от прерывания
его тот факт, что обработчик аварийного сигнала обычно не может вернуть
управление обратно прерванной программе, так как сам факт возникновения
события означает, что состояние системы было изменено неопределённым
образом. Другими словами, может оказаться, что возвращаться некуда.
Содержимому памяти и регистров больше нельзя доверять.

Не все системы поддерживают генерацию такого вида сигналов, так как для
этого аппаратура должна уметь детектировать нарушения; обычно это
присутствует только в дорогих высоконадёжных серверных решениях. Примеры
аварийных ситуаций, вызывающих ситуацию «machine check»:
некорректируемое повреждение содержимого оперативной памяти или кэшей;
перегрев или переохлаждение узлов системы; нарушение целостности
программного обеспечения, отвечающего за криптографическую верификацию
загружаемого кода (что может означать попытку взлома системы через
модификацию доверенных компонент системных программ).

Типичная задача обработчика аварийного сигнала довольно проста —
зарегистрировать факт ошибки в энергонезависимой памяти для последующего
анализа и выключить или перезагрузить машину.

Следует также отметить следующие особенности существующих центральных
процессоров по отношению к исключениям.

Программное прерывание (software interrupt) — событие, вызываемое
специальной инструкцией (например, в IA-32 это INT), обработка
которого напоминает вызов процедуры. Т.е., несмотря на название, оно
соответствует ловушке, а не прерыванию.

В некоторых архитектурах, например SPARC,
подпрограмма-обработчик синхронного события может сама выбрать, следует
ли перезапускать текущую инструкцию. Для возвращения из
подпрограммы-обработчика могут использоваться две различные инструкции —
RETRY для перезапуска (в случае обработки промаха) и DONE для
исполнения следующей команды за текущей (для выхода из обработки
ловушек). Для поддержки такой возможности в архитектуру введён регистр
nPC, в любой момент указывающий на следующую за текущей инструкцию.
Картинка в спойлере испортила всю статью.
Динамическое выделение памяти не нужно

Соглашусь с Вами — для большого класса задач (даже вне области embedded) сущности определены на начало работы системы, и их динамическое порождение/уничтожение излишне.

В нашем проекте все места, где допустимо использование динамической памяти, уже давно известны. Как правило, парные malloc/free при этом содержатся в пределах одной функции. Любой код-ревью, содержащий malloc, вызывает тяжёлый взгляд и долгое обсуждение. А если к нему не идёт free...
То, что в коде на Си вполне можно использовать ООП — это довольно старый, но почему-то малоизвестный широкой публике факт. Поглядите на ядро Linux — написано на Си, при этом имеет развитые иерархии классов драйверов, абстрактные классы для файловых систем и т.п., есть в наличии аналог dynamic_cast (более опасный)...

С++ не привнёс в идеи ООП существенного. Да и исполнение принципов ООП в нём, надо сказать, слабенькое — что угодно кастуется к чему угодно, вся память на ладони.

Что, по-моему, действительно является шагами вперёд по сравнению с Си, — это следующие три вещи.

  1. Пространства имён (namespace). В Си приходится придумывать всякие префиксы к функциям, структурам, типам и т.п. В мелких проектах это несложно, но с ростом масштабов это становится тормозом.

  2. Механизм исключений (exceptions). В embedded-проектах его, как правило, не задействуют (как раз из-за требований по памяти, времени и стеку); этому помогает его необязательность в C++. Но в традиционном программировании возможность разделить обработку ошибок от главной ветки кода очень ускоряет работу.

  3. Шаблоны (templates). Замена костыльным макросам Си. При отладке разбирать макросы — сущее мучение. С шаблонами при отладке чуть полегче. Хотя любой, кто хоть раз пытался разобраться в выводе ошибки компилятора для шаблонизированного кода (пресловутый template vomit) со мной не согласится :-)
Мое приложение не создавалось для прохождения тестов.

Но именно этому посвящена статья — тестированию поведения приложения.

И у меня не линукс.

Это не повод, чтобы писать намеренно зависающие программы, когда есть простой и правильный способ.
Sleep(-1);

Постоянно приходится переучивать студентов, отбивая эту привычку подвешивать своё приложение в конце работы, потому что "окно быстро закрывается, ничего не видно". А ведь такое приложение не пройдёт тесты, в которых принимается стандартный ввод и печатается стандартный вывод, который затем проверяется на корректность. Даже если вывод правильный — программа-то зависла!

Самое брутальное, что приходилось встречать — это
system("PAUSE.EXE");

На такое я всегда отвечал, что на моём Линуксе pause.exe не установлен.

Мой совет: поройтесь в настройках проекта VS и найдите опцию, которая контролирует (не)закрытие окна после завершения программы. Тогда не придётся вставлять это безобразие в каждую программу.
> Сопротивляйтесь добавлению в проект новых библиотек
Золотые слова!

От себя добавлю ещё одну проблему. Она возникает в больших и серьёзных компаниях, когда существенен легальный статус выпускаемого продукта: возрастание затрат времени на анализ лицензий входящих сторонних библиотек. Особенно, если их лицензии меняются по ходу дела.
Например, в прошлом году библиотека softfloat перешла с «самодельного» соглашение на BSD. Во многих программных симуляторах требуется моделировать инструкции над числами с плавающей запятой в формате IEEE 754, и многие из них используют именно для этого именно softfloat. И иногда код ещё и модифицируется (например, включается поддержка 82-битных чисел).
Я имел в виду, что если в модели есть несколько ядер x86, то в пятой версии симулятора они могут быть помещены в домен с общей памятью и при этом симулироваться параллельно. Не для всех моделей разных архитектур эта возможность сейчас поддерживается, т.е. скажем, модель процессора от MMIX (гипотетическая многоядерная) в Симиксе из коробки будет работать только в квотированном режиме на одном хозяйском процессоре. Добавление возможности параллельной симуляции для нового процессора — непростая вещь, и, что немаловажно, далеко не все сценарии симуляции получат от этого выигрыш. Например, при работе системного кода (UEFI, firmware, ...) часты доступы к устройствам, код IO-bound, и от параллелизации толку нет.

> зачем в одной конфигурации замешивать x86, arm или powerpc? Был реальный usage-case?

В современных платформах очень часто вместе с центральным процессором от IA-32 идут контроллеры (security, сенсоры, управление питанием) от иных вендоров или не с архитектурой x86.
В последней версии Simics 5 некоторые модели архитектур ЦПУ x86, ARM, ppc могут исполняться параллельно даже при работе с общей памятью. Если общей памяти не шарится (например, моделируются несколько плат, соединённых по сети ), то такие части можно запускать в параллель, и это работает уже давно.
Я не уверен насчёт деталей master-slave отношений, которые сейчас доступны в Simics. Текущие усилия по интеграции всё-таки были сконцентрированы на переиспользовании существующего кода, чтобы максимально быстро получить модель полной платформы. Хотя нужно понимать, что производительность при этом будет страдать. SystemC модели слишком точные и медленные по сравнению с «родными» функциональными.

> К примеру, использовать драйвер сетевой карты для тестирования RTL модели сетевой карты
Чаще бывает наоборот — драйвер к моменту наличия модели (SystemC/DML — неважно) ещё не готов или совсем ещё плох. Но в принципе, я думаю, и Ваш сценарий на практике кем-то жа был уже опробован.
Толково написано, спасибо.
При этом нельзя сказать, что SystemC на этом рынке является доминирующим решением: существуют и продукты, SystemC не использующие, например WindRiver Simics.

Как один из разработчиков Simics хочу уточнить это утверждение.

Действительно, функциональные (т.е. не потактовые) модели устройств в Simics пишутся на языке DML (device modeling language), который наиболее хорошо выражает типичные функциональные особенности (банки регистров, порты, карты памяти и т.п.). Однако существующую огромную IP-базу моделей, написанных на SystemC, нельзя игнорировать. Поэтому существует возможность интеграции такого кода в модели Simics. Самое интересное при этом — это сопряжение различий в парадигмах моделирования времени в SystemC и Simics.
В оригинальном посте про OS/2 почему-то забыто. Как и про многие другие интересные ОС и системы, например, миграция с VAX/MIPS на Alpha, с x86 на IA-64.

История IBM/Microsoft OS/2 связана с развитием архитектуры Intel 8086 — Intel IA-32 примерно так же, как это было в случае Microsoft. Данная статья же имеет цель показать разные способы решения задачи мигрирования с ОС на ОС. То есть история OS/2 не привносит нового в этот обзор.

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

>что он отслеживает изменения оперативной памяти и оттранслированный блок сразу аннулируется и вытесняется из кэша
Можно привязать инвалидацию блоков трансляции к выполнению той самой инструкции cacheflush, а до того момента задерживать перетрансляцию кода в модифицированных секциях. Такое поведение, как и оригинальное с энергичной инвалидацией, будет удовлетворять спецификациям аппаратуры, которая не гарантирует консистентности кода до выполнения cacheflush, но и не запрещает её. Даже может быть в производительности чуть-чуть выиграем. Но соглашусь, такой путь усложняет модель.
>мне кажется функция достаточно маленькая и умешается в одну страницу кэша
Если размер линии определён архитектурно, то да. В таком случае Qemu может повторять логику железа и просто моделировать кэш-линии. Это будет несколько накладно, однако вполне реализуемо. Симулятор уже следит за страницами, может следить и за более мелкими единицами, особенно если они связаны с кодом, а не с данными.
Если размер кэш-линии не задан архитектурно, то он может быть и 2 байта, и 16, и 1024, и 8192. Нельзя думать, что если сейчас все кэш-линии размером в 16 байт, то завтра (буквально завтра, как часто и бывает!) не будет выпущена серия процов с размеров кэш-линии в 8 байт или в 64 байта.

> Самомодифицирующийся код никакой не криминал
Дело не в SMC, а в отсутствии гарантий на когерентность содержимое кэша и памяти. SMC не только для малвари нужен. Опять же, где гарантии, что на аппаратуре «на мобильном устройстве наблюдалась случайная последовательность вызовов» — ведь последовательность f1 f1 f1 f1 f1 f1 f1 тоже вполне валидная выборка для случайной последовательности. Т.е. опять же требуется проверка статистической гипотезы. Но нужно отметить, что этот противоотладочный метод известен довольно давно, значит, он на практике эффективен.

>поведение на реальном оборудовании всегда будет отличаться от поведения в эмуляторе
never say «never». Дело в том, что чёткой границы «симулятор-реальность» не существует.

> я не верю в то, что он может быть выключен
Это да, кэш никто в здравом уме на телефонах отключать не будет. Но вот от девайса к девайсу его характеристики и алгоритмы могут отличаться в разы.
> Правда второй тест с самомодифицирующимся кодом
А вот это вообще уже не тест, а криминал — софт использует неопределённое поведение железа. При отсутствии вызовов cacheflush в исполняться будет не f1 или f2, а чаще всего смесь этих функций — часть байт из одной, часть — из другой, т.е. мусор. Опять же содержимое страницы будет зависеть от размера кэш-линии, политик вытеснения и замещения, иерархии кэшей, т.е. от марки ЦПУ, вендора, варианта микроархитектуры и прочих факторов. Да и вообще кэш может быть вырублен.
Т.е. на одной железке это будет работать, а на другой — нет. Ложноположительные срабатывания на аппаратуре, отличной от использованной при разработке, обеспечены.

Так ведь и сеть может быть ненастоящая, с задержками специально подобранными!
Ну или вместо гигабитного 4G будет канал через VPN по Bluethooth с сервером на другом полушарии.

Вообще, проверки типа «внутри кастрюли я или нет», основанные на измерении времени, крайне чувствительны ко всему подряд и ведут ложноположительным срабатываниям, и при этом принципиально ничем не лучше чисто функциональных тестов. То железо изменится, то планировщик ОС что-то по-другому сделает, и все задержки сдвинутся в ту или другую сторону.

Существует печальный пример защиты от копирования Starforce, в том числе использовавшей измерения времён доступов к приводу для различения виртуальных и реальных.

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

Необязательно использовать аппаратный V86-режим. Можно обойтись просто интерпретацией набора 8086 и эмуляцией системных устройств, ожидаемых программами под DOS. Благо современные процессоры куда быстрее тех, что оригинально использовались с теми программами, и даже простая схема интерпретатора без всяких наворотов вполне устроит.
Неделю назад катался на фэтбайке с жёсткой вилкой. Давление в шинах у него ≈15 PSI, и шины отрабатывают вместо амортизаторов довольно прилично. Хотя на совсем диких трассах мне, наверное, вилку захотелось бы помягче.
По этой же теме (теоретические основы CS для простых программистов) недавно наткнулся на отличную книгу:
Том Стюарт. Теория вычислений для программистов / Пер. с анг. А.А. Слинкин. — Москва, ДМК Пресс, 2014. — 384 с.
Я был удивлён, как в довольно простой форме, шаг за шагом, в ней объясняются те вещи, которые я не мог понять долгое время из других публикаций, например, числа Чёрча. Все примеры кода в ней на Руби, который я не очень знаю, но опять же, оказалось, что выбранный автором подход очень удачно использует динамический язык для иллюстрации всех идей.

Information

Rating
Does not participate
Registered
Activity