Создаем функциональный блок в виде - снеговика, который будет описывать поведение программы в зависимости от температуры.
Создаем функциональный блок в виде - снеговика, который будет описывать поведение программы в зависимости от температуры.

Хотя я считаю свой проект серьезным - не являюсь узким специалистом по разработке ОС. Проект пока любительский, и могу позволить себе некоторую свободу по разработке. Тут ставил для себя такие задачи:

  • ОС должна работать на виртуальном уровне (полная независимость от железа).

  • Жесткое реальное время.

  • Вытесняющая многозадачность.

  • Динамическое  добавление, изменение, удаление задач.

Так как в примере используется код - виртуальный гибридный ассемблер (собственная ISA) под софт-процессор собственной разработки, опишу свой проект:

  • Собственная система команд: WIA32. Расшифровывается как - широкий непосредственный адрес. 32 битные инструкции.

  • Разрабатывал свой компилятор под WIA32. Не LLVM или что то подобное. Работа с памятью (размещение переменных, выравнивание и прочее), аллокация регистров, оптимизация - собственные алгоритмы. Не настолько продвинут как классические тяжелые компиляторы  - но математически корректный, и достаточен для задачи.

  • Регистровая виртуальная машина  на стороне микроконтроллера для выполнения инструкций WIA32.

Все это программируется или в текстовом редакторе, или в моей собственной графической среде разработки:

Хотя как устроена ВМ для этого нужно отдельный пост, но коротко - с технической точки зрения внутри микроконтроллера, вся программа, данные хранятся в обычном массиве.

uint8_t memory[SIZE] ={код ядра- код пользователя -данные, … стек}

Родной синтаксис моего компилятора, код планировщика и код пользователя:

.DataSection
.Types

StackMap typedef{
  R:     dword[16];
  Size:      dword;
}
Thread typedef{

  Priority:     dword;
  IsActive:     dword;
  Stack:     StackMap;
  Time:         dword;
  Period:       dword;
}
TasksManager typedef{
    tasks:               pointer[8];
    Item:                     dword;
    CurrentContext:           dword;
    TaskCount:                dword;
}

EModule_ typedef
{
  IsInterrupt:    byte;
  Ptr8t:          byte;
  IsPwr:          byte;  
  Rwt:            byte;  
  Rdy_:           byte;  
  Piw:            byte;  
  DI_0:           byte;  
  DI_1:           byte;  
  DI_2:           byte;  
  DI_3:           byte;  
  DI_4:           byte;  
  DI_5:           byte;  
  DI_6:           byte;  
  DI_7:           byte;
  DI_8:           byte;  
  DI_9:           byte;  
  DI_10:           byte;  
  DI_11:           byte;  
  DI_12:           byte; 
  DI_13:           byte; 
  DI_14:           byte;    
  DI_15:           byte;  
}

System typedef
{
  ID:                  word;
  IO_phy:           pointer;
  Interrupt:          dword;
  IP:               byte[4];
  Status:              byte;
  IsActive:            byte;
  emodule_:        EModule_;
  Time:               dword;
  AddrTime:           dword;
}

TG_16proj typedef
{
    NameProcess:     byte[32];
    StartProg:           byte;
    StopExtSrc:          byte;
    CallBackCode:  byte[2][3];
    MSG_IO:         byte[356];
}



.EndTypes
.Declaration
 thread:                     TasksManager;
 system:                           System; 
 system.IO_phy     =           0x40020414;



 tproj:                         TG_16proj;

 tproj.NameProcess = "%s Task A. Parameters";

 tproj.MSG_IO="%clear_   %s Ports State: \n 0] %b(system.emodule_.DI_0)  \n 1] %b(system.emodule_.DI_1)  \n 2] %b(system.emodule_.DI_2)  
 
 \n 3] %b(system.emodule_.DI_3)  \n 4] %b(system.emodule_.DI_4)  \n 5] %b(system.emodule_.DI_5)  \n 6] %b(system.emodule_.DI_6) 
 
 \n 7] %b(system.emodule_.DI_7) \n 14] %b(system.emodule_.DI_14)  \n ";


