EuOS 1.9 Documentation

Running EuOS

Just type ex kernel.ex. This will start EuOS and bring up a simple shell (see below).

Initialization Process

  1. Programs, drivers, filesystems are registered; filesystems are mounted
  2. The init process is run. This then calls the init program.
  3. The init program sets up the console I/O as standard input, output, and error for all programs (standard I/O streams are inherited).
  4. The init program then calls /bin/sh to run the /etc/inittab shell script, and calls k_join on that to wait until it's done.
  5. The init program then calls /bin/sh so you can enter commands etc., and again calls k_join to wait until you exit the shell.
  6. Finally, the init program kills all tasks and unmounts all filesystems.

The Shell

The shell I've written so far is extremely simple. It allows you to run any registered EuOS program, and allows command-line arguments separated by spaces (sorry, quotes are not accepted yet). It also lets you run the program in the background by appending & to the command. There is one built-in shell command, exit. This will exit the shell, but any running programs will continue to run.

Example:

/bin/sieve&

Runs the sieve program in the background.

/bin/hello

Runs the hello, world program in the foreground (I.e. you can't use the shell until the program exits).

/bin/sh /demo.sh

Runs the demo shell script.

/bin/sh

Opens a new shell. Exiting this new shell will bring you back to the shell you launched it from.

exit

This exits the current shell.

API Documentation

Note: error codes and routines normally used internally by the kernel are not documented yet. I'm also going to release this version of the documentation without docs for IPC so you can get started... I'll add more documentation later.

Tasks and threads

EuOS uses cooperative multitasking, since preemptive multitasking is impossible without extending the interpreter or building an interpreter into the kernel (which would be very slow). You must split your program into a set of functions, each of which takes no arguments. Each function returns either the routine_id of the next function to call in this task, or -1 to end this task. See the various included programs (init.e, sh.e, hello.e, and sieve.e) for examples.

The task scheduler is extremely simple. A program is either sleeping or running; there are no priorities. Since this is a cooperative scheduler, you should make your programs cooperate by making each function short.

Tasks are identified by task ids, which are indexes into the kernel's task sequences. Therefore task ids are always positive integers, as are all the ids used in EuOS.

Besides basic task information, the kernel also keeps various other data for each task. This data is referred to by an index. Most data is used internally by the kernel (to determine where to send standard I/O, for example). Index 1 contains the task-specified data - this is 0 by default. Index 2 contains the command line. All data except index 1 is inherited by any new task.

pid = k_newTask(main)

Creates a new task which begins execution with the function referred to by main (the routine_id of the first function). It returns the pid of the new task.

k_killTask(pid)

Kills the task with the specified pid.

k_join(pid)

Makes the current task sleep until the task with the specified pid ends.

k_setPriority(pid, priority)

Sets the priority of the specified task. A priority of 0 means the task should sleep, a non-zero priority means the task should running.

k_setTaskData(pid, index, data)

Sets the data at the specified index for the specified pid to data.

data = k_getTaskData(pid, index)

Returns the data at index for task # pid.

The Filesystem

EuOS uses a Unix-style filesystem, with everything starting at the root (/), and then you mount new filesystems. The directories are arranged somewhat like Unix (currently, there's a /bin filesystem, a /dev filesystem, and the root filesystem which runs on top of the real filesystem from the current directory. /etc is included as part of the root filesystem). The directory separator is the standard Unix /, not \ as in DOS (though the root filesystem will automatically translate so EuOS works on DOS also). Note: unlike Unix, the mount points need not exist before a filesystem is mounted.

Filesystems vs. File Descriptors (FDs) vs. FD Handlers

Filesystems provide an interface to a set of files, whether on disk, in memory, or across a network etc. They can be mounted and unmounted as directories. A filesystem driver is responsible for providing routines to open and delete files, and make and list directories.

File descriptors are used by the kernel to keep track of I/O. They may refer to a file on a filesystem, or to anything which provides routines for I/O (I.e. sockets). These routines are specified in a FD handler, which provides routines to read, write, seek, tell, ioctl, and close a FD.

The Root Filesystem

The root filesystem is mounted to access files from the current directory (the directory containing kernel.ex). It simply wraps calls into built-in Euphoria calls.

The /bin filesystem

The /bin filesystem contains information about programs registered with EuOS. Each program has its own file under /bin. These files are kept in memory, not on disk, and are generated at run time as programs are registered. Each file contains a sequence in the form:

{main, name, version}

main is the routine_id of the main function.

name is the name of the program (not the file name, but the full name)

version is the version of the program (an atom - i.e. 1.1)

The /dev filesystem

The /dev filesystem provides an interface to drivers. It provides the filesystem handler while the individual devices provide the file handlers (including open).

The Filesystem API

fd = k_allocfd(handler)

Allocates a file descriptor with the specified FD handler.

k_freed(fd)

Frees the specified fd.

status = k_close(fd)

Closes the specified file descriptor, returning 0 on success.

data = k_read(fd, n)

Reads up to n bytes from fd. Returns -1 on EOF, or a sequence of up to n bytes if some data was read. If the fd does not have any data available, but an EOF is not reached (I.e. keyboard input), {} is returned.

status = k_write(fd, data)

writes data to fd, returning an error code (exact error codes have not been defined - ignored for now).

status = k_seek(fd, where)

same as seek in file.e

status = k_tell(fd)

returns the position in fd, or -1 if not applicable

status = k_ioctl(fd, op, data)

calls the ioctl function for the specified fd. Op is an inter specifying the operation to preform, and data is an object to pass to the ioctl function.

id = k_new_fd_handler(handlers)

Creates a new handler and returns its id. handlers is a sequence of routine_ids to call for each function. The format is: {close, read, write, seek, tell, ioctl}. The arguments to each are the same as to the functions specified above.

k_register_fs_type(name, handlers)

Registers a new filesystem type with the specified name and handlers. Handlers is a sequence of routine_ids: {open, dir, del, mkdir, onmount, onunmount}. See below and included filesystems (rootfs.e, programs.e, and devfs.e)

fd = k_open(filename, mode)

Same as built-in open.

d = k_dir(directory)

Should be the same as dir() in file.e (note that some filesystems (binfs and devfs) are not fixed to do return standard dir() format yet.)

status = k_mkdir(directory)

returns 0 on success.

status = k_del(path)

returns 0 on success.

k_mount(device, mountpoint, type)

mounts filesystem of specified type at mountpoint. Device is like in Unix mount, but more flexible. For example, in rootfs, device is the root directory on the real filesystem.

k_mount(mountpoint)

unmounts the specified filesystem.

Programs

k_addProgram(filename, data)

filename is the filename under /bin (example: "sh")

data is a sequence of information about the program in the format: {main, name, version}

main is the routine_id of the starting task's starting function

name is the full name (not file name) of the program

version is an atom (I.e. 1.1)

pid = k_exec(programfile, arguments)

runs the specified program with the specified arguments and returns the pid of the initial task for that program. Note: the program name is prepended to the command line arguments given (new in 1.9)

Example: pid = k_exec("/bin/sh",{"/etc/inittab"})

Device Drivers

k_registerDevice(device, p_open)

device is the name of the device (so "stdin" would create /dev/stdin). p_open is the routine_id of an open handler function. The device driver is responsible for providing this function to open the device (initializing it if needed etc.). See console.e for an example.

k_unregisterDevice(device)

Unregisters the specified device.