Adding new system calls is easy and fun - it extends POS! So if you want to help out, this is the best way to start. Open up features/screen.asm in a text editor and paste in the following after the header text:

; -----------------------------------------------------------------
; ossayhello -- Prints 'Hello' to the screen
; IN/OUT: Nothing


mov si, .message
call osprintstring


.message db 'Hello', 0
There we have it: a new system call that prints 'Hello' to the screen. Hardly a much-needed feature, but it's a starting point. The first three lines are comments explaining what the call does, and what registers it accepts or returns (like variable passing in high-level languages). Then we have the ossayhello: label which indicates where the code starts, before a pusha.

All system calls should start with pusha and end with popa before ret: this stores registers on the stack at the start, and then pops them off at the end, so that we don't end up changing a bunch of registers and confusing the calling program. (If you're passing back a value, say in AX, you should store AX in a temporary word and drop it back in between the popa and ret, as seen in oswaitfor_key in keyboard.asm.)

The body of our code simply places the location of our message string into the SI register, then calls another POS routine, osprintstring. You can freely call other routines from your own system call.

Once we've done this, we can access this routine throughout the kernel. But what about external programs? They have no idea where this call is in the kernel! The trick we use is vectors - a bunch of jmp instructions at the start of our kernel code, which jump to these routines. Because these vectors are at the start, they never change their position, so we always know where they are.

For instance, right now, your new system call may be at 0x9B9D in the kernel. But if you add another call before it, or someone else does, it may move to 0x9FA6 in the kernel binary. We simply don't know where it's going to be. But if we put at vector at the start of our kernel, before anything else happens, we can use that as the starting point as the vector will never move!

Open kernel.asm and scroll down to the list of system call vectors. You can see they start from 0003h. Scroll to the bottom of the list and you'll see something like this:

jmp osdosbox_main ; 00E7h
The comment here indicates where this bit of code lies in the kernel binary. Once again, it's static, and basically says: if your program wants to call osstringtokenize, it should call 00E7h, as this jumps to the required routine and will never change position.

Let's add a vector to our new call. Add this beneath the existing vectors:

jmp ossayhello ; 00EAh
How do we know this jmp is at 00D2h in the kernel binary? Well, just follow the pattern in the jmps above - it's pretty easy to guess. Just add a 3 to the previous hex. Adding Hex is easy, Note that hex is a 16 Base number system so :
0 1 2 3 4 5 6 7 8 9 A B C D E F is counting in HEX
So if we want to add 00EAh to 3 then it would be :
00EAh + 3 =
A = 10
10+ 3 = 13 (In base-10)
13 = D (in Hex)
So 00EAh + 3 = 00EDh
If you don't understand this, then better check out hex-adder :
That's all good and well, but there's one last thing: people writing external programs don't want to call an ugly number like 00C9h when they run our routine. They want to access it by name, so we need to add a line to in the programs/ directory:

os_say_hello equ 00D2h ; Prints 'Hello' to screen
Now, any program that includes will be able to call our routine by name. Et voila: a brand new system call for POS!

Last edited Sep 10, 2013 at 10:13 AM by ALLDESP, version 1