Creating a kernel call as well as a system call in Minix 3

Albeit important common principles exist, Minix 3 differentiates itself from other operating systems on many aspects. One such aspect is its microkernel design. While most OS adopt a monolithic kernel in which every process can do everything to everyone else, Minix 3 opts for a layered design.

Its kernel layer, a.k.a microkernel, performs the most basic tasks such as I/O operation and process scheduling. On top of this 1st layer, the 2nd and 3rd layers composing device drivers and servers. The latter refers to several independent funtion modules of the OS, such as process manager, file server and reincarnation server. Layer 4 is the top most layer in which user processes run. Only processes in the 1st layer run in kernel space. All others run in user space.

Processes in layers 2, 3 and 4 have limited privilages. Process in each layer can only call a service provided by its specified adjacent layer(s). In other words, user processes cannot call functions in kernel layer. Instead, the call must be relayed via layer 3 and/or layer 2. A system call refers a call by a process in layer 4 to processes in layers 3 and 2. A kernel call refers to the one from layers 3 and 2 to layer 1, i.e. the kernel.

Therefore, suppose you wish to write a new C library funtion which modifies a value in the kernel, what you need to implement are a library funtion, a system call and a kernel call.

Let’s begin with the kernel call.

1. Define the SYSTASK request type of your kernel call.
In include/minix/com.h, add this line:

# define SYS_MYCALL (KERNEL_CALL + 28)

and also modify line 3819 (of the book version) to increase the allowed maximum number of kernel calls:

# define NR_SYS_CALLS 29

2. Write a kernel call handler. The handler performs the actual task, i.e. change the value of the variable. The handler is composed of a prototype and a definition. First, write a prototype in kernel/system.h:

_PROTOTYPE(int do_mycall, (message *m_ptr));

and implement it by adding a new file do_mycall.c in kernel/system/

#include …
PUBLIC int do_mycall (m_ptr)
{
   // Crash-your-minix-3-here.
}

3. Map the request type to the handler so that the call vector can be properly initialized. In other words, you bind the request type to a hendler. In kernel/system.c, add:

map(SYS_MYCALL, do_mycall);

4. As the last step for creating a new kernel call, you add the call to system library so that servers and drivers can make the call. In lib/syslib/, add a new file sys_mycall.c:

PUBLIC int sys_mycall (some parameter)
{
   int r; // the return variable.
   message m; // create a message for the call.
   m.foo = bar;
   /**
    * The system library invokes a kernel call by
    * sending a message to the kernel with _taskcall().
    */
   r = _taskcall(SYSTASK, SYS_MYCALL, &m );
   return r;
}

Now we have finished with the kernel call. A process in layers 2 and 3 can make the kernel call by calling sys_mycall.

Let’s proceed to implementing the system call.

5. When making a system call, each message contains a number of the desired system call. These numbers are defined in callnr.h.
Edit the file include/minix/callnr.h by increasing the total number of system calls from 95 to 96.

#define NCALLS 96

and also add a line for your new system call:

#define MYSCALL 95

6. Write a system call handler. The handler performs the actual task from layer 4’s point of view, i.e. change the value of the variable by making the kernel call. Similar to step 2, the handler is composed of a prototype and a definition. Suppose we implement the call for the process manager, then in servers/pm/proto.h add:

_PROTOTYPE( int do_myscall, (int) );

… and implement it by adding a new function to file misc.c in kernel/system/

PUBLIC int do_myscall(int x)
{
   sys_mycall(…); // this is the connection point
                  // between system calls and kernel
                  // calls.
}

7. Map the call number to the handler. Method names of all handlers are kept in an array with fixed indices. These indices correspond to the call number as you have defined in step 5. Therefore you must make sure you have added the handler name to the correct position of the array. In servers/pm/table.c, add:

do_myscall;

to the 95th position (counted from 0).

Step 7 concludes the implementation of a new system call. In theroy we have finished all steps. But in order to facilitate C programmers, we need to provide a C library function who invokes the system call. Adding a new C library function is quite easy. First add the prototype of the function in unistd.h. Then implement it in lib/posix. You make the system call in your library funtion by calling:

int _syscall(int PM, int MYSCALL_number, register message *msgptr);

.

Comments & Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>