Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

49 Commits

Repository files navigation

rMach

reallyMach — Mach, но без лишнего.

Смысл

Это микроядро, но без тонны жира.

Здесь мы отошли от стандартных способов сделать многозадачность на MicroPython.

Мы сделали ставку на порты.

Как работает?

У нас есть данные технологии:

  1. Handoff scheduling - шлёшь сообщение и другой процесс уже обрабатывает его.
  2. Reference counting (на порты) - чтобы не было пустых портов в памяти.
  3. VM для процессов - вы скажете что это ненормально, но это единственный способ сделать MicroPython с вытеснением.
  4. O(1) Linux 2.6 scheduler - есть недочеты, но работает примерно в O(1).
  5. State machines - для событий и handoff scheduling.
  6. Асинхронные порты - ну... Единственный способ общение.
  7. Software isolating - в VM нету команд "залезть в чужую память".
  8. Права на порты - чтобы чужие процессы не лезли в порты которые им не дали.

Очень сложный механизм, но 702 строк вы прочитаете с удовольствием.

Также у нас 4 уровня:

  1. Железо. Тут процессор и всё такое
  2. MicroPython.
  3. Ядро.
  4. VM.
  5. Процессы.

Если от нуля считать то их 4.

Да. Ядро на MicroPython. А что?

Твой первый процесс

Создаем порт и выводим сообщение:

CREATE_PORT # 1. Создали порт (ID теперь в стеке)STORE my_port # 2. Сохранили ID в переменную 'my_port' (стек пуст)PUSH42 # 3. Положили данные (42)PUSH0 # 4. Порт для ответа (нам не нужен, пишем 0)FETCH my_port # 5. Достали ID порта из переменной (куда слать)SEND # 6. Улетело! (Стек снова пуст)FETCH my_port # 7. Снова берем ID нашего портаRECV # 8. Ядро лезет в порт, достает '42' и кладет в СТЕК!PRINT # 9. ВОТ ТЕПЕРЬ ОНО ВЫВЕДЕТ 42HALT # 10. Конец

Как работать?

Во-первых, почитайте о Mach. Это будет большой бонус вам.

Во-вторых, почитайте код main.py - там основной функционал.

Вот команды для VirtualMachine:

  • PUSH [val] — Положить число или объект в стек. Основа всего.
  • FETCH [var] — Взять значение переменной из окружения (env) и положить в стек.
  • STORE [var] — Забрать значение из стека и сохранить в переменную.
  • ADD, SUB, MUL, DIV — Классическая арифметика над двумя верхними значениями стека.
  • LT, GT, EQ, NOTEQ — Логика сравнения. Результат (1 или 0) падает обратно в стек.
  • JZ / JNZ [addr] — Переход по адресу, если в стеке 0 (или не 0). Основа циклов и условий.
  • JMP [addr] — Безусловный прыжок. Навигация по байт-коду.
  • SEND — Системный вызов: забрать из стека (данные, порт_ответа, порт_назначения) и отправить.
  • RECV — Системный вызов: ждать сообщения на порту. Если пусто — VM засыпает (WAITING).
  • CREATE_PORT — Рождение новой точки доступа. Возвращает ID порта в стек.
  • LIST / DICT — Сборка сложных структур данных из последних n элементов стека.
  • INDEX — Доступ к элементу списка или словаря по ключу/индексу из стека.
  • APPEND [var] — Добавление элемента в существующий список или словарь.
  • PRINT — Вывод верхнего элемента стека в консоль.
  • RETURN - yield в моей vm.
  • HALT — Завершение исполнения.

Но, если что почитайте src/proc.py.

VM вроде понятная.

Может быть немного проблемно понять стек, но если чуть-чуть поиспытать то у вас все получится.

Также у нас есть инлайнинг:

.func TESTPUSH1PUSH2ADD STORE a.end

Вот как вызвать такое:

TEST

Лучше делать JMP из функций.

И ещё кстати появились метки:

# Программа которая слушает порт.func receive FETCH my_port RECV STORE res FETCH res PRINT.endCREATE_PORTSTORE my_port:LOOP receiveJMP :LOOP

Что-то вроде так ^-^.

В последнее время добавил:

  1. ^ - пробел (hello^world)
  2. ~ - срез строки (hello~world выдаст: hello world)

В-третьих, копируйте все файлы из src на контроллер. Не саму папку.

В-четвертых, py handlers - ваш способ общаться с железкой без VM.

Как регистрировать?

ipc_obj.mk_py_h(handler)

В них приходят сообщения:

(id_вашего_py_handler, куда_вы_отвечаете, сообщение_процесса)

То есть вы делаете:

ipc_obj.syscall_send(py_handler_id, msg[1], res)

Пример такого хандлера:

defmy_handler(msg, ipc):
ipc.syscall_send(msg[0], msg[1], 'hello!')

Ещё предупреждаю - не пишите циклы в py handler'ах. Они не вытесняются и не планируются.

Также, можете почитать rMachIPC() из файла src/ipc.py.

Потребление памяти.

Вот тест памяти:

fromschedimport*fromipcimport*importgcsched=PrioSched()
ipc=rMachIPC()
kernel=Kernel(sched, ipc)
gc.collect()
free=gc.mem_free()
alloc=gc.mem_alloc()
total=free+allocusage_pct= (alloc/total) *100f_kb=free/1024a_kb=alloc/1024print(f"{f_kb:.1f}")
print(f"{a_kb:.1f} ({usage_pct:.2f}%)")

Результат:

181.0
19.9 (9.69%)

181.5 - свободно КБ.

19.9 - КБ используется.

9.90% занято.

Что делать если проблемы с написанием?

Во-первых, платформа ещё в тестировании.

Во-вторых, при багах:

dimasoft976@gmail.com

Мой gmail, пишите что хотите.

Кстати.

Если увидите DIED в конце работы main.py - это нормально. Просто там небольшой race condition.