Skip to content

SystemSoftware2/rMach

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 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' (стек пуст)

PUSH 42        # 3. Положили данные (42)
PUSH 0         # 4. Порт для ответа (нам не нужен, пишем 0)
FETCH my_port  # 5. Достали ID порта из переменной (куда слать)
SEND           # 6. Улетело! (Стек снова пуст)

FETCH my_port  # 7. Снова берем ID нашего порта
RECV           # 8. Ядро лезет в порт, достает '42' и кладет в СТЕК!
PRINT          # 9. ВОТ ТЕПЕРЬ ОНО ВЫВЕДЕТ 42
HALT           # 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 TEST
 PUSH 1
 PUSH 2
 ADD
 STORE a
.end

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

TEST

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

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

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

CREATE_PORT
STORE my_port

:LOOP
  receive
JMP :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)

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

def my_handler(msg, ipc):
    #допустим наш id - 1
    ipc.syscall_send(1, msg[1], 'hello!')

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

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

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

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

from sched import *
from ipc import *
import gc

sched = PrioSched()
ipc = rMachIPC()
kernel = Kernel(sched, ipc)

gc.collect()

free = gc.mem_free()
alloc = gc.mem_alloc()
total = free + alloc

usage_pct = (alloc / total) * 100

f_kb = free / 1024
a_kb = alloc / 1024

print(f"{f_kb:.1f}")
print(f"{a_kb:.1f} ({usage_pct:.2f}%)")

Результат:

181.5
19.5 (9.69%)

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

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

9.69% занято.

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

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

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

dimasoft976@gmail.com

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

Кстати.

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