CAmkES Tutorial
This is a short introduction to using CAmkES and how to create CAmkES-based applications in OKL4 3.0.
- Getting CAmkES
- Tutorial 1: My First Component-based Application
- Tutorial 2: A Compound Component
- Tutorial 3: Using Events
- Tutorial 4: Passing Data Through Dataports
- More Components
- Directory Structure Overview
- Generated Files Overview
- The Code
Getting CAmkES
Instructions for getting and setting up CAmkES are in Getting Started
Tutorial 1: My First Component-based Application
In this section we walk through the process of defining, implementing,
building and running a simple componentised application. The
application consists of two components: HelloClient and
HelloComponent. HelloClient has a connection to HelloComponent and
invokes the hello() method on its Hello interface.
------- ---------- | Hello | |Hello | | Client| |Component | | | | | | |--(O-----| | | | Hello | | ------- ----------
We start by creating a directory for the application:
$ mkdir iguana/apps/hello $ cd iguana/apps/hello
Then we create the SConscript file for the application
$ vi SConscript
$ cat SConscript
Import("*")
app = env.KengeComponentApplication("hello")
Return("app")
$
After this we create a camkes directory and the hello.camkes file.
The hello.camkes file specifies all the components and how they
are connected together.
$ mkdir camkes
$ vi camkes/hello.camkes
$ cat camkes/hello.camkes
import "Hello.idl4";
import "std_connector.camkes";
component HelloComponent {
provides Hello h;
}
component HelloClient {
control;
uses Hello h;
}
assembly {
composition {
component HelloComponent hcomp;
component HelloClient hclient;
connection IguanaRPC hello(from hclient.h, to hcomp.h);
}
}
$
After this we create the include directory and the Hello.idl4
file, which defines the hello interface:
$ mkdir include
$ vi include/Hello.idl4
$ cat include/Hello.idl4
interface Hello {
void hello(in smallstring msg);
};
$
The components and the application are now fully specified.
We then move to the implementations of the two components (HelloClient and HelloComponent)
$ mkdir HelloClient
$ mkdir HelloComponent
$ cd HelloComponent
$ mkdir src
$ vi src/hello.c
$ cat src/hello.c
#include <stdio.h>
#include <HelloComponent_h.h>
void h__init(void) {
}
void h_hello(char *msg) {
printf("This is the hello component saying %s\n", msg);
}
$
The file hello.c contains the implementation of the
HelloComponent. HelloComponent_h.h (which will be generated when
we compile the hello.camkes during the build process) contains the
definition of the functions that HelloComponent must implement. The
name of this file is generated by taking the name of the component and
the interface instance name.
Next we write the SConscript file for the component
$ vi SConscript
$ cat SConscript
Import("env")
comp = env.KengeComponent("HelloComponent", LIBS=["c"])
Return("comp")
$
This specifies the component name (so that we know which component is being built) and any dependencies, compile flags etc.
We then go on to implement HelloClient
$ cd ../HelloClient
$ mkdir src
$ vi src/client.c
$ cat src/client.c
#include <stdio.h>
#include <HelloClient_h.h>
void run(void);
void run(void) {
printf("Starting the client\n");
printf("-------------------\n");
while (1) {
h_hello("hello world");
}
printf("After the client\n");
}
$
The HelloClient component is different from HelloComponent in that it
does not provide any interfaces and has been declared to have a thread
of control (using the control keyword). As such it has a main
function that initialises the component and then spawns a thread to
perform the user-specified run() function. The run method is where
the actual work done by the component is specified. In this example
it repeatedly calls the hello() method on the h interface.
Then we add the SConscript file, specifying how to build the HelloClient component:
$ vi SConscript
$ cat SConscript
Import("env")
comp = env.KengeComponent("HelloClient", LIBS=["c"])
Return("comp")
$
After this we are ready to build the application (see Getting Started for more detail on building and required toolchains).
$ cd ../../../ $ tools/build.py PYFREEZE=False machine=gumstix project=iguana mutex_type=kernel apps=hello
and run it in the simulator:
$ tools/build.py PYFREEZE=False machine=gumstix project=iguana mutex_type=kernel apps=hello simulate
the output should eventually show:
This is the hello component saying hello world This is the hello component saying hello world This is the hello component saying hello world This is the hello component saying hello world ...
Tutorial 2: A Compound Component
A somewhat more complex application uses compound components. We also cover reusable components, that is components that are general and could be used by many different applications. We extend our initial application to look like this.
------- ----------------------------
| Hello | |HelloCompund |
| Client| | |
| | | ------- ------- |
| |--(O-----|-O-| Hello | | World | |
| | Hello | | Comp |--(O-| Comp | |
------- | | |World| | |
| ------- ------- |
| |
----------------------------
The iguana/apps/hello/camkes/hello.camkes file now becomes:
$ cat iguana/apps/hello/camkes/hello.camkes
import "Hello.idl4";
import "World.idl4";
import "std_connector.camkes";
import "HelloComp.camkes";
import "WorldComp.camkes";
component HelloClient {
control;
uses Hello h;
}
component HelloCompound {
provides Hello h;
composition {
component HelloComp hcomp;
component WorldComp wcomp;
connection IguanaRPC hworld(from hcomp.w, to wcomp.w);
connection IguanaExportRPC helloexport(from hcomp.h, to h);
}
}
assembly {
composition {
component HelloCompound hellocomp;
component HelloClient hclient;
connection IguanaRPC hello(from hclient.h, to hellocomp.h);
}
}
$
The HelloClient component remains unchanged.
The HelloComp and WorldComp components will be placed in an
application independent directory for reusable components (that is,
components that can be reused in different applications). This
directory is the components/ directory at the root of the OKL4
directory tree,
$ cd components $ mkdir HelloComp $ mkdir WorldComp
The directory structure for each reusable component is similar to that for application-specific components (e.g., our HelloComponent in the previous example).
$ cd HelloComp $ mkdir camkes $ mkdir src $ cd camkes
The definition for HelloComp is in its own .camkes file
$ vi HelloComp.camkes
$ cat HelloComp.camkes
import "Hello.idl4";
import "World.idl4";
component HelloComp {
provides Hello h;
uses World w;
}
$
The source file for the component is simple
$ cd ..
$ vi src/hellocomp.c
$ cat src/hellocomp.c
#include <stdio.h>
#include <HelloComp_h.h>
#include <HelloComp_w.h>
void h__init(void) {
}
void h_hello(char *msg) {
char *world_msg;
printf("This is the hello component saying hello\n");
world_msg = w_world(msg);
printf("The world component told us: %s\n", world_msg);
}
$
and the SConscript file
$ vi SConscript
$ cat SConscript
Import("env")
comp = env.KengeComponent("HelloComp", LIBS=["c"])
Return("comp")
$
We have a similar structure for WorldComp
$ cd ..
$ cat WorldComp/camkes/WorldComp.camkes
import "World.idl4";
component WorldComp {
provides World w;
}
$ cat WorldComp/src/world.c
#include <stdio.h>
#include <WorldComp_w.h>
void w__init(void) {
}
char world_buf[256];
char * w_world(char *msg) {
sprintf(world_buf, "World got a message: %s", msg);
return world_buf;
}
$ cat WorldComp/SConscript
Import("env")
comp = env.KengeComponent("WorldComp", LIBS=["c"])
Return("comp")
$
For WorldComp there is still one part that hasn't been defined, and
that is World.idl4. We place this in a directory for global
reusable IDL interfaces components/include.
$ cd include
$ vi World.idl4
$ cat World.idl4
interface World {
smallstring world(in smallstring msg);
};
$
Since the Hello interface is now also being used by a reusable
component we move the Hello.idl4 file to components/include as
well.
We can now go back to the root OKL4 directory and do
$ tools/build.py PYFREEZE=False machine=gumstix project=iguana mutex_type=kernel apps=hello simulate
to build and run the new application.
The output should eventually show:
This is the hello component saying hello The world component told us: World got a message: hello world This is the hello component saying hello The world component told us: World got a message: hello world This is the hello component saying hello The world component told us: World got a message: hello world ...
Note that we did not create a HelloCompound directory or component anywhere. Since HelloCompound is a compound component that encapsulates other components, all the code for it is generated automatically.
Tutorial 3: Using Events
In this example we cover components that communicate using Events. The scenario is as follows:
------- ------- | Event | | Event | | Client|--->>----| Hello | | | EHello | | | | ------- | | ------- | | | Event | | |--->>----| World | | | EWorld | | ------- -------
We prepare the directory
$ mkdir iguana/apps/hello3 $ cd iguana/apps/hello3
and the relevant files
$ vi camkes/hello3.camkes
$ cat camkes/hello3.camkes
import "std_connector.camkes";
component EventClient {
control;
emits EHello hevent;
emits EWorld wevent;
}
component EventHello {
control;
consumes EHello hevent;
}
component EventWorld {
control;
consumes EWorld wevent;
}
assembly {
composition {
component EventClient eclient;
component EventHello ehello;
component EventWorld eworld;
connection IguanaAsynchEvent hello_event(from eclient.hevent, to ehello.hevent);
connection IguanaAsynchEvent world_event(from eclient.wevent, to eworld.wevent);
}
}
$ vi SConscript
$ cat SConscript
Import("*")
app = env.KengeComponentApplication("hello3")
Return("app")
$
We also create the relevant files for each of the components.
$ mkdir EventClient EventHello EventWorld
$ vi EventClient/SConscript
$ cat EventClient/SConscript
Import("*")
comp = env.KengeComponent("EventClient", LIBS=["c"])
Return("comp")
$ mkdir EventClient/src
$ vi EventClient/src/client.c
$ cat EventClient/src/client.c
#include <stdio.h>
#include <EventClient_hevent.h>
#include <EventClient_wevent.h>
void run(void);
void run(void) {
printf("Starting the client\n");
printf("--------------------\n");
for (int i = 0; i < 20; i++) {
hevent_emit();
printf("sent hevent %d\n", i);
}
for (int i = 0; i < 20; i++) {
wevent_emit();
printf("sent wevent %d\n", i);
}
printf("Finished the client\n");
}
For an event emitter an appropriate emit() function is generated
for every event interface that a component defines. In this case the
functions are hevent_emit() and wevent_emit().
$ vi EventHello/SConscript
$ cat EventHello/SConscript
Import("*")
comp = env.KengeComponent("EventHello", LIBS=["c"])
Return("comp")
$ mkdir EventHello/src
$ vi EventHello/src/hello.c
$ cat EventHello/src/hello.c
#include <stdio.h>
#include <EventHello_hevent.h>
void run(void);
void run(void) {
int i;
for (i =0; i < 20; i++) {
hevent_wait();
printf("EventHello component says hello\n");
}
}
For an event consumer, there are several different ways to receive
events. The EventHello component uses the generated wait()
function called hevent_wait(). This function blocks until the
appropriate event arrives. If an event arrived before the wait()
function is called then it will return immediately without waiting.
Note that if multiple events arrive between calls to wait() then
they are all merged into a single event.
$ vi EventWorld/SConscript
$ cat EventWorld/SConscript
Import("*")
comp = env.KengeComponent("EventWorld", LIBS=["c"])
Return("comp")
$ mkdir EventWorld/src
$ vi EventWorld/src/world.c
$ cat EventWorld/src/world.c
#include <stdio.h>
#include <EventWorld_wevent.h>
void say_world(void *not_used);
void say_world(void *not_used) {
printf("EventWorld component says world\n");
wevent_reg_callback(say_world);
}
void run(void);
void run(void) {
wevent_reg_callback(say_world);
}
Another way that consumers can use events is to register a callback
function using the reg_callback() function. The EventWorld uses
this approach, registering the say_world() callback using the
wevent_reg_callback() function. The callback is invoked when an
event arrives. Note, however, that a callback does not remain
registered after it is invoked. Since, in this example, we wish the
callback function to be invoked every time an event arrives we
re-register it within the function itself.
Finally, then we can build the application
$ tools/build.py PYFREEZE=False machine=gumstix project=iguana mutex_type=kernel apps=hello3 simulate
When run the output will be something like:
Starting the client -------------------- sent hevent 0 sent hevent 1 sent hevent 2 sent hevent 3 sent hevent 4 sent hevent 5 sent hevent 6 sent hevent 7 sent hevent 8 sent hevent 9 sent hevent 10 sent hevent 11 sent heventEventHello component says hello 12 sent hevent 13 sent hevent 14 sent hevent 15 sent hevent 16 sent hevent 17 sent hevent 18 sent hevent 19 sent wevent 0 sent wevent 1 sent wevent 2 sent wevent 3 sent wevent 4 sent EventHello component says hello wevent 5 sent wevent 6 sent wevent 7 sent wevent 8 sent wevent 9 sent wevent 10 sent wevent 11 sent wevent 12 sent wevent 13 sent wevent 14 sent wevent 15 sent wevent 16 sent wevent 17 sent wevent 18 sent wevent 19 Finished the client EventWorld component says world
Since events are asynchronous, execution doesn't switch to the two receiving components right away, and therefore they don't see all the individual events.
Tutorial 4: Passing Data Through Dataports
Dataports are regions of shared memory between two components. This example covers the basics of implementing a simple dataport connection between two components. The application will look like the following:
------- _ ---------- | Hello |--|_|----|Hello | | Client| Data |Component | | | | | | |--(O-----| | | | Hello | | ------- ----------
We prepare a directory for this application as usual
$ mkdir iguana/apps/hello4 $ cd iguana/apps/hello4
and the relevant files
$ vi SConscript
$ cat SConscript
Import("*")
app = env.KengeComponentApplication("hello4")
Return("app")
$
We also create the relevant files for each of the components.
$ mkdir HelloClient HelloComponent
$ vi HelloClient/SConscript
$ cat HelloClient/SConscript
Import("*")
comp = env.KengeComponent("HelloClient", LIBS=["c"])
Return("comp")
$ vi HelloComponent/SConscript
$ cat HelloComponent/SConscript
Import("*")
comp = env.KengeComponent("HelloComponent", LIBS=["c"])
Return("comp")
$
The next thing we do is create a type for the dataport. This is
defined as a typedef in a C header file placed in the
components/include directory. Support for placing these in
the application's include directory will be added soon.
$ vi components/include/dataport_hellodefs.h
$ cat components/include/dataport_hellodefs.h
#define DATA_SIZE 512
/* Definition used in hello4 */
typedef struct hellodata {
//The current data size
int size;
//The data
char data[DATA_SIZE];
}Data;
$
Unfortunately, there is currently no support for including .h files
directly in .camkes files, so this file must be included in an IDL
interface file that is imported by your camkes file. We'll put it in
the Hello.idl4 file.
$ vi include/Hello.idl4
$ cat include/Hello.idl4
import "dataport_hellodefs.h";
interface Hello {
void hello(void);
};
$
Then onto the main camkes file:
$ vi camkes/hello4.camkes
$ cat camkes/hello4.camkes
import "std_connector.camkes";
import "Hello.idl4";
component HelloComponent {
dataport Data buf;
provides Hello h;
}
component HelloClient {
control;
dataport Data buf;
uses Hello h;
}
assembly {
composition {
component HelloComponent hcomp;
component HelloClient hclient;
connection IguanaRPC hello(from hclient.h, to hcomp.h);
connection IguanaSharedData hellodata(from hclient.buf, to hcomp.buf);
}
}
$
Notice that the type of dataport immediately follows the dataport
keyword. If a dataport is declared with Buf as its type, CAmkES will
treat it as a default, untyped dataport and no type needs to be
specified. If a dataport is given a type that doesn't exist or hasn't
been included in one of the imported IDL files (with the exception of
Buf), you'll most likely get an error when the compiler can't
find a definition for the type. Currently CAmkES just blindly
generates the stub files, and lets the C compiler deal with any
undefined types.
The next step is to implement the client and server components.
$ mkdir HelloClient/src
$ vi HelloClient/src/client.c
$ cat HelloClient/src/client.c
#include <stdio.h>
#include <string.h>
#include <HelloClient_h.h>
#include <HelloClient_buf.h>
void run(void);
void run(void) {
printf("Starting the client\n");
printf("-------------------\n");
//Write data to shared mem section
strcpy(buf_data->data, "hello world");
//Indicate how large the data is and where to read from
buf_data->size = strlen(buf_data->data);
while (1) {
h_hello();
}
printf("After the client\n");
}
$ mkdir HelloComponent/src
$ vi HelloComponent/src/hello.c
$ cat HelloComponent/src/hello.c
#include <stdio.h>
#include <string.h>
#include <HelloComponent_h.h>
#include <HelloComponent_buf.h>
void h__init(void) {
}
void h_hello(void) {
printf("This is the hello component saying: %s (data: %p, size: %d)\n",
buf_data->data,
buf_data->data,
buf_data->size );
}
$
Building and running the example gives us:
This is the hello component saying: hello world (data: 0x80409008, size: 11) This is the hello component saying: hello world (data: 0x80409008, size: 11) This is the hello component saying: hello world (data: 0x80409008, size: 11) This is the hello component saying: hello world (data: 0x80409008, size: 11) ...
More Components
There are several other example components included in the distribution.
fat_app is a more complex application that uses a compound component
that provides a FAT filesystem. It also uses dataport interfaces.
Directory Structure Overview
The relevant directories in the OKL4 tree are
components/
Contains reusable components. These are components that are not specific to a single application but are general and meant to be reused by different applications. Functionality-wise they are a mix between Iguana servers and libraries.
Each component in this directory has its own subdirectory and that
subdirectory has the following structure (with COMPONENT replaced by
the component name):
components/COMPONENT/
SConscript
camkes/
COMPONENT.camkes
include/
*.idl4
src/
*.c
The COMPONENT.camkes file contains the component definition. The
files under include/ are interface files specific to the
component. Note that interface files could also be stored under
components/include if they are more general and not necessarily
specific to this one type of component. The src/ directory
contains the source files that implement the component's
functionality.
components/ also has an include/ subdirectory. This directory holds
any globally usable .camkes files or .idl4 files. For example, the
std_connectors.camkes file, which specifies all the default
connectors, lives here. IDL files which are used by multiple
components can also be placed here.
iguana/apps/
Contains applications. Componentised applications have the following directory structure:
iguana/apps/APPNAME/
SConscript
camkes/
APPNAME.camkes
include/
*.idl4
COMPONENT/
SConscript
camkes/
include/
*.h
src/
*.c
where APPNAME is replaced by the application name and a COMPONENT
directory exists for each application-specific
component (with COMPONENT being replaced by the component
name). APPNAME.camkes contains a CAmkES ADL description of the
application. The include directory contains any interface defintions
that are specific to this application (and not used anywhere outside
the application). There is a COMPONENT directory for each application
specific component used in the APPNAME.camkes file.
Application-specific components cannot be used in other applications.
The directory structure under here is similar to that of reusable
components. An application-specific component can be defined in the
application's .camkes file or in a separate .camkes file in that
components camkes/ subdirectory.
Generated Files Overview
While building a CAmkES-based application there are a lot of files generated. These include header files that define what component code must implement and what it can call to invoke other components, as well as stubs to implement the connections between components, code required to implement compound components, and code containing service loops for components. The build process also generates code to load, create, and initialise the components and connections between them at system startup time.
All of the generated code is put in the build/components/ directory.
Most of the generated code will be put in build/components/APPNAME.
With each application having its own directory. The directory tree in
there follows the component composition hierarchy. There will be a
subdirectory for every top level component instance. If a component
instance is a compound component, then its directory will contain
subdirectories for every component instance that it contains. Each
base component's subdirectory will contain the generated source files
relevant to it. It will also include a SConscript file specifying how to
build that component as an Iguana server -- this involves combining
the programmer specified code and the generated code, compiling it,
and linking it together into an object file that can be included
in the boot image.
The Code
The code for all the tutorials can be found at camkes-apps-tutorial--release--1.0.tgz

