MicroEJ Core Engine

The MicroEJ Core Engine and its components represent the core of the Architecture. It is used to compile and execute at runtime the MicroEJ Application code.

Functional Description

The following diagram shows the overall process. The first two steps are performed within the MicroEJ Workbench. The remaining steps are performed within the C IDE.

MicroEJ Core Engine Flow

MicroEJ Core Engine Flow

  1. Step 1 consists in writing a MicroEJ Application against a set of Foundation Libraries available in the platform.
  2. Step 2 consists in compiling the Application code and the required libraries in an ELF library, using the SOAR.
  3. Step 3 consists in linking the previous ELF file with the Core Engine library and a third-party BSP (OS, drivers, etc.). This step may require a third-party linker provided by a C toolchain.

Architecture

The Core Engine and its components have been compiled for one specific CPU architecture and for use with a specific C compiler.

The Core Engine implements a green thread architecture. It runs in a single RTOS task.

In the following explanations the term “RTOS task” refers to the tasks scheduled by the underlying OS; and the term “MicroEJ thread” refers to the Java threads scheduled by the Core Engine.

A Green Threads Architecture Example

A Green Threads Architecture Example

The activity of the Core Engine is defined by the Application. When the Application is blocked (i.e., when all the MicroEJ threads sleep), the RTOS task running the Core Engine sleeps.

Capabilities

The Core Engine defines 3 exclusive capabilities:

  • Mono-Sandbox: capability to produce a monolithic firmware (default one).
  • Multi-Sandbox: capability to produce a extensible firmware on which new applications can be dynamically installed. See section Multi-Sandbox.
  • Tiny-Sandbox: capability to produce a compacted firmware (optimized for size). See section Tiny-Sandbox.

All the Core Engine capabilities may not be available on all architectures. Refer to section Supported MicroEJ Core Engine Capabilities by Architecture Matrix for more details.

Implementation

The Core Engine implements the [SNI] specification. It is created and initialized with the C function SNI_createVM. Then it is started and executed in the current RTOS task by calling SNI_startVM. The function SNI_startVM returns when the Application exits or if an error occurs (see section Error Codes). The function SNI_destroyVM handles the Core Engine termination and must be called after the return of the function SNI_startVM.

Only one instance of the Core Engine can be created in the system, and both SNI_createVM and SNI_destroyVM should only be called once. When restarting the Core Engine, don’t call SNI_createVM or SNI_destroyVM before calling SNI_startVM again. For more information, refer to the Restart the Core Engine section.

The file LLMJVM_impl.h that comes with the platform defines the API to be implemented. See section LLMJVM: MicroEJ Core Engine.

Initialization

The Low Level Core Engine API deals with two objects: the structure that represents the platform, and the RTOS task that runs the platform. Two callbacks allow engineers to interact with the initialization of both objects:

  • LLMJVM_IMPL_initialize: Called once the structure representing the platform is initialized.
  • LLMJVM_IMPL_vmTaskStarted: Called when the platform starts its execution. This function is called within the RTOS task of the platform.

Scheduling

To support the green thread round-robin policy, the platform assumes there is an RTOS timer or some other mechanism that counts (down) and fires a call-back when it reaches a specified value. The platform initializes the timer using the LLMJVM_IMPL_scheduleRequest function with one argument: the absolute time at which the timer should fire. When the timer fires, it must call the LLMJVM_schedule function, which tells the platform to execute a green thread context switch (which gives another MicroEJ thread a chance to run).

When several MicroEJ threads with the same priority are eligible for execution, the round-robin algorithm will automatically switch between these threads after a certain amount of time, called the time slice. The time slice is expressed in milliseconds, and its default value is 20 ms. It can be configured at link time with the symbol _java_round_robin_period, defined in the linker configuration file linkVMConfiguration.lscf located in the Platform folder /MICROJVM/link/. To override the content of this file, create, in the Platform configuration project, a folder named /dropins/MICROJVM/link/, and copy into this folder the file linkVMConfiguration.lscf retrieved from an existing Platform. Since a symbol cannot be null, the actual time slice value in milliseconds is _java_round_robin_period - 1. Set the symbol to 1 (i.e., time slice to 0) to disable the round-robin scheduling.

Warning

Modifying the time slice value is an advanced configuration that can impact the performances.

Decreasing the time slice will increase the number of context switches. Therefore scheduler will use more CPU time.

Increasing the time slice can create a latency with intensive threads monopolizing the CPU.

Idle Mode

When the platform has no activity to execute, it calls the LLMJVM_IMPL_idleVM function, which is assumed to put the RTOS task of the platform into a sleep state. LLMJVM_IMPL_wakeupVM is called to wake up the platform task. When the platform task really starts to execute again, it calls the LLMJVM_IMPL_ackWakeup function to acknowledge the restart of its activity.

