Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Feb02: Embedded Space


Feb02: Embedded Space

Ed is an EE, PE, and author in Poughkeepsie, New York. You can contact him at [email protected].


Coffee comes in five descending stages: Coffee, Java, Jamoke, Joe, and Carbon Remover. This stuff was no better than grade four.

Glory Road

— Robert Heinlein

Eminently reputable folks tell me Java gained an enthusiastic following because it's just a whole lot easier to write good code with Java than, say, C++. They further insist that the fact Microsoft didn't introduce Java has nothing whatsoever to do with its heady-and-ready acceptance. It's also obvious that Sun's legal footwork hasn't contributed anything to the cause.

I was particularly interested in what's required to use Java in an embedded application, given some seemingly significant disadvantages. Judging from the number of places where Java appeared at the Embedded Systems Conference/Boston, though, it's well into the useful stage.

As I expected, what you actually get may bear little relationship to the hype you've read, so it pays to peer under the lid before you add water and plug it in. In some cases, what you see is definitely not what you get.

Let's take a look at what Java means and how it works, then we can put some of the products I saw into perspective. I'll avoid the language details you can find elsewhere and concentrate on how Java fits into the whole embedded realm.

The Slow Way

The Java language originated at a Sun skunkworks in the early '90s as a way to build set-top boxes, an application area that still hasn't quite lived up to its early revenue target. As a result of that heritage, Java includes the bit-twiddling instructions required for direct hardware control, but oddly, lacks any I/O instructions to read or write those bits.

There was a good reason for that paradoxical state of affairs: Java emphasizes safety and security above raw speed. In the case of I/O, requiring permission beforehand allows a more trusted authority to decide whether the I/O should actually occur. If, for whatever reason, the authority decides the program shouldn't do any I/O, it simply can't.

This is not unlike code running under a protected-mode operating system (that is, any contemporary OS) on an x86 processor. Unfortunately, machine-level x86 code isn't portable to any other architecture, a fatal flaw in an arena where weird architectures are the norm. Java's designers defined a language and CPU architecture that didn't match anything and deemed the result "machine independent" because it didn't run equally well on any extant hardware. Got that?

They defined Java in two stages. At the human-readable level, Java descends from the C branch of the family tree. At the machine level, it's an assembly language that is interpreted rather than run directly on real hardware.

Prior to Java's introduction, interpreted languages were lumped with Basic, the Ur-language burned into the ROM of early PCs and the brains of early hackers. Basic began life in the mid '60s as a time shared, interpreted language and continues today, for better or worse, as the scripting language for Microsoft products.

Being interpreted, languages such as Basic were relatively slow. Being intended for beginners, they were not for true hackers. Being easy to learn and easy to use, they were surprisingly successful with quite a few people.

Essentially by definition, an interpreted language requires an interpreter, a higher authority that parses and executes the program, handles I/O, accesses external routines, and in general, does all the hocus pocus required to get useful work from the source code. Interpreting each line of source code may require tens or hundreds or thousands of actual CPU instructions, which is why interpreted programs had a reputation for stately execution speeds.

Java adds a twist to this notion with a two-stage compile-and-interpret sequence. A compiler translates the human-readable source code into machine instructions, called "bytecodes," for a virtual machine. Java's VM design resembles a stack-based CPU from the time when IBM's competitors thought alternative mainframe architectures conferred a competitive advantage.

The compiler writes its bytecode output into a Java classfile. There are various flavors of bytecode packaging having to do with applets, servlets, JavaBeans, Enterprise JavaBeans, and so forth and so on, but for our purposes, bytecodes represent the original Java program in a compact binary format better suited for interpretation than the source code.

It's worth noting that, unlike Basic, Java is not an inherently interactive language. Contemporary debuggers provide a development environment much like those old Basic consoles, but even with incremental compilation, the interpretation actually occurs at the bytecode level, not the source level.

The Java Virtual Machine emulates the stack-based CPU architecture and serves as the interface between the Java program and the actual hardware. Any correctly written Java program will, in principle, execute identically on any JVM, but each JVM must be custom tailored for a specific hardware environment. It's worth noting that JVMs aren't written in Java because they require direct, unfettered access to the underlying hardware.

The JVM processes a Java program by reading a bytecode from the classfile and regarding it as an assembly language opcode. After decoding the opcode, the JVM fetches any operands, verifies that the instruction is allowed, and performs the operation. After updating the current instruction pointer, it fetches the next bytecode and repeats the same process. That's precisely how hardware would do it, with the proviso that it's easier to tweak a software interpreter than a chunk of silicon.

