1. Processes
A process contains one or more tasks. It holds information that is common for all of its tasks. Each process has a main task and can have multiple child tasks.
A process itself has no id, it is identified by the id of its main task.
2. Tasks
A task is a single stream of execution. g_task
is the data structure
that contains information for a task.
Tasks running in kernel-level only have one stack, user-space tasks have an additional stack that is used during interrupt handling. The stack section describes how these stacks are handled.
2.1. Types
The following constants of type g_task_type
denote types of tasks.
Identifier | Description |
---|---|
G_TASK_TYPE_DEFAULT |
Normal task |
G_TASK_TYPE_VM86 |
Virtual 8086 task |
2.2. Kernel-level
2.2.1. Creating a task
In kernel space, a task can be created by using taskingCreateTask
and
assigning it to scheduling with taskingAssign
.
2.2.2. Considerations
There are some things to consider when implementing a task in kernel-level. When a kernel-lock is taken by a task, scheduling is disabled until the task releases the lock. This is inevitable as it could cause a deadlock if the task is interrupted while holding a lock.
To minimize any blocking, locks should therefore be used around critical parts but acquired as late and released as soon as possible.
2.2.3. Exiting a task
A kernel task must be exited using taskingExit
, otherwise the
task will run into nirvana and cause a failure.
2.3. User-level
2.3.1. Creating a task
To create a task from userspace, the g_create_task
API function is used. This function must be supplied with an address pointing to
code that the task shall execute. Additionally to the entry point, a pointer
to user data can be passed.
A simple example could look like this:
void myTaskCounter(int* counter) {
for(;;) {
(*counter)++;
}
}
int main(int argc, char** argv) {
int counter = 0;
g_create_task_d(myTaskCounter, &counter);
for(;;) {
g_sleep(1000);
printf("The counter is: %i\n", counter);
}
}
Each task has a unique id of type g_tid
. This id can be obtained from within
the task using g_get_tid
.
2.3.2. Internal initialization
The task initialization sequence uses a wrapper function for starting
the task. First, the G_SYSCALL_CREATE_TASK
system call is called
and supplied with a "task setup routine" and the "user entry" (the function
given to g_create_task
).
This setup routine is the first piece of code that is executed from within the
newly created tasnk, but does initially not know where to start execution.
To get this information, it uses G_SYSCALL_GET_TASK_ENTRY
which returns
the user entry to start executing at.
This entire procedure is done to avoid that the kernel must modify the user stack to put the necessary values on it, but rather do it from within code which is inherently ABI compatible.
2.3.3. Exiting a task
A task is automatically destroyed once the entry function finishes execution.
To stop execution from an arbitrary point, the g_exit
API
function can be used.
A single task can not be killed from a different process; using the
g_kill
always causes the entire process to exit.
When the main task of a process dies, all tasks of the process are killed.
2.4. Security Levels
When creating a process, a security level is used to determine what permissions the tasks of the process have. This security level is stored on the process and applied on task creation.
2.4.1. Existing security levels
The following constants of type g_security_level
denote the different
security levels.
Identifier | Description |
---|---|
|
Has no specific permissions. |
|
Has I/O permission level 3 (may use |
|
Runs in kernel space |