FAU UnivIS
Techn. Fak. Dep. Informatik

A templated System-Call Interface for OO/MPStuBS ,Christian Dietrich

We use OOStuBS/MPStuBS in our operating system course. In the first part of our two part lecture, the students implement basic IRQ handling, coroutine switching, and synchronisation primitives. We have no spatial or privilege isolation in place, since this is topic of the second lecture part.

Still, we want to differentiate between a user space and the kernel space. On a technical level, the kernel space or system level is defined by a big kernel lock; the so called guard. If a control flow enters the guard, it transitions to the kernel and leaves the kernel, when the guard is left. The guard concept is heavily coupled with our idea of IRQ handling in epilogues (similar to bottom-halves or deferred interrupt handlers).

Our proposed implementation of the system call interface uses facade pattern to expose some of the system functionality as "Guarded Services".

class Guarded_Scheduler {
     static void resume() {
          Secure section; // does guard.enter() in constructor
          scheduler.resume();
          // guard.leave() is called on Secure destructor
     }
}

The used Secure class uses a Resource Acquisition Is Initialisation pattern to enter the guard on construction, and to leave it upon destruction of the secure object. But, as you see, coding down this pattern is cumbersome and involves a lot of boilerplate. Nobody, especially interested students, want to write boilerplate. But, our OS is implemented in C++, so we have powerful abstractions to implement a usable abstraction. In the following, I will explain, how we can implement an easily extensible system-call interface for a library operating system (everything is linked together, and we have no spatial isolation).

A First Attempt

First, we start with a "simple" templated function that can wrap every member function of an object and call with the guard taken. The actual API usage looks like this:

 syscall(&Scheduler::resume, scheduler);
 syscall(&Scheduler::kill,   scheduler, &other_thread);

The first argument to syscall() might surprise some readers, since it is a seldom used C++ feature. It is a "Pointer to Member" that captures how we can access or call a member when having the corresponding object at hand. The datatype of &Scheduler::resume is void (Scheduler::*)(), which is similar to a function pointer returning nothing and taking no arguments. &Scheduler::kill has the datatype void (Scheduler::*)(Thread *); it is a pointer to a member function, which returns nothing but takes an Thread pointer as argument. Both pointers only make sense with a Scheduler object at hand. When we have a scheduler object at hand, we can use the rarely used .* operator:

 ((scheduler).*(&Scheduler.kill))(thread)

We now can combine this concept with C++11 templates to get the described syscall function:

template<typename Func, typename Class, typename ...Args>
inline auto syscall(Func func,  Class &obj, Args&&... args) -> decltype((obj.*func)(args...)){
    Secure secure;
    return (obj.*func)(args...);
};

Huh, what happens here? Let's take this monster apart to understand its working. So, it is a function template, it generates functions depending on the types it is specialized for. You can think of this specialization process like this: the compiler has a Schablone (german word for template, but with the notion of scissors and paper) at hand. When it sees a function call to syscall() it fills the missing parts in the Schablone with the argument types and compiles the result a new function.

syscall(Func arg0, Class arg1, Args... args2_till_9001)

So, our syscall function takes at least two arguments, but can consume arbitrarily many arguments in its variadic part at the end (the Args...). The type of the first argument is bound to the type "Func", the second argument type is bound to the type "Class", all others are collected in the variadic type "Args". The func argument, which type Func, is pointer-to-member object, the obj argument the actual system object. So, now we can call the function with the other arguments.

 (obj.*func)(args...)

But, our function, still has no return type. What to do? Here comes C++ auto and decltype to the rescue. When using auto as a return type, the compiler excepts -> Type after the closing parenthesis of the function. The decltype() built-in gives the type of the enclosed expresion. So decltype((obj.*func)(args...)) is exactly the return type of the given pointer-to-member-function argument.

Furthermore, we just have to allocate a Secure object to make the guard.enter() and guard.leave() calls. Voila, a system call interface. But it still has some problems. We can call every method on every object in the whole system. We have no notion of "allowed" system calls and forbidden ones. Of course, in a library operating system with no protection this is ok. Furthermore, we always have to give the system object (e.g., scheduler) on each system call. I think, we can do better here. So let's revisit our implementation.

A second Attempt

In our second attempt, we want to restrict the system-call interface to certain classes. This gives coarse-grained control about the methods that can be called via the syscall interface. As a side-effect, we can omit the actual system-object argument such that we can write:

syscall(&Scheduler::resume)
syscall(&Scheduler::kill, that)

We implement a system_object function that returns the system-object singleton instance when called for a given type. We implement this function only for those classes, we want to allow access via syscall. This gives us some control about the possible syscall targets.

template<typename Class>
Class& system_object();

// Get the scheduler singleton
Scheduler &scheduler = system_object<Scheduler>();

The template specialization can be done in the source file and does not have to be put into the header. This allows us to hide the actual system-object symbol from the rest of the system. For example, this could be located in the thread/scheduler.cc file:

