A Tour of the Embedded Linux Workshop

As the preceding section shows, using the Embedded Linux Workshop is fairly straightforward. The way in which the toolkit was put together was also straightforward. When designing the toolkit, emphasis was placed on simplicity and expandability.

The Embedded Linux Workshop Directories

A project using the Embedded Linux Workshop uses two unconnected directory trees: one for the specific project and one for the Embedded Linux Workshop itself. This section deals with the Embedded Linux Workshop directories and files.The next section deals with the project files.

The Top-Level Directory

The top-level directory contains a very few files and a few directories. This makes the directory easy to understand and expand on. The files in the top-level directory are described in the following table.

File Description
CHANGES A free-format change log.
COPYING A copy of the GNU Public License. The ELW package is covered under the GPL. However, it’s important to remember that the software you build with the ELW need not be covered under the GPL. You can use the ELW to compile software and produce images that are not themselves covered under the GPL. Your project contains a config file that can name other directories to search for binaries to load onto the image you’re creating.Again, these binaries don’t have to be covered under the GPL.
VERSION The current version of the Embedded Linux Workshop. This is not the version of the product you create; it’s the version number of the tool you used to create the image.

These are the directories in the root of the Embedded Linux Workshop:

Directory Contents
arch One subdirectory for each of the architectures supported.
bin elw Perl script. This script displays the elw menu and runs through the build process. It also contains all of the style scripts. The style scripts know how to build one or more image styles.
doc Documentation for the current release of the Embedded Linux Workshop.
opt Architecture-neutral files for each of the optional packages that the Embedded Linux Workshop supports.
tools Tool chains for the various architectures that the Embedded Linux Workshop supports.

arch/*/src

The arch/*/src directory contains dozens of subdirectories, one for each source module that the Embedded Linux Workshop supports for a given architecture. Each architecture may support a different set of applications requiring a different set of source modules. Within each source module directory is at least one source archive, an extracted archive in a subdirectory, and a symbolic link pointing to that extracted archive. For example, within the arch/i386/src/sed directory are the following entries:

File Description
sed-3.02.gz The original source code for sed.It’s important to keep this file because you may need it to find out what you changed to make your specific version work. This is especially important if you have to change the Makefile to support your architecture. Just be sure not to blow away your changes if you re-extract the file in the current directory.
sed-3.02 The extracted source.Any changes you need to make for your environment go here.
sed A symbolic link to sed-3.02. All references to anything in the directory should be made through this symbolic link. That way, if you upgrade to sed version 4.0 some time in the future, you can simply download the source code into the arch/i386/src/sed directory, extract it to sed-4.0, make your changes, and point the sed symbolic link to the new directory. None of the build scripts or anything else needs to know about the change.
README It’s nice to know something about the packages in the directories, where they came from, what they’re used for, etc. This file is for the ELW developer’s use.

arch/*/opt

The arch/*/opt directory contains the compiled object produced by the build process. Everything in this directory is normally rebuilt every time you select build from the elw menu. However, if the build process for an opt fails, the directory is left intact; this way, the ELW can be distributed without all of the source. We don’t do this because we’re trying to hide the source; we do this because the source is really big—hundreds of megabytes. It’s far better to just download the source you’re actually interested in.

bin/elw

The heart of the Embedded Linux Workshop is the elw Perl script. Your author is not a Perl monk—indeed, he barely knows where the Church of Perl is—so the elw script looks a lot more like C code than line noise. Hopefully, it’s readable for you.

Usage

Typing elw --help at the command line produces the following:

Usage: 
elw [Options] 
Options: 
 --config DIR         Tell elw where project dir is. 
 --define NAME        Define NAME. Can be used as flag in config. 
 --execute CMD        Execute a CMD other than menu. See source. 
 -?, --help           Show this usage message. 
 -m DIR,--elwhome DIR Set ELWHOME if it's not /usr/local/elw 
                      or already set in $ELWHOME env variable. 
 --newproject DIR     Create a new project in DIR. 

 -v, --verbose        Be verbose. 

The Code

