1
System Calls: A Kernel Project in Linux
CS-550-3: Operating Systems
Fall 2003
Hunter Bell
Table of Contents
Main Body of Text (pages 3 –5)
- Introduction - page 3
- Description of trap-table and user space interaction – page 3
- Creating a new kernel function – pages 3-4
- Revision of system call table – page 4 - 5
- Rebuilding the kernel – page 5-6
- Creation of user-space program – page 6
- Conclusion – page 6
- Bibliography – page 7
Appendices
- Appendix A: alterations to time.c – page 8
- Appendix B: alterations to entry.S – page 9
- Appendix C: user-space program (exercise-5.c) –page 10
- Appendix D: alteration to unistd.h – page 11
Introduction
The system call linkage within any operating system is an important piece of functionality for the computer on which it is running. It provides an interface through which software (in user space) can request an action by the operating system itself. Gary Nutt’s Exercise Five requires the alteration of the system call linkage in Linux. Since the book is based on Linux Mandrake version 2.2.12, Red Hat Linux was not used in this project. Instead, the version of Linux Mandrake (2.2.14) provided with the book was used. On the following pages, the process for successfully adding a new function and system call to the Linux Mandrake kernel is examined and explained in detail.
Description of Trap table and User Space Interaction
Kernel functions appearing in the system call interface cannot be called directly from user space, but must be called through the trap table, also known as the system call table. This is accomplished by creating a new entry in the kernel trap table, which references the newly created function. Once created, the entry in the trap table can be called through a macro-created-stub function from user space. This stub function includes a trap instruction that changes the CPU from user to supervisor (administrative) mode so it can run the kernel function. In Linux, the trap instruction included here causes an interrupt (0x80), and uses the arguments passed through the stub/macro to access the system call table at the correct location. Once identified, the system call function (syscall() ) checks to ensure the requested function (passed as an argument from user space) is acceptable and then calls the kernel function, which executes according to instructions in its body. This will only occur, however, if the compiler agrees that the arguments passed from the stub in user space are correct in number and type with regard to the kernel function (Nutt, 2001).
Creating a New Kernel Function
Creating the new kernel function, named pedagogictime, was a required task in this programming exercise. The body of the function needed to reside in the /usr/src/linux/arch/i386/kernel/time.c (see Appendix A) file within the kernel of the operating system. This location enabled the new code to be compiled along with the pre-existing kernel functions when the kernel was rebuilt. (If the function resided outside of this file (within its own file), it would have been necessary to edit the Makefile within that same directory by adding the new file name to O_OBJS list.) In order to correctly implement the function, a call-by-reference argument was necessary in the function header/signature. This enabled the kernel to write information into a user-space address. This can only be accomplished, however, if the address has been correctly identified in user space (Nutt, 2001).
There exists a kernel function, verify_area, that can ensure the validity of a read/write operation into or out of user space. The return from this function is either a true or false value that identifies whether or not the kernel can access the variable from user space. This function was used as a fail-safe check to exit the kernel function if reading or writing was not possible. If the result was valid, the function would continue operation and call two additional kernel-level functions: copy_to_user and copy_from_user. The suggested functions, memcpy_fromfs and memcpy_tofs, suggested in the exercise description did not work as anticipated, and would not compile. The grep command was used, and searched through the kernel until the copy functions were identified. The copy_to_user function allows the kernel to write new values into user space. In this case, the values contained within the kernel variable xtime were written to the variable passed by reference through calling the function. To ensure the correct values were sent, it was necessary to disable interrupts on the operating system as the values were copied by calling the system function cli(). This guaranteed the values stored in xtime would not change during this process. Once copied into user space, the interrupts were re-enabled using the function sti(). Pedagogictime then called, back from user space, the values copied into the argument by reference. If the argument “flag” (sent as another argument from user space) is true, the function then prints the resulting values using the printk function. Printk is almost identical to the printf function in user space, but it just adds the ability to print to stdout from kernel space (Nutt 2001).
Revision of System Call Table
After having completed the new kernel function, it was necessary to revise the sys_call_table so the operating system would recognize calls and stubs to this function. This was accomplished by editing the table in the /usr/src/linux/arch/i386/kernel/entry.S file, and the table in the /usr/src/linux/asm/unistd.h file. There were already 190 entries in the system call table, so the new function would be identified as number 191 (see appendix B). It was also necessary to update the total number of system calls recognized by the system to 191, and this was done in the same file. In the unistd.h file, an entry was added as definition number 191 so the system would be able to recognize stubs for this function generated by Macros in user space. With these new additions, whenever a trap/interrupt 0x80 occurs with an argument of 191, the new function will be invoked (assuming the kernel has been rebuilt with the new code included) (Nutt, 2001).
Rebuilding the Kernel
The last stage of the adding the function to the kernel project was to rebuild the kernel itself, and this proved to be one of the more challenging aspects of the entire assignment. The process can be time-consuming and very frustrating if your code does not compile (because you are not notified until step 4 of 5 in the process). All of the instructions for rebuilding the kernel in its various phases are found within the Makefile for each directory inside the kernel. That is, each directory has its own specific Makefile that hooks into the bigger Makefile in the top-level kernel directory. This ensures all pieces of the kernel are affected in the proper way when each step of the rebuild process is executed (Nutt, 2001).
The first phase of rebuilding the kernel is to issue the “make clean” statement from the command line prompt. This causes the operating system to remove old object files so they may be replaced during the rebuild process. After this statement executes, the “make <config_opt>” command is issued from the command line. The <config_opt> is replaced by “oldconfig” and creates the kernel configuration based upon the old configuration that was present before the make clean statement was issued. After execution of this step, the command “make depend” is given. This command ensures that certain files within the kernel are scheduled for compilation before others, making certain all dependencies between various files are maintained. The fourth step in the process is issuing the “make” statement. This compiles all of the kernel source code, and results in the executable file version of the kernel. It is in this step where the new code created in the kernel source files is compiled, and where the programmer is notified of successful or unsuccessful compilation of that code. If the code does not compile here, he/she will have to return and make adjustments based upon the errors encountered during compilation. This can prove to be a daunting task, because once the changes are made, all of the previous steps in rebuilding the kernel must be run to ensure integrity throughout the rebuild process. The last step of rebuilding the kernel includes issuing the “make <boot_opt>” command. The <boot_opt> is replaced, in this exercise, by bzImage. This creates a bootable kernel image and installs it within the kernel boot directory. If all five steps complete successfully, the new kernel function has been implemented correctly as far as compilation. The true test will be calling the function from a macro in user space(Nutt, 2001).
Creation of User Space Program
Finally, after the function was added correctly to the kernel, the user-space program was created (see Appendix C). This was an easy task, as compilation and running of the program is all done within user space. A timeval variable in which the values from xtime would be passed to and from the kernel was created, along with an int (flag) for notifying the kernel function to print the results. The next step was generating a stub that allowed the program to call the kernel function from user space. This was accomplished by utilizing a macro. The macro automatically creates a system call stub based upon the number of arguments passed into the macro. The stub then causes the interrupt 0x80 to occur, and having passed the argument 191 into the macro, the interrupt then calls the kernel function defined before rebuilding the kernel (Nutt, 2001)
Conclusion
The successful completion of a change to the system call interface coupled with creating a user space function is a time-consuming process. The end product, however, is more than just a change to the operation of an operating system. It includes attaining hands-on experience, and knowledge of a computer at a level not attainable unless each step of the process is worked through and completed.
Bibliography
Nutt, Gary (2001). Kernel Projects For Linux. New York, NY:
Addison Wesley Longman, Inc. ISBN 0-201-61243-7.
Appendix A: Alterations to time.c
(unaltered code not included)
/*****************************************
* Hunter Bell - CS550 Fall2003
* Term Project - Nutt Exercise-5
* **************************************/
asmlinkage int sys_pedagogictime(int flag,struct timeval *thetime)
{
/*declarations*/
int write_failed;
struct timeval *temp;
/*verify we can write to user space*/
write_failed = verify_area(VERIFY_WRITE,thetime,sizeof(thetime));
if(!write_failed)
{
printk("skelcall: Cannot write into user space");
return 0;
}/*end if*/
cli(); /*disable interrupts*/
/* write into user space the values in xtime */
__copy_to_user(thetimec,xtime,sizeof(xtime);
__copy_to_user(thetime,xtime,sizeof(xtime);
sti(); /*enable interrupts*/
/* verify we can read from user space*/
write_failed = verify_area(VERIFY_READ,thetime,sizeof(thetime));
if(!write_failed)
{
printk("skelcall: Cannot read from user space");
return 0;
}
/*copy back from user space into temporary variable for printing*/
temp = NULL;
__copy_from_user(temp,thetime,sizeof(thetime);
__copy_from_user(temp,thetime,sizeof(thetime);
/*Print time if flag is true*/
if(flag)
printk("%s",ctime(temp->tv_sec));
return 1;
}//end pedagogic time
Appendix B: Alterations to entry.S
(unchanged code not included)
ENTRY(sys_call_table)
.long SYMBOL_NAME(sys_ni_syscall)/* 0 - old "setup()" system call*/
.long SYMBOL_NAME(sys_exit)
.long SYMBOL_NAME(sys_fork)
.long SYMBOL_NAME(sys_read)
.long SYMBOL_NAME(sys_write)
.long SYMBOL_NAME(sys_open)/* 5 */
…
.long SYMBOL_NAME(sys_ni_syscall)/* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
.long SYMBOL_NAME(sys_pedagogictime) /*191 - Hunter Bell*/
/*
* NOTE!! This doesn't have to be exact - we just have
* to make sure we have _enough_ of the "sys_ni_syscall"
* entries. Don't panic if you notice that this hasn't
* been shrunk every time we add a new system call.
*/
.rept NR_syscalls-191
.long SYMBOL_NAME(sys_ni_syscall)
Appendix C: User-Space Program
/* ****************************************
* Hunter Bell - CS550
* exercise-5 (term project)
* this program executes in user space and
* makes a system call to kernel space
***************************************** */
#include <linux/unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <time.h>
int main(int argc, char * argv[])
{
struct timeval *thetime = (struct timeval *) malloc(sizeof(struct timeval *));
int flag = 1;
/* generate a stub for System call for int pedagogictime through a Macro*/
_syscall2(int,pedagogictime,int,flag,struct timeval *,thetime);
return 0;
}//end main
Appendix D: Alteration to unistd.h
#define __NR_exit 1
#define __NR_fork 2
…
#define __NR_vfork 190
#define __NR_pedagogictime 191