As with C and C++, Java programs may make use of external routines ("class libraries" in Java-speak) not written by application programmers, including hardware interfaces, network protocols, and so forth and so on. Those libraries form an essential part of the overall system and must be available to all programs.

Unlike C and C++, the JVM can resolve, locate, and invoke those routines when the Java program calls for them, not when the compiler transmuted the source code into the classfile. In principle, this allows easy updating and maintenance. In practice, late binding can become a refined version of the DLL Hell that afflicts Windows machines.

That's pretty much the way Java worked in the beginning. Since then, a bunch of really smart folks have been chipping away at problems and adding improvements.

The Split Way

Sun's original soundbyte ran "write once, run anywhere" to emphasize that Java programs were machine independent. Unfortunately, while the Java language itself might be machine independent, the various libraries and JVMs weren't exactly identical (although they should be). Even without Microsoft's embrace-and-extend technique, folks found themselves writing once, testing everywhere, and perhaps iterating a few times.

The various class libraries also didn't fit well in embedded applications that might not ever use many of their components. A conforming Java implementation had to supply them so that an arbitrary Java program could run on that system, even on a closed, deeply embedded box. There was no standard way to be nonstandard, despite the obvious necessity for different versions.

After some initial fumbling, Sun fissioned Java into three editions that span the range from vast networking applications down to small embedded systems. Contrary to the soundbyte, though, an arbitrary Java program may now not run on any JVM.

J2EE, the Enterprise Edition, sports all the bells and whistles required for business-level applications. J2SE, the Standard Edition, is what most folks think of when they think of Java. J2ME, the Micro Edition, has just the subset of features required for smaller-scale embedded applications.

