Miosix Processes Quick Start: Difference between revisions

From Miosix Wiki
Jump to navigation Jump to search
Created page with "This tutorial assumes you just completed the quick start guide either for Linux, Windows or MacOS. The screenshots in this tutorial are taken from a Linux computer, but the procedure is the same regardless of your host OS. In the quick start you just followed, you used Miosix as a unikernel, thus writing your application in kernelspace. To use Miosix as a fluid kernel, we'll see how we can move part of..."
 
mNo edit summary
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
This tutorial assumes you just completed the quick start guide either for [[Linux Quick Start|Linux]], [[Windows Quick Start|Windows]] or [[MacOS Quick Start|MacOS]]. The screenshots in this tutorial are taken from a Linux computer, but the procedure is the same regardless of your host OS.
This tutorial assumes you just completed the unikernel quick start guide either for [[Linux Quick Start|Linux]], [[Windows Quick Start|Windows]] or [[MacOS Quick Start|MacOS]]. The screenshots in this tutorial are taken from a Linux computer, but the procedure is the same regardless of your host OS.


In the quick start you just followed, you used Miosix as a unikernel, thus writing your application in kernelspace. To use Miosix as a fluid kernel, we'll see how we can move part of our hello world example application in a userspace process.
In the quick start you just followed, you used Miosix as a unikernel, thus writing your application in kernelspace. To use Miosix as a fluid kernel, we'll see how we can move part of our hello world example application in a userspace process.
In this tutorial we'll break the previous example program that blinked an LED and printed ''Hello world'' in two parts: we'll blink the LED in kernelspace form a kernel thread, while we'll print ''Hello world'' in userspace form a proccess.


= Starting from a template =
= Starting from a template =
To keep this tutorial simple, also in this case we'll start from a template project. More details on the theory of what we're doing can be found in the [[Fluid kernel|fluid kernel]] page, while a more detailed description of the content of the template files can be found in the [[Miosix processes]] page.


In a directory of your choice, create a ''hello_processes'' directory and download a copy of the Miosix kernel as a subdirectory using git. As a refresher, on Linux that would be:
In a directory of your choice, create a ''hello_processes'' directory and download a copy of the Miosix kernel as a subdirectory using git. As a refresher, on Linux that would be:
Line 13: Line 17:
</source>
</source>


