Beyond Logic


uClinux - Understanding the build tools
Why Embed Linux?

    So you now have a uClinux Development System. Here is the summary of the setup.

      • The uClinux development suite should be installed in /opt/uClinux

      • m68k-coff is used to compile the kernel. It has no uC-libc Standard C Library associated with it.

      • The uClinux kernel is installed in /opt/uClinux/linux

      • m68k-pic-coff is the position independent code (PIC) compiler used to compile the userland binaries.

      • uC-libc Standard C Library should be compiled and the resulting libc.a and crt0.o copied to /opt/uClinux/m68k-pic-coff/lib and its include files in /opt/uClinux/m68k-pic-coff/include

      • uC-libm Standard Math Library should be compiled and the resulting libm.a and math.h included in /opt/uClinux/m68k-pic-coff/lib and /opt/uClinux/m68k-pic-coff/include respectively.

      • m68k-pic-coffs’ LD replaced with a script which calls the Linker LD, then COFF2FLT to create a Flat Binary Image from the coff file.

      • The genromfs utility should be built and installed somewhere in the path.

      • Romdisk is extracted to /opt/uClinux/romdisk

      • uClinux userland binary source installed in /opt/uClinux/src

      • The deftemplate.sh script should be placed in /opt/uClinux/

      • The buildenv.sh script should be placed in /opt/uClinux/bin/ and included in the path

    There is two main areas of development. Work can be done on the uClinux kernel or on the userland binaries. As the kernel is normally provided with many platforms, it is most common to work on the userland binaries. Never the less, the uClinux Kernel should be set up first.

Building the uClinux Kernel

    The kernel must be configured before building. For those who have configured their desktop kernels, this process is no different except that uClinux doesn’t support loadable modules. For others it will be a new experience. To start jump into /opt/uClinux/linux and run the configuration utility.

    	cd /opt/uClinux/linux
    	make menuconfig
    

    It is now time to configure the kernel. As you will be targeting different architectures and platforms there is no one good choice here. Select your processor and board from the Platform Dependent Setup and then jump through the menu configuring any extra parameters.

    Once you have gotten over the configuration dilemmas, it is time to put your choices to code. Simply run the following commands and sit back and watch. It can take some time to compile depending upon the speed of your development system.

    	make dep
    	make clean
    	make linux.bin
    

    Make dep will cause make to set up any dependencies. Clean will remove any old entries from previous builds, and make image.bin will build image.bin which is the compiled kernel in pure binary.

    Once this is complete you should have a couple of files in the linux directory (/opt/uClinux/linux). These will be linux.text, linux.data, linux.bin and system map.

Understanding the Kernel Build Process

    Building the kernel will first start by building the individual components/subsystems of the kernel. Once this is done all the subsystem object files will be linked using the sections from the linker file (.LD) which will reside in arch/$(ARCH)/platform/$(PLATFORM)/$(BOARD)/$(MODEL).ld, and the startup C asm code (crt0_rom.S) to create a file called linux.

    	LD -T (MODEL).ld  crt0_$(MODEL).o [objs] –o linux
    

    The linker file defines memory sections which tell the compiler how much memory is available and where to place various pieces of code. The startup asm code is the code executed straight after the reset vector or bootloader which sets up various parts of the microcontroller such as clock dividers, serial port, DRAM, SRAM, memory banks, watchdogs etc which must be set up before the microcontroller can start running the linux kernel. It also sets up the stack, zero outs the .bss segment, copies the .data segment into RAM and then jumps to start_kernel() which is the entry point into the C Code.

    A symbol/system map is then be generated from the linux file. This is handy for debugging showing where each function is located in memory.

    	NM $(LINUX) | grep -v '\(compiled\)\|\(\.o$$\)\|\( a \)' | sort > System.map
    

    Linux.data, a file containing all the data segment code is then created from linux by removing all the readonly segments and other non required segments.

    	objcopy -O binary --remove-section=.romvec --remove-section=.text \
                              --remove-section=.ramvec --remove-section=.bss  \
                              --remove-section=.eram linux linux.data
    

    A Linux.text file is also created containing all the text segment code (fixed code and variables/strings)

    	objcopy -O binary --remove-section=.ramvec --remove-section=.bss \
                              --remove-section=.data --remove-section=.eram  \
                              --set-section-flags=.romvec=CONTENTS,ALLOC,LOAD,READONLY,CODE linux linux.text
    

    The .Text and .Data segments are then concatenated together to produce linux.bin.

    	cat linux.text linux.data  > linux.bin
    

    We are then left with linux.bin which is the binary code of the kernel. This can actually be loaded into memory and executed, however it will fail shortly after trying to expand the ROM filesystem and mounting the device nodes contained within the romfs.

    Building the kernel only needs to be done once while no modifications are made to the kernel. On each build of the system, the rom filesystem will be concatenated with the linux.bin.