The embedded marketplace demands highly specialized hardware, tailored software, and fiercely constrained unit cost control, as Sun discovered to its evident surprise. (Well, so did Microsoft with WinCE, but that's another story.) J2ME now includes Configurations that specify which JVM language features and class libraries must be available for Java programs. Yes, even parts of the language are now optional, at least in some situations.

The Connected Device Configuration assumes an always-on network connection, while the Connected Limited Device Configuration assumes the gizmo can go offline. A Profile within each Configuration defines the exact set of API class library functions available to the program. A "Mobile Information Device" has different requirements than a "Car Navigation System" and, reasonably, the APIs need not be identical.

In principle, a Java program written to a specific Profile and Configuration will run on any system correctly implementing the same Profile and Configuration. In practice, the program may require additional hardware-dependent features that lash it firmly to a specific chassis. But that's not really a portability issue, is it?

Below and beyond the level of J2ME Configurations and Profiles lies the JavaCard language splinter that runs on smart cards. Although it lacks many language features and most of the APIs, it remains Java of a sort. One has trouble imagining a JavaCard program doing anything useful in a J2EE environment, but it should run perfectly well. The converse, of course, is not true.

Having picked the correct edition, configuration, and profile for an application, you'll find even more subtle differences that can affect not only how fast your program will run, but whether it will run at all.

The Faster Way

As a rule of thumb, plain old Java is an order of magnitude slower than the equivalent C or even C++ program with a much larger memory footprint. In embedded applications, either liability can be a showstopper.

There are several ways to improve the performance of a Java program in either speed or space. First and foremost, of course, is choosing the right algorithm and not making any egregious blunders while implementing it. You knew that, of course, but it's always worth repeating; I've seen code that makes you wonder if those folks ever read anything other than the Sunday funnies.

Java overloads the word "compiler" with two distinct meanings. One compiler translates Java source code, the human-readable text, into bytecodes. Those bytecodes may be interpreted by a JVM or further translated by a bytecode compiler into instructions intended for the actual, physical, hardware CPU.

The first compiler produces standard Java bytecodes as defined by the Java spec, which can be interpreted by any conforming JVM. The second compiler produces native code, which is inherently nonportable.

Now, in the old days, a bytecode compiler would have been called an assembler, but that was then and this is now. A bytecode compiler may emit an entire file of native CPU instructions or operate in conjunction with the JVM to process chunks of bytecode for immediate execution.

The former situation, known as Ahead-Of-Time compilation, generally produces faster results because it completely eliminates the JVM's interpretation overhead. Unfortunately, in the process it also eliminates many of the advantages the JVM brings to the Java language, while simultaneously producing a file that's an order of magnitude larger than the original bytecode. If you're storing that file in ROM, a 10× increase will be a killer.

A Just-In-Time compiler, on the other hand, processes bytecodes into native instructions when the JVM first encounters them. The JVM caches the native instructions for later use, which bloats its RAM requirement by an order of magnitude over the interpreted version. A 10× memory increase can be a showstopper, too.

In either situation, buying performance at the cost of space is a familiar software trade-off. Fortunately, you can apply an AOT compiler to only those classes that need high performance and a JIT compiler to the remainder, to gain the most of the speed benefit without incurring a severe space penalty. If, that is, your particular Java development and execution environments support such trickery.

The Hard Way

Once upon a time, back in the mid '80s, Intel announced the 8052AH-BASIC, a microcontroller that directly executed Basic-language programs. You connected your PC to the chip through a serial port, typed in your program, debugged it using an on-chip monitor, then burned it into an EPROM so it would run whenever the power went on.

Roughly a decade later, I developed the firmware for a smaller microcontroller that executed tokenized Basic programs. You typed your program into a PC, compiled it into a tokenized form, downloaded it to the microcontroller's NVRAM, and it ran forever thereafter.

The 8052 stored programs in a mildly compressed format with tokens representing operators and the remainder as ASCII text. The microcontroller I programmed used a format remarkably like bytecodes, although I tailored the language to suit an 8-bit CPU with essentially no memory worth mentioning.

To the outside observer, both systems ran Basic as a native language. In both cases, an assembly-language program actually interpreted the user program, much as a JVM interprets Java bytecodes.

However, bytecodes represent a simple enough language that a hardware implementation is possible, if nontrivial. By moving instruction decoding and execution into hardware rather than software, performance can improve by orders of magnitude.

The first versions of this notion were coprocessors, much along the lines of the venerable Intel 8087 math coprocessors. The main processor, which normally runs the JVM, unleashes the Java hardware on a chunk of bytecode and waits for the results, rather than interpreting the bytecodes as usual.

Assuming that the handshaking overhead remains small, this can significantly speed up Java execution. The additional hardware cost may be a showstopper, as the system now includes a second CPU. Unlike an SMP system, the two processors do not generally run in parallel.

In addition, Java coprocessors typically implement only a subset of the Java language, leaving hard parts such as floating point to the host CPU. Determining exactly which subset of the Java language resides on a given Java chip can be an adventure, as they predate the J2ME Configuration and Profile standards.

Now, if you could run everything on the Java chip, you could eliminate one CPU. Recall that a chip implementing only standard Java bytecodes cannot run a JVM because it can't touch the I/O ports or memory. Vendors, therefore, allocate unused bytecodes, in what seems to be a nonstandard manner, to extra functions that support a JVM written in a sort of system-Java-language extension.

There are, by definition, only 256 bytecodes available, but "escape" bytecodes can extend the set indefinitely. I believe there's no standard definition of this technique, either.

Transistors being relatively cheap these days, other vendors append the Java chip to an existing CPU design. While still sharing the memory port, the two CPUs have a much more intimate and, thus, faster and more capable interface. The Java chip can then translate the bytecodes directly into machine instructions that the other CPU executes directly.

This does not eliminate the need for the JVM, but at least the Java system support functions can be written and executed in native CPU language. In point of fact, a stock JVM with a bit of tweaking will work fine.

The Java processor may thus be an external chip, an internal appendage, or just a wad of IP that you drop into your full-custom gate array. The choices are bewildering, indeed.

Reentry Checklist

With the terminology and a rough overview in hand, next month I'll take a look at how and where some Java products fit in. While not a market survey, you'll see what you're getting and, perhaps more importantly, what's not mentioned.

Until then, you may as well start from http://java.sun.com/ to find out about Java from the source. Many of the handouts and talks from various ESCs are now online at http://www.esconline.com/archive.htm.

No, my tokenized Basic project didn't turn into the famous Basic Stamp. There must have been something going around in tech circles at the time, though.

By the way, my "Time Zones" column (DDJ, October 2001) prompted a note from Michael A. Quinlan, who pointed out that UTC, the acronym for "Coordinated Universal Time," uses neither the English nor the French word order. The committee couldn't agree on the English CUT or French TUC, so they picked an acronym that didn't read correctly in either language. Sound familiar?

For a look at the future of distracted driving, which I mentioned in December, try http://www.mobiledesk.com/expressdesk.htm. When used exactly as directed, it's perfectly harmless; I had a long talk with the president of the company and we're in violent agreement about that. We amicably differ, however, about the advisability of selling such an enabling technology. Check it out.

DDJ



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.