static Scheduler instance;

template<>
Scheduler &system_object() {
    return instance;
}

We still have to call this function from our system call implementation. For this, we need to have the class type of the underlying system object at hand. The only thing we have is the pointer-to-member object that identifies the desired system-call (&Scheduler::resume). But, as you remember, the class type is part of the type of such pointer-to-member types (Func). We only have to extract that information from the given type.

The concept of accessing information about types is called type traits. This is grandiloquent word for "a template that takes a type and provides several types and constants". So let's look at our type trait:

// Fail for all cases...
template<typename> struct syscall_traits;

// ..., except for deconstructing a pointer to member type
template<typename ReturnType, typename ClassType, typename ...Args>
struct syscall_traits<ReturnType(ClassType::*)(Args...)> {
    typedef ReturnType result_type;
    typedef ClassType class_type;
};

This syscall_traits is only specialized for pointer-to-member types and destructs the type of our &Scheduler::resume argument (void (Scheduler::*)()) with the pattern ReturnType (ClassType::*)(Args...). As you see, the templates does only pattern matching on types and binds types to template parameters. This can generally said for templates: The <>-line after the template keyword defines type variables, which can be bound later on or have to be supplied by the user. With our type trait we can simply access the instance class of our pointer-to-member argument and can call system_object():

template<typename Func, typename ...Args>
inline auto syscall(Func func, Args&&... args) -> typename syscall_traits<Func>::result_type {
      // We do everything with a taken guard
      Secure secure;

      // Get traits of systemcall
      typedef typename syscall_traits<Func>::class_type system_object_type;

     // Get a singleton instance for the given base type.
     system_object_type &obj = system_object<system_object_type>();

     return (obj.*func)(args...);
};

The first thing we see is that the deduced return type has changed. It no has to use our type trait, since we have no system object at hand we can use with decltype (-> decltype((obj.*func)(args...)). Within the body of the syscall function, we use the trait to extract the system-object's class type from the Func type and call system_object to gain access to the singleton instance.

If we use syscall on a class that is not exposed via specializing system_object<>, we get an linker error and the developer is informed that he wants to do bullshit.

So, what have we achieved in the second attempt? We have a cleaner system-call interface and do not have to supply the system object directly, but it is deduced from the supplied arguments. Furthermore, only annotated classes are suitable for being called via this interface. Nevertheless, we can still call all functions on these classes. In the third attempt we want to solve this as well.

The third and final Attempt

How can we annotate functions as being system calls? The only real thing we have at hand in static C++ land are types. So we have to annotate the function type of our system call somehow. The type of a method is defined by only a few pieces of information: The argument types, the class type, and the return type. The one thing that is always there, and that is not shared among several functions is the return type. We use the return type for our annotation by wrapping it into an marker struct:

template <typename T=void> struct syscall_return;
template<> struct syscall_return<void> { void get() {}; };

template <typename T>
class syscall_return {
    T value;
public:
    syscall_return(T&& x) : value(x) {}
    operator T() { return value; }
    T get() { return value; }
};

The syscall_return wraps a type and contains a copy of it. Furthermore, it implements a get() method to access this inner object and has the cast operator for T overloaded for easier handling. The void type is special here, and has to be handled special, since it is a no-object type and cannot be instantiated.

We can no annotate functions in our Scheduler class:

struct Scheduler {
    syscall_return<void> resume() {
        printf("resume %d\n", (int)barfoo(23));
        return syscall_return<>();
    }

    virtual syscall_return<int> increment(int i) {
        return i+1;
    }
}

As you see, we have to special case for void again ("Damn you void, you and your voidness!"). But, the implicit cast via the constructor makes it easy to return all other types. But we also have to adapt the rest of our implementation. In the syscall_traits template, the matched pattern strips the syscall_return wrapper from the type. This will also cause all unwrapped return types to fail.

// ..., except for deconstructing a pointer to member type
template<typename ReturnType, typename ClassType, typename ...Args>
struct syscall_traits<syscall_return<ReturnType>(ClassType::*)(Args...)> {
    typedef ReturnType result_type;
    typedef ClassType class_type;
};

In the syscall template, we only have to additionally call .get() on the result:

template<typename Func, typename ...Args>
inline auto syscall(Func func, Args&&... args) -> typename syscall_traits<Func>::result_type {
      // We do everything with a taken guard
      Secure secure;

      // Get traits of systemcall
      typedef typename syscall_traits<Func>::class_type system_object_type;

      // Get a singleton instance for the given base type.
      system_object_type &obj = system_object<system_object_type>();

      return (obj.*func)(args...).get();
};

And voila, we have a system call interface with annotations that prevents the user to call unmarked functions via syscall. All abstractions from above come at zero run-time cost.

The only downside is that the user is still able to call the functions directly. But, this can never be solved in a library operating system.

I hope I could give you an impression what is possible with C++ templates in the context of a bare-metal operating system.