Time

The platform defines two times:

  • the application time: the difference, measured in milliseconds, between the current time and midnight, January 1, 1970, UTC.
  • the monotonic time: this time always moves forward and is not impacted by application time modifications (NTP or Daylight Savings Time updates). It can be implemented by returning the running time since the start of the device.

The platform relies on the following C functions to provide those times to the MicroEJ world:

  • LLMJVM_IMPL_getCurrentTime: must return the monotonic time in milliseconds if the given parameter is 1, otherwise must return the application time in milliseconds. This function is called by the method java.lang.System.currentTimeMillis() It is also used by the platform scheduler, and should be implemented efficiently.
  • LLMJVM_IMPL_getTimeNanos: must return a monotonic time in nanoseconds.
  • LLMJVM_IMPL_setApplicationTime: must set the difference between the current time and midnight, January 1, 1970, UTC. Implementations may apply this time to the whole underlying system or only to the Core Engine (i.e., the value returned by LLMJVM_IMPL_getCurrentTime(0)).

Error Codes

The C function SNI_createVM returns a negative value if an error occurred during the Core Engine initialization or execution. The file LLMJVM.h defines the platform-specific error code constants. The following table describes these error codes.

MicroEJ Core Engine Error Codes
Error Code Meaning
0 The MicroEJ Application ended normally (i.e., all the non-daemon threads are terminated or System.exit(exitCode) has been called). See section Exit Codes.
-1 The microejapp.o produced by SOAR is not compatible with the MicroEJ Core Engine (microejruntime.a). The object file has been built from another MicroEJ Platform.
-2 Internal error. Invalid link configuration in the MicroEJ Architecture or the MicroEJ Platform.
-3 Evaluation version limitations reached: termination of the application. See section Limitations.
-5 Not enough resources to start the very first MicroEJ thread that executes main method. See section Option(text): Java heap size (in bytes).
-12 Number of threads limitation reached. See sections Limitations and Option(text): Number of threads.
-13 Fail to start the MicroEJ Application because the specified MicroEJ heap is too large or too small. See section Option(text): Java heap size (in bytes).
-14 Invalid MicroEJ Application stack configuration. The stack start or end is not eight-byte aligned, or stack block size is too small. See section Option(text): Number of blocks in pool.
-16 The MicroEJ Core Engine cannot be restarted.
-17

The MicroEJ Core Engine is not in a valid state because of one of the following situations:

  • SNI_startVM called before SNI_createVM.
  • SNI_startVM called while the MicroEJ Appplication is running.
  • SNI_createVM called several times.
-18 The memory used for the MicroEJ heap or immortal heap does not work properly. Read/Write memory checks failed. This may be caused by an invalid external RAM configuration. Verify _java_heap and _java_immortals sections locations.
-19 The memory used for the MicroEJ Application static fields does not work properly. Read/Write memory checks failed. This may be caused by an invalid external RAM configuration. Verify .bss.soar section location.
-20 KF configuration internal error. Invalid link configuration in the MicroEJ Architecture or the MicroEJ Platform.
-21 Number of monitors per thread limitation reached. See sections Limitations and Options .
-22 Internal error. Invalid FPU configuration in the MicroEJ Architecture.
-23 The function LLMJVM_IMPL_initialize defined in the Abstraction Layer implementation returns an error.
-24 The function LLMJVM_IMPL_vmTaskStarted defined in the Abstraction Layer implementation returns an error.
-25 The function LLMJVM_IMPL_shutdown defined in the Abstraction Layer implementation returns an error.

Example

The following example shows how to create and launch the Core Engine from the C world. This function (microej_main) should be called from a dedicated RTOS task.

#include <stdio.h>
#include "microej_main.h"
#include "LLMJVM.h"
#include "sni.h"

