NEXT UP previous
Next: My Home Page

Adding New Calls

Once you understand the mechanism by which Linux programs execute system calls, it is a fairly straightforward task to add your own system calls to your kernel.

In order to make sure you understand this process better we will actually develop a very trivial new system call and add it to the kernel. The system call to be added will be:

	int printcons(char *mess, int len);

The printcons() call takes two parameters, mess, which is a pointer to a message stored in an array of characters, and len, which is the number of characters in the array. All that the new call will do is just to display the message characters on the console terminal screen. If successful, printcons() will return the value zero, otherwise -1 will be returned and errno will be set appropriately.

Coding the Call

The first task is to write the code to be added to the kernel. This is just a function which you will add to one of the kernel files, and it is a very similar task to writing a device driver function, something which you have seen previously.

The code for the printcons() system call appears as:

	asmlinkage int sys_printcons(char *mess, int len)
	{
		char *kmess;

		/* 1 */
		if (verify_area(VERIFY_READ, mess, len)) 
			return -EFAULT;

		/* 2 */
		if ((kmess = vmalloc(len+1))==0)
			return -ENOMEM;

		/* 3 */
		memcpy_fromfs(kmess, mess, len);
		kmess[len] = '\0';
		console_print (kmess);

		/* 4 */
		vfree(kmess);
		return 0;
	}

For such a trivial task as printing text to the console screen, this code may seem like overkill, but it does illustrate several points. The simplest place to enter this code, assuming that your kernel sources are in the standard place (/usrlsrc/linux) is at the end of the file:

	/usr/src/linux/kernel/sys.c

The first thing to notice about the code itself is that the name of the function is just the name of the new system call with sys_ added to the front. Parameters are then declared as if the system call was going to be used just like a function.

The text for each of the numbered comments in the code is:

  1. This system call is going to display the contents of a string on the console screen using the console_print() kernel function. The pointer to the text message (mess) is an address in user memory and so the message needs to be copied into kernel memory before it can be printed. The first task, then, is to check that memory accesses to the given address are legal for the user on whose behalf they will be performed. This check is done by calling the verify_area() kernel function. Our system call will return an EFAULT error if the required access is not permitted.

  2. In order to be able to cope with messages of any length, the next step is to use the kernel vmalloc() function dynamically to grab a block of memory into which a user text message can be copied. Notice that the size of the requested memory block is one byte larger than the number of characters to be copied from user space. This is to ensure that space is available into which a zero byte may be written to terminate the message and make it into a C string for printing. The pointer returned by vmalloc() is assigned to the variable kmess. Any failure here results in the return of an ENOMEM error.

  3. The next three lines of code get the user's message, copying it from mess to kmess with the memcpy_fromfs() function, add a zero byte to the end of the message to prepare it for printing and then use the kernel console_print() function to display the message on the screen.

  4. Finally, the last two lines tidy up by using vfree() to release the vmalloc()ed memory back to the system and then the last line returns a zero to the calling process to show that the call was successful.

Linking the Call In

Having written the code for the new system call, the next task is to make the rest of the kernel aware of the existence of the code and then to build a new kernel which contains the system call code within it.

In order to add the links from the existing kernel code into your new function you will need to edit two files. Depending on which version of the kernel you are running, the first of these is either:

	/usr/isrc/linux/include/linux/unistd.h

for kernels up to and including version 1.2 or, for kernels at version 1.3 and higher, the file is:

	/uisr/isrc/linux/include/asm-i386/unistd.h

The file contains a list of #defines used to allocate to each system call a unique number. Each line in the list has the format:

	#define __NR_name	NNN

where name is replaced with the system call name and NNN is that system call's corresponding number. You should add your call's name to the end of the list and allocate to it the next available system call number in sequence. The line for our example system call might be:

	#define  __NR_printcons	149

The second file to change also depends on which version of the kernel you are modifying. For kernels up to and including 1.1 the file is:

	/usr/src/linux/include/linux/sys.h

and what you need to do is to add the name of your new kernel function to the end of the list which is initializing the sys_ca1l_table[] array. By doing this, you are adding a pointer to your new kernel function on to the end of an array of pointers to functions which point to each of the system calls in the kernel. To stop the compiler complaining about your function name in the array you will also need to add an extern declaration of your function to the end of the list of similar declarations that occur before the definition of the sys_call_table[] array.

For kernels of version 1.2 and higher, the second file to modify is:

	/usr/src/linux/arch/i386/kernel/entry.S

which contains the initialization of the same array of pointers as the previous version, but this time written in assembly code rather than in C. Don't worry about the file being in assembly code - you don't need to understand it to modify it successfully. All you do is look for a list of assembler directives with the format:

	long  sys_name

followed by another assembler directive with the format:

	.space (NR_syscalls-NNN)*4

where NNN in the formula is the total number of system calls. All that you need to do is to add a new . long line with your own kernel function name plus an underscore (_) on the front and then increment the NNN value to include your new call. The result for our example could be:

	long .sys_printcons
	.space (NR_syiscalls-149)*4

At this point, all that remains is to rebuild and run the new kernel. In the previous explanation references to i386 in the pathnames of certain files may be replaced with the appropriate names if your work needs to be carried out on other machine architectures to which Linux has been ported.

Using a New Call

All that remains now is to sort out how to use your new system call from within your programs. Don't forget that a code stub for your new system call does not exist in the standard C library, so you must create one for yourself.

This is really easy, using the _syscallN() macros. A short C program to use the new printcons system call could be:

	#include <linux/unistd.h>

	_syscall2(int, printcons, char *, mess, int, len)

	main()
	{
		char message[] = "Print straight to console\n";

		if (printcons (message, strlen (message) )==-1)
			printf("Error occurred in printcons() call\n");
	}

The _syiscall2() macro is used here because our system call has two parameters. The macro itself will be expanded into a function in your program called printcons() which the subsequent call inside main() will execute. The pre-processor generated code inside the printcons() function in your program will contain all the necessary machine code instructions to load the appropriate CPU registers with your system call's parameter values and then execute the int 0x80 exception. When the exception returns, the return value from your kernel function will be examined and an appropriate value returned to your code in main().


NEXT UP previous
Next: My Home Page