Chapter 3
Introduction to OSKit Interfaces
The OSKit’s interfaces provide clean, well-defined intra-process or
intra-kernel protocols that can be used to define the interaction between different modules of an operating system.
For example, the OSKit provides a “block I/O” interface for communication between file systems and disk device
drivers, a “network I/O” interface for communication between network device drivers and protocol stacks, and a file
system interface similar to the “VFS” interface in BSD. However, the OSKit’s interfaces were designed with a
number of properties that make them much more useful as parts of a toolkit than are comparable traditional
OS-level interfaces. These properties partly stem from the use of the Component Object Model (COM), described in
Chapter 4 as the underlying framework in which the interfaces are defined, and partly just from careful
interface design with these properties in mind. The primary important properties of the OSKit interfaces
include:
- Extensibility. Anyone can define new interfaces or extend existing ones with no need to interact with
a centralized authority. Components can simultaneously use and export interfaces defined by many
different sources.
- Simplicity. All of the OSKit’s interfaces take a minimalist design strategy: only the most obvious and
fundamental features are included in the base interfaces. Get it right first; frills and optimizations can
be added later through additional or extended interfaces.
- Full extensibility while retaining interoperability. Adding support in a component for a new or
extended interface does not cause existing clients to break. Similarly, adding support in a client for a
new interface does not make the client cease to work with existing components.
- Clean separation between components. The clean object model used by the OSKit interfaces
ensures that components do not develop implicit dependencies on each other’s internal state or
implementation. Such dependencies can still be introduced explicitly when desirable for performance
reasons, by defining additional specialized interfaces, but the object model helps prevent them from
developing accidentally.
- Orthogonality of interfaces. Like the OSKit components that use them, the interfaces themselves
are designed to be as independent of and orthogonal to each other as possible, so that exactly the set of
interfaces needed in a particular situation can be used without requiring a lot of other loosely related
interfaces to be implemented or used as well.
- No required standardized infrastructure code. The OSKit interfaces can be used irrespective of
which actual OSKit components are used, if any; they do not require any fixed “support libraries” of
any kind which all clients must link with in order to use the interfaces. This is one important difference
between the application of COM in the OSKit versus its original Win32 environment, which requires
all components to link with a standard “COM Library.”
- Efficiency. The basic cost of invocation of an OSKit COM interface is no more than the cost of a
virtual function call in C++. Through the use of additional specialized interfaces even this cost can
be eliminated in performance-critical situations.
- Automation. The simplicity and consistent design conventions used by the OSKit’s interfaces make
them amenable to use with automated tools, such as Flick, the Flux IDL Compiler1,
in the future.
- Binary compatibility. The Component Object Model and the OSKit’s interfaces are designed to
support not only source-level but binary-level compatibility across future generations of components
and clients.
As with all other parts of the OSKit, the client is not required to use the OSKit’s interfaces as the primary
inter-module interfaces within the system being designed. Similarly, the client may use only some of the
interfaces and not others, or may use the OSKit’s interfaces as a base from which to build more powerful,
efficient interfaces specialized to the needs of the system being developed. Naturally, since the specific
components provided in the OSKit must have some interface, they have been designed to use the
standardized OSKit interfaces so that they can easily be used together when desired; however, the OS
developer can choose whether to adopt these interfaces as primary inter-module interfaces in the system,
or simply to use them to connect to particular OSKit components that the developer would like to
use.
3.1 Header File Conventions
This section describes some specific important properties of the design and organization of the OSKit header
files.
3.1.1 Basic Structure
All of the OSKit’s public header files are installed in an oskit subdirectory of the main installation directory for
header files (e.g., /usr/local/include by default). Assuming client code is compiled with the main include
directory in its path, this means that OSKit-specific header files are generally included with lines of the form
‘#include <oskit/foo.h>’. This is also the convention used by all of the internal OSKit components. Confining all
the OSKit headers into a subdirectory in this way allows the client OS to place its own header files in the same
header file namespace with complete freedom, without worrying about conflicting with OSKit header
files.
The OSKit follows this rule even for header files with well-known, standardized names: for example, the
ANSI/POSIX header files provided by the minimal C library (e.g., string.h, stdlib.h, etc.) are all located in a
header file subdirectory called oskit/c. On the surface this may seem to make it more cumbersome for the client
OS to use these headers and hence the minimal C library, since for example it would have to ‘#include
<oskit/c/string.h>’ instead of just the standard ‘#include <string.h>’. However, this problem can
easily be solved simply by adding the oskit/c subdirectory to the C compiler’s include path (e.g., add
-I/usr/local/include/oskit/c to the GCC command line); in fact this is exactly what most of the
OSKit components themselves do. Furthermore, strictly confining the OSKit headers to the oskit
subdirectory, it makes it possible for the client OS and the OSKit itself to have several different sets of
“standard” header files coexisting in the same directory structure: for example, the OSKit components
derived from Linux or BSD typically leave oskit/c out of the compiler’s include path and instead
use the native OS’s header files; this makes it much easier to incorporate legacy code with minimal
changes.
3.1.2 Namespace Cleanliness
A similar namespace cleanliness issue applies to the actual symbols defined by many the OSKit header files. In
particular, all OSKit header files defining COM interfaces, as well as any related header files that they cross-include
such as oskit/types.h and oskit/error.h, only define symbols having a prefix of oskit_, OSKIT_, osenv_, or
OSENV_. This rule allows these headers to be included along with arbitrary other headers from different
environments without introducing a significant chance of name conflicts. In fact, the OSKit components derived
from legacy systems, such as the Linux driver set and the FreeBSD drivers and TCP/IP stack, depend
on this property, to allow them to include the OSKit headers defining the COM interfaces they are
expected to export, along with the native Linux or BSD header files that the legacy code itself relies
on.
Once again, this rule creates a potential problem for header files whose purpose is to declare standard,
well-known symbols, such as the minimal C library header files. For example, string.h clearly should declare
memcpy simply as memcpy and not as oskit_memcpy or somesuch, since in the latter case the “C library” wouldn’t be
conforming to the standard C library interface. However, there are many types, structures, and definitions that are
needed in both the minimal C library headers and the COM interfaces: for example, both the oskit_ttystream
COM interface and the minimal C library’s termios.h need to have some kind of termios structure; however, in
the former case a disambiguating oskit_ prefix is required, whereas in the latter case such a prefix is not allowed.
Although technically these two termios structures exist in separate contexts and could be defined independently,
for practical purposes it would be very convenient for them to coincide, to avoid having to perform
unnecessary conversions in code that uses both sets of headers. Therefore, the solution used throughout the
OSKit header files is to define the non-prefixed versions of equivalent symbols with respect to the
prefixed versions whenever possible: for example, the errno.h provided by the minimal C library
simply does a ‘#include <oskit/error.h>’ and then defines all the errno values with lines of the
form:
#define EDOM OSKIT_EDOM
#define ERANGE OSKIT_ERANGE
|
Unfortunately this is not possible for structures since C does not have a form of typedef statement for defining
one structure tag as an alias for another. Therefore, the few structures that need to exist in both contexts (such as
the termios structure mentioned above) are simply defined twice. Since these structures are generally well-defined
and standardized by ANSI C, POSIX, or CAE, they are not expected to change much over time, so the added
maintenance burden should not be significant and is vastly outweighed by the additional flexibility provided by the
clean separation of namespaces.
3.2 Common Header Files
This section describes a few basic header files that are used throughout the OSKit and are cross-included by many
of the other OSKit headers.
3.2.1 boolean.h: boolean type definitions
SYNOPSIS
#include <oskit/boolean.h>
DESCRIPTION
Defines the fundamental values TRUE and FALSE for use with the machine-dependent
oskit_bool_t type.
3.2.2 compiler.h: compiler-specific macro definitions
SYNOPSIS
#include <oskit/compiler.h>
DESCRIPTION
Defines a variety of macros used to hide compiler-dependent ways of doing things.
-
OSKIT_BEGIN_DECLS, OSKIT_END_DECLS:
- All function prototypes should be surrounded by
these macros, so that a C++ compiler will identify them as C functions.
-
OSKIT_INLINE:
- Identifies a function as being inline-able.
-
OSKIT_PURE:
- Identifies a function as “pure.” A pure function has no side-effects: it examines
no values other than its arguments and changes no values other than its return value.
-
OSKIT_NORETURN:
- Identifies a function as never returning (e.g., _exit).
-
OSKIT_STDCALL:
- Indicates that a function uses an alternative calling convention compatibile
with COM. In particular, this option indicates the called function will pop its parameters
unless there were a variable number of them.
3.2.3 config.h: OSKit configuration-specific definitions
SYNOPSIS
#include <oskit/config.h>
DESCRIPTION
This file is generated by the configure program. It identifies a number of environment-dependent
parameters. Currently these are all related to the compiler and assembler used to build the OSKit.
-
HAVE_CR4:
- Defined if the assembler supports the %cr4 register.
-
HAVE_DEBUG_REGS:
- Defined if the assembler supports the debug registers.
-
HAVE_P2ALIGN:
- Defined if the assembler supports the .p2align pseudo-op.
-
HAVE_CODE16:
- Defined if your assembler supports the .code16 pseudo-op.
-
HAVE_WORKING_BSS:
- Defined if your assembler allows .space within .bss segments.
-
HAVE_PACKED_STRUCTS:
- Defined if your compiler groks __attribute__((packed)) on structs.
-
HAVE_PURE:
- Defined if your compiler groks __attribute__((pure)) on functions.
-
HAVE_NORETURN:
- Defined if your compiler groks __attribute__((noreturn)) on functions.
-
HAVE_STDCALL:
- Defined if your compiler groks __attribute__((stdcall)) on functions.
3.2.4 machine/types.h: basic machine-dependent types
SYNOPSIS
#include <oskit/machine/boolean.h>
DESCRIPTION
This header file defines a number of types whose exact definitions are dependent on the processor
architecture and compiler in use.
The following set of types are guaranteed to be exactly the indicated width regardless of processor
architecture; they are used to get around the fact that different C compilers assign different meanings to
the built-in C types such as int and long:
-
oskit_s8_t:
- Signed 8-bit integer
-
oskit_s16_t:
- Signed 16-bit integer
-
oskit_s32_t:
- Signed 32-bit integer
-
oskit_s64_t:
- Signed 64-bit integer
-
oskit_u8_t:
- Unsigned 8-bit integer
-
oskit_u16_t:
- Unsigned 16-bit integer
-
oskit_u32_t:
- Unsigned 32-bit integer
-
oskit_u64_t:
- Unsigned 64-bit integer
-
oskit_f32_t:
- 32-bit floating point type
-
oskit_f64_t:
- 64-bit floating point type
The following types depend in various ways on the target processor architecture:
-
oskit_bool_t:
- This type represents the most efficient integer type for storage of simple boolean
values; on typical architectures it is the smallest integer type that the processor can handle
with no extra overhead.
-
oskit_addr_t:
- This is an unsigned integer type the same size as a pointer, which can therefore
be used to hold virtual or physical addresses and offsets.
-
oskit_size_t:
- This is an unsigned integer type equivalent to oskit_addr_t, except that it is
generally used to represent the size of a data structure or memory buffer, or a difference
between two oskit_addr_ts.
-
oskit_ssize_t:
- This is a signed integer type the same size as oskit_size.
-
oskit_reg_t:
- This is an unsigned integer type of the same size as a general-purpose processor
register; it is generally but not necessarily always equivalent to oskit_addr_t.
-
oskit_sreg_t:
- This is a signed integer type the same size as oskit_reg_t.
3.2.5 types.h: basic machine-independent types
DESCRIPTION
This header file defines a few basic types which are used throughout many of the OSKit’s COM
interfaces. These types correspond to standard POSIX types traditionally defined in sys/types.h;
however, this does not mean that the client OS must use these types as its standard definitions
for the corresponding POSIX symbols; it only means that the client must use these types when
interacting with OSKit components through the OSKit’s COM interfaces. All of the type names
are prefixed by oskit_, for exactly this reason.
-
oskit_dev_t:
- Type representing a device number; used in the oskit_stat structure in the
OSKit’s file system interfaces. Note that the OSKit’s file system components themselves
don’t know or care about the actual assignment or meaning of device numbers; it is up to
the client OS to determine how these device numbers are used, if at all.
-
oskit_ino_t:
- Type representing a file serial number (“inode” number), again used in the
OSKit’s file system interfaces.
-
oskit_nlink_t:
- Type representing the link count of a file.
-
oskit_pid_t:
- Type representing a process ID. This type is used in a few COM interfaces, such
as the oskit_ttystream interface which includes POSIX-like job control methods. Currently
the OSKit currently does not include any process management facilities, but this type and
the related methods that use it are included in case such a facility is added in the future.
-
oskit_uid_t:
- Type representing a traditional POSIX user ID. The current OSKit components do
not know or care about POSIX security, but for example the NetBSD file system component
knows how to store and retrieve user IDs in BSD file system partitions.
-
oskit_gid_t:
- Type representing a traditional POSIX group ID. The same considerations apply
as for oskit_uid_t.
-
oskit_mode_t:
- Type representing a file type and access permissions bit mask; again used in the
file system interface.
-
oskit_off_t:
- Type representing a 64-bit file offset; used in the file system interface.
-
oskit_wchar_t:
- Unicode “wide” character.