count32:       dword;
locconst:      dword;
savvr:         dword;


system.Interrupt=1000;
system.emodule_.Ptr8t=1;

.EndDeclaration
.EndDataSection

.Program
RWCNTX RDCD                             R0;

JISBIT R0 0                    Taskmanager;
JISBIT R0 1                    SaveContext;

CALL InitialManager;
JMPI Taskmanager;
NOP 0;

SaveContext:
RWCNTX     WRITE                thread.CurrentContext;
RWCNTX     WRCD                                    2;
JMPI                                      Taskmanager;
NOP 0;


InitialManager:
MOV   R0                                 R28;
SDRI  R0                     system.AddrTime;
MOVI  R31                                  1;
ADDRL R0           @LabelAddress(redrobmarg);
MOV   R29                                 R0;
 
MOV   R28                                 SP;
SUBI  R28                                120;

MOVI  R0                                   0;
SDRI  R0                    thread.TaskCount;




MOVI   R13                               5000;
ADDRL  CNXT           @LabelAddress(IO_Scan_);
CALL                                  AddTask;


MOVI   R13                               7000;
ADDRL  CNXT              @LabelAddress(TaskA);
CALL                                  AddTask;


MOVI   R13                               3000;
ADDRL  CNXT              @LabelAddress(TaskB);
CALL                                  AddTask;



MOVI   R13                               4000;
ADDRL  CNXT              @LabelAddress(TaskC);
CALL                                  AddTask;




LDRI    R0                   thread.TaskCount;
SDRI    R0                        thread.Item;

InitIO:
NOP;

RET;
NOP 0;






AddTask:
LDRI    R7                     thread.TaskCount;
MULI    R7                                    4;
ADDRL   R6         @SymbolAddress(thread.tasks);
ADD     R7         R7                        R6;
SDR     R7                                  R28;
PUSH                                        R28;
SUBI    R28                       @size(Thread);
ADDI    R28      @SymbolAddress(Thread.Stack.R);
SDR     R28      CNXT        @SymbolAddress(PC);
POP     R7;
SDR     R28      R7          @SymbolAddress(FP);
SUBI    R7                        @size(Thread);
SDR     R28      R7          @SymbolAddress(LP);
SDR     R28      R7          @SymbolAddress(SP);
LDRI    R15                    thread.TaskCount;
ADDI    R15                                   1;
SDRI    R15                    thread.TaskCount;                              
SUBI    R7                                   32;
MOV     R28                                  R7;
RET;




Taskmanager:
CALL                                              ItemsUp;
LDRI     CNXT                                 thread.Item;
MULI     CNXT                                           4;
ADDRL    R13                 @SymbolAddress(thread.tasks);
ADD      CNXT                R13                     CNXT;
LDR      R15                                         CNXT;
MOV      CNXT                                         R15;
SUBI     R15                                @size(Thread);
ADDI     R15               @SymbolAddress(Thread.Stack.R);
SDRI     R15                        thread.CurrentContext;
RWCNTX   READ                       thread.CurrentContext;

NOP 0;


ItemsUp:
LDRI     R4                      thread.Item;
LDRI     R5                 thread.TaskCount;
JGEQ     R4          R5                    4;
ADDI     R4                                1;
SDRI     R4                      thread.Item;
RET;
MOVI     R4                                0;
SDRI     R4                      thread.Item;
RET;




IO_Scan_:
LDRI     R0                      system.Time; 
LDRI     R1                  system.AddrTime; 
LDPHY    R1          R1                    0;
SUB      R0          R1                   R0; 

MOVI     R2                             1000;
JGEQ     R2          R0                    4; 
SDRI     R1                      system.Time;
ADDRL    R0    @SymbolAddress( tproj.MSG_IO);
EXTFN    R0          R0      R0           32; 

