Implementierung des Reflectionzugriffes in ein einfaches Zielsystem

Implementierung des Reflectionzugriffes in ein einfaches Zielsystem

Inhalt


Topic:.inspc.targetProxyTarget.

Der Aufwand, den Reflectionmechanismus in ein Zielsystem (target) zu implementieren ist durchaus hoch. Es muss relativ viel const-Speicher vorhanden sein, um die entsprechenden symbolischen Zugriffe zu definieren, es muss eine Stringverarbeitung unterstützt werden und relativ viele C-Quellen aus der CRuntimeJavalike müssen mit compiliert werden. Für den Zugriff muss es einen leistungsfähigen Mechanismus, Sockets und Ethernet, geben.

Das alles kann nicht immer vorausgesetzt werden:

In diesen Fällen ist es möglich, einen Vermittler (Proxy) zu verwenden. Das Proxy kennt die symbolischen Reflection-Informationen des Target und wird jeweils mit aktualisiert, wenn sich die Software im Target ändert. Im Proxy wird eine Vorverarbeitung durchgeführt. Das Proxy kommuniziert mit dem Target über ein relativ einfaches Protokoll, was sich beispielsweise mit Dual-Port-RAM realisieren lässt, wenn beide Prozessoren auf der selben Hardware laufen. Alternativ kann es auch eine serielle Verbindung sein, oder ein einfaches Socket-Protokoll auf der Basis weniger Daten. Dann ist das Proxy ein getrenntes Gerät, was aber softwareseitig mit den Reflection-Informationen des Target aktualisiert wird. Das Proxy kann auch auf einem nicht stationärem PC (Maintenance-Notebook) laufen, muss dann aber mit den richtigen Reflectioninformationen geladen werden.


1 Anforderung an das Target

Topic:.inspc.targetProxyTarget.reqInspc.

Das Target muss über eine Kommunikationsschnittstelle verfügen, über die wenige Worte für den Reflectionzugriff übertragen werden.

Im Target muss die Kommunikationsschnittstelle abgefragt werden. Das kann auch in der Hintergrundschleife erfolgen. Diese Arbeitsweise ist empfohlen, da ein Handshake-Zugriff benutzt wird, der beliebig langsam sein kann. Im Target wird dann 'nur' die Restrechenzeit genutzt und nicht etwa zusätzliche Rechenzeit für den Inspectorzugriff benötigt.

Die Kommunikationsschnittstelle muss die für den Inspector gedachten Daten ablegen und abholen. Das ist speziell zu programmieren. Ablegen und Abholen kann in einem globalen Speicher erfolgen. Bei einer Dual-Port-Ram-Schnittstelle braucht dazu nichts programmiert zu werden, da das Ablegen und Abholen im Dual-Port-Ram-Bereich bereits erledigt ist.

Die Daten im Target müssen baumartig organisiert sein. Es muss eine main-Datenstruktur geben, von der alle anderen Daten aus erreichbar sind. Einzelne globale Variable sind ganz schlecht. Das scheint eine extreme Anforderung zu sein, da oft in einfachen Targets mit einzelnen globalen Variablen gearbeitet wird. Aber:

Die Organisation aller Daten in einer struct, die dann global statisch instanziiert wird, ist identisch it dem Anlegen von einzelnen globalen Variablen. Die Schreibweise ist etwas anders. Der Zugriff im Maschinencode ist identisch. Die Umarbeitung von globalen Einzelvariablen nach einer struct ist also relativ einfach und ein Weg hin zu strukturiertem (oder objektorientiertem) C-Code:

int x1;
int array[20];  //Einzelvariable
MyType data34;  //oder einzelne statische struct


ist maschinentechnisch identisch mit


typedef struct Maindata_t {
  int x1;
  int array[20];  //Einzelvariable
  MyType data34;  //oder einzelne statische struct
} Maindata_s;

Maindata_s data = {0};


Der Zugriff in Routinen kann unverändert global erfolgen: statt array[ix] wird nur data.array[ix] geschrieben. Für bestehende Sources ist das ein einfaches 'search and replace'. Man hat aber dann für die Weiterentwicklung der Software nunmehr auch die Möglichkeit, über eine Referenz zuzugreifen und beispielsweise in Testumgebungen auch Mehrfachinstanzen anzulegen. Der Zugriff über Zeiger ist in der Regel nicht etwa langsamer als der direkte globale Zugriff, wie man meinen könnte (notwendige Adressrechnung). Moderne Prozessoren, auch kleine, führen dies parallel zu anderen Operationen aus. Der Zugriff über Zeiger ist ggf. sogar schneller, da die Adresse im Maschinencode keine Voll-Adresse zu sein braucht (beispielsweise 32 bit breit) sondern es wird nur ein viel kleinerer Offset angegeben. Der Maschninenbefehl selbst wird also kürzer:

void mySubroutine(Maindata_s* data) {

  data->array[ix];
  

Ein Target, das mit einzelnen globalen Variablen arbeitet, sollte also umgeschrieben werden. Will man das nicht, dann geht auch die Anlage einer Main-Struktur, die mit den Referenzen der weiter genutzen globalen Variablen gefüllt wird:

int x1;         //Globale variable bleiben bestehen:

int array[20];  //Einzelvariable
MyType data34;  //oder einzelne statische struct

typedef struct Maindata_t {
  int* x1;
  int** array;  //Einzelvariable
  MyType* data34;  //oder einzelne statische struct
} Maindata_s;

Maindata_s data = {
  &x1
, array
, &data34
};


Die Reflectiongenerierung und der Zugriff kann mit diesen Zeigern umgehen.

In der Hintergundschleife oder im Kommunikationsthread muss dann zyklisch die Routine im Quellfile CRJ/Inspc/InspcTargetProxyTarget.c gerufen werden.


2 Anpassen der Datenzugriffsroutine processInspcCmdOnTarget_Inspc(...)

Topic:.inspc.targetProxyTarget.cmdAddr.

Es