The rom filesystem

    While there is only one common source directory for the linux kernel, you can have multiple projects of the rom filesystem and userland binaries. On a central server you could have the one common kernel, but have multiple software designers working on the one system using their own rom filesystems.

    To set up a working environment simply create a directory in a convenient location, typically home/<username>/ for multiuser systems and then run buildenv

    	cd /home/cpeacock/
    	mkdir ucsimm
    	buildenv
    

    buildenv will build a uClinux development environment, firstly my creating one Makefile in your directory. If you then type make, the development environment is copied across from the /opt/uClinux/ directory, and a image.bin file created.

    	make
    

    The directory should now have the following structure,

    	Makefile  deftemplate.sh  image.bin  linux  romdisk  romdisk.img  romdisk.map  src
    

    This development environment consists of userland binaries in a ROM filesystem which is mounted by the kernel at bootup. Every executable specified in the /src/makefile is built and its flat binary placed in /src/bin. The deftemplate.sh file is then executed to copy specified binaries into the romdisk tree which will provide the base of our romfs image. It should be noted that the deftemplate script only copies the binaries across to the romdisk tree. It does not remove them, thus to do this you must uncomment the relevant line in the deftemplate script and manually remove the file from the romdisk tree.

    The romdisk tree also includes the configuration files and directory structure of the rom filesystem.

    	/bin  /dev  /etc  /htdocs  /lib  /proc  ramfs.img  /sbin  /tmp  /usr  /var
    

    The romdisk.img file system is then automatically generated from the above tree using

    	genromfs -v -V "ROM Disk" -f romdisk.img -d romdisk 2> romdisk.map
    

    The romdisk.img is then concatenated with /linux/linux.bin (the binary of the kernel we created earlier) to create

    	cat linux/linux.bin romdisk.img > image.bin
    

    image.bin, a file which contains the linux kernel plus the rom filesystem. This image is now complete and ready to download to your uClinux System. To get a better picture of the complete build process, a typical 2.0.38 uCsimm memory map is shown here.

    The bootloader flash is already present in the development system we are using, in this case the uCsimm. This is a dragonball module from Lineo. The ROM Vectors, .TEXT and .DATA segments are created as part of the kernel compilation. These three sections make up the linux.bin file which can be found in /opt/uClinux/linux.

    The userland binaries are compiled into flat binary executables and placed in the romdisk directory. Genromfs then packs this up into the romfs.img which is concatenated with linux.bin to provide the image.bin file. This will grow or reduce in size depending upon what binaries and files you include in the ROM filesystem. The only limitation is the maximum memory you have in your system.

    This image.bin is a binary file which is loaded into the development system via the bootloader.

Customising the romfs.

    At the present moment we have built enough code to upload the image.bin and successfully log into the target uClinux system. However we are using the default IP address and other parameters straight out of the box.

    Enter into your romdisk directory. You should have a structure simular to this.

    	/bin  /dev  /etc  /htdocs  /lib  /proc  ramfs.img  /sbin  /tmp  /usr  /var
    

    Just like any linux system, the critical configuration files can be found in /etc

    	inetd.conf  inittab  issue  passwd  rc  resolv.conf  services
    

    The default rc file looks like this,

    	#!/bin/sh
    	#
    	# system startup.
    
    	# set up the hostname
    	/bin/hostname uCsimm
    
    	# attach the interfaces
    	/sbin/ifattach
    	/sbin/ifattach \
    	--addr 192.168.1.200 \
    	--mask 255.255.255.0 \
    	--net 192.168.1.0 \
    	--gw 192.168.1.100 eth0
    
    	# expand the ramdisk
    	/sbin/expand /ramfs.img /dev/ram0
    
    	# mount ramdisk, proc and nfs
    	/bin/mount -t ext2 /dev/ram0 /var
    	/bin/mount -t proc proc /proc
    	/bin/mount -t nfs 192.168.1.11:/home/jeff/kit /usr
    
    	# start up the internet superserver
    	/sbin/inetd &
    
    	# that's it... success
    	exit 0
    

    You will need to change the hostname and IP address, mask, network and gateway parameters to suit your network. The other thing you will want to change is the nfs mount, /bin/mount -t nfs 192.168.1.11:/home/jeff/kit /usr

