Minix System Call Exercise 1

In this exercise you will write a system call that makes the Minix File Server (called VFS) print out a message. This will give you practice modifying an operating system service and will give you an idea of how to write system calls.

These instructions will guide you through the following steps to implement the VFS server system call.

  1. in servers/vfs/proto.h define the system call's function prototype
  2. in servers/vfs/table.c add an entry to the VFS server system call table/array that maps a call number (the array position) to the system call
  3. in include/minix/callnr.h add a call number definition for the table entry added in step 1
  4. in servers/vfs create the vfsexercise.c file that contains the implementation of the function defined in proto.h
  5. in servers/vfs/Makefile add vfsexercise.c to the list of SRCS to compile with the VFS server

The system call you add (do_printmsg) results in the VFS server printing a message. In Section 8, you will also write a corresponding library function for use by application programs.

The csc2025-syscall-exercises.zip archive contains a csc2025-syscall-exercises directory with the initial set of files and scripts you need to implement the system call.

You modify files in the csc2025-syscall-exercises directory and use the update.sh script to copy the changes to the corresponding Minix directories in /usr/src. You can use revert.sh to revert the /usr/src directory tree to its original state and undo any changes you have made.

Contents

  1. Defining the system call prototype
  2. Adding the VFS server table entry
  3. Defining the system call number
  4. Implementing the system call
  5. Including the system call in VFS server compilation
  6. Compiling and testing the system call
  7. Passing a parameter to a system call
  8. Writing a user library function for the system call

1 Defining the system call prototype

Users of the do_printmsg system call will need a function prototype in order to invoke do_printmsg. The function prototype is defined in servers/vfs/proto.h. Open proto.h and you will see a number of function definitions. Add the do_printmsg function definition after the worker.c list of definitions.

Find the following lines in servers/vfs/proto.h:

/* worker.c */
int worker_available(void);
struct worker_thread *worker_get(thread_t worker_tid);
struct job *worker_getjob(thread_t worker_tid);
void worker_init(struct worker_thread *worker);
void worker_signal(struct worker_thread *worker);
void worker_start(void *(*func)(void *arg));
void worker_stop(struct worker_thread *worker);
void worker_stop_by_endpt(endpoint_t proc_e);
void worker_wait(void);
void sys_worker_start(void *(*func)(void *arg));
void dl_worker_start(void *(*func)(void *arg));

And add the following lines:

/* CSC2025 mod start */
int do_printmsg(void);
/* CSC2025 mod end */

before the #endif line to define the system call prototype.

The CSC2025 mod comments identify where the code has been modified.


2 Adding the VFS server table entry

The file servers/vfs/table.c includes the following declaration of a table (or array) of system calls implemented by the VFS server:

int (*call_vec[])(void) = {
    no_sys,         /*  0 = unused  */
    no_sys,         /*  1 = (exit)  */
    no_sys,         /*  2 = (fork)  */
    do_read,        /*  3 = read    */
    do_write,       /*  4 = write   */
    do_open,        /*  5 = open    */
    do_close,       /*  6 = close   */
    no_sys,         /*  7 = wait    */
    do_creat,       /*  8 = creat   */
    do_link,        /*  9 = link    */
    do_unlink,      /* 10 = unlink  */
    ...
    ...
    no_sys,         /* 69 = unused  */
    ...
    ...
};

The table is an array of pointers to functions. There is one entry in the table for each system call and the array index position in the table is the system call number.

To add a system call to the VFS server, replace the unused table entry at position 69 with the name of the function that will implement the system call (do_printmsg).

Replace the line:

    no_sys,         /* 69 = unused  */

with:

    /* CSC2025 mod start */
    do_printmsg,    /* 69 = printmsg  */
    /* CSC2025 mod end */

3 Defining the system call number

In the file include/minix/callnr.h there is a list of macro definitions of the form:

#define EXIT        1 
#define FORK        2 
#define READ        3 
#define WRITE       4 
#define OPEN        5 
#define CLOSE       6 
#define WAIT        7
#define CREAT       8 
#define LINK        9 
#define UNLINK     10 
...
...

These macros define the system call number and symbols (EXIT, FORK, READ etc.) that can be used in place of the call number.

To define the new system call number, add the following lines to callnr.h:

/* CSC2025 mod start */
#define PRINTMSG   69
/* CSC2025 mod end */

PRINTMSG is now the system call name that maps call number 69 to the entry for do_printmsg in servers/vfs/table.c.


