The Java development and runtime environment, with its "write once,
run anywhere" paradigm, brings enormous advantages to the embedded
industry. Java code is highly reliable, easily ported, and includes
features such as Internet readiness, security, and the ability to
download code at runtime to upgrade or extend applications.
As such, it's ideal for Internet appliances (such as set-top
boxes, Internet screen phones, or handheld devices) as well as
traditional embedded devices (such as printers, medical devices,
measurement and control instruments, telecommunication and
data-communication equipment now connecting to the Internet).
In Part 1 of this article (JDJ, Vol. 6, issue 9), we
discussed some of the challenges of using Java for developing
embedded systems and some of the solutions that are available to
developers. In Part 2, we describe the benefits Java brings to the
world of embedded software development. We also illustrate, based on
a practical case study, how using Java for writing and running
embedded software can benefit application developers.
Is Java Right for Your Embedded Device?
By asking the questions at the right, you should be able to
determine if Java is the right development environment for your
embedded device.
- Will you want to download code to the device?
- Will the device run in a network with a similar class of
devices from other manufacturers or any other kind of devices?
- Will there be multiple third-party content providers for the device?
- Will user's experience (look and feel, stability, security)
matter much?
- Are you working within a short-development time frame?
If you answered yes to any of these questions, Java may be
the right choice for you. Here's why.
Java Benefits
The intrinsic advantages of Java are portability,
reusability, and development life-cycle enhancements. They're
invaluable for any software engineering project, including embedded
software development. The benefits include:
- Dynamic extensibility: A Java program can download code at
runtime by fetching new class files and either replacing the same
classes already loaded into memory or adding them to the application.
Dynamic extensibility enables developers to extend the functionality
of an application, upgrade classes with new versions of code, or
simply fix software problems. It's also the base for implementing an
application-management framework and a service-delivery platform.
- Security: Strict code verification prior to execution ensures
that code doesn't try to disregard the protections imposed by the
language, build direct memory access pointers, or use the wrong
object. Java applets also obey limitation rules (known as the sandbox
concept) that Web browsers enforce to forbid access to system
resources, such as files and network hosts. It's impossible in theory
to introduce malicious code into a Java application. Java threads
can't corrupt the execution space of another thread.
- Portability: Java provides standard programming interfaces to
OS-level services such as multithreading, networking, and graphics.
This enables embedded system developers to write platform-independant
code more quickly and easliy.
- Reliability: Java provides the reliability that embedded
systems require, such as the obligation of source structuring, strong
compile and runtime verifications, and an efficient
exception-handling mechanism. Java's memory-management mechanism
removes sources of many problems associated with dynamic memory
allocation, such as memory leaks or corrupted pointers. Java also
relieves the embedded programmer of the hazardous object
de-allocation task.
- Reusable code: Java provides ease of code development, code
reuse, and better code quality due to its true object orientation,
simplicity, and development environment. A wide variety of affordable
and efficient Java development tools are available as are many
trained engineers.
Hands-on Porting Exercise
To illustrate how using Java for writing and running embedded
software can benefit application developers, we selected a single
healthy-sized example of a Java application - suitable and also
useful for embedded applications. The example includes the following
characteristics: it uses a simple graphical user interface, it's
network-oriented, it's based on an existing version of C for easy
comparison, and it's simple enough for a quick port, but large enough
to be a significant case study.
Several aspects of this hands-on porting exercise are
described below, basing comments on code snippets. The complete
application source code is available for download on the JDJ Web site
at www.sys-con.com/java/sourcec.cfm and can be freely used, modified,
and distributed. The download package consists of application source
code, documentation in javadoc format, Makefile, and precompiled
class files and a .jar file.
The application we chose is called jtalk, a port of the Unix
talk command. Here's an excerpt from the manual pages on the talk
command on a FreeBSD system (obtained by typing man talk on the
FreeBSD 4.3 system):
Talk is a visual communication program that copies lines from
your terminal to that of another user.
When first called, talk sends the message:
Message from TalkDaemon@his_machine...
talk: connection requested by
your_name@your_machine.
talk: respond with: talk your_name@your_machine
to the user you wish to talk to.
At this point, the recipient of the message should reply by typing:
talk your_name@your_machine
The talk utility is a two-way, screen-oriented communication
program. Once communication is established, the two parties can type
simultaneously, with their output displayed in separate regions of
the screen.
The talk application sets up a peer-to-peer communication
session with a remote user on another host. A talkd daemon has to be
running on both the caller's and the callee's machines and act as a
repository for active talk invitations from one party to another. The
caller has to be able to leave an announcement on the callee's
machine that an invitation exists and has to leave the invitation
itself on the caller's own machine. Once the session is connected,
the talk daemon isn't used; it's just peer-to-peer between the two
talk programs.
Since talkd daemons exist for Unix machines only, a port of
talkd in Java was also created as part of this case study so talk can
be used on Windows machines as well as in a non-Unix embedded Java
application environment, such as Wind River's VxWorks and Personal
JWorks.
Note: There are two incompatible talk protocols of different
ages. The older one uses UDP port 517 and is in use on Solaris. The
newer one (which we chose to implement) uses UDP port 518 and is in
use on BSD systems, such as FreeBSD or Wind River's BSD/OS.
Technically, the port 517 protocol is called talk and the
port 518 protocol is called ntalk. The port number 518 is currently
hard-coded into jtalk and jtalkd. But it wouldn't help to change them
to 517 because the older talk protocol on port 517 is incompatible.
To run the jtalk program on your JVM, download the case study
package, put jtalk.jar somewhere on your file system, add the
location of jtalk.jar to your classpath, and invoke as follows:
java com.windriver.jtalk.jtalk {person} [{terminal}]
If the machine on which you're running jtalk doesn't run a
talk daemon already, you'll have to first launch jtalkd with the
following command:
java com.windriver.jtalk.jtalkd
The syntax for {person} can be any of the following:
username
username@hostname
hostname!username
hostname:username
{terminal} is optional. It's required when responding to an
invitation from someone with more than one login to be sure you get
to the right one.
If you can't run multiple JVMs on your system (which is the
case on most embedded RTOSes), the download package includes a basic
"shell" with a simple self-explanatory GUI to help you launch the
jtalkd and jtalk applications:
java com.windriver.jtalk.jtalkShell
If you want to recompile the source code, you can use the
Makefile for GNU make included in the download package.
The original C files from which jtalk was created are also
included in the download package for your convenience. These files
come from the FreeBSD 4.3 distribution.
Choosing to port an existing Unix application, coded as a C
program, was deliberate so a head-to-head comparison could be easily
done between the C code and the Java code. Table 1 summarizes the
major characteristics of the two versions.
The experience with jtalk showed that Java can be used more
productively as a language, and as a programming environment, than
C/C++ for certain types of applications, especially in the embedded
area.
Some of the productivity advantages of Java are at the
language level. For example:
- The language's insistence on strong type checking promotes
the careful arrangement of code into well-designed classes, saving
effort when supporting the code later.
- The jtalk program is cleanly organized in a directory
structure com/windriver/jtalk following the package organization,
leading to clean, easily understandable, and maintainable code. Each
Java source file starts with:
package com.windriver.jtalk;
- The C code for the talk program has a strange organization,
with global variables shared by many modules. Most of the time it
took to port the code was actually figuring out how that code worked.
In addition, the use of global variables makes the application
non-reentrant, which can be a major issue for many embedded systems.
As a consequence, it's impossible to have multiple talk sessions on
an embedded system.
- The Java language's class inheritance (where a subclass
extends a superclass, adding new features or overriding other
features) and interface concepts (making implementations pluggable)
both lead to more flexible reuse of existing Java code. In C, you
usually have to modify to reuse code.
- The file jtalkSessionClient.java (see Listing 1) defines an
interface for a client session implemented among other interfaces in
the AWT area, and makes the creation of GUIs much easier by the main
class jtalk in jtalk.java (see Listing 2).
- Exception handling is another fantastic built-in language
feature that Java programs can efficiently take advantage of to
manage abnormal situations or a rupture in the usual flow of
execution. For example, jtalk defines a new RuntimeException called
jtalkSessionException (see Listing 3) that is thrown by jtalk when a
problem occurs in jtalkSession.java (see Listing 4) when trying to
get the hostname information.
- Another example of productivity gains from language-level
features of Java is automatic garbage collection, freeing developers
from tedious management of dynamic memory allocations, often prone to
a generation of nasty bugs.
Productivity advantages of Java from the overall programming
environment are, for example:
- The existence of the javadoc tool means that you can generate
helpful documentation in HTML format for Java code right from the
program source, leading to better documented source code and more
available documentation during all engineering phases.
- Figure 1 shows a snapshot of the HTML documentation page for
the jtalkSession class,
- The existence of standard API libraries for almost every
imaginable application programming need leads to quick coding of
applications. This also leads to smaller application code size, as
seen in this case study.
- If you're an application developer, you can write your
application faster using Java and the preexisting standard APIs. If
you're a system developer, however, you may find Java too abstract or
high level, lacking features like direct access to memory or
interrupt handling.
- The most important preexisting API sets relevant to embedded
applications running in PersonalJava types of platforms, such as Wind
River's Personal JWorks, are networking and user interface APIs.
Thus, if your application performs network communication and has a
user interface, you should at least consider using Java to more
productively code those portions of your application.
Figure 1:
To understand Java's suitability to write networking code,
look at the original C source for the talk program. Note all the
occurrences of signal() calls to set or clear a handler for the
SIGALRM used to manage connection timeouts. Instead of setting a
timeout on a socket the way it's done in the Java program, the C
version sets a timeout for the process; if the timeout occurs,
something is done. In one case, in invite.c, there's even a
setjmp/longjmp pair, with the longjmp coming from the tail end of the
SIGALRM handler used there. The effect is to create a crazy kind of
"loop"(see "flowchart" in Listing 5).
This loops every 30 seconds, remaking the invitation
repeatedly until at some point accept() manages to complete before
the SIGALRM is delivered.
In the Java sources, we simply set a 30-second timeout on the
ServerSocket. The equivalent accept() call then returns if there's no
incoming connection before the 30 seconds elapse. We just have a
simple loop in the Java sources (see Listing 6).
A Language and Platform
In this case study, we've demonstrated how Java can benefit
embedded software application developers, especially those focusing
on graphics and multithreaded networking. Java can benefit these
users when used both as as a programming language and a development
platform.
Java's standard, complete, and easy-to-use APIs allow
developers to write portable, more compact, more efficient, simpler,
and more easily maintainable code than in C, for which network,
threading, and graphics APIs are usually proprietary. In addition,
developers can also benefit from the language's advanced concepts
such as object orientation, packages, exceptions, and strong typing,
as well as from the advantages offered by Java's development
platform, including documentation generation, cross-platform
portability, and security features (which enable them to write more
efficiently for better code).
Although it wasn't covered in this article, Java is ideal as
an execution platform. Java's capability for dynamic and secure code
updates provides the essential building block for creating an
application and content-management framework that enables a
service-delivery platform. Java's standardized API and portable code
allows application developers to write platform-independent software
content or services independently from embedded-system manufacturers.
As long as applications are written to a Java specification that's
compatible with the system's Java application environment, end users
of such systems will be able to download, install, and run those
applications on their systems.
Author Bios
Vincent Perrier is Wind River's product manager for Java platforms.
He has a computer science and engineering degree from the University of Nantes in France.
vincent.perrier@windriver.com
Steven Schwarz is a senior member of the technical staff at Wind
River. Recently he has been making processor-independent ports of
CLDC and MIDP-reference implementations to VxWorks.
steven.schwarz@windriver.com
Listing 1: jtalkSessionClient.java
public interface jtalkSessionClient
{
/**
* This method is called by the session to deliver a status
message
* to the client.
*/
public void jtalkSessionStatus(String s);
/**
* This method is called by the session to get a character
typed
* by the local user of the session; each such character is
* transmitted by the session to the remote user of the ses
sion.
*/
public int jtalkSessionITyped();
/**
* This method is called by the session to inform the client
of a
* character typed by the remote user of the session.
*/
public void jtalkSessionHeTyped(int b);
/**
* This method is called by the session when it is about to
shut
* down.
*/
public void jtalkSessionEnding();
}
Listing 2: jtalk.java
public class jtalk
extends Frame
implements ActionListener,
KeyListener,
WindowListener,
jtalkSessionClient
Listing 3: jtalkSessionException.java
public class jtalkSessionException
extends RuntimeException
{
/**
* Construct a jtalk session exception without a detail mes
sage.
*/
public jtalkSessionException() {
super();
}
/**
* Construct a jtalk session exception with a detail message.
*/
public jtalkSessionException(String s) {
super(s);
}
}
Listing 4: jtalkSession.java
try {
myInetAddr = java.net.InetAddress.getLocalHost();
if (args[arg].equals("-host")) {
myInetAddr = java.net.InetAddress.getByName(args[arg + 1]);
arg += 2;
nargs -= 2;
}
myHostName = myInetAddr.getHostName();
}
catch (Throwable t) {
t.printStackTrace();
throw new jtalkSessionException("can't determine local host name");
}
client.jtalkSessionStatus("talk@" + myHostName + " starting.");
Listing 5: In the process context
1. <announce invitation to callee's talkd daemon>
2. <leave actual invitation on local talkd daemon>
3. <use setjmp() to establish this as a jump-to point>
4. <set SIGALRM timer for 30 seconds>
5. <call accept() to accept an incoming connection>
6. <cancel SIGALRM timer>
Code snippet from invite.c:
void
invite_remote()
{
...
setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
message("Waiting for your party to respond");
signal(SIGALRM, re_invite);
(void) setjmp(invitebuf);
while ((new_sockt = accept(sockt, 0, 0)) < 0) {
if (errno == EINTR)
continue;
p_error("Unable to connect with your party");
}
In the SIGALRM handler context:
1. <announce the invitation again>
2. <leave the invitation again>
3. <use longjmp to jump to the point 3 in the process context>
Code snippet from invite.c:
void
re_invite(signo)
int signo;
{
message("Ringing your party again");
waddch(my_win.x_win, '\n');
if (current_line < my_win.x_nlines - 1)
current_line++;
/* force a re-announce */
msg.id_num = htonl(remote_id + 1);
announce_invite();
longjmp(invitebuf, 1);
}
Listing 6: jatlkSession.java
void issueInvitation()
{
...
for (;;) {
if (current_idnum >= 0) {
client.jtalkSessionStatus(RINGING);
current_idnum++;
}
client.jtalkSessionStatus(ANNOUNCING);
msg.setMessage((byte) talkdMessage.ANNOUNCE,
current_idnum,
myUserName,
hisUserName,
hisTerminal,
server,
control,
myInetAddr);
controlTransaction(hisInetAddr);
remote_idnum = rs.getIdNum();
if (rs.getAnswer() != talkdResponse.SUCCESS) {
throw new jtalkSessionException("invitation rejected: " +
rs.decodeAnswer());
}
client.jtalkSessionStatus(INVITING);
msg.setMessage((byte) talkdMessage.LEAVE_INVITE,
current_idnum,
myUserName,
hisUserName,
hisTerminal,
server,
control,
myInetAddr);
controlTransaction(myInetAddr);
local_idnum = rs.getIdNum();
client.jtalkSessionStatus(WAITING2);
try {
data = server.accept();
data.setSoTimeout(1 * 1000);
dataInput = data.getInputStream();
dataOutput = data.getOutputStream();
}
catch (InterruptedIOException x) {
current_idnum = remote_idnum;
continue;
}
catch (Throwable t) {
t.printStackTrace();
new jtalkSessionException("can't get connection");
}
finally {
msg.setMessage((byte) talkdMessage.DELETE,
local_idnum,
myUserName,
hisUserName,
hisTerminal,
null,
control,
myInetAddr);
controlTransaction(myInetAddr);
msg.setMessage((byte) talkdMessage.DELETE,
remote_idnum,
myUserName,
hisUserName,
hisTerminal,
null,
control,
myInetAddr);
controlTransaction(hisInetAddr);
}
break;
}
}