Porting mono was not an easy task at all.
First of all I really would like to thank the people hanging around on #mono on irc.gimp.org. It was a pleasure talking to them, they were really friendly and are definitely very experienced and competent. A big thanks to you!
OK, now to the actual porting part:
As usual when I'm trying to port big software packages to SkyOS I just try a ./configure; make.
Every line of code which generates a compile or link error gets replaced with a "#if 0" or with a
printd("FUNCTION NOT IMPLEMENTED %s@%d", __FUNCTION__, __LINE__);
Right after I changed all the problematic files and functions I realized that a lot was missing or not correctly implemented in order to make Mono work. Here is a small summary of possible problems I discovered because of this replacement run:
- SkyOS didn’t have mmap support. In particular: mmap, munmap, msync, mprotect
- Mono/Garbage Collect makes excessive use of signals (may make problems)
- Mono/Garbage Collect makes excessive use of libpthread. Only a very rudimentary pthread implementation is available
- No IPC
- No pthread semaphore library
- Other missing functions like sched_yield, getdtablesize, ....
- A few not POSIXly correct header files
Ok, after I had this list I started implementing the "easy to fix" missing kernel/libsky functions. getdtablesize, sched_yield and other functions have been implemented in a matter of minutes.
Now the mmap. Mmap support was a big task. Once the implementation was done, almost all user memory region mapping code was rewritten. Memory region reference counting was implemented, demand paging was updated, etc..
The good point of this rewrite is that finally all memory leaks are gone. Ok, so finally, mmap, munmap, msync and mprotect has been implemented.
After writing a few new test cases for the SkyOS test suite, all obvious bugs in these functions were fixed.
Because of the now working mmap I decided to implement a new malloc library using mmap for large heap objects and implement a fine grained malloc user space locking.
To test the signals I decided to just compile the Boehm Garbage Collector because it makes excessive use of signals. (stopping all threads via signals, nested signal handling, …)
Unfortunately I was not able to compile libgc at all because of missing pthread functions.
I just decided to recode libpthread from scratch and now it implements all required functions.
Again, a few test cases and libpthread worked perfectly.
Now back to the signals. Right after playing with the libgc header files and compiling it I tried to start a test application. First result: deadlock.
After debugging this problem a while I realized that signal handling was not really atomic were it should have been. Once this was fixed, there
were more crashes. Nested signal handling was not supported. Fixed. A few more minor “not POSIX compliant” signal problems later libgc didn’t deadlock or crash anymore.
Next was porting the mono io-layer . Ok, because of the working libgc and the io-layer the make process continued and finally built mono.app. But as soon as the make process executed mono.app to start mcs.exe.. deadlock.
The SkyOS semaphore/critical section implementation was not really libpthread mutex compatible. Instead of fiddling around with pthread I decided to modify the kernel semaphore implementation to support pthread specific features. Additional 16 libpthread/mutex testcases have been implemented.
Ok, no deadlock anymore, but a crash in one of mono’s type/hash functions. After debugging this for a while and disabling the mono included Boehm Garbage Collector the crash was gone. (Looks like the mono developers made changes to the Boehm Garbage Collection. The libgc downloaded from the official Boehm Garbage Collector worked without any problems after adding the SkyOS modifications. But the one that comes with mono doesn’t really work on SkyOS yet).
Next problem was mcs.exe itself. Mono tries to dlopen this file. Using PE as native SkyOS binary format the kernel immediately tried to dynamic link mcs.exe against .NET dlls like mscoree.dll. Furthermore, the SkyOS dynamic library loader code jumped into the mcs.exe main function. By adding new flags to not runtime link and to not call the initialisation code this was fixed.
Now, mono actually started mcs.exe!!
Unfortunately mcs.exe complained about not being able to open C# source files. Mono debug trace revealed that the file handle returned from open in CreateFile was too high. Fixed getdtablesize.
Now the file handle was valid but mcs stopped with a “No-random-device Exception”. Quickly added a suitable random device driver which can be used by mono correctly. Run ./configure and make again.
Ok, random device working but mono complains about not being able to write data into the output file. Regarding to the debug output, the write syscall returned –EFAULT. After reading through POSIX specification again it looks like that a write call with buffer equal to NULL and size equal to zero must succeed always. SkyOS returned –EFAULT in such a case. Ok, fixed the write syscall.
Ok, another ./configure and make run.
No way! mcs.exe is finally compiling C# files!
Right after compiling a few hundred C# files the build process stopped with an invalid parse error in a .IL file (Microsoft.VisualBasic.il). Looking into this file revealed following suspicious line(s):
IL_0099: ldc.r8 e-06
IL_02fe: ldc.r8 e-06
This IL file which gets parsed by a perl script was produced by the mono disassembler. The code responsible for producing this instruction is located inside the mono disassembler itself.
After modifying the SkyOS libc vfprintf function to correctly execute vfprintf(f, “%.20g”, aDoubleValue) suddenly the mono disassembler produced correct output.
Mono continued compiling the .NET 1.1 and .NET 2.0 assemblies. From time to time a few minor makefile or source changes had to be made. Hours later the entire Mono .NET assemblies have been built.
Time for a “make install”.
Make install stopped in the middle of processing the mcs/utils directory while starting mono itself. After changing the DLLMapping (a file which maps Microsoft/.NET dlls to native SkyOS DLL’s for PINVOKE), “make install” finally succeeded.
And now the interesting part, compiling a sample C# source file right inside SkyOS. Result => Success. The ‘Hello World’ application run successfully.
Another test: Create a Microsoft Visual Studio 2003 Console Based Hello World C# application. Compiled it, transferred it to SkyOS using Samba, run... => Success. An application compiled in Windows ran successfully on SkyOS.
To prove the correct working of Mono I compiled a few more applications: XML parsing, Network communication,…
Furthermore, the binary PE loader was updated to automatically start a .NET appliction in the context of mono when you double click a .EXE file.
In order to get the Boehm Garbage Collector and Mono core parts to work, 26 kernel changes had to be made, 14 libsky changes, a complete new pthread library had to be implemented, new kernel semaphore/mutex implementation, 8 standard C header files had to be changed, vfprintf and a few math functions had to be updated, and 28 new test cases had to be implemented.
Fortunately the SkyOS POSIX compatibility is really high now because of the supported features like mmap and a pthread implementation.
For people interested in porting mono to their favorite operating system:
In order to port Mono/libgc the OS must support/have:
- General very good POSIX compatibility
- Full mmap implementation. The kernel must also notify the process when invalid mapped memory is accessed so that Mono can change the page table access bits manually.
- Pthread library (mutexes, shared resources, semaphores, etc…)
- IPC (semaphores, shared memory)
- Full working signal handlers
- Hookable functions like thread creation, forking, process/thread termination
Ok, now to Managed.Windows.Forms
First, Cairo and libgdiplus had to be ported. I removed all X11 dependencies from libgdiplus.
Next was implementing a Window System Driver for Windows.Forms.
Related: Changelog listing all made changes required to make mono work
Next was getting Managed.Windows.Forms ported to SkyOS. First, cairo had to be ported. A new SkyGI Cairo backend has been implemented. libgdiplus was next. There have been a few X11 references which had to be removed.
Overall, porting libgdiplus and cairo was not a difficult task.
Next, was writing a new Window Driver skeleton where all methods are replaced with "throw new NotImplementedException ();".
Around 25 managed to native code methods had to be implemented.
Other managed methods were implemented step by step.
After approximately 200 "comile and test" try and error cycles, I got most parts working.
|CreateWindow||Support for toplevel, child and popup windows. Interpreted flags: WS_CHILD, WS_POPUP, WS_CAPTION|
|Mouse support||Press/Release/Doubleclick left, right and middle button|
|Keyboard||Not supported yet|
|Other||All other functions are either not implemented or not working yet|
So, whats so nice about this? It is implemented using System.Windows.Forms, isn't this cool!
And it is fully .NET compatible. For instance, you can just copy the executable to a windows/linux machine and start this application there.