MOVI  R15                      0;

LDBI  R14   system.emodule_.DI_0;
SHLI  R14        0             0;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_1;
SHLI  R14        0             1;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_2;
SHLI  R14        0             2;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_3;
SHLI  R14        0             3;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_4;
SHLI  R14        0             4;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_5;
SHLI  R14        0             5;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_6;
SHLI  R14       0              6;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0             7;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0             8;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0             9;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0            10;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0            11;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0            12;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0            13;
OR    R15   R14              R15;


LDBI  R14  system.emodule_.DI_14;
SHLI  R14        0            14;
OR    R15   R14              R15;

LDBI  R14   system.emodule_.DI_7;
SHLI  R14        0            15;
OR    R15   R14              R15;

LDRI  R14          system.IO_phy;
SDPHY R14                    R15;
JMPI                 Taskmanager;





redrobmarg:
NOP 0;





TaskA:
LDBI  R6 system.emodule_.DI_0;
RBIT  R6                    0;
SDBI  R6 system.emodule_.DI_0;
CALL Ladder;
JMPI TaskA;
NOP 0;

TaskB:
LDBI  R6 system.emodule_.DI_7;
RBIT  R6                    0;
SDBI  R6 system.emodule_.DI_7;
CALL Processbinary;
JMPI TaskB;
NOP 0;

TaskC:
LDBI  R6 system.emodule_.DI_14;
RBIT  R6                     0;
SDBI  R6 system.emodule_.DI_14;
CALL ProcessMath;
JMPI TaskC;
NOP 0;



Processbinary:
LDRI R0  system.Interrupt;
MULI R0                2;
BinaryItem:
SUBI R0 1;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
LDBI   R15 system.emodule_.Ptr8t;
JFLSE R15 BinaryItem;
JTRUE R0  BinaryItem;
RET;





Ladder:
LDRI R0  system.Interrupt;
MOVI R15 1;
LadderItem:
SUBI R0 1;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
ANDM  R15 system.emodule_.Ptr8t;
JTRUE R0  LadderItem;
RET;




ProcessMath:
LDRI R0   system.Interrupt;
MathItem:
SUBI R0 1;
LDRI R15 count32;
LDRI R14 locconst;
ADD   R13 R14      R15;
SDRI R13 savvr;

LDRI R15 count32;
LDRI R14 locconst;
SUB   R13 R15      R14;
SDRI R13 savvr;

LDRI R15 count32;
LDRI R14 locconst;
MUL   R13 R15      R14;
SDRI R13 savvr;

LDRI R15 count32;
LDRI R14 locconst;
DIV   R13  R15      R14;
SDRI R13  savvr;

LDRI R15 count32;
LDRI R14 locconst;
ADD   R13 R14      R15;
SDRI R13 savvr;

LDRI R15 count32;
LDRI R14 locconst;
SUB   R13 R15      R14;
SDRI R13 savvr;

LDRI R15 count32;
LDRI R14 locconst;
MUL   R13 R15      R14;
SDRI R13 savvr;

LDRI R15  count32;
LDRI R14 locconst;
DIV   R13  R15      R14;
SDRI R13  savvr;

LDRI R15 count32;
LDRI R14 locconst;
MUL   R13 R15      R14;
SDRI R13 savvr;

LDRI R15  count32;
LDRI R14 locconst;
DIV   R13  R15      R14;
SDRI R13  savvr;

JTRUE R0 MathItem;

RET;


Exitprog:
NOP 0;

.EndProgram

StackMap - содержит сохраненный контекст.

Thread   - содержит поля с описанием задачи, и содержит внутри структуру StackMap .

TasksManager - это главная структура планировщика. в ней массив указателей на задачи (количество в зависимости от доступной памяти).

EModule_ - хранит цифровое представление входов выходов контроллера.

Так как любая задача независимо может менять поведение выводов, EModule_  - есть единой точкой работы с физическими выводами.