4 Implementing the system call

You must now provide an implementation of do_printmsg. The initial implementation of the system call will simply print the message:

   System call do_printmsg called

In the servers/vfs directory, create a new C file called vfsexercise.c with the following contents:

#include <stdio.h>

int do_printmsg() {
    printf("System call do_printmsg called\n");
    return 0;
}

You now have the definition of a system call prototype, a system call table entry, the definition of a system call number and an implementation of the system call.

The next section tells you how to ensure that the new system call is compiled as part of the VFS server.


5 Including the system call in VFS server compilation

To include the system call in the VFS server compilation you need to add the name of the file created in section 4 to the VFS server's Makefile. Open the file servers/vfs/Makefile and find the following line:

.include <minix.bootprog.mk>

To include the implementation in the compiled server, add the following lines before the .include line.

# CSC2025 mod start
SRCS+= vfsexercise.c
# CSC2025 mod end

This adds vfsexercise.c to the list of SRCS to be compiled.


6 Compiling and testing the system call

You compile the system call and include it in the OS boot image by completing the following steps:

  1. In csc2025-syscall-exercises run the update.sh script:
        # ./update.sh
    to copy your updates to the system /usr/src directory tree
  2. In /usr/src/releasetools compile and install services:
        # make services
        # make install
  3. shutdown Minix and restart it to run the new version of the OS

The first time you do make services the kernel and all OS services will be recompiled, and you may see some compiler warnings. You can ignore compiler warnings that are not generated by the files you changed. Do not ignore warnings generated by your changes. Subsequent make services compilations will take less time (unless you make clean in between compilations).

You can ignore the "git: not found" message at the end of compilation in step 2 above.

make install installs a new Minix OS boot image in /boot/minix. You can do ls /boot/minix to see all the boot images that have been created. You can also select a boot image to use from the Minix start-up menu. The most useful menu options are 2 to boot from the latest boot image (i.e. the one you just compiled) and option 6 to boot from the earliest boot image (revision 1). Option 6 is useful if you make a mistake and the latest boot image is corrupted. It means you can revert to the version of the Minix boot image before you made any changes. To test changes, you must start with the latest version of the boot image (option 2).

If all of the above succeeds, you now have a new system call in Minix. You do not yet have a user level library interface to the system call but you can still use it by invoking the Minix specific _syscall kernel call.

To test the new system call, in csc2025-syscall-exercises create the following program:

#include <lib.h>    // provides _syscall and message
#include <stdio.h>

int main(void) {
    message m;  // Minix uses a message to pass parameters to a system call
    
    _syscall(VFS_PROC_NR, PRINTMSG, &m);
        /* _syscall asks the kernel to ask the server process identified by
         * VFS_PROC_NR (the VFS server process) to execute the system call
         * identified by call number PRINTMSG with any parameters in the 
         * message m (accessed through its address &m).
         */   
}

Save the program as test_do_printmsg.c and compile it as normal using cc. Run the program. In the Minix console, you should see the following output from the VFS server:

   System call do_printmsg called

7 Passing a parameter to a system call

In this section you will learn how to pass a parameter to a Minix system call. Instead of just printing out a fixed message string, you will modify the PRINTMSG system call to accept a single integer parameter and print the given integer in the message.

To do this you need to understand a little about Minix messages.

A message is a struct defined in /usr/src/include/minix/ipc.h. The struct contains a union of message types.

For this exercise you are only interested in message type 1 (mess_1). Message mess_1 has member fields that allow you to pass up to three int parameters and up to three pointers (char * types - or the start address of an array of bytes).

    message m;  // Minix uses message to pass parameters to a system call

declares a message in the user-level calling process. Just like any other variable, the message is allocated and has an address in the address space of the calling process. This address is passed to the kernel (as a result of the call to _syscall). The kernel then copies the message to the receiving process endpoint (VFS in this case). In the receiving process' address space, the message is available as the global variable m_in. That is, the message is available as m_in for the do_printmsg function to use.

To pass an integer parameter you just have to assign to a member of the message. ipc.h defines some useful names for the members of different messages. For example, m1_i1 is the 1st integer parameter of a message of type 1 (mess_1). To pass an int parameter to the system call you assign a value to the member m1_i1 of the message. For example:

    m.m1_i1 = 10;

would pass 10 as the first integer field of the message.