Mounting a NFS volume for Development

    NFS or Network File System is a way of exporting and mounting directories across UNIX and UNIX aware systems. This may come in extremely handy during development, allowing the facility to export your working directory to your uClinux platform. This allows the ability to compile code on your linux development system and then run it on your embedded systems’ mounted network drive without the need of copying the flat binary or flashing a new ROMFS. As you can imagine this save an enormous amount of time.

    With a flashloader/ethernet flash loader utility for your desired platform, you can perform flash updates over the wire. This also speeds up your development. Transferring a 1MB image.bin file over a 115,200 serial link is not fast nor exciting.

    To use NFS you will first need to set up your development box to export your desired directories. Exporting any directories over the network can lead to security issues thus it is recommended you spend a little time tying your machine down if you are on a public network or have a dial up Internet connection. NFS exports are defined in the /etc/exports file. You will need to edit this file as root.

    A typical export entry is

    	/home	(ro)
    

    This exports the home directories to anyone with read only rights. If you have other sensitive information in your home directories you can either specify the exact development environment or specify which machines have access to this exported directory eg,

    	/home/cpeacock/ucsimm uCSimm(ro)
    

    where uCsimm is the host name of your uClinux based system. This would have to be included in your /etc/hosts file or the IP address explicitly specified.

    After changing the exports directory you can either restart your computer (mainstream windows users) or restart the NFS daemons. This can be accomplished by

    	/etc/rc.d/init.d/nfs stop
    	/etc/rc.d/init.d/nfs start
    

    on most systems. Please consult your linux distribution documentation if this does not work.

    Once this has been successfully completed, modify the rc file to include your new mount details – in my case,

    	mount -t nfs 192.168.0.1:/home/cpeacock/ucsimm /usr
    

    You can also log into your uClinux system and run this at the command line. This is handy after your uClinux systems have been committed to the field and you need to do a quick few things or a reimage.

The RAM FileSystem

    uClinux has a RAM file system (ramdisk) for its scratch pad /tmp and /var areas in the absence of a hard disk drive. The current distribution of uClinux’s romdisk comes with a ramfs of 256kbytes unformatted and is missing a /var/tmp directory causing a cd tmp to report a bad directory. The /tmp directory in the root is a symbolic link which points to /var/tmp. var is the mount point for the RAM file system.

    The ramfs is uncompressed and mounted in the rc startup script,

    	# expand the ramdisk
    	/sbin/expand /ramfs.img /dev/ram0
    
    	# mount ramdisk, proc and nfs
    	/bin/mount -t ext2 /dev/ram0 /var
    

    It consists of a ext2 filesystem which is compressed using a Zero Run Length Encoding algorithm (ZRTE). This is uncompressed using the expand utility to the RAM block device /dev/ram0. Once this process has been completed it is then mounted as an ext2 file system with the mount point of /var.

    If we have a need to fix the tmp link, we could simply add a command in the rc startup script to make a tmp directory in var after the ramfs has been mounted. However if you need to increase the size of the ramdisk, this isn’t as simple. You will need to create a new ramfs.img. In some applications a larger ramdisk is required. You may want to log data to a file that is then downloaded using anonymous FTP, HTTP etc.