Программа начинается с нулевого адреса виртуального ОЗУ uint8_t memory[0].

RWCNTX RDCD            R0;// Читаем статус контекста
/Проверяем статус*/
JISBIT R0 0   Taskmanager;
JISBIT R0 1   SaveContext;
CALL InitialManager;
JMPI Taskmanager;

События сбрасывают счетчик команд - в ноль, а там по коду события определяем что произошло. Если RDCD  - пуст, значит это первоначальный старт и переходим в инициализацию CALL InitialManager; Так же сюда вписываются адреса обработчиков других событий (ошибок и прочее).

При нативной инициализации ВМ,на аппаратном уровне,  в регистре R28 - хранится указатель на системный таймер 

cpu.R[28].ui = (unsigned int)(&cpu.Time);

Поэтому уже на виртуальном уровне важно не затереть указатель, и сразу сохранить в виртуальную переменную (Ну или не затирать значение R28).

Сохраняем указатель на аппаратный таймер, для использования на виртуальном уровне

MOV   R0                                 R28;

SDRI  R0                     system.AddrTime;

Добавление задач.

Несмотря на то мой компилятор поддерживает достаточно продвинутый виртуальный ассемблер с высокоуровневыми данными, проще всего работать через мою графическую среду которая автоматически транслирует LD\FBD в этот ассемблер и разносит задачи.

Но сама организация добавления задач выглядит так:

AddTask:

LDRI    R7                     thread.TaskCount;

MULI    R7                                    4;

ADDRL   R6         @SymbolAddress(thread.tasks);

ADD     R7         R7                        R6;

SDR     R7                                  R28;

PUSH                                        R28;

SUBI    R28                       @size(Thread);

ADDI    R28      @SymbolAddress(Thread.Stack.R);

SDR     R28      CNXT        @SymbolAddress(PC);

POP     R7;

SDR     R28      R7          @SymbolAddress(FP);

SUBI    R7                        @size(Thread);

SDR     R28      R7          @SymbolAddress(LP);

SDR     R28      R7          @SymbolAddress(SP);

LDRI    R15                    thread.TaskCount;

ADDI    R15                                   1;

SDRI    R15                    thread.TaskCount;                              

SUBI    R7                                   32;

MOV     R28                                  R7;

RET;

Потом каждая следующая задача добавляется так:

MOVI   R13                               7000;
ADDRL  CNXT              @LabelAddress(TaskA);
CALL                                  AddTask;

Под каждую задачу выделяется память на виртуальном стеке. В виртуальном стеке создаем переменную Thread и инициализируем регистры контекста (на каком адресе задача, где ее стек и прочее).

После того как добавили все задачи, переходим в Taskmanager который извлекает задачи хранящиеся thread.tasks

Taskmanager:
CALL                                              ItemsUp;
LDRI     CNXT                                 thread.Item;
MULI     CNXT                                           4;
ADDRL    R13                 @SymbolAddress(thread.tasks);
ADD      CNXT                R13                     CNXT;
LDR      R15                                         CNXT;
MOV      CNXT                                         R15;
SUBI     R15                                @size(Thread);
ADDI     R15               @SymbolAddress(Thread.Stack.R);
SDRI     R15                        thread.CurrentContext;
RWCNTX   READ                       thread.CurrentContext;

У каждой задачи есть поля типа  приоритета, времени срабатывания и прочее, но так как тут в примере используется макро-ассемблер, я не стал раздувать код.

Каждая задача зациклена (в вечном цикле) инвертирует свой вывод, чем моргает светодиодом.

TaskA:
LDBI  R6 system.emodule_.DI_0;
RBIT  R6                    0;
SDBI  R6 system.emodule_.DI_0;
CALL Ladder;
JMPI TaskA;
NOP 0;

Так же внутри есть переход демонстрирующий трансляцию графических языков типа LD в виртуальный ассемблер:

ANDM  R15 system.emodule_.Ptr8t;

ANDM  R15 system.emodule_.Ptr8t;

