![]() |
Hello All!
Делиться со-всякими алго и наработками уже входит у меня в привычку, и в продолжении цикла этих заметок предлагаю разбор всякого хлама из старого сундука. На повестке дня сегодня следующее: 1. Фиктивный стек; 2. Список установленных программ в реестре; 3. Базовые операции с текстом; 4. Заключение. ---------------------------------------------- 1. Фиктивный стек Герою восточного фольклора Ходже Насреддину приписывают выражение: -"Если гора не идёт к Магомеду, то Магомед пойдёт к горе". В этой части статьи попробуем спроецировать данное утверждение на системный стек, но для начала рассмотрим микро-архитектуру центрального процессора, и какое место занимает в ней этот стек. На рисунке ниже представлена структурная схема входящих в состав CPU основных блоков. При запуске нашей программы, функции системного загрузчика с префиксом LDR загружают образ двоичного кода с диска в оперативную память DDR-SDRAM (Synchronous Dynamic Random Access Memory), после чего код становится доступным центральному процессору CPU. Как только регистр-указатель EIP упрётся в точку-входа в программу EntryPoint, диспетчер памяти тут-же в пакетном режиме считывает из ОЗУ как-минимум по одной 4 Кбайтной странице из секции-кода и секции-данных (итого 8Кб), и сбрасывает их в кэш процессора L3. Такого алго придерживаются процессоры только на старте, а дальше – данные читаются из ОЗУ исключительно по-востребованию, блоками по 64-байт, чтобы их можно было поместить в одну линейку кэш "Cache-Line". Если софт гигантских размеров типа Photoshop или Word из пакета Office, то диспетчер может заполнить кодом\данными весь кэш L3, что влечёт за собой тормоза на старте. Здесь всё в штатном режиме, а вот дальше уже интересней.. Кэши L2 и L3 не разделяют информацию на код и данные, более того в архитектуре НТ (гипертрейдинг) они являются общими для всех ядер одного процессора. Зато кэшей L1 уже два – отдельно для кода и отдельно для данных. Структура кэш-линеек такова, что помимо самой информации, в них имеются и специальные поля под названием "Tag", где хранится старшая часть виртуального адреса ОЗУ, от куда была скопирована инфа. Проверяя эти теги процессор ищет в L2 байты, которые принадлежат секции-кода и отправляет их в L1-инструкций, и далее в исполнительный конвейер. Соответственно если в теге прописан адрес секции-данных, то линейка отправляется в L1-данных, который ведёт диалог исключительно с блоками Load\Store ядра процессора Execute, минуя его Front-End. https://forum.antichat.xyz/attachmen...eaed329d05.png Теперь рассмотрим ситуацию, когда декодер обнаруживает в L1 инструкцию PUSH – это может быть, например, передача параметров функции через стек. Поскольку стек представляет собой своеобразную секцию-данных, процессору приходится перегонять операнд инструкции PUSH по большой ветке кровообращения, из L1 инструкций, в L1 данных и обратно. Здесь становится очевидно, что в алгоритме вызова процедур и функций имеются недочёты, поскольку бесполезный транспорт данных явно снижает общую производительность. Для инженеров это было легче запрограммировать, чем искать компромиссы, ..тем-более что ситуации бывают разные и лучше выбрать золотую середину. Однако фанатиков нетрадиционного кода такой расклад не устраивал, и ещё на третьих пеньках они придумали вполне разумное решение этой проблемы (салам Магомед). В основе оригинальной мысли лежал тот факт, что если в секции-данных заранее подготовить стековый фрейм с готовыми аргументами, можно будет не копировать их в стек, а наоборот натравить на этот фрейм регистр-указатель стека ESP (Stack-Pointer). Поскольку процессор слепо верит этому регистру, то примет подложный стек за чистую монету и без лишних слов отработает запрос. Тут главное правильно расположить все аргументы функции в секции-данных, не забыв при этом зарезервировать место под адрес-возврата, куда его неявно помещает инструкция CALL. Посмотрим на такой пример: C-подобный: Код:
.• Избавиться от лишних пробелов в строке – ещё одна часто встречающаяся задача. В виду того-что готовой функции API для этих целей в природе не существует, всё приходится делать в ручную. Суть в том, чтобы запоминать предыдущий символ, и сравнивать его с текущим. Если оба пробелы, то пропускаем перезапись текущего в буфер, иначе всё в штатном режиме, без изменений. Вот простая как 2-копейки реализация, зато пользу от неё можно наблюдать в консоли: C-подобный: Код:
format pe console4. Заключение. Мелочи подобного рода сильно отравляют жизнь начинающим асматикам, а так.. (на случай, если грянет гром) "зонт" у нас уже имеется. В скрепке можно найти исполняемые файлы для тестов. Надеюсь ещё встретимся в сообществе античат , всем удачи и пока. |
Приятно видеть такого опытного человека, темболее ассемблерца. Лайк однозначно за труду
|
Цитата:
|
Тимур, спасибо за статью! Когда читаю Ваши статьи, особенно с такими отсылками, мне кажется что я учусь у какого-то древнего восточного мудреца.
|
Цитата:
главное разобраться вспоминаю деление в ассемблере из предыдущих техник из сундука логическими операциями спасибо за статью! пс. @Mikl___ книгу, которую рекомендовал, "Алгоритмические трюки для программистов" заказал |
Цитата:
|
Цитата:
Я вот буду си учить, а там плюс минус с ассемблером надо будет сталкиваться, чтоб лучше все понимать . И вообще, надо учить компьютер сайенс, как можно сейчас называть, все сразу на места становится, имею в виду понимание процессов. Даже элементарное понятие, почему индексация с 0, а не 1 @Marylin , я прав? |
Цитата:
|
Классная статья. Like за возврат из call по jmp. Я похожее для avr реализовывал, но там возвращался не из функции, а из аппаратного прерывания, но принцип тот же. Надо было проверять событие и в зависимости от него прыгать в свою часть программы.
Цитата:
|
@rusrst если вы поделитесь с нами вашими наработками AVR было-бы классно.
|
| Время: 06:52 |