C Sharp

From OSDev Wiki
Jump to navigation Jump to search

Please note that the correct title of this article is C#, however because of technical restrictions it's called "C Sharp".

C# is an object-oriented programming language developed by Microsoft and standardized by the ECMA and ISO. Its syntax is similar to C++, but with significant differences in functionality. It was designed to be used to create applications to run under Microsoft's .NET infrastructure. The idea was that it would be compiled into an intermediate bytecode language called CIL which was then just-in-time compiled into machine code by the .NET runtime. The runtime also provided several useful functions to the code with regards to garbage collection, run time type information and dynamic compilation. A standard library (called mscorlib) would provide string and other data structure classes (similar to the stdlib in C++) with calls into the Windows API for other tasks (predominantly I/O, GUI and process and thread management).

Why write a kernel in C#?

There are many disadvantages to writing a kernel in C#. Firstly, one would need to provide a substantial portion of both the runtime and the standard library along with some method of converting the kernel into machine code just to allow the kernel itself to run. Secondly direct memory access is difficult (although not impossible - see the 'unsafe' keyword for further information). The benefits are primarily those provided by type and memory safety. Given a CIL to native code compiler that works as expected, and without using the 'unsafe' keyword, it should be possible to write a kernel that guarantees against accessing code or data outside of certain pre-defined regions. This idea can then be extended to all user programs which would prevent user programs attempting to access areas of the kernel that it is not allowed to or areas of other programs. What this means is that you could theoretically run all programs in the same address space without them either accidentally or maliciously interfering with each other, provided that the address space is big enough. It is probable that the expanded address space provided with x86_64 is large enough for this.

Compiling your kernel to machine code

There are two approaches one could take to converting C# code to machine code:

1) Write a compiler which converts C# (or a superset of it) directly to machine code. This is the approach taken by Microsoft's research OS, Singularity, which defines the Sing# language for easier interaction with the underlying hardware. The Sing# code is converted to machine code by their compiler, Bartok.

2) Write a compiler which converts CIL to machine code. This leverages the C# to CIL compilers already present in Microsoft.NET or the mono project. This is the approach taken by many open source C# kernels, including SharpOS, Cosmos, the MOSA project and tysos.

In addition, both of the above approaches could be performed ahead-of-time (AOT) or just-in-time (JIT). The AOT approach is probably simpler to begin with - you have your compiler running on your development system which is used to produce executable files (e.g. ELF) which can then be loaded directly by a standard bootloader. A JIT design would require a not insubstantial amount of code that is loaded before your kernel which then converts your kernel into machine code before running it. The problem here is that the JIT compiler would likely need a lot of services which only your kernel could compile. The best combination is probably to AOT compile your kernel and JIT compiler and have them loaded, and then all other processes can be JIT compiled.

The runtime

Several C# commands (or their equivalent in CIL) require a functioning runtime (Microsoft calls this the Virtual Execution System, or VES). For example, the expected result of the 'newobj' CIL command is to create an object on a garbage managed heap. This would require a malloc function and then some method to perform gargabe collection on the heap. With clever coding (mainly using static or stack-defined objects), it is possible to have your kernel not use the newobj command until you have set up a heap and malloc function.

You will also need to provide an encapsulation of the run-time type information for a class and some way to implement the System.Object.GetType() and typeof() functions to return a class semantically equivalent to System.Type.

The standard library

Mscorlib.dll is a basic component of the .NET runtime and provides definitions of all the standard types (e.g. System.Int32 for a 32-bit signed integer, System.String for a string, System.Collections.Generic.List<T> for a list of objects of type 'T'). To write any sort of meaningful code in your kernel you are probably going to need to provide implementations of most of these. Options are:

1) Attempt to compile the Microsoft mscorlib.dll with your compiler. I strongly recommend that anyone who considers this please check all the legal implications prior to doing it. You have been warned.

2) Use the corlib provided by the Mono project. Note that a lot of the functions in their implementation are marked either 'InternalCall' or 'PInvoke'. What these mean is that you have to provide an implementation of the function, either as a compiler builtin or provided by your kernel. The library is quite complex and large, however, and you will need to make sure your compiler produces accurate code for every function - unit tests are your friend here.

3) Write your own basic corlib containing only those functions you need.

4) Don't use a corlib library - as long as you don't try and use any of the functions contained in it within your kernel you should be okay. There is nothing stating that because you use C# to write your kernel you have to use the corlib library.


Whilst is is possible to write a kernel in C# (as many of the projects listed above have demonstrated), it is not something that (currently) can be done quickly. You will need a combination of kernel, compiler, runtime and standard library to get it running properly. It may, however, be possible to re-use the compiler and standard library used in many of the projects listed above once they have reached sufficient maturity, so that 'all' you need to do is write the kernel and some memory management functions. Please note, however, that most of the ahead-of-time compilers used in these projects are closely tied to the operating system they are being used to develop. If you choose to write your own compiler, be warned: to paraphrase Languages - writing a compiler is as hard if not harder than writing a kernel.

Part of reason it is not so simple is that all current C# compilers output MSIL or .NET bytecode, which involves an extra step to convert it to machine code. However, it would be theoretically possible that one day somebody produces a C# frontend for GCC (so you can compile GCC straight to raw machine code) and this would be a completely different story.

See Also


External Links

  • C# on Wikipedia