The Simple Process Library (“sproc”) provides a way to implement simple processes that run in user mode and which have separate address spaces. Multiple threads can run in a single process simultaneously. The library also provides a mechanism for defining system calls allowing process threads to request privileged services. The sproc library uses the NetBSD UVM library (See Section 28 to implement address spaces. Currently the sproc library is only implemented on the x86.
Note that this is only a first attempt at a process library. Hopefully, this library will be improved in the future.
A sample kernel in the examples/x86/sproc directory demonstrates the use of the Simple Process Library. That directory contains a sample kernel and sample user programs that run on the kernel.
In order to use the sproc library, the kernel implementor must do the following:
oskit_sproc_syscall_func | entry; | /* pointer to a system call function | */ | |
int | nargs; | /* # of args in sizeof(int) | */ | |
/* process description */ struct oskit_sproc_desc { /* maximum number of system call number */ int sd_nsyscall; /* system call table */ struct oskit_sproc_sysent *sd_syscalltab; /* trap handler that captures all traps while executing user mode code */ int (*sd_handler)(struct oskit_sproc_thread *sth, int signo, int code, struct trap_state *frame); }; |
This structure is bound to a process to deal with all traps such as system calls, protection or page faults. This structure is passed to oskit_sproc_create when creating a process.
sd_syscalltab is a pointer to the system call dispatch table described above. The size of the table should be stored in sd_nsyscall. sd_handler is a pointer to a handler function that is called when a trap is received while executing user mode code. The parameters passed to the handler are:
/* kernel thread definition */ struct oskit_sproc_thread { queue_chain_t st_thread_chain; struct oskit_sproc *st_process; oskit_addr_t st_entry; jmp_buf st_context; struct oskit_sproc_stack *st_stack; /* machine dependent part */ struct oskit_sproc_machdep st_machdep; }; |
The library uses the int 0x60 instruction for implementing system calls on the x86 architecture. 0x60 is defined as SYSCALL_INT in <oskit/machine/sproc.h>.
System call implementing functions (Syscall function for short) have a type oskit_sproc_syscall_func defined as follows in <oskit/sproc.h> .
Syscall functions are called from the system call trap handler provided by the library. A system call can either succeed or fail. On success, the syscall function must return 0. On an error, it should return a non-zero error number. Please refer to Section 30.4.2 for how system call errors are handled.
The passed sth parameter conveys the context information of the thread that issued the system call. The arg parameter is a pointer to the first of the system call arguments, which were already copied to the kernel memory space by the system call trap handler.
The return value from the system call can be either 32 or 64 bits wide and is returned using rval. The return value is meaningful only when the system call succeeded. On the x86 architecture, the default implementation use %EAX for rval[0] (and %EDX for rval[1] if 64bit) to return the value. System call stubs will get the value from these registers.
A sample syscall stub can be seen in examples/x86/sproc/user_syscall.S.
Initialize the Simple Process Library. Must be called after the UVM library is initialized.
On the x86 architecture, this function initializes the system call trap vector by calling gate_init, setting up two GDTs: USER_CS for a user process’s code segment and USER_DS for its data segment. Also this function installs two hook functions: the UVM library’s context switch hook to keep track of the currently executing thread and a signal hook to catch signals generated by the processor.
#include <oskit/sproc.h>
oskit_error_t oskit_sproc_create( const struct oskit_sproc_desc *desc, oskit_size_t size, [out] struct oskit_sproc *outproc);
Create a process. This API creates a virtual address space and binds it to the specified process description.
Returns 0 on success, or an error code specified in #include <oskit/errno.h>, on error.
oskit_sproc_destroy
Destroy a process. The virtual address space bound to the process is discarded.
Returns 0 on success, or an error code specified in #include <oskit/errno.h>, on error.
#include <oskit/sproc.h>
oskit_error_t oskit_sproc_stack_alloc( struct oskit_sproc *sproc, [in/out] oskit_addr_t *base, oskit_size_t size, oskit_size_t redzonesize, [out] struct oskit_sproc_stack *out_stack);
Allocate a stack within a process’s user address space, that can be used for oskit_sproc_switch later. Information about the created stack is stored in the oskit_sproc_stack structure.
Returns 0 on success, or an error code specified in #include <oskit/errno.h>, on error.
oskit_sproc_stack_push
#include <oskit/sproc.h>
oskit_error_t oskit_sproc_stack_push(struct oskit_sproc_stack *stack, void *arg, oskit_size_t argsize);
Push parameters onto a stack allocated by oskit_sproc_stack_alloc. This function can be called multiple times to stack more parameters. The stacked parameters will be passed to a user program.
Returns 0 on success, or an error code specified in #include <oskit/errno.h>, on error.
oskit_sproc_stack_alloc
#include <oskit/sproc.h>
void oskit_sproc_switch(struct oskit_sproc *proc, oskit_addr_t entry, struct oskit_sproc_stack *stack);
Switch the calling thread to user mode and let it execute from the specified address entry. The stack pointer is changed to the specified stack. Multiple threads can be executed within a single process. This function does not return until the user mode code executes an exitlike system call. Refer to the description of OSKIT_SPROC_RETURN for more information.
When the thread starts execution in user mode, %CS is set to USER_CS and %DS, %ES are set to USER_DS. %FS and %GS are set to zero. %ESP points the first parameter on the stack. The frame pointer (%EBP) is set to zero.
oskit_sproc_stack_alloc, oskit_sproc_stack_push OSKIT_SPROC_RETURN
#include <oskit/sproc.h>
OSKIT_SPROC_RETURN(struct oskit_sproc_thread *sth, int code) |
This is a macro to be used in an exit-like syscall function to terminate the calling thread run in the user program. This macro does not return. This macro returns control to oskit_sproc_switch.
#include <oskit/sproc.h>
int oskit_sproc_load_elf(struct oskit_sproc *proc, const char *file, [out] exec_info_t *info_out);
Map an ELF executable file onto a user’s address space. This function uses the exec_load function internally (See Section 35). The ELF file’s header must indicate an address range for the file within the user space address range. The file is loaded on demand.
Returns the value returned from exec_load.