Пишем свою операционку для Arduino. Шаг 5 — настоящий sleep и yield

Сегодня мы продолжаем делать нащу операционку для AVR и, в частности, Arduino. В предыдущих частях мы уже реализовали полноценное переключение контекстов процессов и добились многозадачного выполнения нескольких процессов. Теперь пора добавить поддержку таких системных вызовов, как sleep и yield. Первый будет предоставлять процессу возможность приостановить выполнение на конкретный промежуток времени, не занимая при этом процессорные ресурсы, а второй - просто передавать выполнение следующему по очереди процессу. Кроме того, мы внесем некоторые усовершенствования в само ядро нашей операционной системы atmos.

atmos sleep

Читать далее «Пишем свою операционку для Arduino. Шаг 5 — настоящий sleep и yield»

Пишем свою операционку для Arduino. Шаг 4 — первое ядро и многозадачный код

Сегодня настанет момент истины: мы напишем первое ядро нашей операционной системы atmos и запустим на нем простую многозадачную программу на Arduino!

coding in cpp

Вот это будет простыня, ребята...

Читать далее «Пишем свою операционку для Arduino. Шаг 4 — первое ядро и многозадачный код»

Пишем свою операционку для Arduino. Шаг 2 — односвязный список

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

А сегодня мы будем писать на C++ односвязный список. Так как наша операционная система для AVR будет работать в крайне ограниченных по ресурсам условиях, мы будем стараться сэкономить как можно больше места и сделать код быстрее. Без какой-либо структуры данных для хранения списка запущенных (а позднее и приостановленных) задач не обойтись. Односвязный список займет мало программной памяти, потребует очень мало оперативной памяти и в целом хорошо подойдет для наших нужд. Он предельно прост, но при этом мы постараемся максимально его оптимизировать.

forward list

Читать далее «Пишем свою операционку для Arduino. Шаг 2 — односвязный список»

Пишем свою операционку для Arduino. Шаг 1 — настраиваем таймер

Люблю я на досуге поиграться с 8-битными AVR-микроконтроллерами. Почему? Потому что они просты в использовании и программировании, у них весьма очевидное внутреннее устройство, но при всем этом они позволяют быстро, дешево и без sms запиливать достаточно нетривиальные проекты. Arduino (и всевозможные дешевые китайские клоны, разумеется) - вообще достаточно популярная железка (и среда разработки) среди погромистов и инженеров. Имея на руках пару breadboard'ов и клубок проводов для них, с Arduino вы сможете без пайки собрать макет какого-нибудь проекта и закодить его, практически не напрягаясь.

Но я сюда пишу не для того, чтобы поднять продажи китайцам, а чтобы заняться настоящей хардкорной разработкой под AVR. Сегодня мы с вами начнем писать настоящую операционную систему с вытесняющей многозадачностью, которую потом запустим на обычных железяках Arduino! Но это еще не все, ведь мы будем писать эту ОСь на C C++14, сдабривая все это щедрым количеством ассемблерных вставок и макросов. Вот это будет пламенная смесь! А назовем мы нашу операционную систему пафосно - Atmos!

Перед тем, как начать, я должен сказать, что подобные ОС достаточно профессионального уровня и с длительной поддержкой уже существуют. Это, например, кросс-платформенная богатая на фичи и толстая FreeRTOS, или компактная (но не очень удобная) Femto OS. Вы, конечно, можете открыть их исходные коды и постараться разобраться, в чем я вам желаю удачи. Но я в этом цикле статей поясню базовые принципы, которые стоят за созданием такой операционки для AVR и буду писать ее по шагам, разжевывая подробно каждый этап. Если вы готовы вместе со мной окунуться в раскаленные пучины кода, то вперед!

Читать далее «Пишем свою операционку для Arduino. Шаг 1 — настраиваем таймер»

Пишем упаковщик по шагам. Шаг шестой. TLS.

Предыдущий шаг здесь.

Появилась новая версия библиотеки для работы с PE-файлами (0.1.5). Перекачайте и пересоберите ее.

Пришло время заняться обработкой такой важной вещи, как Thread Local Storage (TLS) - локальной памяти потока. Что она из себя представляет? Это небольшая структура, которая говорит загрузчику PE-файлов о том, где находятся данные, которые должны быть выделены в памяти для каждого потока. Загрузчиком также производится вызов функции TlsAlloc, и значение, возвращенное ей, записывается по адресу, также указанному в этой структуре (называется это индексом). Кроме того, эта же структура может содержать адрес массива, хранящего набор коллбэков (адресов функций), которые будут вызваны загрузчиком при загрузке файла в память или при создании нового потока в процессе.

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

Читать далее «Пишем упаковщик по шагам. Шаг шестой. TLS.»

Пишем упаковщик по шагам. Шаг пятый. Ресурсы.

Предыдущий шаг здесь.