ANDM  R15 system.emodule_.Ptr8t;

что является аналогом контактов/катушек типа. Среда IDE автоматически делает этот перевод:

Каждая задача работает отведенный ей квант времени (если она сама себя не остановит), ее прерывает системный таймер.

void SysTick_Handler(void)
{
	cpu.Time++;
	cpu.TimeOut++;
	if(PC<=cpu.R[29].ui || cpu.State ||cpu.TimeOut<cpu.R[31].ui){return;}
      cpu.State = MODETASKMANAGER_;
      cpu.TimeOut = 0;
}

В PC<=cpu.R[29].ui регистре хранится граница ядра, отделяющая код ползователя от кода ядра планировщика. Так же в начале программы мы ставим в регистр cpu.R[31].ui - значение, с которой будем менять задачи, в данном случае - каждую миллисекунду.

При иннициализации виртуальной ОС мы читаем адрес метки redrobmarg

ADDRL R0           @LabelAddress(redrobmarg);
MOV   R29                                 R0;

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

Но пользователь тоже может добавить задачу ниче метки redrobmarg, и тогда его задача так же сработает от начала до конца и не будет остановлена, как например наша IO_Scan_:  которая изменяет физические порты.

Такая архитектура выбрана потому что - ОС может вовсе удалена (для экономии памяти), Или посреди программы пользователь может запретить смену задачи установив

cpu.R[29].ui = 0xFFFFFFFF; 

тогда весь код от начала и до конца будет работать без прерываний, то есть станет привилегированным или однопоточным.

Таким образом сам пользователь сможет:

  • вообще убрать ОС, 

  • написать свою ОС без какого либо вмешательства в нативный код

  • обновить “ОС” по воздуху.

Возможно ОС - это пока громко сказано, драйверов работы с оборудованием тут не на работано, но сути не меняет.

Тестовые программы:

Так как наша ВМ запущена на STM32F722 Nucleo, у нас четыре задачи

  • IO_Scan_: обновляет выходы ПЛК. А так же каждую секунду отправляет форматированную строку на внешнее устройство.

tproj.MSG_IO="%clear_   %s Ports State: \n 0] %b(system.emodule_.DI_0)  \n 1] %b(system.emodule_.DI_1)  \n 2] %b(system.emodule_.DI_2)  
 
 \n 3] %b(system.emodule_.DI_3)  \n 4] %b(system.emodule_.DI_4)  \n 5] %b(system.emodule_.DI_5)  \n 6] %b(system.emodule_.DI_6) 
 
 \n 7] %b(system.emodule_.DI_7) \n 14] %b(system.emodule_.DI_14)  \n ";

На этапе компиляции , компилятор заменяет названия переменных типа %b(system.emodule_.DI_7) и подставляет - адрес переменных, а железо уже выводит каждую секунду форматированную строку о состоянии выводов. Мы можем например динамически создавать строки   в формате JSON и передавать по RS485 или TCP (если такой есть) на удаленные сервера или устройства. Без %s будет выводиться поток байт.

  • TaskA: Задача с LD цепями , и моргает 0 портом (зеленый).

  • TaskB Задача LD цепями, моргает 7 портом (синий).

  • TaskС Задача имитирующая математические операции внутри LD цепей. Моргает 14 портом (красный).

Столько нюансов с регистрами, контекстом, сохранении и восстановлении и прочее, что когда программа исправно работает день, два, неделю - вызывает хорошее настроение, так как это требует предельно слаженной работы всех отдельных проектов которые ты сделал :

  • компилятора который точно просчитывает адреса/переходы сложных данных и извилистых путей инструкций. Так как упоминал это не LLVM который многое делает из коробки.

  • Самой физической виртуальной машины, которая может показаться простой - но легко запутаться в указателях на стеки, кадры стека и всякие смещения.

  • И конечно же среды IDE .

Так тяжело моргание светодиодом никогда не давалось.

Комментарии (0)