Ker, win

Michael David WINIKOFF winikoff@mulga.cs.mu.OZ.AU
Sun, 14 Mar 93 0:15:11 EST


Hi everyone -- as promised my sketch for a simple kernel.
feel free to flame, comment, criticise, compliment (:-) or suggest additions
or changes.


The kernel has the following system calls:

contextswitch()
pid = self()
pid = spawn(ptr to code, ptr to stack, initial pc)
kill(pid)

send(pid, message)
mesg = receive()
bool = poll()

ptr = alloc(size)
free(ptr, size)

reqid = service(devid, service_number, ptr to result/arg buffer, flags)
bool = ready(reqid)

getdev(devid, service_number, var pid, var code)
setdev(devid, service_number, pid, code)


-----
contextswitch: Calls the scheduler. This is not likely to be used from within
user code.

self: Returns the current process/task's id, useful for exit() = kill(self())
[And also for sending messages to self. ??]

spawn -- this creates another task/process.
The details here are very hazy. The current call is meant to be very light
weight -- all it does is add an entry in the scheduler, allocating the memory,
loading the code (and setting up virtual memory) is done somewhere else.

send and receive work as expected (rec. blocks if no message is available.
It is an open question whether we have a buffer and if so how large is it
-- or possibly unbounded?)

A comment on messages: Sending large message raises issues.
We can either just copy them or fiddle with address spaces so they're accessible.
(possibly using copy on write??)

Currently think of mesg as being a single atomic value. 
This can be a pointer -- setting up he VM / copying data can be done elsewhere.

poll returns TRUE if there is a waiting message.

allocate and free dish out (physical) memory. 

The kernel has a device table. This is simply a mapping from
(devid, service_number) to (pid, code)

The system call "service" looks up the appropriate location and calls the
result process passing on the code.

Note that service is asynchronous -- it returns a reqid.
This can be used for determining when the operation has been carried out.
I feel that asynchronous IO is better in that you can simulate sunchonous 
IO using it but not the other way around (at least not without creating extra
processes ...)

One of the flags to the device could be whether the process wishes a message
to be sent to it when the operation has been carried out.

The caller could do:

reqid = service(....)
a:
mesg = receive(...)
IF not(ready(reqid)) THEN
	remember mesg
	GOTO a
ENDIF
getdev and setdev are used for querying the table and for changing it.
Obviously some notion of priviliege is needed -- we don't want all processes
to be able to read and modify the dev-table.

I'd envisage devicees as being reasonably high levelinterfaces to hard ware.

Eg the disk would provide seek, block read and block write but not a file system

Some devices include:
	disk (floppy and hard), ports (serial and parallel)
	clock, screen, sound, mouse, joystick, keyboard.


-------
An initial "shell" could be a program that lets you make system calls ...
This would be more like a system debugger/monitor ...

-------

A sample implementation
~~~~~~~~~~~~~~~~~~~~~~~~~

Note: This is (very!) simplistic. 
It illustrates one possible implementation.

There is a ticker that generates an intterupt every n milliseconds.
(how else can you do preemption? :-)

THis forces a call to contextswitch()

The scheduler can use simple round robin 

Process table:

next_free
running-process

for each process:
	state: waiting / ready / running / invalid
	pid: int
	pc
	registers
	current message
	message valid : bool

----------
IMPORTANT: Except when a system call has a call to contextswitch it will
be assumed to run atomically.
(eg. on a single procesor implementation system calls disable interupts)

contextswitch: changes state of running-process to ready
	finds next ready process, sets it to running, sets running-process
	to it.
	load registers and pc.

kill: removes an entry from the table (set state to invalid)

self: return running-process

spawn: Find an invalid entry. Change it to ready and initialise it

send: 
	a:
	if target of send is invalid return error
	if the message_valid is false then
		message_valid = true
		current_message = mesg (passed as argument to call)
		return
	else
		contextswitch()
		goto a
	endif
	
	/* note that we don't have a facility for waiting. 
	** due to the context switch this should be reasonable efficient.
	** if message sending is a bottleneck we should have a queue of 
	** messages rather then a single entry
	*/

receive:
	if message_valid then
		message_valid = false
		return current_message
	else
		state = waiting
		contextswitch()
	endif

poll: return message_valid

-----
Memory allocation:

A simple way of managing memory is with a bitmap.
If we have 1 bit = 1K byte then the amount of memory taken by the bitmap is
0.1% -- this is fine if we are managing physical memory.

alloc(size) 
	round size upto next kilobyte
	search bitmap for next free chunk of that size
	set the region to taken
	return pointer

free(ptr,size)
	reset bits in bitmap

Note: We can't compact physical memory without using virtual memory ...

-----

We also have a device table.
The "ready" call could be implemented either in the kernel or  in the device
drivers.



--------------------------------------------------------------------------------
Michael Winikoff
winikoff@cs.mu.oz.au
Computer science honours. University of Melbourne, Australia.