Пора усовершенствовать наш упаковщик. Он уже способен упаковывать и запускать самые простые бинарники, имеющие лишь таблицу импорта. Бинарники с экспортами, ресурсами, TLS, DLL с релокациями ему пока что не под силу. Нужно над этим работать. Для начала сделаем обработку второй по важности вещи после импортов - директории ресурсов.

Читать далее «Пишем упаковщик по шагам. Шаг пятый. Ресурсы.»

Пишем упаковщик по шагам. Шаг четвертый. Запускаем.

Предыдущий шаг: здесь.

Появилась новая версия библиотеки для работы с PE-файлами (0.1.4). Перекачайте и пересоберите ее.

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

Читать далее «Пишем упаковщик по шагам. Шаг четвертый. Запускаем.»

Пишем упаковщик по шагам. Шаг третий. Распаковываем.

Предыдущий шаг здесь.

Идем дальше! Пришло время написать распаковщик, именно этим мы начнем заниматься в этом шаге. Обрабатывать исходную таблицу импорта мы пока не будем, так как и в этом уроке нам будет, чем заняться.

Начнем мы вот с чего. Для работы распаковщика нам стопроцентно потребуются две WinAPI-функции: LoadLibraryA и GetProcAddress. В своем старом упаковщике я писал стаб распаковщика на MASM32 и вообще не создавал таблицу импорта. Я искал адреса этих функций в ядре, что несколько сложно и хардкорно, кроме того, это может вызвать неиллюзорные подозрения у антивирусов. Давайте в этот раз создадим обычную таблицу импортов и сделаем так, чтобы загрузчик сам нам сообщил адреса этих функций! Разумеется, набор из двух этих функций в таблице импорта так же подозрителен, как и полное их отсутствие, но ничто нам не мешает в будущем добавить еще другие левые случайные импорты из различных DLL-файлов. Куда загрузчик будет записывать адреса этих двух функций? Пора расширить нашу структуру packed_file_info!
Читать далее «Пишем упаковщик по шагам. Шаг третий. Распаковываем.»

Пишем упаковщик по шагам. Шаг второй. Пакуем.

Предыдущий шаг здесь

Сразу скажу, что по мере написания этого цикла статей я кое-что правлю и дорабатываю в своей библиотеке для работы с PE-файлами. Поэтому вам стоит ее перекачать и пересобрать - сейчас уже есть версия 0.1.3.

И мы продолжаем написание собственного упаковщика. В этом шаге пора переходить непосредственно к упаковке PE-файла. Я достаточно давно выкладывал простенький упаковщик, который был малоэффективным по двум причинам: во-первых, он использовал стандартные Windows-функции для упаковки и распаковки данных, обладающие достаточно низкой степенью сжатия и скоростью, во-вторых, паковались все секции PE-файла по отдельности, что не очень-то оптимально. В этот раз я сделаю по-другому. Мы будем считывать данные всех секций сразу, слеплять их в один кусок и упаковывать. В результирующем файле, таким образом, будет только одна секция (на самом деле две, потом поясню, почему), в которой мы сможем разместить и ресурсы, и код распаковщика, и сжатые данные, и вспомогательные таблицы. Мы получаем некоторый выигрыш, потому что не нужно тратить размер на файловое выравнивание, кроме того, алгоритм LZO явно более эффективен, чем RtlCompressBuffer, во всех отношениях.

Читать далее «Пишем упаковщик по шагам. Шаг второй. Пакуем.»

Пишем упаковщик PE-файлов по шагам. Шаг первый.

Раз уж я закончил разработку библиотеки на C++ для работы с PE-файлами, грех не использовать ее в каком-то более-менее серьезном проекте. Поэтому я разработаю с ее помощью упаковщик, поясняя по шагам, что я делаю, а либа на C++ сильно упростит нам жизнь. Итак, с чего же начать разработку упаковщика? Наверное, с выбора какого-нибудь несложного бесплатного алгоритма сжатия. После непродолжительных поисков таковой был мной найден: LZO. Он поддерживает множество различных видов сжатия (можно считать, разновидностей), и LZO1Z999 - самая эффективная по степени сжатия из всех доступных. Это, конечно, не ZIP, но приближается к нему по эффективности: 550-килобайтный файл был сжат zip'ом с максимальной степенью сжатия в 174 килобайта, в то время как LZO сжал тот же файл до 185 килобайтов. Однако у LZO гораздо более быстрый распаковщик. Он также оказался базонезависимым, то есть, его можно разместить по любому виртуальному адресу, и он будет работать без всяких корректировок адресов. Размер распаковщика приятно удивил: Visual Studio с оптимизациями по размеру и отключением исключений и проверок буферов дала результат в 613 байтов кода! Этот алгоритм нам подойдет.

Читать далее «Пишем упаковщик PE-файлов по шагам. Шаг первый.»