This time, however, we won't use the perl script ''init_project_out_of_git_tree.pl'' and instead manually copy '''the content of''' the ''miosix-kernel/templates/processes'' in our propject directory.
This time, however, we won't use the perl script ''init_project_out_of_git_tree.pl'' and instead manually copy '''the content of''' the ''miosix-kernel/templates/processes'' in our project directory. Then, we'll copy '''the directory''' (this time not the content of, but the entire directory) ''miosix-kernel/miosix/config'' again in our project directory.
 
The result should look like this:
 
[[File:Miosix-processes-template.png]]
 
Since this time we have not used the perl script, we'll have to fix the Makefile paths manually, so it can find the kernel and configuration properly. Open the ''Makefile'' and replace the lines:
 
<source lang=bash>
KPATH := ../../miosix
CONFPATH := $(KPATH)
</source>
 
to the following value:
 
<source lang=bash>
KPATH := miosix-kernel/miosix
CONFPATH := .
</source>
 
We should also edit the ''CMakeLists.txt'', but since in this tutorial we'll use the Makefile build system, we won't bother. Information on how to do so can be found in the [[Miosix and git workflow]] and [[CMake]] pages.
 
== Configuring the kernel ==
 
Just like in the previous tutorial we'll have to select a board in [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/Makefile.inc config/Makefile.inc]:
 
<source lang="bash">
...
#OPT_BOARD := stm32f469ni_stm32f469i-disco
OPT_BOARD := stm32f746zg_nucleo
#OPT_BOARD := stm32f765ii_marco_ram_board
...
</source>
 
But this time, there are two more configuration steps to perform. First, we'll need to compile the kernel with the filesystem and processes support enabled.
 
Open the [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/miosix_settings.h config/miosix_settings.h] and uncomment the following lines:
 
<source lang="CPP">
#define WITH_FILESYSTEM
#define WITH_PROCESSES
</source>
 
Note that the process loader depends on filesystem support, so the filesystem is a dependency of process support.
Also a note if you read the [[Miosix code size optimization]] page: the kernel requires C++ exceptions when enabling processes, so you can't disable exceptions in the kernel. You can, however, disable exceptions in the userspace programs (look for the ''PROC_OPT_EXCEPT'' instead of ''OPT_EXCEPT'' in ''Makefile.inc'').
 
There's one more step: select an appropriate linker script. This is done in the board-specific ''Makefile.inc'' file. This can be found in ''config/board/<your board name>/Makefile.inc''. Since this tutorial is based on the stm32f746zg_nucleo board, that would be [https://github.com/fedetft/miosix-kernel/blob/master/miosix/config/board/stm32f746zg_nucleo/Makefile.inc config/board/stm32f746zg_nucleo/Makefile.inc].
In this file, you should uncomment whatever linker script is selected, and uncomment the linker script whose name ends with processes:
 
<source lang="bash">
## Select linker script
#LINKER_SCRIPT := unikernel.ld
LINKER_SCRIPT := processes.ld
</source>
 
Note that some boards (those with an external off-chip RAM), may have multiple linker scripts that end with processes. For this tutorial, any of them will work. Some boards may not have any linker script option with processes. This usually happens because the board has too little FLASH or RAM to support processes. The minimum required memory to use processes is 128KB of FLASH and 32KB of RAM, even though at least twice that amount is recommended.
 
== Modifying the kernelspace main() ==
 
The ''main.cpp'' that we copied from the template application should look like this:
 
<source lang="CPP">
#include <cstdio>
#include <sys/wait.h>
#include <signal.h>
#include <spawn.h>
 
using namespace std;
 
int main()
{
    pid_t pid;
    const char *arg[] = { "/bin/hello", nullptr };
    const char *env[] = { nullptr };
    int ec=posix_spawn(&pid,arg[0],NULL,NULL,(char* const*)arg,(char* const*)env);
    if(ec!=0)
    {
        iprintf("spawn returned %d\n",ec);
        return 1;
    }
    pid=wait(&ec);
    iprintf("pid %d exited\n",pid);
    if(WIFEXITED(ec))
    {
        iprintf("Exit code is %d\n",WEXITSTATUS(ec));
    } else if(WIFSIGNALED(ec)) {
        if(WTERMSIG(ec)==SIGSEGV) iprintf("Process segfaulted\n");
        else iprintf("Process terminated due to an error\n");
    }
    return 0;
}
</source>
 
This is a generic code to spawn a process that should work on any POSIX-compliant OS, including Linux and MacOS, except for the use of ''iprintf'' instead of ''printf'' to save code size.
 
However, in this tutorial, we also want to run a kernelspace application that blinks an LED. To do so while main() spawns and waits for the process, we'll use a thread.
 
Modify the main() as follows:
 
<source lang="CPP">
#include <cstdio>
#include <sys/wait.h>
#include <signal.h>
#include <spawn.h>
#include <thread>
#include <interfaces/bsp.h>
 
using namespace std;
 
void blinker()
{
    for(;;)
    {
        this_thread::sleep_for(500ms);
        miosix::ledOn();
        this_thread::sleep_for(500ms);
        miosix::ledOff();
    }
}
 
int main()
{
    thread t(blinker);
    t.detach();
 
    pid_t pid;
    const char *arg[] = { "/bin/hello", nullptr };
    const char *env[] = { nullptr };
    int ec=posix_spawn(&pid,arg[0],NULL,NULL,(char* const*)arg,(char* const*)env);
    if(ec!=0)
    {
        iprintf("spawn returned %d\n",ec);
        return 1;
    }
    pid=wait(&ec);
    iprintf("pid %d exited\n",pid);
    if(WIFEXITED(ec))
    {
        iprintf("Exit code is %d\n",WEXITSTATUS(ec));
    } else if(WIFSIGNALED(ec)) {
        if(WTERMSIG(ec)==SIGSEGV) iprintf("Process segfaulted\n");
        else iprintf("Process terminated due to an error\n");
    }
    return 0;
}
</source>
 
== Modifying the userspace main() ==
 
The userspace main() is the ''process_template/main.cpp'' file. Open it and replace its content with:
 
<source lang="CPP">
#include <cstdio>
#include <thread>
 
using namespace std;
 
int main()
{
    for(;;)
    {
        this_thread::sleep_for(2s);
        iprintf("Hello world\n");
    }
}
</source>
 
== Compiling and flashing ==
 
Just like in the previous tutorial, compile with ''make'' and flash using ''make program'' on Linux or with the chip vendor's flashing tool on Windows. Note that this time you'll need to flash ''image.bin'' and '''not''' ''main.bin''.
 
You should see the LED blink and the ''Hello world'' being printed on the serial port.

Latest revision as of 08:36, 19 May 2026

This tutorial assumes you just completed the unikernel quick start guide either for Linux, Windows or MacOS. The screenshots in this tutorial are taken from a Linux computer, but the procedure is the same regardless of your host OS.

In the quick start you just followed, you used Miosix as a unikernel, thus writing your application in kernelspace. To use Miosix as a fluid kernel, we'll see how we can move part of our hello world example application in a userspace process.

In this tutorial we'll break the previous example program that blinked an LED and printed Hello world in two parts: we'll blink the LED in kernelspace form a kernel thread, while we'll print Hello world in userspace form a proccess.

Starting from a template

To keep this tutorial simple, also in this case we'll start from a template project. More details on the theory of what we're doing can be found in the fluid kernel page, while a more detailed description of the content of the template files can be found in the Miosix processes page.

In a directory of your choice, create a hello_processes directory and download a copy of the Miosix kernel as a subdirectory using git. As a refresher, on Linux that would be:

mkdir hello_processes
cd hello_processes
git clone https://github.com/fedetft/miosix-kernel.git

This time, however, we won't use the perl script init_project_out_of_git_tree.pl and instead manually copy the content of the miosix-kernel/templates/processes in our project directory. Then, we'll copy the directory (this time not the content of, but the entire directory) miosix-kernel/miosix/config again in our project directory.

The result should look like this:

Since this time we have not used the perl script, we'll have to fix the Makefile paths manually, so it can find the kernel and configuration properly. Open the Makefile and replace the lines:

KPATH := ../../miosix
CONFPATH := $(KPATH)

to the following value:

KPATH := miosix-kernel/miosix
CONFPATH := .

We should also edit the CMakeLists.txt, but since in this tutorial we'll use the Makefile build system, we won't bother. Information on how to do so can be found in the Miosix and git workflow and CMake pages.

Configuring the kernel

Just like in the previous tutorial we'll have to select a board in config/Makefile.inc:

...
#OPT_BOARD := stm32f469ni_stm32f469i-disco
OPT_BOARD := stm32f746zg_nucleo
#OPT_BOARD := stm32f765ii_marco_ram_board
...

But this time, there are two more configuration steps to perform. First, we'll need to compile the kernel with the filesystem and processes support enabled.

Open the config/miosix_settings.h and uncomment the following lines:

#define WITH_FILESYSTEM
#define WITH_PROCESSES

Note that the process loader depends on filesystem support, so the filesystem is a dependency of process support. Also a note if you read the Miosix code size optimization page: the kernel requires C++ exceptions when enabling processes, so you can't disable exceptions in the kernel. You can, however, disable exceptions in the userspace programs (look for the PROC_OPT_EXCEPT instead of OPT_EXCEPT in Makefile.inc).

There's one more step: select an appropriate linker script. This is done in the board-specific Makefile.inc file. This can be found in config/board/<your board name>/Makefile.inc. Since this tutorial is based on the stm32f746zg_nucleo board, that would be config/board/stm32f746zg_nucleo/Makefile.inc. In this file, you should uncomment whatever linker script is selected, and uncomment the linker script whose name ends with processes:

## Select linker script
#LINKER_SCRIPT := unikernel.ld
LINKER_SCRIPT := processes.ld

Note that some boards (those with an external off-chip RAM), may have multiple linker scripts that end with processes. For this tutorial, any of them will work. Some boards may not have any linker script option with processes. This usually happens because the board has too little FLASH or RAM to support processes. The minimum required memory to use processes is 128KB of FLASH and 32KB of RAM, even though at least twice that amount is recommended.

Modifying the kernelspace main()

The main.cpp that we copied from the template application should look like this:

#include <cstdio>
#include <sys/wait.h>
#include <signal.h>
#include <spawn.h>

using namespace std;

int main()
{
    pid_t pid;
    const char *arg[] = { "/bin/hello", nullptr };
    const char *env[] = { nullptr };
    int ec=posix_spawn(&pid,arg[0],NULL,NULL,(char* const*)arg,(char* const*)env);
    if(ec!=0)
    {
        iprintf("spawn returned %d\n",ec);
        return 1;
    }
    pid=wait(&ec);
    iprintf("pid %d exited\n",pid);
    if(WIFEXITED(ec))
    {
        iprintf("Exit code is %d\n",WEXITSTATUS(ec));
    } else if(WIFSIGNALED(ec)) {
        if(WTERMSIG(ec)==SIGSEGV) iprintf("Process segfaulted\n");
        else iprintf("Process terminated due to an error\n");
    }
    return 0;
}

This is a generic code to spawn a process that should work on any POSIX-compliant OS, including Linux and MacOS, except for the use of iprintf instead of printf to save code size.

However, in this tutorial, we also want to run a kernelspace application that blinks an LED. To do so while main() spawns and waits for the process, we'll use a thread.

Modify the main() as follows:

#include <cstdio>
#include <sys/wait.h>
#include <signal.h>
#include <spawn.h>
#include <thread>
#include <interfaces/bsp.h>

using namespace std;

void blinker()
{
    for(;;)
    {
        this_thread::sleep_for(500ms);
        miosix::ledOn();
        this_thread::sleep_for(500ms);
        miosix::ledOff();
    }
}

int main()
{
    thread t(blinker);
    t.detach();

    pid_t pid;
    const char *arg[] = { "/bin/hello", nullptr };
    const char *env[] = { nullptr };
    int ec=posix_spawn(&pid,arg[0],NULL,NULL,(char* const*)arg,(char* const*)env);
    if(ec!=0)
    {
        iprintf("spawn returned %d\n",ec);
        return 1;
    }
    pid=wait(&ec);
    iprintf("pid %d exited\n",pid);
    if(WIFEXITED(ec))
    {
        iprintf("Exit code is %d\n",WEXITSTATUS(ec));
    } else if(WIFSIGNALED(ec)) {
        if(WTERMSIG(ec)==SIGSEGV) iprintf("Process segfaulted\n");
        else iprintf("Process terminated due to an error\n");
    }
    return 0;
}

Modifying the userspace main()

The userspace main() is the process_template/main.cpp file. Open it and replace its content with:

#include <cstdio>
#include <thread>

using namespace std;

int main()
{
    for(;;)
    {
        this_thread::sleep_for(2s);
        iprintf("Hello world\n");
    }
}

Compiling and flashing

Just like in the previous tutorial, compile with make and flash using make program on Linux or with the chip vendor's flashing tool on Windows. Note that this time you'll need to flash image.bin and not main.bin.

You should see the LED blink and the Hello world being printed on the serial port.