#ifdef __cplusplus
   extern "C" {
#endif

/**
 * @brief Creates and starts a MicroEJ instance. This function returns when the MicroEJ execution ends.
 * @param argc arguments count
 * @param argv arguments vector
 * @param app_exit_code_ptr pointer where this function stores the application exit code or 0 in case of error in the MicroEJ Core Engine. May be null.
 * @return the MicroEJ Core Engine error code in case of error, or 0 if the execution ends without error.
 */
int microej_main(int argc, char **argv, int* app_exit_code_ptr) {
        void* vm;
        int core_engine_error_code = -1;
        int32_t app_exit_code = 0;
        // create Core Engine
        vm = SNI_createVM();

        if (vm == NULL) {
                printf("MicroEJ initialization error.\n");
        } else {
                printf("MicroEJ START\n");

                // Error codes documentation is available in LLMJVM.h
                core_engine_error_code = (int)SNI_startVM(vm, argc, argv);

                if (core_engine_error_code < 0) {
                        // Error occurred
                        if (core_engine_error_code == LLMJVM_E_EVAL_LIMIT) {
                                printf("Evaluation limits reached.\n");
                        } else {
                                printf("MicroEJ execution error (err = %d).\n", (int) core_engine_error_code);
                        }
                } else {
                        // Core Engine execution ends normally
                        app_exit_code = SNI_getExitCode(vm);
                        printf("MicroEJ END (exit code = %d)\n", (int) app_exit_code);
                }

                // delete Core Engine
                SNI_destroyVM(vm);
        }

        if(app_exit_code_ptr != NULL){
                *app_exit_code_ptr = (int)app_exit_code;
        }

        return core_engine_error_code;
}

#ifdef __cplusplus
   }
#endif

Restart the Core Engine

The Core Engine supports the restart of the MicroEJ Application after the end of its execution. The application stops when all non-daemon threads are terminated or when System.exit(exitCode) is called. When the application ends, the C function SNI_startVM returns.

To restart the application, call again the SNI_startVM function (see the following pattern).

// create Core Engine (called only once)
vm = SNI_createVM();
...
// start a new execution of the MicroEJ Application at each iteration of the loop
while(...){
        ...
        core_engine_error_code = SNI_startVM(vm, argc, argv);
        ...
        // Get exit status passed to System.exit()
        app_exit_code = SNI_getExitCode(vm);
        ...
}
...
// delete Core Engine (called before stopping the whole system)
SNI_destroyVM(vm);

Note

Please note that while the Core Engine supports restart, MicroUI does not. Attempting to restart the MicroEJ Application on a VEE Port with UI support may result in undefined behavior.

Dump the States of the Core Engine

The internal Core Engine function called LLMJVM_dump allows you to dump the state of all MicroEJ threads: name, priority, stack trace, etc. This function must only be called from the MicroJvm virtual machine thread context and only from a native function or callback. Calling this function from another context may lead to undefined behavior and should be done only for debug purpose.

This is an example of a dump:

=================================== VM Dump ====================================
Java threads count: 3
Peak java threads count: 3
Total created java threads: 3
Last executed native function: 0x90035E3D
Last executed external hook function: 0x00000000
State: running
--------------------------------------------------------------------------------
Java Thread[1026]
name="main" prio=5 state=RUNNING max_java_stack=456 current_java_stack=184

java.lang.MainThread@0xC0083C7C:
    at (native) [0x90003F65]
    at com.microej.demo.widget.main.MainPage.getContentWidget(MainPage.java:95)
        Object References:
            - com.microej.demo.widget.main.MainPage@0xC00834E0
            - com.microej.demo.widget.main.MainPage$1@0xC0082184
            - java.lang.Thread@0xC0082194
            - java.lang.Thread@0xC0082194
    at com.microej.demo.widget.common.Navigation.createRootWidget(Navigation.java:104)
        Object References:
            - com.microej.demo.widget.main.MainPage@0xC00834E0
    at com.microej.demo.widget.common.Navigation.createDesktop(Navigation.java:88)
        Object References:
            - com.microej.demo.widget.main.MainPage@0xC00834E0
            - ej.mwt.stylesheet.CachedStylesheet@0xC00821DC
    at com.microej.demo.widget.common.Navigation.main(Navigation.java:40)
        Object References:
            - com.microej.demo.widget.main.MainPage@0xC00834E0
    at java.lang.MainThread.run(Thread.java:855)
        Object References:
            - java.lang.MainThread@0xC0083C7C
    at java.lang.Thread.runWrapper(Thread.java:464)
        Object References:
            - java.lang.MainThread@0xC0083C7C
    at java.lang.Thread.callWrapper(Thread.java:449)
--------------------------------------------------------------------------------
Java Thread[1281]
name="UIPump" prio=5 state=WAITING timeout(ms)=INF max_java_stack=120 current_java_stack=117
external event: status=waiting

java.lang.Thread@0xC0083628:
    at ej.microui.MicroUIPump.read(Unknown Source)
        Object References:
            - ej.microui.display.DisplayPump@0xC0083640
    at ej.microui.MicroUIPump.run(MicroUIPump.java:176)
        Object References:
            - ej.microui.display.DisplayPump@0xC0083640
    at java.lang.Thread.run(Thread.java:311)
        Object References:
            - java.lang.Thread@0xC0083628
    at java.lang.Thread.runWrapper(Thread.java:464)
        Object References:
            - java.lang.Thread@0xC0083628
    at java.lang.Thread.callWrapper(Thread.java:449)
