Check out the new USENIX Web site. Next Previous Contents

2. Implementation

The emulator needed to handle the following major issues (detailed below): system call handling along with remapping of arguments, flags, return values and error codes; library pathname lookup and remapping; ioctl mappings for the various major devices and differences in the tty subsystems.

2.1 Design goals

2.2 Difference in syscall handling

System call handling is implemented differently between Linux and Intel UNIX System V platforms; that is each uses a different instruction to implement the switch to kernel mode.

Intel Unix System V uses a "lcall $0x07". In Linux an "int $0x80" software interrupt instruction is used, which jumps to the system-call-handling portion of the Linux kernel. On SCO systems, "int $0x80" is an unused vector and therefore causes a general protection trap resulting in a SIGSEGV signal. Lxrun intercepts these signals and calls the SCO equivalent of the system call that the Linux program attempted. In many cases this is a direct map or call of the equivalent native system call, in other cases some mapping or translation of arguments passed in and flags and error codes passed out is required. Where there is no equivalent system call available on the emulated system or the equivalent syscall mapping has not been implemented the system call fails and returns an errorcode of ENOSYS.

2.3 Pathname for loading Linux libraries

Because lxrun works at the system call level, any Linux shared libraries required by the application must be present in the emulation environment. This leads to a possible filename-space conflict between native and Linux binaries. To resolve this problem, lxrun remaps any pathnames beginning with /lib, /usr/lib, /usr/local/lib, or /usr/X11R6/lib by prepending them with a "Linux root" path. This path is specified at compile time and can be overridden by setting the LINUX_ROOT environment variable. This remapping allows the user to install a set of Linux libraries in a separate directory hierarchy from the native system libraries, thus avoiding conflicts.

2.4 Device major number mappings

The arguments to an ioctl call alone do not provide enough information to remap the command number correctly. This is because the same command number can have different meanings to different drivers.

Lxrun works around this problem by maintaining a mapping from open file descriptors to drivers. On the first ioctl call to a new file descriptor, lxrun determines the associated major device number. It compares this with a table of drivers and major device numbers set up at run-time. This mapping is then cached to improve performance on future ioctl calls to that file descriptor. Lxrun can then take the driver into consideration when remapping ioctl command numbers.

2.5 Differences in kernel tty systems

The following is a discourse on lxrun tty handling from Robert Lipe (robertl@dgii.com), the principal author of lxrun's tty handling code:

"The kernel tty systems are very different. The user level tty systems are actually quite similar, fortunately for lxrun. Most of the members of things like termio and struct termios are the same size, alignment, and offset. Even most of the bitfields fall into place. While it would have been much safer to do


        if ( (lx_tio->c_cflag & LX_CBAUD) == LX_B50)
                tio->c_cflag |= B50;
        if (lx_tio->c_iflag & LX_ICANON) 
                tio->c_iflag |= ICANON;

and repeat this for each of a couple hundred flags, the reality was that it generated horrible code that would have performed poorly and been a nightmare to maintain.

The only sticky spot was that Linux has four distinctly separate members in c_cc (the control character array) for VMIN, VTIME, EOF, and EOL. Most System V's including OpenServer (I can't recall what SVR[45] does here) use the same offset and multiplex this into two bytes. In reality, since they can never both be active at the same time (if ICANON is set, EOF and EOL are used. If ICANON is clear, VMIN, VTIME are used) this doesn't turn out to be much of a issue.

[Since] TCGETA and TCSETA are by far the most frequently used, I implemented them first (and their derivatives that wait and flush and the derivatives of each of those for POSIX struct termios). With those 8 in hand, I started firing up applications that were known to do wierd things to the tty. When elvis (the Linux vi binary I had at the time) worked well enough, I submitted the changes. Then, I just used more and more applications and picked up a few stragglers like FIONREAD that mapped very simply.

Are there hazards in any of this? Certainly. Each system has a few bits in each of the available ioctls that don't exist in the other system. There is some overlap. We haven't seen any real world failures becuase of this. The reality seems to be that the programs that drive serial ports to the crazy edge don't make sense to emulate anyway. For example, someone once asked me why Linux ecu (a kermit-like program) didn't work well. Since source for it is available and it supports OpenServer just fine, use the native binary instead. Someone once asked me about running Linux ppp, but that's similarly nonsensical, though doomed to failure for different reasons."

2.6 Miscellaneous issues


Next Previous Contents