oreilly.comSafari Books Online.Conferences.


Linux Compatibility on BSD for the PPC Platform
Pages: 1, 2, 3, 4

Some other syscalls do not work the same way on different architectures, due to different argument sizes or different argument transmission mechanisms (in registers vs on stack). For some of them, there are already alternative implementations of the wrapper function. For instance, a call to mmap() is implemented by linux_sys_mmap() on the Alpha, and it is implemented by linux_old_mmap() on the i386.

Now, the idea is to get a good but not perfect syscalls.master, and to fix problems as they arise later. So once syscalls.master looks good, we build the linux_syscallargs.h, linux_syscalls.h by typing "make" in sys/compat/linux/arch/powerpc, and we can start trying to build a kernel.

Building the first kernel

Now when we try to build a kernel, of course it will fail, because most of the required source code is still missing, but the idea is that a failed build will tell us which gaps to fill.

First, we want to tell the config(8) tool that we added files to the kernel. Here we will work on the NetBSD/macppc port, but everything remains true for other ports. In the sys/arch/macppc/conf directory, we have a file called files.macppc that lists the files used to build a kernel for macppc. In order to modularize the compatibility code in the kernel, we will just add two include statements. These statements will tell config(8) to include the file describing what is needed for the machine-independent part of Linux compatibility (sys/compat/linux/files.linux), and the file describing what is needed for the machine-dependent part (sys/compat/linux/arch/powerpc/files.linux_powerpc):

# Linux binary compatibility (COMPAT_LINUX)
include "compat/linux/files.linux"
include "compat/linux/arch/powerpc/files.linux_powerpc"

There is also the OSS audio compatibility framework, which is required in order to link a kernel with Linux compatibility. This is included by the following lines:

# OSS audio driver compatibility
include "compat/ossaudio/files.ossaudio"

We then have to create the latter files.linux_powerpc file, and fill it with all the source files created in sys/compat/linux/arch/powerpc so far. Again, the idea is just to grab the i386 version of that file from sys/compat/linux/arch/i386, and to comment out or remove every line referencing files that are not yet in the powerpc directory.

Then we can add the COMPAT_LINUX option to our favourite kernel config file, and start a kernel build. (If you need some documentation, please read the documentation here). Of course it will fail; we expected it. During the various failures, we can discover that the source code in sys/compat/linux/common needs a lot of macros prefixed with LINUX_ and a lot of typedefs and struct definitions prefixed by linux_. The idea is always the same: to grab the i386 version of the file containing the requested macro/typedef/struct definition, and to adapt it for the PowerPC.

During this work, the linux/include/asm-ppc and linux/include/linux directories from the Linux kernel sources will be useful. It is essential to avoid just copying the i386 version of the different files needed in the powerpc directory such as linux_termios.h or linux_types.h. There are very few differences between most of the i386 version and the PowerPC version of the Linux includes we need to define, but a careful check of every value will avoid lots of trouble finding out what went wrong later.

After adding a lot of header files, the sys/compat/linux/arch/powerpc directory starts looking like its i386 counterpart. There are only a few .c files missing. We then have to define a few functions that are defined in i386/linux_machdep.c and i386/linux_ptrace.c, else the kernel will not build. Most of the linux_machdep.c file holds functions related to signal delivery, whereas the linux_ptrace.c file holds functions that enable Linux's gdb use on emulated binaries. Obviously, we don't need most of this now. So the idea is to write empty functions that just return zero without actually doing anything. The goal is to have a kernel that builds, and to add the missing code later.

Remember that each time a .c file is added to the sys/compat/linux/arch/powerpc directory, it has to be added to sys/compat/linux/arch/powerpc/files.linux_powerpc, and then, the config(8) utility must be rerun. This integrates the new file into the kernel build process. Otherwise, the new file will be ignored.

Matching the Linux binaries

Once we have a working kernel, we can try our first Linux binary on it. To do this, we go on a LinuxPPC machine and compile the following program, linked as a static binary. This is done using the -static flag with gcc.

 * hello.c -- A hello world test
 * Build with gcc -static -o hello hello.c
#include <stdio.h>
int main (int argc, char **argv) {
    printf ("Hello world!\n");
    return 0;

Then we try to run the compiled binary on the NetBSD system. Normally, it shouldn't work. Most likely, we get a strange message explaining that a syntax error occurred after a "(", and this sounds like the kernel decided this was a shell-script and gave it to the shell to execute. The dynamic version should just crash, but we will take care of it later.

Our problem is that the kernel was not able to recognise the executable as a Linux binary. This can be outlined by running ktrace(1) and kdump(1) on the executable. If the kernel had matched the executable as a Linux binary, then the kernel trace should contain a EMUL "linux" record.

So we have to get a working Linux binary-matching mechanism. When starting a new binary (on execve() calls), the NetBSD kernel performs some probe tests to find out what to do. Practically, the kernel maintains a list of struct execsw (struct execsw is defined in sys/sys/exec.h) describing the available ways of executing a program: native ELF, native a.out, shell scripts, Linux emulation, and so on. This list is initialized from sys/kern/exec_conf.c, and is used in sys/kern/kern_exec.c. A member of the struct execsw is a pointer to a probe function, whose job is to return 0 if it matches the executable. For Linux ELF32 emulation, this function is linux_elf32_probe(), which is implemented in sys/compat/linux/common/linux_exec_elf32.c.

Pages: 1, 2, 3, 4

Next Pagearrow

Sponsored by: