Explore Apps, Exes and Processes
Now that you've created your first Legato app, helloWorld, we are going to build upon it and introduce some of the key concepts of the Application Framework. helloWorld is a simple app that logs a string to the syslog
. In this next section we are going to break helloWorld into a printClient and a printServer component and then use these two components to demonstrate the different approaches that can be used to design application architecture. The first demonstration starts with the two components existing within a single process, they have full access rights and communication to each other and processes are not protected from each other. Next each component will get moved to their own process and will need to communicate over IPC and an API will need to be defined to facilitate the communication. Lastly, we will move each component into it's own application and use an API to facilitate IPC communication. The two apps will only be able to communicate over IPC and only have access to what is defined in the API.
- Note
- When you are designing your own components or applications it's important to find a configuration that allows the access that your app needs while maintaining a secure system. We encourage the use of IPC and application sandboxing as they allow for secure communications between apps while protecting the internal components.
To prepare for this tutorial we first need to create a second component that will take the string from the first component and then use the second component to log it to the syslog.
Create Components
For these tutorials we are going to create two components: printClient
and printServer
.
Create Directories
First make the directories:
$ mkdir helloIPC $ cd helloIPC $ mkdir printClient $ mkdir printServer
Your directory structure should look like:
helloIPC/ ├──printClient/ └──printServer/
Create printServer Component
cd
into the printServer directory and create your source file: server.c
. The server.c
file is going to take the passed in perimeter from the client and print it to the logs.
server.c
should contain the following:
#include "legato.h"#include "interfaces.h"void printer_Print(const char* message){LE_INFO("******** Client says '%s'", message);}{}
The file interfaces.h
is auto-generated based on the contents of printServer's Component.cdef
. It'll contain a prototype of the function print_Print()
, which we implemented in server.c
.
Next, create the Component.cdef
file so the component is built correctly with mktools
.
sources:{server.c}
Create printClient Component
Next, create the printClient component:
cd
into the printClient component directory and create a source file called client.c:
client.c
should contain 2 lines, the first telling the log it's asking the print function to print Hello, world! and then calls the print function in the server.c component to print the message to the log:
#include "legato.h"#include "interfaces.h"{LE_INFO("Asking server to print 'Hello, world!'");printer_Print("Hello, world!");}
Next create the Component.cdef
file: vim
Component.cdef
The Component.cdef should contain:
sources:{client.c}
Now we have the printClient and printServer components created, we will use this as a basis for the rest of the tutorial.
Your directory for helloIPC should look like:
helloIPC/ ├── printClient/ │ ├── client.c │ └── Component.cdef └── printServer/ ├── Component.cdef └── server.c
2 Components within 1 Executable
Now that we have the two components we need to make them talk to each other from within the executable. To do this we need a standard header file in the printServer component, as this is the function that was want to expose outside of itself.
Create printServer Header
$vim server.h
The file should contain the following code:
#ifndef SERVER_H_INC#define SERVER_H_INC#endif
By default all symbols are hidden in the Legato build process, to export symbols you need to explicitly define them in the header. Because we want printClient to be able to use the printer_Print function in the printServer we need to add LE_SHARED
to the function prototype so that the function name is added to the component's exported symbol table and printClient can use the function.
Next add #include
"server.h"
in both the client.c
and server.c
files.
#include "legato.h"#include "interfaces.h"#include "server.h"
Now we need to tell the build tools where to find the server.h
header file. The server component printServer
will be able to find this file automatically. This is because the header file is in the same directory as the source file, server.c
. However, in the client component's code the file .h is located in a different directory than client.c
.
So, what we need to do is we need to tell the build tools where to find find the header file server.h
. In the component printClient's Component.cdef
add a cflags section as follows:
cflags:{-I $CURDIR/../printServer}
The -I
tells the compiler we want to add a new directory to the search path. When you use the #include
directive you tell the compiler to search for that file. The compiler uses a list of directories called a search path to perform this search. If the file in question is not in one these directories it is considered not found, and the build will fail.
The $CURDIR
in the directory path is a build variable. It is automatically set by the mktools. Inside of a Component.cdef
the variable $CURDIR
automatically points to the directory that the Component.cdef
file was found.
Create Executable
We are now ready to create an executable with both components and bundle the executable into an app and install it on the target.
First, create your .adef
$ vim helloApp.adef
Add the following:
executables:{helloPrint = ( printClient printServer )}processes:{run:{( helloPrint )}}
The two components are added to the same executable, helloPrint. helloPrint is then given a process to run in and bundled into the app.
Your directory structure for helloIPC should look like:
helloIPC/ ├── helloApp.adef ├── printClient/ │ ├── client.c │ └── Component.cdef └── printServer/ ├── Component.cdef ├── server.c └── server.h
Create App
Next run mkapp
to create the update bundle for your target (the same as in helloWorld
) and install it on your target.
$ mkapp -t wp85 helloApp.adef $ app install helloApp.wp85.update # add the target IP to the command if DEST_IP is not set
Running logread
on your target you should see logs like:
<time> <target> user.info Legato: INFO | supervisor[461]/supervisor T=main | proc.c proc_Start() 1394 | Starting process 'helloPrint' with pid 976 <time> <target> user.info Legato: INFO | supervisor[976]/supervisor T=main | proc.c proc_Start() 1359 | Execing 'helloPrint' <time> <target> user.info Legato: INFO | helloPrint[976]/printClient T=main | client.c _printClient_COMPONENT_INIT() 7 | Asking server to print 'Hello, world!' <time> <target> user.info Legato: INFO | helloPrint[976]/printServer T=main | server.c printer_Print() 7 | ******** Client says 'Hello, world!'
- Note
The
app is called helloPrint and only has one PID, you can see that both components are running in the same PID in thehelloPrint
executable.
1 App with 2 Processes
Legato components can provide APIs for other components to use. It can be done conventionally by writing a C header file to define the interface. But a C header file can only be used by components also written in C, and functions defined in a C header file can only be implemented by C code. C compilers won't generate IPC code, so unless you write your own, your API implementation and its user are forced to run inside the same process. This can severely limit the re-usability of components and can force using a programming language not ideally suited to a particular problem domain or developer skill-set. It also leaves inter-process communication (IPC) to be implemented manually, which can be time-consuming and fraught with bugs and security issues.
Now that we've got our app going, we are going to modify it and show the same application but with the components running in two separate processes. With each component run in own process this allows the capability to restart each process individually.
For the two components to talk to each other in different processes then an API must be used to facilitate the communication. The Legato Application Framework provides coding standards and an overview on how to create APIs.
Create API
First we need to remove the server.h
file and it's references as it's not needed for this communication.
$ rm printServer/server.h
Remove #include
server.h
from both printClient/client.c
and printServer/server.c
.
Remove the cflags
section in the printClient/Component.cdef as it's not needed anymore.
Next, we create the API file, the API file should be created in the root of your project.
$ vim printer.api
In your API you first want to DEFINE the constant as a maximum length for a the message string. Defining this as a constant means that it can used later in the client.c
or server.c
by referencing PRINTER_MESSAGE_LEN
(API Name_Define Name).
Next you need to define your function Print()
and accept the message to pass to the function in server.c
.
Define the function for the API:
In the printer.api
file:
DEFINE MESSAGE_LEN = 100;FUNCTION Print(string message[MESSAGE_LEN] IN ///< message to be printed into the log, max 100 characters.);
Next, we need to tell the printServer component that it is providing the API:
Add the following to printServer/Component.cdef
before the sources:
section.
provides:{api:{printer = printer.api}}
This declares that the component named printServer
(the name of the directory is the name of the component) provides a service called printer
accessed using the API defined in printer.api
while the source code can be found in the file server.c
.
- Note
- The source code file name extension is used to identify the programming language it's written in. (e.g.; The file server.c determines that this is c code)
The function printer_Print()
can be called when a client binds to our printer
service
calls the API function printer_Print()
.
The format of the generated function names is:
<interface-name>_<api-function-name>
The interface-name
is the name given to the provided interface. In our example, it's the name printer
before the =
in the line printer
=
printer.api
. printer
in this case is optional and doesn't have to be the same name as the .api file. We chose to implement it that way as it follows the server and client para-dime we use for this tutorial.
- Note
- If you forget to implement a service function on the server or if you give it the wrong name, the link stage will fail and complain that the symbol is unresolved. You'll know you missed a function, and you'll be able to see what the correct name of the function should be.
Use API in printClient
Next we need to tell the client app that it requires the printer API to be able to print messages.
Add the following to printClient/Component.cdef
before the sources:
section.
requires:{api:{printer = printer.api}}
Next, we have the printServer Component providing an API and the printClient Component using that API. It's time to create our app and have them start communicating.
Your directory structure should contain the following files now:
helloIPC/ ├── helloApp.adef ├── printClient/ │ ├── client.c │ └── Component.cdef ├── printer.api └── printServer/ ├── Component.cdef └── server.c
Create App
We are now ready to create the .adef and bundle the two components into one app. This is very similar to what we did before except that we will be defining two executables and two processes.
Start by creating the .adef
$ vim helloIPC1.adef
Add the following into the helloIPC1.adef:
executables:{helloClient = ( printClient )helloServer = ( printServer )}processes:{run:{( helloClient )( helloServer )}}bindings:{helloClient.printClient.printer -> helloServer.printServer.printer}
The bindings: section is used to connect the client interface to the server interface so that the client can call the printer functions on the server. When the app is built it will generate the system information for the client so that it knows that it is allowed to use the print function in the server exe. Because both exe's are within one app, they have unrestricted permissions to each other. mkapp
will check that all client connections are satisfied.
We are now ready to bundle up the application and install it on your target:
Install App
$ mkapp -t wp85 helloIPC1.adef $ app install helloIPC1.wp85.update 192.168.2.2
Doing a logread
on the client you should be able to see the following logs:
<date> <target> user.info Legato: INFO | supervisor[1235]/supervisor T=main | proc.c proc_Start() 1394 | Starting process 'helloClient' with pid 11185 <date> <target> user.info Legato: INFO | supervisor[11185]/supervisor T=main | proc.c proc_Start() 1359 | Execing 'helloClient' <date> <target> user.info Legato: INFO | supervisor[1235]/supervisor T=main | proc.c proc_Start() 1394 | Starting process 'helloServer' with pid 11186 <date> <target> user.info Legato: INFO | supervisor[11186]/supervisor T=main | proc.c proc_Start() 1359 | Execing 'helloServer' <date> <target> user.info Legato: INFO | helloClient[11185]/printClient T=main | client.c _printClient_COMPONENT_INIT() 6 | Asking server to print 'Hello, world!' <date> <target> user.info Legato: INFO | helloServer[11186]/printServer T=main | server.c printer_Print() 6 | ******** Client says 'Hello, world!'
You can see that each of the components start their own process independently from each other, and each process has it's own PID. This allows you to kill one process if needed and not the other. This also allows you to monitor specific processes or if your app has a critical process set up a Watchdog monitor on it.
The benefits of providing components their own interface are:
- process isolation, while each component is in the same sandbox but code is isolated.
- complexity of the app internals are hidden, the components can still talk to each other but are bundled as one app.
Depending on your security model you may want to separate your Components into separate apps. Currently the client and the server have access to the entire environment. If this server needs to keep files or resources protected from the client, there is no mechanism to do that.
Migrating to 2 Applications
We can set up the printServer and printClient as two separate apps, and have them completely secure from each other and only able to talk to each other through the defined APIs. This is the most secure method of creating apps and we recommend it when you are creating your own applications within the Legato Application Framework.
All the code that we did in the previous step is reusable, we will just need to modify the .adef files to create 2 separate applications, one for the print client and one for the print server.
Create helloClient.adef
First, remove helloApp.adef
$ rm helloApp.adef
Next, create helloClient.adef
$ vim helloClient.adef
helloClient.adef
should contain the following sections:
executables:{helloClient = ( printClient )}processes:{run:{( helloClient )}}bindings:{helloClient.printClient.printer -> helloServer.printer}
Binding the interfaces also sets up permissions so that you are explicitly allowing the client app to communicate with the server app.
Create helloServer.adef
Next, we need to create a .adef for
helloServer
.
In the helloServer.adef
we need to add an extern section. The extern section publishes the API as public and will let other apps connect to it.
$ vim helloServer.adef
helloServer.adef
should contain the following sections:
executables:{helloServer = ( printServer )}processes:{run:{( helloServer )}}extern:{printer = helloServer.printServer.printer}
The extern section publishes the printer api external to itself so that other applications can use it. In the .adef for the helloClient app we already bound the app to this external interface.
Create Apps
We are now ready to bundle up the applications and install them on your target. Because we have two application we need to run mkapp
on them separately and create two application bundles for your target.
First, we will create the server app:
$ mkapp -t wp85 helloServer.adef $ app install helloServer.wp85.update 192.168.2.2
Connect through ssh and enter logread
on the target you should be able to see the following logs:
<date> <target> user.info Legato: INFO | supervisor[1235]/supervisor T=main | proc.c proc_Start() 1394 | Starting process 'helloServer' with pid 13079 <date> <target> user.info Legato: INFO | supervisor[13079]/supervisor T=main | proc.c proc_Start() 1359 | Execing 'helloServer'
Now we repeat the process with helloClient
.
$ mkapp -t wp85 helloClient.adef $ app install helloClient.wp85.update 192.168.2.2
<date> <target> user.info Legato: INFO | supervisor[1235]/supervisor T=main | proc.c proc_Start() 1394 | Starting process 'helloClient' with pid 13469 <date> <target> user.info Legato: INFO | supervisor[13469]/supervisor T=main | proc.c proc_Start() 1359 | Execing 'helloClient' <date> <target> user.info Legato: INFO | helloClient[13469]/printClient T=main | client.c _printClient_COMPONENT_INIT() 6 | Asking server to print 'Hello, world!' <date> <target> user.info Legato: INFO | helloServer[13079]/printServer T=main | server.c printer_Print() 6 | ******** Client says 'Hello, world!'
You can see that the Client successfully starts and then sends a message to the helloServer
app and asks it to print 'Hello, world!'
You have now successfully explored the multiple ways to create components and bundle them within executables and applications as well as the benefits for each method. This should get you started in designing and creating your own apps.
You can also refer to our How To section for other tutorials and our Sample Apps section for examples of howToMain to include platform services and other Legato Application Framework features within your own applications.