--------------------------------------------------------------------------------
Java Thread[1536]
name="Thread1" prio=5 state=READY max_java_stack=60 current_java_stack=57

java.lang.Thread@0xC0082194:
    at java.lang.Thread.runWrapper(Unknown Source)
        Object References:
            - java.lang.Thread@0xC0082194
    at java.lang.Thread.callWrapper(Thread.java:449)
================================================================================

============================== Garbage Collector ===============================
State: Stopped
Last analyzed object: null
Total memory: 15500
Current allocated memory: 7068
Current free memory: 8432
Allocated memory after last GC: 0
Free memory after last GC: 15500
================================================================================

=============================== Native Resources ===============================
Id         CloseFunc  Owner            Description
--------------------------------------------------------------------------------
================================================================================

See Stack Trace Reader for additional info related to working with VM dumps.

Dump The State Of All MicroEJ Threads From A Fault Handler

It is recommended to call the LLMJVM_dump API as a last resort in a fault handler. Calling LLMJVM_dump is undefined if the VM is not paused. The call to LLMJVM_dump MUST be done last in the fault handler.

Trigger VM Dump From Debugger

To trigger a VM dump from the debugger, set the PC register to the LLMJVM_dump physical memory address.

The symbol for the LLMJVM_dump API is defined in the header file LLMJVM.h. Search for this symbol in the appropriate C toolchain .map file.

Note

LLMJVM_dump (in LLMJVM.h) needs to be called explicitly. A linker optimization may remove the symbol if it is not used anywhere in the code.

Requirements:

  • Embedded debugger is attached and the processor is halted in an exception handler.
  • A way to read stdout (usually UART).

Check Internal Structure Integrity

The internal Core Engine function called LLMJVM_checkIntegrity checks the internal structure integrity of the MicroJvm virtual machine and returns its checksum.

  • If an integrity error is detected, the LLMJVM_on_CheckIntegrity_error hook is called and this method returns 0.
  • If no integrity error is detected, a non-zero checksum is returned.

This function must only be called from the MicroJvm virtual machine thread context and only from a native function or callback. Calling this function multiple times in a native function must always produce the same checksum. If the checksums returned are different, a corruption must have occurred.

Please note that returning a non-zero checksum does not mean the MicroJvm virtual machine data has not been corrupted, as it is not possible for the MicroJvm virtual machine to detect the complete memory integrity.

MicroJvm virtual machine internal structures allowed to be modified by a native function are not taken into account for the checksum computation. The internal structures allowed are:

  • basetype fields in Java objects or content of Java arrays of base type,
  • internal structures modified by a LLMJVM function call (e.g. set a pending Java exception, suspend or resume the Java thread, register a resource, …).

This function affects performance and should only be used for debug purpose. A typical use of this API is to verify that a native implementation does not corrupt the internal structures:

void Java_com_mycompany_MyClass_myNativeFunction(void) {
             int32_t crcBefore = LLMJVM_checkIntegrity();
             myNativeFunctionDo();
     int32_t crcAfter = LLMJVM_checkIntegrity();
     if(crcBefore != crcAfter){
             // Corrupted MicroJVM virtual machine internal structures
             while(1);
     }
}

Generic Output

The System.err stream is connected to the System.out print stream. See below for how to configure the destination of these streams.

Dependencies

The Core Engine requires an implementation of its low level APIs in order to run. Refer to the chapter Implementation for more information.

Installation

The Core Engine and its components are mandatory. In the platform configuration file, check Multi Applications to install the Core Engine in “Multi-Sandbox” mode. Otherwise, the “Single application” mode is installed.

Abstraction Layer

MicroEJ Core Engine Abstraction Layer implementation can be found on MicroEJ Github for several RTOS.

Memory Considerations

The memory consumption of main Core Engine runtime elements are described in the table below.

Memory Considerations
Runtime element Memory Size in bytes (Mono-sandbox) Size in bytes (Multi-Sandbox) Size in bytes (Tiny-Sandbox)
Object Header RW 4 8 (+4) 4
Thread RW 168 192 (+24) 168
Stack Frame Header RW 12 20 (+8) 12
Class Type RO 32 36 (+4) 32
Interface Type RO 16 24 (+8) 16

Note

To get the full size of an Object, search for the type in the SOAR Information File and read the attribute instancesize (this includes the Object header).

Note

To get the full size of a Stack Frame, search for the method in the SOAR Information File and read the attribute stacksize (this includes the Stack Frame header).

Use

Refer to the MicroEJ Runtime documentation.