uCommon Telephony Libraries

by David Sugar

A number of years ago I wrote an article [see our publication Linux Journal, November 1997, ``Linux as a Telephony Platform''] about the use of GNU/Linux for telephony applications. At the time I introduced the DBS_Server, a freely licensed telephony server I wrote that integrates with the Panasonic DBS telephone system. I speculated that the future would be very bright for the use of GNU/Linux solutions in the telecommunications industry.

While the DBS Server had itself languished in obscurity for a long time since then, in fact, much has happened to promote free solutions for telecommunications in general and GNU/Linux-powered solutions in particular. Certainly the adoption of my Bayonne package, the first freely available telephony voice response server, as the official free telephony server of the GNU Project earlier last year, and the recent introduction of many other freely licensed telephony projects and packages of various kinds since then, has greatly pleased me.

However, I have also always been interested in smaller solutions--especially the use of free software for embedded applications. While Bayonne can be deployed on small systems, including 486-class machines with as little as 12MB of RAM, it hardly qualifies as a true ``embedded package''. I had thought of several different things I had worked on in the past when I started thinking about doing a free embedded package.

While the original DBS Server continued to provide reliable service for my home since it was first introduced, I had already started thinking about doing something with the DBS Server and the still somewhat new ``µCsimm'' (a microcontroller module built for Lineo's uClinux OS) when I unexpectedly heard from Peter Courtney who runs a company in the UK that was trying to introduce a general-purpose embedded ``PBX integration'' solution. From Peter, I learned about the Axis ETRAX product, which they use. Since it seemed their company was in part inspired by the ideas presented in my original Linux Journal article [see the November 1997 issue], and their product FAQ even quotes a few parts of it, I saw this as a good opportunity and platform to build a new and more modern version of my original DBS Server that could be deployed as an embedded Linux solution.

When one considers an embedded solution, one has to think about new tools and methods. Certainly C++, with the stdlibc++, would overwhelm the memory of a highly embedded system--even before the application was built. While I had spent much of the past few years working on C++ class libraries and servers like Bayonne, all these design approaches were inappropriate for the realm that this project would operate in. Finally, Axis, like the µCsimm people at Lineo and many other highly embedded vendors that support free software, had turned to uClinux methods and tools like the small uClibc replacement for glibc, to shoehorn a complete application, Linux kernel and supporting library that can run in 2MBs of system memory.

To start with, many of these highly embedded systems do not use traditional x86-based microprocessors and often do use specialized versions of gcc-based toolchains. The Axis toolchain is called cris and offers a gcc that supports their target CPU that can build tiny statically linked images. I was able to download and compile cris as a cross-compiler without any great difficulty.

One nice thing about the Axis toolchain and build environment is that they have set up some simple makefile rules that make it possible to compile both to the target device and on the host machine itself. The latter is useful for testing. With this in mind, I immediately went to work on a new DBS Server.

In the old DBS Server, I created a ``portable'' C-interface library that simplified many chores by offering convenience functions that I liked and bridged differences between systems. This was found in the sdk directory of the old DBS Server and proved useful in many other C-based projects that I was doing at the time. When I migrated to doing primarily C++ development, I created a class library with a similar purpose initially, which was called APE. This later became Common C++ and is now part of the GNU Project.

Similarly, I felt a need to create a common-embedded library, not just for writing this newer DBS Server, but for use in other embedded projects as well. Certainly it needed to be very small and yet prove able to contain measurably useful functions. Static linking would certainly help cut down in runtime size because only those functions actually needed by a given application would get linked in. Also, it had to be generic and not tied to a specific embedded environment and certainly also must compile under, and be usable with, a standard GNU/Linux host so that one could build and test embedded applications conveniently. Finally, it would have to be freely licensed. The result was uCommon.

uCommon

With uCommon I brought forward and updated some of the functions that were part of the original portable library found in the DBS Server. I also looked at my current Common C++ for further inspiration. I am now happy with the result and will be distributing this package as uCommon-R1 by the time this article is in print.

While most packages use a major.minor numbering scheme, including most of my own, I have chosen to use a single version release designation for uCommon. This represents the very low frequency of change that I expect with the library now that it has been tested through.

Much like Common C++, uCommon can be broken down into several fixed categories of functionality. These include configuration file parsing, user/password management and authentication, process management, sockets, serial I/O and string manipulation. These represent things that I believe are useful and necessary in many of the embedded applications I would imagine writing.

These functional families can be viewed as building upon what (g)libc or embedded runtimes like uClibc already provide. In this sense uCommon is meant to augment existing runtime services and the existing libc, rather than replace it. uCommon is also meant to compile either on a standard glibc-based Linux host or to be used in building embedded applications that use things like diet-c or uClibc.

The process model was chosen for uCommon rather than threading, as found in Common C++ for example, because most uC and similar tiny embedded Linux systems do not include a thread-base library like pthread. In fact, I am not even certain if the smaller libc derivatives commonly used on these systems are threadsafe or reentrant.

Within the process model, uCommon offers a number of IPC services and wrappers to make life easier when writing application services that are fork( )'d. These services include pidpipe, pidpacket and pidstream. pidpipe forms a two-way process pipe to a detached fork through two anonymous pipe handlers (much like what the | shell operation does). pidpacket uses UNIX domain UDP sockets for discreet message (packet) passing between a client and server process. pidstream uses an anonymous UNIX domain stream socket. pidpipe has the advantage and limitation that the size of the system kernel buffering is both known and small (4K).

Another convenience function is the pidfile. This implements the .lock file logic commonly found when protecting a filesystem resource against arbitrary access from multiple processes. The pidfile implementation provided is clean from common race conditions. While pid files do not work very well on network-mounted filesystems, this is not an issue with embedded applications.

Another set of process functions use SIG_ALRM to implement a process timer. This differs from using ``alarm'' alone in that the code needed to test whether a timer has expired exists directly, and one does not have to create a signal handler using these services.

Process functions signal_posix and signal_intr clarify and standardize signal handling. With signal_posix, true POSIX behavior is assured, while signal_intr can be used for signal handlers that potentially interrupt system calls.

One group of functions form a paged-memory allocator, which can be used to allocate small chunks of memory incrementally from pre-allocated page buffers. This will eliminate the need for many multiple ``malloc'' operations and fragmented memory blocks that can also prove very wasteful of memory in an embedded system. All allocated memory can be disposed of cleanly at once with freepager (uCommon library function that purges the allocation from the paged-memory allocator).

Finally, the process group of uCommon includes functions which make the creation and use of SysV-style semaphores easier by offering a basic interface that allows one to create, release, lock and unlock a semaphore object, without having to build semop tables in your code. A pdetach function enables a process to ``dæmonfy'' itself, essentially by detaching from the controlling terminal and making init (pid 1) its parent process.

A group of functions called keydata are used to parse configuration files. They assume such configuration files are formed from keyword pairs and grouped together under the bracketed ([ ]) section name. Hence, if one has an /etc configuration file like, for example:

/etc/myapp.conf
[device]
serial=/dev/ttyS1
speed=19200
[network]
bind=127.0.0.1
port=7000

one can use keydata to parse this file. The file is processed as a node of keypaths, where one complete section is loaded into memory and may then be examined. Comment lines are automatically skipped, and multiple instances of the same keyword in a given section are actually formed into a linked list.

The most common means of accessing a keydata section is with initkeydata. This will load a specified section into a keydata_t referenced object, and can include an optional list of default values. Keypaths include the file path and the section path, as referenced from /etc. Hence, one might, in the above, use:

static keyinit_t netdefs[] = {
        {"port", "7000"},
        {"bind", "*"},
        {NULL, NULL}};
keydata_t *netkeys = initkeydata("/myapp/network", netdefs);

The resulting pointer, netkeys, then holds the content of the loaded configuration file. Individual keyword values can be extracted with getkeylast. For example, to get the bind address, one might use:

char *bind = getkeylast(netkeys, "bind");
If multiple ``bind'' keywords existed in [network], the first one could be found with getkeyfirst, and getkeylist could be used to extract a list of all variants loaded. This allows for config files to contain logical lists. The getkeyindex function can be used to extract the list of keywords loaded for a given [section].

One can also manipulate the contents of the keydata_t keywords after they have been loaded into memory with setkeydata and clrkeydata. This could be used to insert override values based on argv[ ] command-line switches, for example. These values never replace the loaded ones; they are instead added to the ``chain'' of values for a given keyword and are visible with getkeylast( ). The getkeylist will return all prior values, including those set with setkeyvalue at the end.

Another set of functions covers authentication or at least, provides the minimum vrfpwent function to validate a user id and password combination against the /etc/passwd file. This function doesn't examine the /etc/shadow file because embedded Linux systems generally do not use shadow passwords or the overhead of additional passwd entry routines that this would add to the runtime libraries. The other function currently provided in the auth set, sockident, extracts the connecting user's ``local username'' using the ident server.

Serial routines are provided to handle serial I/O. While not every embedded Linux controller may have a network interface, all do seem to include serial I/O. Serial I/O is also used to integrate many external devices, as well as many kinds of telephone systems such as the Panasonic DBS.

The purpose of the uCommon serial I/O functions is to simplify serial port attributes and to provide a stable base configuration for use with binary and block I/O operations.

The first serial function is getserial, which simply opens the serial device node with the specified data format and speed, and with hardware handshaking enabled. It returns a valid file descriptor when successful.

Various I/O functions provided to read and write data from the serial device offer timeouts. Timeouts can be used to monitor a successful operation and specify when to stop waiting for more data to arrive. Some further improvements can and will be made to uCommon serial functions, perhaps even prior to distribution of R1.

The socket functions are meant to replace the use of complex sockaddr structures and name resolver libraries with simple string-based functions. Hence, one can perform a getsocket against a logical host name, for example socket = getsocket("www.linuxtoday.com", 80, SOCK_STREAM). This operation would normally involve using gethostbyname, building a sockaddr_in structure, and finally a socket and connect call. Named strings can also be used for names of interfaces to bind sockets and servers to locally.

In addition to internet connections, a similar set of convenient functions are provided for named UNIX domain sockets. Finally, sockets provide a recvline function. This performs line-oriented input from a socket descriptor directly, without the need for, or use of, stdio buffering.

Finally, uCommon provides some convenient string functions including case-insensitive comparisons, case-conversion functions and string trimming. The trim functions essentially are used to remove a specified set of lead and/or trailing characters from an existing string.

Making and Using uCommon

The Make system is a refined replacement for the Make ruleset provided with the Axis cris system. Mostly this was done to make it easier to drop in Make rules for other and different embedded target development systems and cross-compiler environments in the future.

One unpacks the uCommon tarball into a directory like most other free software. You should also install the cris compiler and tools. I assume the procedure would be similar for other eLinux-based target development systems, but so far I have worked primarily with only the Axis tools.

Once the tarball is extracted, you should look into the master ``rules'' file. This file holds definitions for use with your compile target environment and may have to be altered based on your cross-compiler arrangement. The values provided are based on my personal configuration. I choose to put the cris system in /arch/cris rather than the more common /usr/local/cris, for example.

If you are using something other than the ETRAX, you may need to refine the rules file further for your use. If you do, please send an e-mail explaining what you changed and why. I would like to incorporate rules configurations for other embedded target development environments as part of the standard distribution of uCommon.

If you are using a standard GNU/Linux host and just wish to build and/or use uCommon to build GNU/Linux applications on your host machine directly, you should not need to modify any files in the distribution. Simply enter make host followed by make. You can also do a make install.

To use uCommon in your projects, simply include the appropriate headers, found in the uCommon header directory, and add -luCommon in your list of link libraries.

Other Projects

The new DBS Server is, of course, being written using the uCommon library. In addition, uMedia library is planned to deal with network audio and rtp, and uScript library, based on my ccscript, will offer a small and easily embedded script-language interpreter.

Resources

David Sugar is a maintainer for several GNU packages, including Bayonne and Common C++, as well as the author of numerous other lesser known free software packages. David is an active member of the Embedded Linux Consortium, the public's elected representative to the International Softswitch Consortium and acting CTO of Open Source Telecom Corp.

Load Disqus comments