Creating a new ramfs

    Creating a new ramfs is a reasonably simple task. One of the problems you need to watch is byte order, whether your system is big endian or little endian.

    Start by zeroing out the ram block device. Later, we will use a zero run length compression utility, thus in the interests of getting a more compressible image, it’s recommended you carry out this step. You will also need to choose what size a ramdisk you need. A larger ramdisk will allow you to store more logs or temporary files but at the expense of system RAM. It will normally depend upon what applications you are using with your uClinux system.

    	# dd if=/dev/zero of=/dev/ram0 bs=1k count=1024
    	1024+0 records in
    	1024+0 records out   
    

    Next we make a second extended file system. The –v flag turns on verbose mode so we can see some of the stats. By default 5% of the blocks is reserved for superuser. We turn this off using –m0. We also turn off any extra features of the ext2fs using -Onone. On post 2.2 kernel systems, sparse_super and filetype options are automatically turned on when creating an ext2fs. However both these features are not correctly supported by kernels pre 2.2 and as a result mount will report a failure when mounting the filesystem on your 2.0.38 uClinux system.

    	# mke2fs -vm0 -Onone /dev/ram0 1024
    	mke2fs 1.19, 13-Jul-2000 for EXT2 FS 0.5b, 95/08/09
    	Filesystem label=
    	OS type: Linux
    	Block size=1024 (log=0)
    	Fragment size=1024 (log=0)
    	128 inodes, 1024 blocks
    	0 blocks (0.00%) reserved for the super user
    	First data block=1
    	1 block group
    	8192 blocks per group, 8192 fragments per group
    	128 inodes per group
     
    	Writing inode tables: done
    	Writing superblocks and filesystem accounting information: done  
    

    The next step is to add extra folders or files to the ramdisk. This is an optional procedure thus you can omit this step, if not needed. By default when the ext2 filesystem is created a lost+found directory is added. This will be present in /var/lost+found once mounted on your uClinux system. It is also recommended to create a tmp folder so the tmp symbolic link works. To add files and folders, first mount the file system as ext2.

    	# mount -t ext2 /dev/ram0 /mnt/ramdisk 
    	# cd /mnt/ramdisk
    	# mkdir tmp
    

    Then once you are finished, unmount the file system to ensure a clean mount in the future.

    	# umount /mnt/ramdisk
    

    Now it’s time to move the filesystem from the ram block device to a local file. This copies the file system byte for byte to ramfsraw.img. For a 1MB ramfs, this file will be 1MB in size.

    	# dd if=/dev/ram of=ramfsraw.img bs=1k count=1024
    	1024+0 records in
    	1024+0 records out  
    

    It would be quite inefficient to add this 1Mbyte file to the romdisk in its current form, and since the majority of the filesystem is free, we can compress it extremely efficiently using ZRLE (Zero Run Length Encoding). You may see some documentation describing the process as “making a file with holes”.

    What ZRLE does is record the blocks of non-zero data, prefixing it with the length of the block and its position in the file. As most of the data is zero, it can strip this out. The expand utility will simply zero out the entire length of the expanded file, then copy the blocks of data back to its relevant locations indicated by its position header.

    The compression can be done with a utility called holes. There are a couple of versions floating around which have endian and/or block size reporting problems. You can download the source and compiled binaries from ftp://ftp.beyondlogic.org/uClinux/ramfsutils.tar.gz These binaries will run on Linux little endian platforms.

    	# /holes/holes ramfsraw.img >ramfs.img
    	Length of original file 1048576
    

    Listing the two files will show the run length compression at work. Now we can add it to our romdisk and not waste considerable space. Of course if you do put some files in the ramdisk, then it won’t compress as well.

    	# ls ram* -l
    	-rw-r--r--    1 root     root         2639 Mar  4 16:00 ramfs.img
    	-rw-r--r--    1 root     root      1048576 Mar  4 15:59 ramfsraw.img 
    

    You may also notice that our generated ramfs.img is smaller than the uClinux stock ramfs.img (3340), even that an extra folder has been added. Just another reason why you should update and generate your own. (For some reason, the run length block sizes on the stock ramfs are no larger than 200-300 bytes even though expand.c allocates a 2048 byte buffer when expanding the image).

    Testing your RAM filesystem

    You can test your newly created ramfs on your uClinux platform by firstly unmounting the existing ramfs and then expanding and mounting your new ramfs.

    	/bin/umount /var
    	/sbin/expand /ramfs.img /dev/ram0
    	/bin/mount -t ext2 /dev/ram0 /var
    

    However you may also wish to test it on your linux desktop. Coping the expand.c file from /src/init/ and recompiling it for x86 linux is going to lead you into endian problems. Either fix up the ntohl() define, or use the precompiled expand binary for x86 from the ramfsutils.tar.gz tarball.

