3.5. Scheme Operation
A kernel scheme is implemented directly within the kernel, while a userspace scheme is typically handled by a daemon. A scheme is created in the root scheme and listens for requests through the event scheme.
Root Scheme
The root scheme is a special scheme provided by the kernel. It acts as the container for all other scheme names. In vortexOS, the root scheme is referenced as ":", so when creating a new scheme, the scheme provider calls File::create(":myscheme")
. The file descriptor returned by this operation becomes a message-passing channel between the scheme provider and the kernel. File operations performed by regular programs are converted into message packets by the kernel, which the scheme provider reads and responds to using this file descriptor.
Event Scheme The event scheme is another special scheme provided by the kernel, enabling a scheme provider or other programs to listen for events occurring on a file descriptor. Scheme providers that need to manage multiple sources of requests or events use the event scheme to block until an event occurs, process it, and then return to waiting for the next event. Simpler scheme providers may not require the event scheme.
Daemons and Userspace Scheme Providers
A daemon is a background program, generally initiated during system startup and often running with root privileges. It operates continuously, handling incoming requests and responding to events. On some operating systems, daemons are restarted automatically if they terminate unexpectedly. vortexOS does not currently offer this feature but may include it in the future. In vortexOS, a userspace scheme provider is typically a daemon, although this is not a strict requirement. The scheme provider informs the kernel by creating a scheme, e.g., File::create(":myscheme")
. The colon prefix indicates that the scheme is a new entry in the root scheme, which the kernel interprets as a new scheme. In future versions, the scheme will register in a namespace using a different path format.
Namespaces When a program is started, it becomes a process and exists within a namespace. The namespace contains all the schemes, files, and directories that the process can access. If a process spawns another program, its namespace is inherited by the new process, allowing it to access the same resources. A parent process can limit a child process’s access by sandboxing it during creation.
Currently, vortexOS starts all processes in the "root" namespace. However, future versions will introduce more robust sandboxing for user programs, hiding most schemes and system resources. vortexOS also offers a "null" namespace, where processes are restricted from opening new files or schemes by name and can only use already open file descriptors. This feature is mainly a security measure for daemons running with root permissions, preventing them from being exploited to access unintended resources. A daemon typically opens its required schemes and resources during initialization, and then requests to be placed in the null namespace to prevent any further resource access.
Providing a Scheme To provide a scheme, a program follows these steps:
Create the scheme and obtain a file descriptor by calling
File::create(":myscheme")
.Open a file descriptor for each resource required to provide the scheme’s services (e.g.,
File::open("/scheme/irq/{irq-name}")
).If needed, open a timer file descriptor (e.g.,
File::open("/scheme/time/{timer_type}")
).Open a file descriptor for the event scheme if required (e.g.,
File::open("/scheme/event")
).Move the process to the null namespace to restrict access to additional resources (
setrens(0, 0)
).Register the file descriptors with the event scheme using
event_fd.write(&Event{fd, ...})
.
Once set up, the program enters a loop:
Wait for an event to occur. For simple schemes, this may involve a blocking read on the scheme file descriptor.
When an event is received, determine whether it is a timer event, a resource event (e.g., a device interrupt), or a scheme request.
For resource events, handle the necessary actions, such as reading from the device and queuing the data for the scheme.
For scheme requests, read the request packet, which contains information about the operation (e.g., open, read, write) and the requested resource.
Process the request and respond through the scheme file descriptor. If the request requires further handling, the response may be delayed until the necessary actions are completed.
The scheme allocates a handle for the requested resource and maps it to a descriptor. Descriptors are used by the scheme provider to manage internal data structures related to the resource. These descriptors are not the same as the file descriptors used by client programs; the kernel maps between them as necessary.
Kernel Actions The kernel plays a crucial role in supporting schemes by handling access to resources as file operations and converting user program requests into messages for scheme providers. When a blocking read or write occurs, the user program is suspended while the kernel sends event packets to wake the scheme provider. The provider processes the requests and sends responses, which the kernel translates back into return values for the user program.
When a blocking read or write is completed, the kernel ensures the user program is ready to resume execution by placing it in the run queue.
Last updated