This lab will be done with your forever CS45 lab partner:
Lab partners and machine assignments
The lab 2 version of: virtual box guide for lab 2
Content:
For these projects, you will do your lab work on one specific CS lab machine, and you and your partner will do your development work using a new jointly shared CS account. This account is to be used for CS45 work only (we will remove them at the end of the semester).
You and your partner will log into the machine tow which you have been assigned using your new CS account. It is important for correct networking between the host machine and the virtualbox VM (Virtual Machine), that you do not run on the same physical machine as any other group.
You can ssh into your machine from any other machine in our labs; you don't need physical access to your machine to run VirtualBox on it.
Instructions for setting up your virtualbox VM, compiling,
installing, and running Linux kernel code, and ssh'ing and scp'ing between
VM and the host machine are available in the:
Virtual Box Guide for CS45 Students.
Both you and your partner should then clone a copy into your private cs45/labs subdirectory. here are the instructions for doing this from lab1.
Then one of you or your partner can copy over some starting point code into your repo, add and push it. I included an example gitignore file with the starting point code. First move it to .gitignore before adding and pushing it (and add in any names of executable files or other files you do not want git to add to your repo).
$ cp ~newhall/public/cs45/lab02/* . $ ls Makefile README syscall_impl_startingpt.c tester.c $ mv gitignore .gitignore $ git add .gitignore $ git add * $ git commit $ git push origin masterThe starting point code contains a starting point for a user-level test program that you can use to test one or both of your system calls: feel free to write multiple test programs, this is just one, incomplete, example that includes some header files that may be useful. Also, note the comment in the Makefile about setting the -I include directory path so that it can find the header files for your version of the kernel. I've made a guess at what you may name it, but make sure it is correct for what you actually do name it.
You may need to add additional #includes to your test program
to include any header files you add to the kernel that contain definitions
that are needed by user-level programs. For example, if you define
a new struct type and a user-level program passes one by reference
to a system call, the struct type needs to be defined in a .h file that
the user-level program includes.
/* * flag: if non-zero print out the time at kernel level * tval: passed by reference to system call, the current time * will be "returned" through this parameter * returns: 0 on success, -1 on error */ long getcurrenttime(int flag, struct timespec *tval)NOTE: as you modify existing kernel modules, I recommend first making a copy of of the .c or .h or .s file as orig_filename.c, then modify filename.c. This way if you really break something you can easily go back to the original kernel source. In the worst case you can always grab a new copy of the linux source, but it is nice to not have to do that every time you really mess up one file and what to start over.
To get some ideas of how to implement the system call function, look at other example code in kernel/ that access xtime, and look at the linux/time.h header file for interface functions and type definitions.
Steps for implementing a system call:
There are three general steps to implementing a system call:
Details: In this example, I'm adding a new system call named
getsecretcode
that takes has one int parameter and one
int passed by reference that the kernel fills with a secret
code. It returns 0 on success and an error value otherwise.
Changes for 64-bit x86 (this is our platform):
linux-2.6.32.44/arch/x86/include/asm/unistd_64.h
add a new entry:
#define __NR_getsecretcode 299 __SYSCALL(__NR_getsecretcode, sys_getsecretcode)If you look in the file
linux-2.6.32.44/arch/x86/kernel/syscall_64.c
, you can
see how the 64-bit system call table is generated.
The change that you make in unistd_64.h will result in header files
being added to include/asm-generic and in include/asm-x86/ with your
new system call information in it.
Changes for 32-bit x86. You DO NOT need to make these changes for building on our system; this is just to show you that this part of the kernel is architecture-specific, and to port your system call to other platforms you would need to modify all the architecture-specific files:
arch/x86/kernel/syscall_table_32.S
add a new entry:
.long sys_rt_tgsigqueueinfo /* 335 */ .long sys_perf_event_open .long sys_getsecretcode
linux-2.6.32.44/arch/x86/include/asm/unistd_32.h
add:
#define __NR_perf_event_open 337 #define NR_syscalls 338
linux-2.6.32.44/include/asm-generic/syscalls.h
add a function prototype for your system call:
#ifndef sys_getsecretcode asmlinkage long sys_getsecretcode(int flag1, int flag2); #endif
linux-2.6.32.44/include/linux/
(e.g. getsecretcode.h). We don't need to do this for
the getsecretcode system call (and you don't for getcurrenttime).
When adding new header files to the kernel, make sure to use good .h file boilerplate code, also you can specify parts of the header file that are only visible to code at the kernel level by putting those parts between #ifdef __KERNEL__ and #endif:
/* * file: getsecretcode.h with really good comments */ #ifndef __GET_SECRET_CODE_H__ #define __GET_SECRET_CODE_H__ // add any type definitions etc. that may be needed at user-level here // look at other .h files in here to see some examples: #define MYSECRETCODE_MAX 1234567 // kernel and user level can use #ifdef __KERNEL__ // anything between "#ifdef __KERNEL__" and "#endif" // is only visable at kernel-level #define MYSECRETCODE_ANSWER 13 // only available at kernel level #endif #endif // #ifndef __GET_SECRET_CODE_H__
linux-source-2.6.32.44/kernel/
.
Your system call function must have "asmlinkage" prepended to its header
and a "sys_" prefix to its name. However, use the SYSCALL_DEFINEX macros
to generate this for you:
SYSCALL_DEFINE2(getsecretcode, int , pflag, int __user *, codeval) {See the macro definitions for SYSCALL_DEFINEX in include/linux/syscall.h. These generate function definitions for system calls with different numbers of parameters. The above will generate something like the following: For example:
asmlinkage long sys_getsecretcode(int pflag, int __user *codeval) // asmlinkage: tells gcc to pass parameters on the stack // __user: means the address value is from user-spaceHere is an example:
/* * file: getsecretcode.c : with a very awesome comment */ #include <linux/kernel.h> // can only be included at kernel-level #include <asm/uaccess.h> #include <linux/syscalls.h> // you also may need to include other header files based on what // types and functions your system call implementation needs to use // (linux/time.h might be useful for getcurrenttime) // if you added a .h file then, you may want to use some of its defs // in your system call implementation, so make sure to include it: #include <linux/getsecretcode.h> /* getsecretcode system call: * an awesome comment */ SYSCALL_DEFINE2(getsecretcode, int , pflag, int __user *, codeval) { /* to print a debug message use printk, which is similar * to printf: the kernel's stdout goes to the console and * to the files: /var/log/[syslog/kern.log] */ printk("Inside system call getsecretcode\n"); /* copy argument values that are passed by reference from user * space to to kernel space: * * (1) first call access_ok() to check if the space * pointed to by thetime is valid * (2) then call copy_from_user() to copy to kernel space * * note: pflag's value is passed on the stack so it does not need to be * copied to kernel space...its value can be grabbed off the stack */ /* If you access any kernel variables that could be * modified by interrupt handlers that interrupt our syscall, * or by other processes simultaneously running in kernel mode, * then you need to put some synchronization around their access. * For existing kernel objects, there should be a lock or semaphore * you can use (in inlcude/linux there are interface files to * spinlock, seqlock, and semaphore) For new kernel state you add, you * need to add spinlock or semaphores (this is likely not necessary * for lab2 where you are accessing existing kernel state) */ /* copy pass by reference "return" values to user-space * * (1) first call access_ok() to check if the space * pointed to by codeval is valid (if you have not already done so) * (2) then call copy_to_user() to copy the value to user space * * You CANNOT directly access the space pointed to by codeval. * Instead you need to copy values to/from kernel space to the * space pointed to by the pass-by-reference parameters. */ /* a successful return from the system call */ return 0; }
obj-y = ... async.o getsecretcode.o # add to the end of this list obj-y += groups.oThen, re-build the linux kernel.
You can add new user accounts from which to run test code by running 'addusr username', which creates a new user account for a user named username, with a home directory in /home/username.
There
are two ways to call a system call. One way is to make a call to
syscall
passing the value of your system call number
and its arguments:
int code; ret = syscall( 299, 1, &code) // 299 is the number of your system callThe second is to define a wrapper function or macro around around a call to syscall, and then call the wrapper function in user code. Here is the macro way:
#define getsecretcode(arg1, arg2) syscall(299, arg1, arg2)Then just make calls to
getsecretcode
in user-level code:
// this way, if you "hide" the wrapper function; the user level code just calls // getsecretcode and doesn't need to know the system call number, 299. ret = getsecretcode(1,&code);
In addition to other header files your user-level program includes, it should include:
#include <unistd.h> #include <errno.h>and, any header files you added that it needs for type defs:
#include <linux/getsecretcode.h>
TO COMPILE you need to tell gcc where to find your kernel's header files. Specify the include path using -I in your gcc command (of course this really should be added to your Makefile):
gcc -g -o tester -I/usr/src/linux-headers-2.6.32.44-lab2-cs45/include tester.cSpecific implementation hints:
printk
, then
incrementally add more functionality and test.
linux-source-2.6.32.44/include/linux/
.
Header file contents (prototypes and definitions) that
only should be visible inside the kernel (not at user level) should be inside
#ifdef __KERNEL__
, and #endif
preprocessor
directives (for this lab assignment you will not need these kernel-only defs,
but you may in future labs).
In addition, rgrep is useful for recursively greping for symbols in linux source subdirectories.
getprocinfo
. It takes
two arguments: a process identifier value; and a reference to a
process information struct that you need to define in a new header file.
getcurrenttime fills in the field values of the second argument
with information about the specified process. If the pid argument is 0,
then getprocinfo
should "return" information about the
calling process, otherwise it should "return" information about the process
with a matching pid value.
The system call returns 0 on success, and one of the following error
values otherwise (feel free to add additional error return values):
Start by defining a the proc_info_struct in a new header file that you
create in include/linux/
. The struct should have the
following fields:
pid_t pid; /* pid of process */ pid_t parn_pid; /* pid of its parent process */ pid_t gid; /* group id */ unsigned long user_time; /* total CPU time in user mode*/ unsigned long sys_time; /* total CPU time in system mode*/ long state; /* its current state */ unsigned long long sched_avg_running; /* its scheduled ave running time */ unsigned int time_slice; /* its scheduling time slice */ unsigned int policy; /* its scheduling policy */ unsigned long num_cxs; /* number of context switches it has had (sum of voluntary and involuntary cxs) */ int num_children; /* the number of child processes it has */ char prog[16]; /* its exec'ed file name (e.g. a.out) */You can fill in these values by accessing a process'
task_struct
that is defined in include/linux/sched.h
. Field values in the
proc_info_struct
that correspond to null pointer values in
the process' task_struct
should be set to -1. Not all field
values in your struct match the names of fields in the task_struct, so you
may need to read through some code and/or try some things out before you get
the right values for these fields. Additionally, some values may need to
be obtain indirectly via task_struct fields.
Types are defined in types.h files in
architecture neutral and architecture specific subdirectories of include.
For example:
linux-source-2.6.32.44/include/linux/types.h
.
Errors are
defined in linux-source-2.6.32.44/linux/asm-generic/errno.h
When dealing with pointer parameters, you need to make sure that your system call doesn't dereference bad addresses: the kernel should never crash when the user passing in a bad address value to a system call. Also, remember that you need to explicitly copy values passed-by-reference to/from kernel space from/to user space. Use the functions access_ok, copy_from_user, and copy_to_user. Look at other system calls to see how these are used. A few things to help you determine what to do and if your system call returns correct information:
@garlic:/local/me_and_pal/linux-headers-2.6.32.44-lab2-cs45_1.0_amd64.deb @garlic:/local/me_and_pal/linux-image-2.6.32.44-lab2-cs45_1.0_amd64.debDO NOT MODIFY THESE AFTER THE DUE DATE. If you want to rebuild your kernel after the due date, use a brand new --append-to-version flag so that it will not overwrite these files.
During your 15 minute demo slot you and your partner will demonstrate that your solution works. It is up to you to determine how to demonstrate this to me. When I meet with you, your kernel should be up and running on VirtualBox (unless there is something that happens during the boot process that you want to show me), and your demo should be ready to run; if you spend your entire demo slot setting up VirtualBox, then I can only conclude that your solution does not work.
A demo is something that you and your partner should practice before you give it; you want to make sure that it runs correctly and that it demonstrates that your solution is correct and complete. Make sure that you are demo'ing both how your system call works under normal conditions and how you are handling error conditions.
You will want to show me one thing at a time, and discuss what is happening after each thing. As a result, do not just run a big script of commands and show me the output. Instead, have a script of commands (or tests), but run them one and a time and think about what they show and how you can show me what they show. Also, be prepared to answer questions during your demo about your implementation and about your test programs.
Often times demonstrating that your solution works means that you will
need a way to run a version of your kernel with debugging output enabled,
and you may need to show via unix commands or /proc information that your
system calls obtain the correct information or do the right thing.
In addition, for most demos, you will want to write one or more
interactive demo applications (menu driven program), where you choose from
a menu of options for invoking your system call(s), execute a
system call, examine system state or kernel output to verify that it
did the right thing, then choose the next system call to execute, and so on;
you want to be prepared to discuss and to demonstrate the effects after any
single system call.