Root filesystem on NFS

    Mounting your uClinux development directory to /usr helps to speed development along. This allows the developer to compile the m68k binaries on the linux development system and have it instantly available via the NFS mounted volume on the target.

    However there are other situations where you may want to modify other files in FLASH, such as the configuration files in /etc for your Internet daemons, http web servers etc. One option is the flash and burn method - simply change the file, rebuild the image, download the image to your uClinux target system, hold your breath and hope that the changes were correct. If not pull one more hair out and simply repeat the process.

    A much speedier approach and thus the better option around this problem is to mount the root uClinux directory as a NFS volume which is pulled from your development machine. This allows modifications to be performed on any file in the file system and have it instantly available on your uClinux system. Quite clearly this can save considerable time.

    The downside is off course, speed. Every request will interrogate your NFS server causing latency on many commands. However this process is normally only done during development, thus speed should not pose any problems.

    Setting up a root NFS filesystem requires a little tinkering with the kernel. First edit the setup.c file in /arch/{arch}/kernel/ and add the \ following string copy command to place your NFS server and host details in the kernel command line buffer.

    	ROOT_DEV = MKDEV(BLKMEM_MAJOR,0);
    
    +	#ifdef CONFIG_ROOT_NFS
    +		strcpy(command_line,
    +         		"root=/dev/nfs "
    +         		"nfsroot=192.168.0.2:/ucsimm/romdisk "
    +         		"nfsaddrs=192.168.0.200:192.168.0.2:192.168.0.1:255.255.255.0:"
    +         		"ucsimm:eth0:none");
    +	#endif
    	
    	/* Keep a copy of command line */
    	*cmdline_p = &command_line[0];
    
    	memcpy(saved_command_line, command_line, sizeof(saved_command_line));
    	saved_command_line[sizeof(saved_command_line)-1] = 0;
    

    The format of these parameters is as follows,

    	root=/dev/nfs 
    

    This is used to enable root NFS. It is not a real device but a pseudo-NFS-device.

    	nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] 
    

    server-ip is the IP address of the NFS server you wish to mount the filesystem from. The root directory specifies the name of the directory on the NFS server to mount as root. This is followed by a comma and any standard NFS options. The option field is normally left blank, as is the case in the example. Extra options can be found in the nfsroot.txt file as part of the documentation you receive with the kernel.

    	nfsaddrs=<client-ip>:<server-ip>l:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>
    

    The client IP is the IP address you wish to give your embedded uClinux target. This is normally assigned by ifattach in your rc file, but since this will no longer be available locally, you must provide these details here. The server-ip once again specifies the NFS server’s IP - the gateway and netmask should be self explanatory, the hostname is your local devices name, the device specifies which interface to set up, while autoconf specifies if BOOTP or rarp should be used. None specifies no auto-configuration.

    These three parameters are all appended together. Note the space after each command.

    Once these changes have been made and the changes saved, configure the uClinux kernel to support root NFS. This is found in the Filesystems menu - select both "NFS filesystem support" and "Root file system on NFS". Then rebuild your kernel. As the rom filesystem will be mounted from the NFS server there is no need to generate the ROM file system and append this to the end of the linux.bin file. Simply load the linux.bin file as is.

    At this point make sure the directory you specified in is exported correctly and reset your uClinux target. If all goes well it should boot up with

    	eth0: Attempting TP
    	eth0: using 10Base-T (RJ-45)
    	Root-NFS: Got file handle for /ucsimm/romdisk via RPC
    	VFS: Mounted root (nfs filesystem).
    

    And provide the user with a log-in prompt as per usual. If the NFS server is not present the following message will result. The uClinux target will then keep trying in an endless loop until the NFS server comes on-line.

    	eth0: Attempting TP
    	eth0: using 10Base-T (RJ-45)
    	NFS server 192.168.0.2 not responding, still trying.
    

    However should you get the following message, this would suggest that your exports are not set up correctly or you are trying to export the directory from the wrong server. In this case, check your exports file and restart the NFS service if required. After a kernel panic, you will need to reboot your uClinux target before it will attempt to connect again.

    	eth0: Attempting TP
    	eth0: using 10Base-T (RJ-45)
    	Root-NFS: Server returned error 13 while mounting /ucsimm/romdisk
    	VFS: Unable to mount root fs via NFS, trying floppy.
    	VFS: Cannot open root device 02:00
    	Kernel panic: VFS: Unable to mount root fs on 02:00
    


    Copyright 2001-2005 Craig Peacock - 15th June 2005.