Let’s take a look at the code. In the interest of space, much of the code is left out—just the interesting bits are reproduced here.To look at the rest of the code, you’ll have to download the Embedded Linux Workshop.

#!/usr/bin/perl 
###################################################################### 
# elw: Build an Embedded Linux Workshop project 
# This code is distributed under the terms and conditions of the 
# General Public License (GPL) 
###################################################################### 
use Getopt::Long; 

###################################################################### 
# Parse command line 
###################################################################### 
$VERB=`basename $0`; chomp $VERB; 
$ELWHOME=""; 
$CONFIG=""; 
$EXECUTE=""; 
%DEFINES = (); 
GetOptions(
        "elwhome|m=s "   ==>      $ELWHOME, 
        "config=s"       ==>      $CONFIG, 
        "define=s"       ==>      \%DEFINES, 
        "execute=s"      ==>      $EXECUTE, 
        "help|?"         ==>      $HELP, 
        "newproject=s"   ==>      $NEWPROJECTNAME, 
        "verbose|v"      ==>      $VERBOSE, 
); 
$ELWHOME=$ENV{"ELWHOME"}        if ($ELWHOME eq ''), 
$ELWHOME="/usr/local/elw"       if (($ELWHOME eq ''), 
chomp($CONFIG=`pwd`)            if ($CONFIG eq ''), 
$EXECUTE="menu_run"             if ($EXECUTE eq ''), 
usage()                         if ($HELP); 

One thing I really like about Perl is the do_this() if (this_is_true); construction. It makes the code much more readable—whenever I write C code, I find myself missing it.

###################################################################### 
# Calculated constants 
###################################################################### 
$ELW_VERSION    = qx(cat $ELWHOME/VERSION 2>dev/null); 
chomp($ELW_VERSION); 
if ($ELW_VERSION eq ""){
        print "$0: Set the ELWHOME environment variable.
"; 
        exit; 
} 

This finds the ELW version and verifies that the $ELWHOME variable points to the Embedded Linux Workshop’s home directory.

if (    ("$CONFIG" eq "")  || 
        ("$CONFIG" eq "/") || 
        (! -d "$CONFIG")   || 
        (! -f "$CONFIG/config") 
){
        die "$0: '$CONFIG' is not a valid config directory
" 
} 

This verifies that the $CONFIG variable is pointed to a good project. I’ll talk more about projects in a later section.

@OPTDIR=("$CONFIG","$ELWHOME");          # OPTDIR - where to look for opts 

The @OPTDIR variable tracks the possible locations of opt packages. The config and *.style files can also add to this variable if they need to define other places where opt directories may be found. Not that the $CONFIG directory (where the specific project we’re working on lives) is named first. This way, you can modify an opt package for a specific project.

###################################################################### 
# Load in configuration file 
###################################################################### 
eval `cat $CONFIG/config`; 

if ( ! -r "$ELWHOME/bin/$STYLE.style"){
        die "Unable to load style: $ELWHOME/bin/$STYLE.style"; 
} 
eval `cat $ELWHOME/bin/$STYLE.style`; 

Ah, the power (and peril) of self-modifying Perl code. The first eval loads the configuration file for the project. Yes, the configuration file is simply Perl source code—so it can do anything. Not only can you add simple name=value pairs (such as doing the necessary task of setting the $STYLE variable to name the build style of your choice), but you could do such things as add options to the menu, and so on.

The second eval loads the style chosen by the $CONFIG file.

###################################################################### 
# main()... 
###################################################################### 
eval $EXECUTE; 

Most likely, the $EXECUTE variable contains menu_run, so it will run the menu. But you can run specific commands by using the -e option as the usage explains.

bin/syslinux.style

The syslinux.style script fragment is loaded into the running elw script when the $STYLE variable is set to syslinux in the project’s config file. When you create a new project using the elw --newproject command, this is the default project selected in the configuration file. syslinux.style, like all .style files, contains the code to actually build the target image. It takes as input the arch/*/opt directories, the kernel binary, and any startup code, and outputs files into the obj directory within the project.

Briefly, the syslinux.style file looks like this:

###################################################################### 
# syslinux.style:       ELW .style perl scriptlet to build a syslinux 
#                       image 
# This code is distributed under the terms and conditions of the 
# General Public License (GPL) 
###################################################################### 

###################################################################### 
# Simple constants 
###################################################################### 
$REQUIRED_DEV   = "console fd0 hda1 null zero"; 
$REQUIRED_DEV   = "$REQUIRED_DEV vcs0 vcs1 vcs2 vcs3 vcs4"; 
$REQUIRED_DEV   = "$REQUIRED_DEV vcs5 vcs6 vcs7 vcs8 vcs9"; 
$REQUIRED_DEV   = "$REQUIRED_DEV tty0 tty1 tty2 tty3 tty4"; 
$REQUIRED_DEV   = "$REQUIRED_DEV tty5 tty6 tty7 tty8 tty9"; 
$REQUIRED_DEV   = "$REQUIRED_DEV " .. `basename $APP_ROOT`; 

Only a very few devices are absolutely required to boot. Most of these are just nice to have floating around in the image. /dev files cost very little in the image, so don’t be afraid to add them.

###################################################################### 
# Update items from the config file 
# add later items to top of list 
###################################################################### 
@OPT=("syslinux",@OPT); 
@OPT=("busybox",@OPT); 
@OPT=("libc",@OPT); 
@OPT=("halt",@OPT); 
@OPT=("insmod",@OPT); 
@OPT=("expr",@OPT); 
@OPT=("ash",@OPT); 
@OPT=("base",@OPT); 

The @OPT array lists the opt packages that are part of a project.These opt packages are required for every project of this style. It doesn’t matter whether the package is accidentally named twice in the array; duplicates are culled.The Makefile in each opt package names the packages on which that opt package depends, so some packages that are not specifically named anywhere may also make it into the project.

####################################################################### 
# image 
####################################################################### 
sub image {

The image() function actually takes all the bits and pieces lying around in the arch/*/opt directories and puts them all together, along with the kernel, into a bootable image.

# Some locals 
# -----------
my($BUILD)="$CONFIG/.build"; 
my($MNT)="$BUILD/mnt"; 
my($TMP)="$BUILD/tmp"; 
my($IMG)="$CONFIG/obj/rootfs"; 
my($IMAGEROOT)="$CONFIG/obj/imageroot"; 
my($DISK)="$CONFIG/obj/image"; 
my($CFG)="$TMP/CONFIG"; 
my($REPORT) = "
------------------------------------------------------
"; 

# Setup 
# -----
sy("umount $MNT 2> /dev/null"); 
if (esy("(cd $CONFIG && rm -rf $BUILD)")){return;} 
if (esy("mkdir -p $MNT")){return;} 
if (esy("mkdir -p $TMP")){return;} 
if (esy("rm -rf $IMG.gz $IMG $DISK")){return;} 

After creating some local shortcut variables, it sets up by making sure that nothing is left over from the last build, and creating the local $TMP directory for building and the $MNT for the new root filesystem.

# Update the build number 
# -----------------------
my($APP_BILD)=`cat $CONFIG/BUILD 2> /dev/null`; 
$APP_BILD = $APP_BILD + 1; 
if (esy("echo $APP_BILD > $CONFIG/BUILD")) {return;} 

Each build is numbered, so you can easily tell if you have two different versions of code.

# Create the image file, put a filesystem on it and mount it 
# -----------------------------------------------------------
if (esy("dd if=/dev/zero of=$IMG bs=512 count=$INITRDSZ")) {return;} 
if (esy("/sbin/mkfs.ext2 -O none -F -vm0 -N $INITRDINODES $IMG")) {return;} 
if (esy("mount -t ext2 -o loop $IMG $MNT")) {return;} 

This is where it starts to get interesting. A brand new file of all zeros is created to store the root filesystem. It’s important to start out with a fresh file every time, since empty parts of the filesystem will compress better if they’re all zeros than if they contain garbage left over from a previous build.

Once the new image is created with the dd command, a filesystem is created on top of it with the mkfs.ext2 command. Finally, the empty filesystem is mounted.

# Tell application about itself 
# ----------------------------
my($now) = `date +%Y%m%d`; chomp($now); 
sy("echo export APP_DATE=\"$now\" >> $CFG"); 
sy("echo export APP_ROOT=\"$APP_ROOT\" >> $CFG") if ($APP_ROOT ne ""); 
sy("echo export APP_NAME=\"$APP_NAME\" >> $CFG") if ($APP_NAME ne ""); 
sy("echo export APP_SNAM=\"$APP_SNAM\" >> $CFG") if ($APP_SNAM ne ""); 
if ($APP_VERS ne ""){
     @VERS=split( /./, $APP_VERS ); 
     sy("echo export APP_VERS=\"$APP_VERS\" >> $CFG"); 
     sy("echo export APP_VMAJ=\"$VERS[0]\" >> $CFG"); 
     sy("echo export APP_VMIN=\"$VERS[1]\" >> $CFG"); 
     sy("echo export APP_VREL=\"$VERS[2]\" >> $CFG"); 
} 
sy("echo export APP_BILD=\"$APP_BILD\" >> $CFG") if ($APP_BILD ne ""); 
sy("echo export APP_UPGR=\"$APP_UPGR\" >> $CFG") if ($APP_UPGR ne ""); 


# Copy the CONFIG file to the obj directory 
# ----------------------------------------
sy("cp -f $CFG $CONFIG/obj"); 

The CONFIG file appears in the root directory of the application’s root filesystem. It contains static information about the application: its name, version number, build date, and so on.

# Include optional packages in rootfs.gz 
# -------------------------------------
for my $pkgflg (@OPT){
     my ($pkg,$flg) = optparse( $pkgflg ); 
     print "------------------------------------------------------------
"; 
     print "                                          $pkg
"; 
     print "------------------------------------------------------------
"; 
     $OPTDSTDIR = optmake( $pkg, 0 ); 

        # Remove files then add them from this directory 
       if ( 1+index( $flg,"m")){
          if (esy("cd $OPTDSTDIR/; tar czf $CONFIG/obj/$pkg.tgz *")){return}; 
       } else {
          if (esy(
          "cd $OPTDSTDIR && RM=`find . -type f -print` && cd $TMP && rm -f $RM" 
          )){return;} 
          if (esy("(cd $OPTDSTDIR/; tar cf - *) | (cd $TMP; tar xf -)")){return}; 
      } 
} 
print "============================================================
"; 

Essentially, this code copies all of the files in each of the opt packages that this project uses into a directory that will become the root filesystem.We have to delete all the filenames in the destination directory before we copy because symbolic links that are already there will cause trouble.

# Copy requred devices and user specified devices 
# -----------------------------------------------
if (esy("(cd /dev;tar cf - $REQUIRED_DEV)|(cd $TMP/dev;tar xf -)")){return;} 
sy("[ --n "$DEV" ] && (cd /dev;tar cf - $DEV)|(cd $TMP/dev;tar xf -)"); 

As noted earlier, some devices are required by the style. Others (the ones named in the $DEV variable) are enumerated in the project’s config file. Note that we cheat a little and just grab the device files from the host machine’s /dev directory.This could pose a problem if the Linux version on your target differs greatly from that on your host machine. Be careful!

# Configure the shared libraries 
# ------------------------------
if (!$STYLE_UCLIBC){
        return if (esy("cp $ELWHOME/arch/$TARGET_ARCH/opt/libc/nomedia/ldconfig 
                   $TMP/bin/temp")); 
        return if (esy("/usr/sbin/chroot $TMP /bin/temp -v")); 
        return if (esy("rm -f $TMP/bin/temp")); 
} 

The syslinux.style is set up for dynamic libraries. As such, the copied opt directories should contain all the libraries necessary for all the application binaries to run. Once those libraries are copied, we must run the ldconfig command to set up the dynamic library cache properly.

# Fix ownership 
# -------------
if (esy("cd $TMP && chown -R root:root *")) {return;} 

syslinux.style assumes that UNIX security is of no concern, which is almost certainly true in an embedded application.

# Copy tmp to the new filesystem 
# -------------------------------
if (esy("(cd $TMP; tar cf - *) | (cd $MNT; tar xf -)")) {return;} 
$REPORT = $REPORT . `df -h $MNT` . "
"; 
if (esy("umount $MNT")) {return;} 
$REPORT = $REPORT . `ls -l $IMG` ; 
if (esy("gzip -9 $IMG")) {return;} 
$REPORT = $REPORT . `ls -l $IMG.gz` ; 

Until now, we haven’t actually done anything to the filesystem we created at the beginning of this script. Everything has been done in a directory on the host machine (in the directory named in the $TMP variable).This set of code copies the directory tree we’ve been building to the filesystem we created and then unmounts it.This way we get the maximum possible compression.

# Cleanup 
# ------
if (esy("rm -rf $TMP")){return;} 

# Create the obj/imageroot & copy files to it 
# ------------------------------------------
return if (esy("cd $CONFIG/obj && rm -rf imageroot && mkdir imageroot")); 
return if (esy("cp $LINUX/$KERNEL $IMAGEROOT")); 
return if (esy("cp $IMG.gz $IMAGEROOT")); 
return if (esy("cp $ELWHOME/arch/$TARGET_ARCH/opt/syslinux/nomedia/ldlinux.sys 
               $IMAGEROOT")); 
foreach $file (`(cd $CONFIG/mnt && ls )`){
        chomp $file; 
        return if (esy("cp $CONFIG/mnt/$file $IMAGEROOT")); 
} 

We now gather all the pieces that don’t go into the root filesystem (the kernel, pieces of SYSLINUX, and so on) into a directory that will become the floppy image.

# Create blank, make the filesystem, mount it & copy files to it 
# --------------------------------------------------------------
return if (esy("/sbin/mkfs.msdos -f 1 -C $DISK $DISK_BLOCKS")); 
return if (esy("mount -t msdos -o loop $DISK $MNT")); 
foreach $file (`(cd $IMAGEROOT && ls )`){
        chomp $file; 
        return if (esy("cp $IMAGEROOT/$file $MNT")); 
} 
$REPORT = $REPORT . `df -h $MNT` . "
"; 

We then create a floppy image and copy all the files we just gathered onto the mounted floppy image.

# Unmount the image 
# ----------------
return if (esy("umount $MNT")); 

# Syslinux the image 
# -----------------
return if (esy("cd $CONFIG && rm -rf $BUILD")); 
return if (esy("($ELWHOME/arch/$TARGET_ARCH/opt/syslinux/nomedia/syslinux 
               $DISK )")); 

print $REPORT; 
} 

Finally, we unmount the image and run SYSLINUX on it so it can boot.

opt/*

Each directory within the opt tree contains the Makefile for that optional package, any directories that are named by the Makefile (unless the Makefile creates them itself), and any miscellaneous files that are specific to the package but are not created elsewhere.

opt/*/Makefile

The Makefile within the root of each opt directory has one or more of these targets:

Directory Description
binaries This target attempts to make the binaries in each of the source directories by changing to the root of the source directory and running make.The binaries target then copies the pieces it wants from the source directory structure to one of its own directories. It then strips it if necessary, attempting to make each binary as small as possible. This target is used by the elw Perl script when the user selects Build Binaries from the menu. Before the script runs the make binaries command, the entire opt directory is copied to the arch/*/opt directory. Binary executable files will never appear in the opt directory, only in the arch/*/opt directory.
slowbinaries This target works just like the binaries target, but is intended for packages that take a long time to build, such as the C library. Most binaries take only a few seconds to build, so it’s no big deal to build them several times a day, whenever a source file changes.The C library can take several tens of minutes or more to build—and it almost never changes.
dependencies This target simply prints the names of the opt packages on which this opt package depends.The dependencies target is used by the elw script to build the complete list of opt packages needed for a project.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.28.197