You can modify the test program in section 6 to pass any integer you like to the system call, as follows:

#include <lib.h>    // provides _syscall and message
#include <stdio.h>
#include <stdlib.h> // provides atoi

int main(int argc, char **argv) {
    if (argc < 2)
        exit(1);    // expecting at least 1 integer parameter to test program
        
    int i = atoi(argv[1]);  // convert argv[1] to an int 
    
    message m;      // Minix uses message to pass parameters to a system call
    
    m.m1_i1 = i;    // set first integer field of message to i
    
    _syscall(VFS_PROC_NR, PRINTMSG, &m);
        /* _syscall asks the kernel to ask the server process identified by
         * VFS_PROC_NR (the VFS server process) to execute the system call
         * identified by call number PRINTMSG with the parameter in the 
         * message m (accessed through its address &m).
         */   
}

You can compile and run the above test program. It will work but the output of the do_printmsg system call will not change because you have not modified do_printmsg to use the message field m.m1_i1. So, modify do_printmsg to print "System call do_printmsg called with value i\n" (where "i" is replaced with the value of the integer parameter m.m1_i1). To do this just modify the system call implementation from section 4. The new version of servers/vfs/vfsexercise.c is:

#include <stdio.h>
#include "fs.h"         // provides global variables such as m_in

int do_printmsg() {
    int i = m_in.m1_i1; /* m_in is a global variable set to VFS's
                         * incoming message, 
                         * m_in.m1_i1 is the integer parameter set in the
                         * test program.
                         */
    
    printf("System call do_printmsg called with value %d\n", i);
    return 0;
}

Once again follow the update and compilation steps for your system call:

  1. In csc2025-syscall-exercises run the update.sh script:
        # ./update.sh
    to copy your updates to the system /usr/src directory tree
  2. In /usr/src/releasetools compile and install services:
        # make services
        # make install
  3. shutdown Minix and restart it to run the new version of the OS

Now run the test program with the argument 10:

    # ./a.out 10

and you should see the following output from the VFS server in the Minix console:

System call do_printmsg called with value 10

You can execute the test program with different values and the output from do_printmsg should change accordingly.


8 Writing a user library function for the system call

When you invoke a system call such as open or read you do not have to deal with the low level Minix details of messages or system call numbers or calling _syscall. You simply invoke a library function, that in turn invokes the underlying system call with an appropriate message. You will now provide this facility for the do_printmsg system call.

The library function will be called printmsg and will have the following function prototype:

int printmsg(int val);

The implementation of the printmsg function is:

#include <lib.h>          // provides _syscall and message
#include "vfsexlib.h"  // provides function prototype 

int printmsg(int val) {
    message m;      // Minix message to pass parameters to a system call
    
    m.m1_i1 = val;  // set first integer of message to val
    
    return _syscall(VFS_PROC_NR, PRINTMSG, &m);  // invoke system call
}

It is essentially the code you used to test the do_printmsg system call in section 7.

Make a user level library by creating the following files in csc2025-syscall-exercises:

The content of vfsexlib.h should be:

#ifndef __VFS_EXLIB_H__     // the macro directives prevent multiple inclusion
#define __VFS_EXLIB_H__

int printmsg(int val);

#endif                      // end of macro if statement

You can now test the library function. In csc2025-syscall-exercises, create the following user test_printmsg.c program.

#include <stdlib.h>
#include "vfsexlib.h"  

int main(int argc, char **argv) {
    if (argc < 2)
        exit(1);    // expecting at least 1 integer parameter to test program
        
    int i = atoi(argv[1]);
    
    printmsg(i);
}

You will have to compile the test program with vfsexlib.c, as follows:

    # cc test_printmsg.c vfsexlib.c -o test_printmsg

If you run the program as:

    # ./test_printmsg 10

you should see the same output to console as in section 7.

The user program test_printmsg is not written in terms of low-level Minix functions but the higher level printmsg library function that in turn invokes the lower-level system call.

Look at how other VFS system calls are implemented in /usr/src/servers/vfs (e.g. open, read, etc.). Ignoring the detail of the implementations, you will see that they all follow a similar pattern of definitions in table.c, callnr.h and proto.h. Implementations are then compiled with the VFS server (and included in the Makefile). Minix messages are used to pass parameters to system calls. Library functions are provided as part of the standard library.

A second system call exercise shows you how to copy information from the VFS server back to the user-level calling process.


Back to CSC2025 resources index ...