`What does it mean by speak, friend, and enter?' asked Merry.
'That is plain enough,' said Gimli. `If you are a friend, speak the password, and the doors will open, and you can enter.'
—The Fellowship of the Ring, Book 2, Chapter 4
System calls are the fundamental, most stable interface that is provided by the operating system. They are how user programs request the vast majority of kernel actions: creating, reading, and destroying files; allocating and freeing dynamic memory; executing new programs, etc.
In this studio, you will:
Please complete the required exercises below, as well as any optional enrichment exercises that you wish to complete. We encourage you to please work in groups of 2 or 3 people on each studio (and the groups are allowed to change from studio to studio) though if you would prefer to complete any studio by yourself that is allowed.
As you work through these exercises, please record your answers, and when finished upload them along with the relevant source code to the appropriate spot on Canvas. If you work in a group with other people, only one of you should please upload the answers (and any other files requested) for the studio, and if you need to resubmit (e.g., if a studio were to be marked incomplete when we grade it) the same person who submitted the studio originally should please do that.
Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.
man 2 syscalls
.
Boot up your Raspberry Pi, open up a terminal window, and outside of the linux source directory in which you are keeping built versions of the Linux kernel make a new directory for your userspace programs (you will use sftp
to transfer files from shell.cec.wustl.edu into this directory).
SSH into shell.cec.wustl.edu
, use the qlogin
command to log into one of the linux lab machines, and in the top level personal directory you created in the first studio for compiling kernels and other code (but outside the linux source directory) also create a new directory for your user space programs. In that directory create a new file called
lib_call.c
. In Linux, all users have an associated user ID number, and the Linux library functions getuid
and setuid
can be used to get and set that id, respectively. In that file, write a short C program that reads the user ID and prints it out, attempts to set it to 0 (the root uid) and indicates whether or not that attempt was successful, and then again reads and prints out the user ID.
Read the man 2 getuid
and man 2 setuid
man pages to understand (1) what syntax is used to call those functions and what types of values they return, and (2) what header files you need to include in order for your program to compile.
Since the call to setuid
may fail, please do proper unix
style error checking: store the return value from setuid into a variable,
and follow it up with a test like:
if( return_val != 0 ) printf("Error: setuid failed! Reason: %s\n", strerror(errno));
Again, you can use the appropriate (section 3) man pages to determine which header files to include
for those additional library functions: man 3 printf
(not man printf
or man 1 printf
), man 3 strerror
, and
man 3 errno
(not man errno
or man 1 errno
).
Save your program, and try to compile it on the linux lab machine, as in:
gcc lib_call.c -o lib_call
which if successful will produce a binary file called lib_call
that you can run in a terminal window.
Fix any coding mistakes (or missing header files, etc.) in your program, until it compiles successfully with no errors or warnings, and then run your program, as in:
./lib_call
As the answer to this exercise, copy and paste the output from running your program.
sftp
into shell.cec.wustl.edu
, navigate to the appropriate directory, and get the lib_call.c
file. Quit sftp
and compile and run your program.
As the answer to this exercise, please copy and paste the output of running the program on your Raspberry Pi, both with and without sudo, along with a brief description of any differences you noticed in the output of the program (or the compilation, etc.) on your Pi vs. on the linux lab machine.
man 2 syscall
(singular - this is a different page
than man 2 syscalls
- plural).
You are strongly encouraged to continue to develop and store your code via one of the linux lab machines (reached from shell.cec.wustl.edu
via the qlogin
) command so that if your Pi freezes up you don't lose your work, and then
when it's time to move things over to the Pi, use sftp
to do so.
Copy your lib_call.c
file into a new file called native_call.c
,
and in that new file replace the calls to getuid
and setuid
with calls to syscall
.
To do so, you will have to determine their ARM
architecture specific system call numbers by looking at the linux source file
arch/arm/include/uapi/asm/unistd.h
and
at the generated header file
arch/arm/include/generated/uapi/asm/unistd-common.h
in your kernel source directory.
Note that it is good programming practice, and also makes your code more portable
(e.g., between the linux lab machines and your Raspberry Pi) if you
use the manifest constants for them (e.g., __NR_getuid
and
__NR_setuid
) instead of hard-coding the numbers for them directly
in the code you write. To do that, you may need include the header file asm/unistd.h
in
your program.
Compile your program, fixing any problems and recompiling as needed, and run it on your Raspberry Pi. As the answer to this exercise, copy and paste the output from running your new (native call) program on your Raspberry Pi.
Note:When making changes to the linux source
code, add a comment like //CSE422 MMDDYY
before each section (where MMDDYY is the month, day, and year on which the change is being made). This, along with generating
file diffs, will make it easier for you to keep track of the changes made to the kernel, and to revert them if needed.
There are five distinct tasks we need to accomplish to do this:
First, ssh into shell.cec.wustl.edu
using your WUSTL Key id and password,
then log into the Linux Lab cluster by running qlogin -q all.q
Then, navigate into your linux source directory,
(which should be in /project/scratch01/compile/your-username)
,
then into the linux
directory under it
(or whatever you renamed it to, from the long name produced by the linux .tar.gz
file) then into include
and then into the linux
directory
Before modifying any files, at least make a backup
of any file you will be changing, as in:
cp syscalls.h syscalls.h.MMDDYY
where
MMDDYY is the current month, day, and year (or, use svn or git or another
version control system, and commit whenever you have a working version).
Declare two new function prototypes, one that take no arguments and one that
takes a single int
argument, at the
bottom of include/linux/syscalls.h
(make sure to put them before the closing #endif
).
We recommend calling your new functions sys_noargs()
and sys_onearg(int arg)
.
You can use the prototype for
sys_getuid
as a template for doing this. Make sure that you use
void
in the argument list to indicate no arguments, since in some versions of C a function declared f(void)
(which takes no arguments)
is not the same as a function declared f()
(which inidicates that the function may take any number of parameters of unknown type).
As the answer to this exercise, show the two function prototypes you've added.
arch/arm/kernel/
.
The naming convention for a file that only implements a system call is to
call the file by the syscall name, e.g., if our function that takes no arguments is called
sys_noargs
, you would create the file sys_noargs.c
(additionally, there are other places we could have put this file, but since
we're only adding this call for the (ARM architecture) Raspberry Pi, this is an
appropriate place to put this code). Make a second file for your second system call in the
same directory, using the same naming convention.
For the implementation of the function that takes no arguments, copy and paste the
contents of the file found here. Take a moment
to look through this file. Notice that the function declaration isn't a normal
declaration, but actually is made through the macro SYSCALL_DEFINE0
(the zero in the name of the macro comes from the fact that this syscall takes
zero arguments, and is defined in include/linux/syscalls.h
).
For the implementation of your second syscall, use the code in
sys_noargs.c as a
template. You'll need to change the SYSCALL_DEFINE0
macro to
reference the function name you came up with, as well as the fact that this function
accepts an integer parameter. You do this by passing the type and the name of
the parameter to the macro, as in:
SYSCALL_DEFINE1( your_name, int, param_name )
In the body of this syscall, use the kernel function printk
to print a message that contains the value of the parameter that was passed to it.
You can use printk
much as you would use printf
. Be sure to return
a proper return value from this function.
As the answer to this exercise, show your implementation of the second function.
Makefile
(after, of course first making a copy of it named
Makefile.MMDDYY
) in the same directory as your source code files
(which should be arch/arm/kernel/
) and add our two new files to the end of the
object file list, which starts on the line with obj-y
(make sure
you do not add your files after a \
character, as this specifies
the start of a new line). Then, change the file extensions for your files from
.c
to .o
(this implicitly tells the build system to
generate the object (.o
) file it needs by compiling the source (.c
)
file that has the same name except for the file extension). Be sure to add only
the names of the files, with space characters in between them - as you may guess
from the instructions above about where to put the names, formatting rules for how
a Makefile
is laid out can be a bit picky.
As the answer to this exercise, show the newly modified line of the Makefile
,
that lists the new .o
files to generate.
arch/arm/tools/syscall.tbl
to tell the
kernel to install our new system calls into the hardware's system call table.
Before modifying this file, make a copy of it called syscall.tbl.MMDDYY
.
Then, at the bottom of the original syscall.tbl
. file, create two new lines, following the scheme
used throughout the file. You will need to increment the value in the first column to allocate unique numbers for each of your new system calls. Furthermore, note the format of
the last two columns: the system call name, followed by the actual function
definition, which by convention is the name prefixed with sys_
.
As the answer to this exercise, show the changes you added to the syscall.tbl file.
linux
source
directory (the one with subdirectories arch
, init
,
drivers
, etc.) you should first issue the command make
clean
to remove the artifacts from your previous build.
Then, to differentiate your new kernel from the one you built in the last
studio, we'll modify the kernel LOCALVERSION. Rather than using the
menuconfig
interface like we did last time, this time we will modify
the configuration directly. In the base directory, edit the file
called .config
(the leading period means that this is a hidden
file that is not normally displayed) and modify the
CONFIG_LOCALVERSION
string to reflect the fact that this new
kernel implements the extensions from this syscall studio.
You should now build and install your new kernel following the steps outlined in
studio 1
(starting with step 6, after you had already unpacked the Linux distribution, set up the configuration, etc.)
and studio 2
(starting with step 7 after you had already successfully set up and booted into your Raspberry Pi).
Once your new kernel is booted and running on your Raspberry Pi,
open up a terminal window and run the command uname -a
to confirm the new
version is running (you should see the new LOCALVERSION string you used).
As the answer to this exercise please give the output that was produced by running uname -a
on your Pi.
You are now ready invoke your new system calls.
First, use sftp to retrieve arch/arm/include/generated/uapi/asm/unistd-common.h
from your kernel source directory.
Open this file on your Raspberry Pi.
You should notice two new #define __NR_
statements at the end of the file
corresponding to your new system calls.
Next, copy this file to the appropriate include directory for GCC:
sudo cp unistd-common.h /usr/include/arm-linux-gnueabihf/asm
Now, copy native_call.c
to a new file called new_call.c
, and in that new file replace the native calls that invoke the
getuid
and setuid
syscalls with the appropriate native calls to the new
functions you wrote (i.e., replace the syscall number for getuid
with the __NR_
manifest constant for the new syscall that takes no
arguments and replace the syscall number for setuid
with the one for the new syscall you added that takes one argument).
Please note that you probably will need to declare and initialize an
int
variable in your new_call.c
code to pass to your syscall that takes one argument - the syscall interface may not correctly invoke your implementation of that syscall if you just pass in a (constant) numeric value.
On your Raspberry Pi, compile and run this new program, and after running it check
the system log by issuing the command dmesg
. As the answer to
this exercise, copy and paste the lines of the system message log that show the
messages that were output to it by your new syscall functions.
For example: diff -up unistd.h unistd.h.MMDDYY > unistd.h.diff
For this studio, you will submit copies of the following files:
lib_call.c
native_call.c
new_call.c
arch/arm/kernel/
syscalls.h
syscall.tbl
files you modified.
If you didn't keep an original copy for any of these files, you can find them in the original source code package.
As the answer to any of these optional exercies that you would like to try, please briefly describe what you learned (and give answers to any questions the exercise contains).
syscall
function), implement
a third interface that directly utilizes the assembly routines necessary to
invoke a system call. See the slides from today's lecture to see
the steps that need to be taken to invoke a system call on ARM. You will need
to make use of the gcc inline assembly function via the GNU asm
extension.
If you want to know more about the machine-level actions that occur
during a syscall, you can start with the detailed explanation given in the
manual to the native system call interface, man 2 syscall
.
The kernel code that contains the entry point for system calls is found
in arch/arm/kernel/entry-common.S
.
We will later discuss several methods that Linux provides for timekeeping and benchmarking program execution. One method, which is not specific to the Linux kernel, but is instead provided directly by the Raspberry Pi hardware platform, involves reading from the Cycle Count Register (CCNT) on the Raspberry Pi's ARM CPU. This register is not accessible directly by user-space programs. However, since it is accessible by the kernel, we can write a system call that allows a program to request the value from the kernel.
First, you will need to download a header that defines architecture-specific functions
for interracting with the Raspberry Pi's performance monitors.
Please download this file,
and place it in the arch/arm/include/asm
directory in your Linux kernel source tree.
As you did in Step (5) above,
modify your include/linux/syscalls.h
header
to declare another function prototype,
this time taking a single unsigned long long *
argument.
We named the function sys_read_ccnt
.
Like in Step (6), create a new file for your syscall's implementation in
arch/arm/kernel
.
You may use the provided file sys_read_ccnt.c.
Similar to Step (7), add your new file to the end of the object file list
in arch/arm/kernel/Makefile
.
As in Step (8), add your new system call to arch/arm/tools/syscall.tbl
.
Proceed with Exercise (9) above to compile your kernel with your new system calls, and install the kernel on your Raspberry Pi.
Proceed with Exercise (10) to write a program that implements your new system calls. In particular, call your system call twice to read two cycle counts in a row. As the answer to this exercise, please say how many cycles it takes to run the system call.
The read_ccnt
system call that you just implemented is unsafe.
It allows the caller to pass a pointer to any address,
to which the system call will then write the current cycle count.
Because an arbitrary address may be passed,
including one that may not be writable by the userspace process itself,
it provides a means for circumventing the kernel's memory protection mechanisms,
which can lead to memory corruption.
Modify the system call to avoid this issue.
You will need to use the copy_to_user()
function described on
page 76 of Robert Love's Linux Kernel Development.
Have the system call return the -EFAULT
error code if the copy fails.
As the answer to this exercise, please provide the code for your new implementation of the system call.