2013-12-12

2013-12-12 Benchmarking Doom

I have put some benchmarking code into the Doom engine to count the number of ticks (each tick being 1/100th of a second currently) that it takes to render 20 frames and then writes it to the stdout.  It provides me with a way of getting a good FPS reading without needing to worry about floating point arithmetic.  Even though the screen is running in mode 0x12, I have wired up the kernel stdout to Bochs' "Port 0xE9 Hack" so that any text written by the kernel also appears in Bochs' separate console window.  This provides console output that works alongside the graphical output of Doom so I can get the benchmarks out without affecting the render code.

At Rich's suggestion, I also tested running Doom without the Parrot Dithering in the VGA driver, the results are:

Average with dithering is 158 ticks per frame = 1.58 seconds per frame = 0.6 FPS
Average without dithering is 115 ticks per frame = 0.87 FPS.

In order to try to work out where I can save time, I have tested a function which just pushes data into the VGA memory as fast as it can so I can see how fast this part can be (bearing in mind that it's a planar Mode 0x12 screen).  Using 32-bit writes (which may not work on all hardware) seems to allow me to paint all the planes in under one tick, so that's reasonable.

Moving on from this, I've created a planar back buffer in memory to store the data ready to be pushed to the VGA memory.  The plot and drawing routines now write to this buffer (which is faster because it can access all the planes without having to change any registers), and I've added a function to update the front buffer (the VGA hardware) from the back buffer.

All of these changes, and Doom now runs at a blistering ... 0.95 FPS ... Oh :(

It may be that the VGA card is not the slowdown here, I'll have to do some proper benchmarking ... maybe after Christmas.

2013-12-03

2013-12-03 DoomVideo

A quick screen capture video to show just how bad the frame rate is (still awesome to watch though).

The intro screen lasts for about 40 seconds, so you may want to skip past it.


I'm searching through my copy of Programmer's Guide to the EGA, VGA, and Super VGA Cards by Richard F. Ferraro (very good book) to find a better way to get the data into the VGA memory.

I tested writing all of each plane at a time using 32-bit writes, and that filled the screen in under 1/100th of a second, so fast enough for double-buffering, which is what I'm investigating.

2013-12-01

The Biggest Letters in the Whole Wide World

So, I was watching TV with my daughter the other day and a character in one of the kids programs said they were trying to make "the biggest '9' in the whole wide world".  This got me to thinking, where is the biggest '9' in the world?  For that matter, what about all the other letters?

I'd say the only ground rules are that the letter has to be a physical structure, and that the structure was intended to represent that letter (so an American Football goalpost doesn't count as a 'Y').  For the moment, I'll restrict this to the Latin alphabet

I guess most of them are going to be found on buildings or perhaps ships.  The "HOLLYWOOD" sign might scoop six of the letters, but where are the others?  Some of them may actually be quite small.  Suggestions in the comments ...


  • A
    • "READYMIX[5] 240 metres tall.
    • "BISMARK[10] 87 metres tall (estimated).
    • "CASAS BAHIA 1[7] 71 metres tall (estimated).
    • "Coca Cola[3] 15.5 metres tall (estimated).
    • "PADDY POWER[1] 15.2 metres tall.
  • B
    • "BISMARK" [10] 87 metres tall (estimated).
    • "CASAS BAHIA 1" [7] 71 metres tall (estimated).
  • C
    • "LUECKE[4] 950 metres tall (estimated).
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "Coca Cola" [3] 32 metres tall (estimated)
  • D
    • "READYMIX[5] 240 metres tall.
    • "GOD'S WISDOM[9] 28 metres tall (estimated).
    • "PADDY POWER[1] 15.2 metres tall.
    • "HOLLYWOOD[2] 14 metres tall.
  • E
    • "LUECKE[4] 950 metres tall (estimated).
    • "READYMIX[5] 240 metres tall.
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "PADDY POWER[1] 15.2 metres tall.
  • F
    • "FOWL[11] 55 metres tall (estimated).
  • G
    • "GOD'S WISDOM[9] 28 metres tall (estimated).
  • H
    • "CASAS BAHIA 1[7] 71 metres tall (estimated).
    • "HOLLYWOOD[2] 14 metres tall.
    • Pink suggested a Helipad somewhere?
  • I
    • "READYMIX[5] 240 metres tall.
    • "BISMARK[10] 87 metres tall (estimated).
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "CASAS BAHIA 1[7] 71 metres tall (estimated).
  • J
    • "JOSTWINE.COM" [6] 75 metres (estimated).
  • K
    • "LUECKE[4] 950 metres tall (estimated).
    • "BISMARK[10] 87 metres tall (estimated).
    • "FOWL[11] 55 metres tall (estimated).
  • L
    • "LUECKE[4] 950 metres tall (estimated).
    • "Coca Cola[3] 32 metres tall (estimated).
    • "HOLLYWOOD[2] - 14 metres tall.
  • M
    • "READYMIX[5] 240 metres tall.
    • "BISMARK[10] 87 metres tall (estimated).
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "Platte Mound M" - 73 metres tall.
  • N
    • "JOSTWINE.COM[6] 75 metres (estimated).
  • O
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "FOWL[11] 55 metres tall (estimated).
    • "GOD'S WISDOM[9] 28 metres tall (estimated).
    • "Coca Cola[3] 15.5 metres tall (estimated).
    • "PADDY POWER[1] 15.2 metres tall.
    • "HOLLYWOOD[2] 14 metres tall.
  • P
    • "PADDY POWER" [1] 15.2 metres tall.
    • "PLAZA MAYOR[8] 14 metres tall (estimated).
    • "P&O" Cruise Ships 8 metres tall (estimated)
  • Q
  • R
    • "READYMIX[5] 240 metres tall.
    • "BISMARK[10] 87 metres tall (estimated).
    • "PADDY POWER[1] 15.2 metres tall.
    • "PLAZA MAYOR[8] 14 metres tall (estimated).
  • S
    • "BISMARK[10] 87 metres tall (estimated).
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "GOD'S WISDOM[9] 28 metres tall (estimated).
  • T
    • "JOSTWINE.COM[6] 75 metres (estimated).
  • U
    • "LUECKE[4] 950 metres tall (estimated).
    • "Block U" 30 metres tall.
  • V
  • W
    • "JOSTWINE.COM[6] 75 metres (estimated).
    • "FOWL[11] 55 metres tall (estimated).
    • "GOD'S WISDOM[9] 28 metres tall (estimated).
    • "PADDY POWER[1] 15.2 metres tall.
    • "HOLLYWOOD[2] 14 metres tall.
  • X
    • "READYMIX" [5] 240 metres tall.
  • Y
    • "READYMIX[5] 240 metres tall.
    • "PADDY POWER[1] 15.2 metres tall.
    • "HOLLYWOOD[2] 14 metres tall.
    • "PLAZA MAYOR[8] 14 metres tall (estimated).
  • Z
    • "PLAZA MAYOR[8] 14 metres tall (estimated).
  • 0
  • 1
    • "CASAS BAHIA 1[7] 71 metres tall (estimated).
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9



  • [1]  "PADDY POWER" http://www.telegraph.co.uk/advertising/news/7465528/Worlds-biggest-advertising-sign-built-overlooking-Cheltenham-Racecourse.html

    [2]  "HOLLYWOOD" https://maps.google.com/maps?ll=34.134029,-118.321611&spn=0.001039,0.002064&sll=34.092809,-118.328661&sspn=0.06653,0.132093&t=h&z=20

    [3]  "Coca Cola" https://maps.google.co.uk/maps?ll=-18.529371,-70.249503&spn=0.00238,0.004128&sll=-18.481898,-70.250197&sspn=0.019049,0.033023&t=h&z=19

    [4]  "LUECKE" https://maps.google.co.uk/maps?ll=30.081684,-97.140995&spn=0.00869,0.016512&sll=52.8382,-2.327815&sspn=12.439947,33.815918&t=h&z=20

    [5]  "READYMIX" https://maps.google.co.uk/maps?ll=-32.21714,125.361149&spn=0.016992,0.033023&sll=52.8382,-2.327815&sspn=12.439947,33.815918&t=h&z=16

    [6]  "JOSTWINE.COM" https://maps.google.co.uk/maps?ll=45.800234,-63.381817&spn=0.003501,0.008256&sll=52.8382,-2.327815&sspn=12.439947,33.815918&t=h&z=18

    [7]  "CASAS BAHIA 1" https://maps.google.com/maps?ll=-23.23482,-46.862687&spn=0.004614,0.008256&t=h&z=18

    [8]  "PLAZA MAYOR" https://maps.google.com/maps?ll=36.655336,-4.47956&spn=0.001007,0.002064&sll=36.655314,-4.479591&sspn=0.001007,0.002064&t=h&z=20

    [9]  "GOD'S WISDOM" https://maps.google.com/maps?ll=42.46578,-72.08783&spn=0.001852,0.004128&sll=42.465956,-72.089957&sspn=0.014816,0.033023&t=h&z=19

    [10]  "BISMARK" https://maps.google.com/maps?ll=46.815614,-100.722774&spn=0.001718,0.004128&sll=46.81627,-100.720757&sspn=0.003436,0.008256&t=h&z=19

    [11]  "FOWL" https://maps.google.com/maps?ll=44.829679,-87.905474&spn=0.00178,0.004128&t=h&z=19



    2013-11-30

    2013-11-30 Doom Demo

    Not too much time to investigate things today, but I thought I'd briefly look into the problem with the demo not playing in Doom.

    Turns out the demo was for version 1.09 and the game is version 1.10.  Also turns out if you callously comment out the version check and pretend it's all ok, it works  :)


    The frame rate is currently about 0.9 frames per second, but almost all of that time is my video routine drawing the 24-bit bitmap to the screen, plenty of room for improvement.  I'll probably make a video benchmarking function to maybe count the time it takes to render 50 frames so that I can clearly test each modification I make.  Maybe over Christmas ...

    2013-11-30 Doom Progress Continued

    So last night I cleared a few more bugs in my kernel and libraries, and got Doom to the point where it stopped outputting text to the console because it gone into its main loop ...

    Loading Operating System ...
    Kernel Initialised
    Loading ELF library
    
                                DOOM Registered Startup v1.10
    V_Init: allocate screens.
    M_LoadDefaults: Load system defaults.
    dos_openFile: Opening "devdatadefault.cfg".
    Could not find the device.
    Z_Init: Init zone memory allocation daemon.
    W_Init: Init WADfiles.
    dos_openFile: Opening "DH1:Doom/base/doom.wad".
     adding DH1:Doom/base/doom.wad
    ===========================================================================
                     Commercial product - do not distribute!
             Please report software piracy to the SPA: 1-800-388-PIR8
    ===========================================================================
    M_Init: Init miscellaneous info.
    R_Init: Init DOOM refresh daemon - [.....                     ]
    InitTextures
    InitFlats............
    InitSprites
    InitColormaps
    R_InitData
    R_InitPointToAngle
    R_InitTables
    R_InitPlanes
    R_InitLightTables
    R_InitSkyMap
    R_InitTranslationsTables
    P_Init: Init Playloop state.
    I_Init: Setting up machine state.
    D_CheckNetGame: Checking network game status.
    startskill 2  deathmatch: 0  startmap: 1  startepisode: 1
    player 1 of 1 (1 nodes)
    S_Init: Setting up sound.
    S_Init: default sfx volume 8
    HU_Init: Setting up heads up display.
    ST_Init: Init status bar.
    

    My original plan was then to design and write the device management subsystem, migrate the vga driver code, implement the graphics library, then get Doom to open the graphics library in order to output to the screen.

    Unfortunately (fortunately?), my impatience got the better of me and I thought I might just try a quick 'n' dirty system call to get the bitmap that Doom had generated onto the screen.  I pulled much of the code from the VGA driver that I used in the 16 colour image article, and called the initialise() function to switch from the mode 3h VGA text screen to the mode 12h VGA graphics screen, with 640x480 pixels by 16 colours.

    Doom's bitmap uses 8 bits per pixel, each pixel's byte is used as an index into a colour mapping array to get a new byte, which is then used as an index into a palette array to get three bytes for red, green, and blue.  People familiar with the VGA hardware will recognise these two levels of translation as features that VGA provides natively.  I guess that's why the original Doom was written that way.  In any case, I figured I could get something recognisable if I just took the bitmap array and drew it directly, so the initial bitmap gave the value 0-255 for the blue component of each pixel.

    I wrote a system call which accepted the bitmap pointer, width and height, and used a quick function to call the plot() function in the VGA driver, which gave me a quarter screen blue image, but one which bore a passing resemblance to the Doom intro screen.

    This image only filled quarter of the screen, so a few tweaks had it full-screen.  Doom was designed to run a 320x200 mode 13h screen with 256 colours, so it needed enlarging.


    After spending some time investigating the format of the Doom bitmap and finding out about the palette, I modified the Doom render routine.  I wanted to give the 24-bit bitmap to the system call, rather than worrying about passing it the colour map and palette.  I allocated a second screen buffer that was 3 bytes per pixel for the doom screen and added a loop to copy each pixel, translating it for the colour map and palette, and wrote the 24-bit colour to the second buffer.

    I used the same bitmap drawing routine that was used for the parrot, and draw the image without dithering.  This was the first of the images that I posted yesterday.  Turned on the dithering and posted the second image.  My brain told me the image was a little "off", but I wasn't sure how.  I looked for a comparison image on Google and then spotted that red and blue were reversed.  Behold!


    The vertical cyan lines are left-overs because I didn't clear the screen buffer after switching to Mode 12h, and the bitmap drawing routine supports transparency, which is why they show through the black areas.


    Bugs fixed


    These are the bugs I fixed in order to get Doom to its main loop.  We don't need to go into details ...

    • strncasecmp() was checking one more character than it should have been, so it couldn't find "SW1BRCOM".
    • I had stripped all of the netcode from Doom because I don't have my network stack in this kernel yet, but apparently some of it's required, so I had to put some of it back in.
    • I hadn't implemented the precision parameter of sprintf() so it didn't construct the font texture names correctly.  It was looking for "STCFN33" rather than "STCFN033".
    • sprintf() also wasn't writing a null-terminator, which apparently it needs (who knew?), so it was looking for "STTNUM10" rather than "STTNUM1".

    Next Steps

    When I next get time on this, I plan to address the following:

    • "Demo is for a different version of the game".  This well-known Doom error is causing the game to exit shortly after it starts.  If I can fix this, Doom should go into playing its opening demo.
    • Keyboard input.  I haven't looked into how Doom accesses the keyboard, but it will need at least key-up and key-down events which is more than the C library provides.  I'll need to think about this some more, but I'll probably create some form of message queue and port which Doom can open and listen for events.  This will be akin to how it will work under a window manager anyway.
    • Implement access().  Doom uses the access() function to determine which wad files it has available.  If I finish the implementation of this, I could upload an archive of the emulator and the game (without the wad file, maybe with a shareware wad), for people to toy with.
    • Write the device manager.  Not specifically for Doom, but the full implementation of the vga driver needs the device manager.
    • Write the proper vga driver (not the hacky one that's there now)
    • Write a sound library.  This'll probably need the device manager, and is the one big thing I don't have a pre-existing kernel for testing.
    • Make it faster!  It's not fast, we need to go faster, more speed!

    2013-11-29

    2013-11-29 Doom Progress


    ... and with a bit of parrot-based dithering ...


    Oops, of course red and blue were backwards.



    2013-11-24

    2013-11-24 Doom Progress

    Continuing my work on porting Doom ... actually, that isn't really fair.  I'm not making Doom work with my OS, I'm writing my OS to work for Doom, semantics.

    Continuing my work on my OS, Doom is locating and opening the wad file now.  It uses the effectively finished path of Program -> C Library -> Kernel API -> DOS Layer -> File-system -> Block Device to locate the file and open it.  Still happy with that set-up.

    Doom is, however, complaining that the wad file doesn't have the correct signature.  That may have something to do with not having connected the read() function into the DOS layer ... there, fixed.

    The kernel is now exploding (technical term), screen gibberish and inconsistent results.  After an hour or so of debugging, I've tracked down the two causes:

    1. Doom was allocating 36k of memory on the stack ... the stack is only 16k in size.  The rest of the space was overwriting the kernel ... d'oh!  (Makes stack bigger ... shhhh!)
    2. It appears as if all the remaining memory in the memory allocator is being used suddenly, causing an out-of-memory situation.
    Another hour of debugging later and I've found the problem.  Every allocation of memory has a header and a footer.  They both include information such as the length of the block, a magic number (to help detect overflows), and a flag which indicates if the block is currently used or free.


    When freeing a block, the memory allocator looks to see if the block being freed and the preceding block can be combined to make a bigger free block, then does the same for the following block.  It examines the footer of the preceding block (which is immediately before the header of the block being freed, obviously) to see if that block is free.  If so, it uses the length stored in the tail of the preceding block to find the header of the preceding block, then updates everything to combine them.  The bug was that in one circumstance, the tail pointer free flag was not being set which created a block where one flag said it was free and the other said it wasn't.  :(

    The allocator looked at the previous tail flag and it said the block was free, so it dutifully added the length of the block being freed onto the previous block.  Seeing that the following block was also free (the following block being all the remaining free memory on the system), it added that in as well to make one big contiguous free block ... except that the previous block wasn't free.  The tail flag said it was free, the head flag said it was in use.  In a flash, freeing a block of memory had consumed every available byte of memory  :(

    Easy to fix, I'm glad that it happened now, and I'm glad that my memory allocator has one fewer bug in it.

    Doom approves of my fix:

    Loading Operating System ...
    Kernel Initialised
    Loading ELF library
    
                                DOOM Registered Startup v1.10
    
    V_Init: allocate screens.
    M_LoadDefaults: Load system defaults.
    dos_openFile: Opening "devdatadefault.cfg".
    Could not find the device.
    Z_Init: Init zone memory allocation daemon.
    W_Init: Init WADfiles.
    dos_openFile: Opening "DH1:Doom/base/doom.wad".
     adding DH1:Doom/base/doom.wad
    ===========================================================================
                     Commercial product - do not distribute!
             Please report software piracy to the SPA: 1-800-388-PIR8
    ===========================================================================
    M_Init: Init miscellaneous info.
    R_Init: Init DOOM refresh daemon - Error: W_GetNumForName: PNAMES not found!
    Exited!
    

    Next time:  Investigating what PNAMES is and why it wasn't found.

    2013-11-23

    2013-11-23 Constructors and Library Initialisation

    The last few weeks have been spent researching initialisation functions, constructors, and how to write and call them.

    Now that libkernel and libc are separate entities, they both have some initialisation code that needs to be run.  At the very least, these two are going to have to resolve the kernel system call functions so that they can make syscalls.  Libc can cheat at this because it provides the start() function that is called first when the program is loaded, so it can take this opportunity to set itself up (this is fairly common amongst C libraries).  Libkernel has a slightly tougher job.  I'd rather not have the program need to explicitly initialise it, and libc shouldn't assume that libkernel should be there, so how do we do it?

    Well, GCC already has a way of doing this which it uses for C++ static constructors and for a few rare cases in C, but the details of it are spread around so let's try to make sense of it for our specific situation.

    The first port of call is to RTFM, which in this case is http://gcc.gnu.org/onlinedocs/gccint/Initialization.html .  Unfortunately, this article is very heavy going, talks in very abstract terms and isn't very easy to follow.  Also, searching around for some of the terms used, especially the #defines in this article doesn't help either.

    The few sources I was able to find with information on how this works generally suggest that the ELF file contains a section called .ctors which contains a list of addresses to constructor functions which are to be called at the beginning of the program (and a section called .dtors for deconstructors for the end of the program).  It makes sense that this section should be created by the compiler itself (as the linker won't know which functions need to be in this) so the linker must just combine together the pointers from all the different object files.

    Step 1 : Compiling


    Let's have a look at this first hand.  We know that C++ does for some cases, so let's try this:

    main()
    {
     asm("int $1");
    }
    
    volatile int RandomRoll()
    {
     asm("int $2");
     return 4;
    }
    
    int mTest = RandomRoll();
    

    Which can be compiled into an object file with:
    g++ hello.cpp -nostdlib
    (ignoring the warning about the undefined entry symbol.)

    This file needs some initialisation in order to work properly, specifically that the program must call RandomRoll() to set the value of mTest before main() is called, so we expect to find some clues in here.  The assembly interrupts aren't expected to be run, they are there to help us identify the code when we start disassembling it.

    Having a look at the object file with objdump allows us to see what's going on.

    > objdump a.out -h
    
    a.out:     file format elf32-i386
    
    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         00000052  08048074  08048074  00000074  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      1 .eh_frame     00000098  080480c8  080480c8  000000c8  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      2 .ctors        00000004  08049160  08049160  00000160  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      3 .bss          00000004  08049164  08049164  00000164  2**2
                      ALLOC
      4 .comment      00000011  00000000  00000000  00000164  2**0
                      CONTENTS, READONLY
    
    
    

    Ok, that's good, we have a section called .ctors with a size of four bytes which contains data.  This is exactly the right amount to hold an address on a 32-bit platform.

    Back to objdump to make certain we understand this (I've skipped the data from some of the sections as they weren't relevant):

    > objdump a.out -sS
    
    a.out:     file format elf32-i386
    
    Contents of section .ctors:
     8049160 aa800408                             ....
    
    Disassembly of section .text:
    
    08048074 <main>:
     8048074:       55                      push   %ebp
     8048075:       89 e5                   mov    %esp,%ebp
     8048077:       cd 01                   int    $0x1
     8048079:       b8 00 00 00 00          mov    $0x0,%eax
     804807e:       5d                      pop    %ebp
     804807f:       c3                      ret
    
    08048080 <_z10randomrollv>:
     8048080:       55                      push   %ebp
     8048081:       89 e5                   mov    %esp,%ebp
     8048083:       cd 02                   int    $0x2
     8048085:       b8 04 00 00 00          mov    $0x4,%eax
     804808a:       5d                      pop    %ebp
     804808b:       c3                      ret
    
    0804808c <_z41__static_initialization_and_destruction_0ii>:
     804808c:       55                      push   %ebp
     804808d:       89 e5                   mov    %esp,%ebp
     804808f:       83 7d 08 01             cmpl   $0x1,0x8(%ebp)
     8048093:       75 13                   jne    80480a8 <_z41__static_initialization_and_destruction_0ii data-blogger-escaped-x1c="">
     8048095:       81 7d 0c ff ff 00 00    cmpl   $0xffff,0xc(%ebp)
     804809c:       75 0a                   jne    80480a8 <_z41__static_initialization_and_destruction_0ii data-blogger-escaped-x1c="">
     804809e:       e8 dd ff ff ff          call   8048080 <_z10randomrollv>
     80480a3:       a3 64 91 04 08          mov    %eax,0x8049164
     80480a8:       5d                      pop    %ebp
     80480a9:       c3                      ret
    
    080480aa <_global__sub_i_main>:
     80480aa:       55                      push   %ebp
     80480ab:       89 e5                   mov    %esp,%ebp
     80480ad:       83 ec 08                sub    $0x8,%esp
     80480b0:       c7 44 24 04 ff ff 00    movl   $0xffff,0x4(%esp)
     80480b7:       00
     80480b8:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
     80480bf:       e8 c8 ff ff ff          call   804808c <_z41__static_initialization_and_destruction_0ii>
     80480c4:       c9                      leave
     80480c5:       c3                      ret
    

    There's a lot going on here and I'm not sure I wrote all of it.  Let's have a look.

    We can see our main() and RandomRoll() functions (not sure why the name is munged, not important right now) but we also have two new functions, _global__sub_i_main() and _z41__static_initialization_and_destruction_0ii().  _global__sub_i_main() seems to call _z41__static_init_blah_blah(), which in turn seems to call our RandomRoll() function so without getting into it too deeply, I'd be happy to assume that _global__sub_i_main() is our initialisation function.

    At the top of the disassembly is the contents of the .ctors section which contains that one 32-bit value, which is (interpreting the little-endian encoding) 0x080480aa, which is exactly the memory address of the _global__sub_i_main().
    \ o /

    At this point, my Google searching failed me.  For the life of me I can't figure out how to coerce the C compiler to create a .ctors section and populate it in the way that C++ does (answers on a postcard), however, I have a plan.

    At the end of the day, the value that C++ put into the .ctors section is just your average common-or-garden initialised pointer variable, but in a special place, so what about this:

    void test_init()
    {
    asm("int $3");
    }

    void* __attribute__ ((section (".ctors"))) test_init_ref = test_init;

    I have my function, and I have a global variable which points to my function.  I've used some GCC specific runes to say that I want my variable to live in a section called .ctors.  Let's build this and see if it gives us the desired result (some sections removed for brevity):

    >gcc hello.c -nostdlib
    >objdump a.out -sS
    
    a.out:     file format elf32-i386
    
    Contents of section .ctors:
     80490b4 74800408                             t...
    
    Disassembly of section .text:
    
    08048074 :
     8048074:       55                      push   %ebp
     8048075:       89 e5                   mov    %esp,%ebp
     8048077:       cc                      int3
     8048078:       5d                      pop    %ebp
     8048079:       c3                      ret
    

    Success!  We can see our function, and we can see the entry in .ctors which points to it, just as we saw in C++.  It's not the prettiest of solutions, but it will work for now.

    Step 2 : Running


    While we've gotten that all working, that's only half of the battle,  we still need to actually call all these from our program.

    For the most part, all our program needs to know is where the .ctors array is and how long it is.  It can then iterate all the constructors and run them.  (These are function pointers after all, so these will always be relocated as required by the loader.  If the relocation didn't update function pointers, the program has more to worry about than constructors.)

    One of the other searches I was running while doing this was to investigate how other systems dealt with this problem.  While that search yielded nothing of note, it did give me the idea to check out the default GCC  link script.  Currently my kernel uses a custom link script, but the applications still use the default one, where I found something interesting:

      .ctors          :
      {
        /* gcc uses crtbegin.o to find the start of
           the constructors, so we make sure it is
           first.  Because this is a wildcard, it
           doesn't matter if the user does not
           actually link against crtbegin.o; the
           linker won't look for a file to match a
           wildcard.  The wildcard also means that it
           doesn't matter which directory crtbegin.o
           is in.  */
        KEEP (*crtbegin.o(.ctors))
        KEEP (*crtbegin?.o(.ctors))
        /* We don't want to include the .ctor section from
           the crtend.o file until after the sorted ctors.
           The .ctor section from the crtend file contains the
           end of ctors marker and it must be last */
        KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*(.ctors))
      }
    

    So, my link-script-fu is not great, but I can understand enough of this to see that crtbegin.o and crtend.o have special handling when it comes to the .ctors section.  Anything for the .ctors section from crtbegin.o goes at the beginning of the output, and anything from crtend.o goes at the end, effectively book-ending the array.  (I'll admit I didn't know what crtbegin and crtend were for, but GCC complained that I didn't have them when I created libc, so I put in empty ones.)

    This information, coupled with what we just did for shuffling variables into the .ctors section gives me an idea.

    I can put a variable in crtend.c like this:
    int __attribute__((section(".ctors"))) __libc_internal_ctor_end = 0;

    ... and then put this in crtbegin.c:
    extern int __libc_internal_ctor_end;  // <<-- in crtend.o
    int __attribute__((section(".ctors"))) __libc_internal_ctor_start = 0;
    
    void call_ctors()
    {
     // Loop between __libc_internal_ctor_start and __libc_internal_ctor_end
    }
    

    The linker will put these two variables at the beginning and end of the .ctors list, and then the call_ctors() function can use them to work out where all the other constructors are.  Note that the content of those two variables is completely irrelevant, only their locations in memory is useful.

    Step 3 : Testing


    This blog post is way longer than it was going to be, so I'll be brief and say what I usually say.  It failed a bunch of times because I made some stupid mistakes, but after much hammering, it all works.

    crt0.s now performs calls:

    • the C Library initialisation function,
    • the constructors function in crtbegin.c, and
    • the program's main() function.
    The really good article I found the other day ( https://blogs.oracle.com/ksplice/entry/hello_from_a_libc_free ) also helped me to confirm at least some of what this was supposed to do, but only after I know what I was looking for.

    I also found out along the way that when a library (such as libkernel.a) is linked in, the linker only includes those object files which are actually needed.  This gave a minor testing headache because I expected my init() function and its .ctor reference to appear just because the library was linked, but it didn't.  That was easily fixed though.

    2013-11-03

    2013-11-03 Doom Continued, and printf upgrade.

    With Doom reaching the main() function, it's time to start working through it making sure everything is implemented and working.

    Bearing in mine that sound and video are a way off yet, the short term goal is getting Doom to display its opening text, loading what files it wants, then "running", then exiting.

    So the next thing it wants is a WAD file.  Doom searches to see which .wad files are available, but for now I'll just hardcode the path to the .wad file.

    I seem to getting a strange problem with printf().  It's printing text to the screen.  That may not sound that odd, except for the fact that I haven't written it yet.  It appears that GCC is optimising calls to printf() with a constant string to be calls to puts() instead (which is written) so that why some of them were working.  (I thought -O0 turned off optimisations?)

    V_Init: allocate screens.
    M_LoadDefaults: Load system defaults.
    Z_Init: Init zone memory allocation daemon.
    W_Init: Init WADfiles.

    The next thing that Doom needs is a sprintf() function which I don't have.  Originally, my printf() routine was a simple routine written by someone else (one of the few pieces of code my OS has used that I didn't write).  It was designed for an embedded system and didn't support all the features and options of printf().

    Not long ago I sat down and wrote my own version, adding a lot more support for the various tokens, but it would still only output to the screen.  This version of the function is still used in the kernel as kstdio_printf() which gets called with debug messages all over the place.

    So today I've taken that function and extended it once more.  After researching variadic functions and how they are handled in C, I've written implementations for the eight different version of printf():

    • fprintf() - variadic arguments, outputting to a stream
    • printf() - variadic arguments, outputting to stdout (a special case of above).
    • sprintf() - variadic arguments, outputting to a buffer.
    • snprintf() - variadic arguments, outputting to a buffer with a limit.
    • vfprintf() - va_list arguments, outputting to a stream.
    • vprintf() - va_list arguments, outputting to stdout (a special case of above).
    • vsprintf() - va_list arguments, outputting to a buffer.
    • vsnprintf() - va_list arguments, outputting to a buffer with a limit.

    All of these are actually serviced by a single internal printf function which is capable of performing them all which reduces code duplication.

    With all of these in place, I think that's good place to leave it for today.


    2013-11-02

    2013-11-02 Doom!

    I don't know if I mentioned, but a while ago I downloaded the source code to the Linux port of Doom with the intention of getting it working with my OS.

    "Why Doom?" I hear you ask.

    • Doom is a complex C program with proven functionality.  While I'm making Doom work with my OS, it's going to require me to write a lot more of the basic kernel and C library functionality.  Also the code should work so if there's a problem, it's probably in my code.
    • Doom is well known.  When I mention to people that I'm writing an OS, I commonly get asked "Does it run World of Warcraft / Counter-Strike / Half-life 3 etc.?"  I like to think that they're trying to get an idea of the capabilities of my OS in terms that they understand, so by being able to say that it'll run Doom, they'll get exactly that.
    • Doom has modest system requirements.  Given that my test systems have at most maybe 128Mb and maybe a 1GHz processor, Doom should run nicely, even with the inevitable inefficiencies of my code.
    • Doom doesn't require multiple threads.  Doom is intended to run on a DOS like system, so has no inbuilt dependency on the operating system to provide multi-threading support.
    • Doom has modest graphical requirements.  Given that my OS is firmly seated in the world of 640x480x4 (16 colours) for now, having a program which doesn't need many colours or a large screen resolution is a good thing.
    • Doom is awesome.  I mean come on, it's DOOM!  (Remember when first-person shooters were called "Doom clones"?)


    So all that aside, how do we do this?

    Firstly, Doom has two files (i_video.c and i_sound.c) which handle ... well ... video and sound.  Given that both of these are going to need some special consideration and a lot of work to get going, we'll put these off until later, so let's strip out the content of the all the functions.  I've a lot of work before it will call any of these anyway.

    It looks like Linux has two different interfaces for accessing files, the C library fopen() and fread() functions, and the open() and read() functions from fcntl.h and unistd.h.  Doom uses both of these in varying measure.  Rather than pollute my current libc with functions that don't belong there, I should probably create a libkernel to contain everything else.

    ... some time passes ...

    Ok, so with a lot of reorganising of header files, adding a new library to the project, and writing a lot of stub functions (one of which being the scanf-type functions, that's not going to fun to write), the OctoDoom executable builds.

    While I do have ELF loading code (used for the HelloWorld program), I still haven't wired it up to the file loading routines so that I can load an actual executable file (it's on my to-do list).  For now, I'll have to load the Doom executable the same why as I do the HelloWorld one, that is, using Bochs to load the binary into memory at some high address.

    So the first test run of Doom wasn't very successful (when are they?).  I think that the problem might be that I'm loading a program that's 987Kb into a memory that was only 1Mb to start, probably not going to have a good time.  That and the ELF loader call to kmalloc wasn't sanity checking the return value for a valid pointer.  So I had run out of memory, malloc returned NULL and the ELF loader carried on trying to use that pointer, schoolboy error.

    Oh yeah, now it's loading Doom and getting as far as the main() function ... this stuff just got real!

    2013-10-31

    2013-10-31 FAT and ATA drivers

    So I've migrated the ATA driver from the DriveAccess kernel, it's nothing special, but it can read hard drives on an ATA interface (IDE) and also interpret standard partition tables to allow devices to specify the partition and resolve blocks relative to it.

    I've also migrated much of the FAT file-system driver, but with modifications to implement the LookupFile() function which will (in future) allow the file locking system to work.

    I did work out that with 32 (I think) FAT directory entries in a 512-byte block, using a uint32 to uniquely identify each directory on the FAT volume does actually limit me to partitions of 128Gb in size.  This is something I will have to address at some point.

    I've created mountlists for the two hard drives that the emulated system has (one hardfile for Grub and a "hardfolder" for the kernel and other files) and I'm using these to mount the hard drives:

    dos_mount
    (
     "DH0:",
     &dvrata_driver,
     "PARTITION=0",
     &fat_driver,
     ""
    );
    
    dos_mount
    (
     "DH1:",
     &dvrata_driver,
     "UNIT=1,PARTITION=0",
     &fat_driver,
     ""
    );
    
    

    I've fixed up a bug where the path splitting function was splitting on a semi-colon rather than a colon, and written file-system functions to open a file from a FileID (basically a pointer to the Directory Entry), to seek to a location within a file (with each seek reading and caching the current block), and to read a section of a file (using the seek function to actually perform the read).

    The FAT Filesystem context contains the following data:

    uint32 DirSectorNumber;
    uint32 DirEntryNumber;
    tFAT_DirectoryEntry DirectoryEntry;
    uint32 FilePointer;
    uint32 FirstCluster;
    uint32 FileLength;
    uint32 CurrentCluster;
    uint32 CachedBlockNum;
    uint8* CachedBlock;
    

    and finally a small snippet of code which loads and prints a test text file to the output:

    int32 lTestFile = dos_openFile( "DH1:test.txt", 0 );
    uint32 lReadSize = 256;
    uint8* lBuffer = kmalloc(lReadSize);
    uint32 lDataRead;
    while( ( lDataRead = dos_read( lTestFile, lBuffer, lReadSize ) ) == lReadSize )
    {
     kernel_write( 1, lBuffer, lDataRead );
    }
    kfree( lBuffer );

    All in all, it works.  It's mounting the device, reading the FAT parameters from the disk via the block device, looking up a file by path and opening it, and reading the content of the file.  It's a really big step forward to have the Octopus kernel able to load files, and with Bochs capable of taking a folder from the host OS and presenting it to the emulated OS as a FAT filesystem, it'll make some bits of testing a LOT easier.


    2013-10-28

    2013-10-28 - Keyboard Handler Complete

    The kernel now has a keyboard driver, the driver is being loaded into the list of Dos Devices as KBD:, KBD: is being opened as file number 0 (which corresponds with the standard input device).  The C library file functions are wired up to the kernel functions (using the function lookup mechanism) and the CLI uses the C library functions for its input and output.

    At some point, the CLI could easily be extracted and made into a program with minimal changes.

    Now, just to carefully enable ... the ... IRQ ... handler ... and ... BANG!  :(  General Protection Fault.

    Ok, there may have been a few last issues with the interrupt handler, but with those fixed, the console is receiving key-presses  from the keyboard via the Dos layer, file descriptor table, kernel API, and the C library.

    One thing I still need to address is when in the process the system converts scan codes into ASCII codes.  In fact, the C library only deals with characters in the stream rather actual key-presses with key-down and key-up which some programs will need, so I guess I'll need to add some API for that.  We'll leave that for another day though.

    2013-10-22

    2013-10-22 Keyboard Handler Progress

    Still working through the keyboard handler and updating it for Octopus.  I've settled on the basic functions that a character device needs to expose so the keyboard handler now looks like a character device.

    I've also made use of the stub functions for process_lock() and process_unlock() to allow the keyboard read() function to block until some keyboard data is available.  Currently process_lock() just does a spinlock as I haven't migrated the multi-tasking code yet, but as the keyboard is an interrupt-based device it doesn't hang the kernel forever.

    The DOS layer has its list of DOSDevices into which the keyboard is being registered, and the interrupt system has a list of interrupt handlers into which a handler for IRQ 1 is being added.

    A logical consequence of the way that the object-orientated device drivers are now working, I've had to enhance the interrupt handler so that a pointer to some kind of context object (the object instance, the state, whatever you want to call it) gets passed in when the interrupt is registered, and then gets passed back to the interrupt handler when the interrupt occurs.  In the case of the keyboard handler, this is a "keyboard context" object that contains, amongst other things, the keyboard input buffer itself.

    I can see how this interrupt context will be really useful for all the other interrupt handlers, and I'm kind of annoyed that I didn't think of it when writing the original DriveAccess kernel as it would have made life a lot simpler.

    2013-10-09

    2013-10-09 DOS Layer ... still

    I'm still working through the DOS layer that I built for the DriveAccess kernel and updating it to be more complete.

    Previously, the DOS layer only supported block devices and file systems so could only load and save files.  Now I'm adding character devices into it to support keyboards, mice, serial ports, parallel ports and whatnot.

    Once it's all in, I can add the keyboard handler as a character device called "KBD:", then open it as stdin which will allow the console to work.

    Once the character device for the keyboard is working, it'll be trivial to migrate the various file systems (FAT) and block device drivers (ATA and RAM) from DriveAccess into Octopus so that I can get files loading and saving via the C library.

    2013-09-26

    2013-09-26 The Command Interpreter and stdin.

    I've linked the C library into the kernel so that I can use the standard C library functions in the kernel to simplify development and moving code from the kernel to programs.  I've written a routine to tie up all the kernel API calls so that the kernel doesn't have to use system calls to resolve its own symbols like a lowly program.

    I've pulled the command interpreter out of a previous test kernel (this one dates back to 2005, wow) and started to rewrite it to use the standard C library functions.  That way it can be in the kernel for now, then be pulled out into a separate program later.

    For the console, I obviously need a keyboard handler and a way to "open" the keyboard.  It might be worth setting up a proper file descriptor table and pulling the DOS layer from the DriveAccess kernel (circa. 2010) and actually making it a proper device that can be opened by name.

    2013-09-22

    2013-09-22 It says "Hello World!"

    After spending quite a bit of time debugging the ELF loading, the test program being loaded into the kernel is able to output to the stdout stream and to have that written to the screen.

    I finally managed to debug all the relocation gubbins and track down all the stupid mistakes (which were stupid).  From the forgotten "ret" at the end of "_start" (which made it try to run the program again), to the loop in write() which accidentally printed the characters backwards.  It's all been ironed out

    I know it seems as if it's taken a long time to get the Hello World! stage, but the way that it's showing the Hello World is very complete as far as how finished kernels do it.

    The Hello World program itself is identical to a Hello World program in C for any other OS.  This conversely means that any program that only outputs text to the screen can be compiled up against my C library and run on my OS now.

    With a little bit more work, the kernel is now able to pass command-line arguments to the program, so I can make an Echo program as well.

    Of course, the set of programs that only output text to the screen is fairly limited, so my next task is to get the keyboard wired up to stdin in some way so things can be a bit more interactive.

    2013-09-15

    2013-09-15 Displaying Images in 16 Colours

    I've added a new page that discusses colour mapping and error diffusion to a 16 colour palette.  This is based on a test kernel I developed back in 2011.

    After posting the page, I did notice that there are some strange data errors in the output images (a line of incorrect pixels toward the bottom right corner) but I'm reasonably certain that's just Bochs and I disagreeing on who should be using that memory.  The same problem is not exhibited on real hardware.


    2013-09-14

    libc, crt0, and What Executables Do When They Start?

    One of the articles I found while I was investigating bootstrapping executables was Hello from a libc-free world!.

    This article gets a special mention because of how detailed and well-written it is, and how it's one of very few articles I was able to find which actually discusses this topic in detail with worked examples and explanations.

    If anything here is interesting, then that article is definitely worth a read.

    2013-09-13

    2013-09-13 ELF Relocatable Executables

    Last night I added an article about ELF Relocatable Executables, and as I was talking through it with Pink today, we came to the conclusion that getting the arbitrarily linked executable with relocation information (the one generated by --emit-relocs) was the best way to go.  If I take this approach, as I load each section, I need only store a single value for each which indicates the difference in memory address between where it was supposed to be loaded and where it ended up.  With this table, I could then parse the relocation table and just add the offset for the corresponding section to each relocation value.  Things are slightly more complicated for relocations which are Program Counter Relative in the code, but not hugely so.

    Working on the ELF loader some more.   Originally it was a COFF loader which worked on the premise of there being one text section, one data section, and one BSS section, which is what DJGPP was generating for me at the time.  I'm now rewotking it to be less of a COFF-loader-adapted-to-ELF and more ELF friendly.

    I've had to change a number of elements to change what was an ELF library loader to load executable files with relocation information in them.  After many hours of stepping through each section as I was modifying it, it has completed a first attempt.  All the signs look good that it loaded the executable, and performed the relocation to correct the file for where it was loaded, and called into the entry point.  The System Call interrupt handler fired, now just to see why it didn't seem to print the message.

    libs_libraryDefinition at 0x40003C
    LibraryNode: 0x0
    LibraryName: 0x0
    LibraryVersion: 0x0
    LibraryLength: 0x0
    SymbolTable: 0x4000CC
    SymbolCount: 0x13
    StringTable: 0x4001B0
    EntryPoint: 0x4002B0
    SectionCount: 0x16
      Section[0]: 0x0   Section[1]: 0x400294   Section[2]: 0x0
      Section[3]: 0x4004A1   Section[4]: 0x4004C0   Section[5]: 0x0
      Section[6]: 0x400670   Section[7]: 0x400670
    --------------------------------------------------------------------------------
    Interrupt 48 at 0x40044F
    --------------------------------------------------------------------------------
    0x10B8C0                                       B5 BE 02 00                µ¾..
    0x10B8D0   B4 BE 02 00 38 B9 10 00 EC B8 10 00 20 BD 02 00    ´¾..8¹..ì¸.. ½..
    0x10B8E0   D5 03 00 00 03 00 00 00 00 00 00 00 30 00 00 00    Õ...........0...
    0x10B8F0   00 00 00 00 4F 04 40 00 08 00 00 00 06 02 00 00    ....O.@.........
    0x10B900   D1 1D 10 00 88 42 10 00                            Ñ...ˆB..
    SysCall
    --------------------------------------------------------------------------------
    -- Fin --
    

    Ok, I've confirmed that it's gotten the resolve() function, and called it get the read, write and lseek API functions.  write() is then being called, but it's being called twice, once with a gibberish file descriptor, and once with 0.

    I've put in the code to initialise the stdout FILE structure (oops), but it's still not calling write() with the correct parameters, and seems to sometimes spin off into the weeds.

    There's probably something wrong with the relocation, maybe if I dump the RAM from the emulator and disassemble it to see if everything ended up as it should.  But probably tomorrow.

    So as I've now fixed up the FILE structure for stdout, this has created relocations for the .data section.  What are the chances I'm not processing them?

    2013-09-05

    2013-09-05 Loading ELFs (Elves?)

    So I copied the ELF loader from a previous kernel into this one, compile errors akimbo because it was already there, n00b!

    The previous loader loaded the ELF binary and maintained a symbol table so that I could resolve library symbols.  Because I want it to load executables, I added a wrapper function to load the ELF binary as a library, then call the entry point, then free the memory.  It's crude, but it'll demonstrate loading the elf file.

    First attempt threw an interrupt 13 (general protection fault), which is odd as there shouldn't be any protection on the memory (and my code should all work first time anyway).  This showed two problems, malloc() returned 0 (which I wasn't trapping) and I hadn't loaded the GDT so it was running with whatever GRUB loaded.

    Got the GDT loading code from the earlier kernel ("DriveAccess") and got that part fixed.

    Now back to the malloc.  Oh, wait, maybe I should use the kmalloc(), rather than the unimplemented libc malloc() (which is hard-coded to return 0 currently) ... sad face.

    Ok, stupid errors fixed up, it claims to be loading the ELF file ...

    2013-09-04

    2013-09-04 Command Line Tools

    While I'm developing the helloworld executable as a project inside NetBeans, I did start to wonder if each of the commands I want to implement in the basic command set would be projects in their own right.  Most of them are only going to be a main.c file with a few functions, so I could just create a c file for each one under a project called Commands or something, then customise the makefile to build each into a separate executable.  Not sure what NetBeans would think of this though.

    Looking through the list of commands available in standard command line interpreters, I think I'll need to consider implementing the following commands (probably in this order):

    helloworld - :D
    echo - Tests command line arguments
    type - Tests reading files

    ls - Tests reading directory information
    more - Tests command piping and buffering
    screen - Combines the command interpreter and more
    touch - Tests creating a file
    copy - Combines type and touch
    join - Combines type and touch
    mkdir - Tests creating a directory
    rm - Tests deleting a file
    move - Combines copy and rm
    head - Extends more
    tail - Extends more
    od - Gives a more consistent for my memdmp() function.

    Next step is to remove all the reference to the libc stuff from the kernel so that I can link in the single libc.  This will mean I need to add some form of define so that when the library is linked with the kernel, it uses a special function to associate the various kernel functions (read, write, etc.) directly, rather than using the method that executables use.

    2013-08-31

    2013-08-31 LibC and Doom

    Another late night, this time I was working through the Linux Doom source code and implementing header files and prototypes into the libc that it needed, spending quite a bit of time investigating things I didn't recognise.

    Oh, by the way, I now have a libc project in the NetBeans project group that implements some of the C library functions.  This takes the older implementations of various C functions and combines them together, it also includes any function that are in the kernel but should be in the C library.

    The libc project contains the beginnings (if you pardon the pun) of the crt0.o file which sets up the stack, finds kernel functions, opens the stdin, stdout and stderror streams, then calls the program's main() function.  It doesn't actually do any of this yet, but the framework is there.

    2013-08-26

    2013-08-26 Interrupts and Printf

    As part of the ongoing effort to get the Hello World program loading and running, I've been reimplementing the interrupt system.  This is needed to allow my OS to reply to the interrupt request which the program makes in order to get the first hook into the kernel.

    The interrupt system comprises 48 assembly routines, one for each interrupt, which build a consistent stack frame, then push all the relevant registers of the state to the stack, including the number of the interrupt thrown.  A generic C handler for the interrupts is then called which dispatches messages to the various required handlers.

    For three days I've been trying to work out why the interrupt number being printed from the interrupt handler wasn't correct.  I've put test values in the assembly, added infinite loops, examined the registers in Bochs,  even suspended the emulator and examined the memory by hand to check the stack frame.  I couldn't find anything wrong except that the wrong values were being printed to the screen.

    I finally figured out what was wrong and tested the printf routine itself.  It failed the test.  Printf is, of course, a variadic function and the actual function itself is actualled called kstdio_printf().  I had created another function called kprintf() which then called it, but I messed up the call so it only passed the format parameter and not all the following parameters :(

    Oh well, at least it's all working now.

    2013-08-17

    Welcome

    Welcome


    This blog chronicles the design, implementation, development, and (maybe some day) execution of my home-grown operating system with the working title "Atomicity".

    I have a host of existing log entries which will be migrated into here and backdated.

    Check out the About page at the top for an overview of what this is all about.

    2013-08-16

    2013-08-16 - Memory Manager, Console, and Hello World

    The memory manager, started about 8 weeks ago, has finally been debugged and is working in tests.  There were a few issues with pointer handling, and I wasn't updating the memory header sometimes when changing the free list, and I was calculating the block name length differently in two places, but I think that's all sorted.

    Another good thing is that the pesky bug with the printf handling of integers has also been fixed (that was really bugging me), I had a signed integer instead of an unsigned integer as the working variable.

    Next thing I would like to do is to write an intermediate console.  It will read blindly from the keyboard, and write blindly to the screen.  It will spinlock itself between operations.  It will still have a fixed list of commands.  The aim is to create an environment which will support the basic Hello World program above, which will then lead onto the development of the stdin and stdout file functionality.  This will then be retro-fitted into the console to create the next version.

    The console will have a console invoke function as its entry point.  The keyboard handler will have to feed into a buffer.  The console will spinlock until the buffer has data to process (similar to how read() will work eventually).

    As I discussed previously, the hello world program will make an interrupt call to get the address of the function to be used for finding other functions.  From there, the hello world program will find the "write()" system call, and will call it passing 1 (for stdout) and the string data, it will then exit


    I've put together a rough skeleton of HelloWorld.  The start() method uses assembly to call an interrupt and fetch EAX into a global function pointer for resolve().  It then uses resolve() to find write(), then calls main().  main() then uses write() to output the Hello World string to STDOUT (file descriptor 1).

    Now all I need to do is write all the other code to underpin this ...

    2013-07-06

    2013-07-06 - Memory Manager and printf()

    Before I can fully test the Memory Manager, I'm writing the memdmp() function to display a section of memory on the kprintf console.  Unfortunately this means working out more of an implementation of printf than I had bothered before.

    In a way, this is a good thing because it's the one part of the my OS that, until now, was not written by me and subject to third-party copyright and license.

    Also thought, I wonder if Bochs can have an additional output stream that I could (ab)use to give me the kernel output on a separate stream/window.  I could make the OS output it through the serial port, which I guess a real machine would use.

    Another thought I had earlier, create a macro for malloc when allocating structures.  The macro would call a version of malloc that wrote the name and length of the structure somewhere so that memdmp() could display a lot more.  Could this be extended to the definition of structs so that the kernel had a table of all the fields in each struct so knew how to interpret "all" the data in memory?

    2013-07-05

    2013-07-05 - Memory Manager Progress

    Finally finished typing up the memory manager implementation and fixing some of the issues in it.  It now compiles and appears to be OK.

    2013-07-01

    2013-07-01 - Memory Manager (Version 2)

    My recent long train journey provided an opportunity to write down the implementation of a memory manager, one which uses a linked list of free blocks and where every block has a header and a footer.  I read about something like this somewhere, but I don't remember where.  It should work very well for lots of things, however, for small allocations, the size of the header and footer introduce a significant space inefficiency.

    Anyway, I'll get the implementation in and see how it goes.  I think I have a problem with my footer structure not being a multiple of 16 bytes, and I'm not sure if I should worry about putting the used blocks into a linked list of their own, or only worrying about the free blocks.  If I need a map of the memory, I can walk the structures.

    2013-06-21

    2013-06-21 - How Programs Talk to the Kernel

    So I've decided how I'm going to implement the mechanism by which the newly created executables find the kernel library.

    The original Linux kernel used software interupts for system calls which, while being a bit slow, were easily accessible from everywhere.  Later on, the linux kernel moved to using memory mapped kernel functions which are faster.

    What I'm going to do is have a single interrupt based system call which provides the (process-specific?) address for a FindSymbol() function.  Using this mechanism, a new process can find this function, then use it to search for other symbols such as "write".  This way, as the program starts up, it can find the addresses of symbols it requires and set itself up.  This may cause an overhead on programs which are frequently started and stopped such as console tools, but this should be acceptable for now.

    In the meantime, I still haven't actually put the interrupt handling back in yet.  Should probably do that, and may as well use the List structure to combine the handlers.  I guess I should create a list for each interrupt number, then just do an AddTail() for each handler in turn.  I should also put in the kmalloc() and kfree() using a slightly upgraded technology from last time.

    2013-06-18

    2013-06-18 - Hello World ELF

    As well as pulling in sections of code from previous iterations of the OS, I've been thinking more and more about the library, driver, and executable system.  Between the system calls from Linux, the POSIX specification and the functions required by NewLib, I've been able to whittle down a short list of system calls which I need to develop, and a rough order in which to do them.

    My plan is to create a Hello World! program, but this program will not have a crt0.o, and will not include a LibC.  Instead it will use the OS method of getting a handle on the kernel.library (which will provide methods for opening libraries and finding symbol locations), from there it will find the "write" symbol, and call it to write the string (for the moment, it can assume that the stdout is at a specific file number).

    Once this program is ready, I'll need to finish all the kernel code to underpin this.  For the moment, the following simplifications will be made:

    1. The program binary will be loaded into memory by Grub (to save the filesystem code yet)
    2. The program will be linked to start at 4Mb, and will be loaded into the kernel memory space at this address.
    3. The process model will not create a separate process for the program.


    The workflow needed in order to achieve this, including required components is:

    • Create enough of the process table to store the open file list.
    • Create the Open File list (including file operations).
    • Create a basic console as previous versions, except using the stdin and stdout streams via read and write.  The read() call will spinlock inside the process subsystem until keys are pressed.
    • Get the kernel process to call into the console.
    • Configure an exit command from the console to return to the kernel.
    • Define the method of finding the kernel library and then finding other symbols.
    • Reimplement the 
    • Pull in the ELF loader


    Distracted again, but Bochs has implemented a basic beep capability, and I've put the start of a "music" player based loosely on the the ZX Spectrum +2 PLAY command in so that I can program Greensleeves ...

    2013-06-14

    2013-06-14 - Bootloader Progress

    Ok, so I have persuaded NetBeans to start a new project, and I've rewritten the example OS's multiboot header code, and wired in my Grub-Bochs image.
    It now compiles the (noddy) kernel, links it into an ELF binary, and loads Bochs so Grub can load the kernel.

    I'm considering how modules, libraries and executables will load as I want to take this opportunity to separate them from the kernel completely.

    I think I understand now what crt0.o does in the grand scheme of things.  When you write a program, you start your program with a "void main()" and stuff like "stdout" already exists and stuff like "printf" works.  When your executable is first loaded, the OS actually passes control to the crt0.o section in your program (the start() function).  This routine (which is part of the toolchain which builds programs specifically for that platform) then uses some platform-specific voodoo to get references to things, it gets a reference to the environment somehow, it gets some library to power write() and open(), and generally sets up stuff that the C library will need to operate.

    Now, I imagine I'm going to end up with two different types of entity, Executables and Libraries.
    An Executable will be loaded into memory, a new thread (or process) will be created, and sent in to the start address of the executable.  If this is a C toolchain program, it'll land in crt0.o and start the process.  If it's a seat-of-the-pants assembly marvel, it will do something else.
    When the program exists, either by calling an Exit() function or by the main thread leaving the building, the process will end, and the memory for the program will be freed.
    This can all be achieved by me creating a crt0.o as part of my C library, and compiling programs as normal.  They'll run and won't know the difference.

    A Library on the other hand, is slightly different.  It will be loaded into memory, and will present a set of entry points.  It won't have a main thread or process given to it, and it won't have a defined exit point.
    I imagine that all libraries will need some form of Open() entry point so that the C library linked into the library can connect all of its functions to the basic kernel functions.  I guess it must be like a crt0.o, but it won't set up stdin or stdout, and it might not look to call the main() function once it's finished (although it may call a further initialise() in the library code itself, in case it needs to open further libraries or do other set-up).

    Beyond that, the libraries could just be ELF .o files (which have no unresolved symbols) and just has symbols exposed which are its entry points.
    (I'd have to investigate how to make a linked .o file with specific symbols exposed and others not (public vs. private) )

    This all comes down to whether the device drivers should be executables or libraries.  I suspect the answer is that they should be libraries, and will be loaded in the kernel memory space (for the moment) but some of the drivers may launch a new kernel thread in their initialise.

    So.  A device driver is a library.  The kernel will have to be told to load it at some point (possibly by a driver list file, possibly by some form of probe program).  The library will be loaded and will have its Open() method called (as all libraries will).  I guess the open code will register the device driver with the kernel, or I guess it could start probing the system for compatible devices.

    In the Amiga world, the device driver library would be a normal library but with some specific entry points to make it into a driver and then it's loaded especially as a driver ( http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node029E.html ).

    I think the device drivers are going to work in a similar manner.  The Device driver will be a library (ELF .o file) and will be loaded into the library list,  .... (thought interrupted) ...

    2013-04-27

    2013-04-27 - Bootloader Design

    So, my plan is this ...

    The 512-byte boot sector loads a small 16-bit loader.  This loader will then load a text file detailing modules to load, the first being the 32-bit kernel.  The 16-bit loader will then load all of these 32-bit modules.  Its last act will be to switch to 32-bit mode and start the 32-bit kernel proper.

    The downside to this is that the 16-bit loader will have to understand the various file-systems it may encounter, but this can be mitigated by requiring a small boot partition which contains the modules.

    The upside is that the config file can be modified to account for many different boot devices, including, but not limited to, the following situations:

    • Booting from a floppy (which will need a floppy driver)
    • Booting from a hard drive (which will need a S/PATA driver)
    • Booting from a CD-ROM drive (which will need an ATAPI driver)
    • Booting from a network (which requests file by name)
    • Booting from a USB device (which requires PCI, USB, and class drivers)


    The bigger upside is that the 16-bit bootloader will be able to request blocks from any (bootable) block device using the BIOS routines and will be able to load any file by name from a PXE network boot using the BIOS routines.  That way, we're not doing the same thing as a certain other OS of booting from a USB drive using the BIOS, jumping into protected mode, then finding that our boot device went away.

    One downside is that we only have 640k of space to load the modules into (which should be OK as they can be read-only drivers for the various  drives)

    The 16-bit loader will also have to create an identifier from the boot device in order to pass this to the 32-bit kernel so that I can find the same boot device using the 32-bit drivers (when the BIOS is no longer available).
    It could even be made "intelligent" to the point of identifying (guessing) what device drivers it will need, but it is dependant on the BIOS if it's even possible to get any information about what the booted device is.

    Most BIOSes just pretend that a USB hard drive is a normal hard drive, leaving a surprise for the OS when they jump to 32-bit armed with an ATA driver and find no drive to boot from