Add a new system call to the kernel
Get comfortable with the concept of a syscall from both kernelspace and userspace
Learn how userspace programs make system calls
Learn how those calls are handled by the kernel
Understand how C relates to assembly language
Write a C program to call your system call
Write the same program in the assembly language of your platform
A patch which adds a directory username/P1
to the assignment repo.
The first patch should add your kernel patch as a .patch file to our repo.
Generate a single patch from your commit to the Linux kernel using the command git format-patch -1
within your linux repo
Take the .patch
file outputted, and put it in your named directory (e.g. cp 0001*.patch ~/ILKD_submissions/your_name/P1
Add the email patch itself to the staged git files and commit this.
The second patch adds your C program to your directory
Include a makefile that can compile it
Make sure to have compiler warnings enabled (at least -Wall
but ideally -Wextra
and -Wpedantic
too, or even -Weverything
if you use clang)
Make sure that your code doesn't have any warnings or errors.
The third patch adds your assembly program to your directory.
as
and ld
The fourth patch adds the output of running strace
on your assembly program as it uses the new syscall.
Don't forget a cover letter.
All email code patches are expected to pass checkpatch.pl
as described in the policies and procedures
Submit your patches to programming1@COURSE_DOMAIN
Start by adding a new syscall to your copy of the linux kernel.
Create a new .c
file in the kernel
directory within the linux repo named kdlp.c
Using the appropriately numbered SYSCALL_DEFINE
macro, define a syscall entry function for a new kdlp
syscall in the kdlp.c
file that behaves as follows:
Takes two arguments, an appropriately marked pointer to a buffer from userspace of type char *
, an integer specifying the size of the buffer of type size_t
.
Formats a message into a local buffer that includes at least the student's name and the name of the current task that is running on the CPU (see the get_task_comm
macro).
Determines the amount of data to send - equal to the lesser of the size of the kernel message and how much space the user provided (see min
macro).
Copies that many bytes from the local message buffer into the user pointer (see the copy_to_user
function).
-EFAULT
(errno 14, bad address) if the copy is not successful.Otherwise, returns the amount of data that was copied.
The syscall implementation should take care to prevent any possibility of a buffer overflow.
Modify the Makefile for the kernel subdirectory in kernel/Makefile
to add kdlp.o
to the list of objects that are always compiled (add it to the end of the obj-y
variable).
Add an asmlinkage
declaration of sys_kdlp
to the bottom of include/linux/syscalls.h
(take inspiration from the numerous examples of other syscalls in that file).
Add an entry to the syscall table for your architecture.
On x86-64, this is in a special table format located at arch/x86/entry/syscalls/syscall_64.tbl
for historical reasons.
You need to pick a number for it, put it in order with the next highest number after the last common
entry in the table
Your syscall will be common
(i.e. shared between 32 and 64 bit userspace programs transparently by the kernel)
The name is kdlp
and the entry point is sys_kdlp
Take inspiration from the other nearby syscalls
On aarch64 the list comes from the modern shared table located in include/uapi/asm-generic/unistd.h
.
Find where __NR_syscalls
is defined and increment the value.
Just above that line, add a #define
for __NR_kdlp
with the next syscall number that is free
add an invocation of the __SYSCALL
macro with the __NR_kdlp
number and and the sys_kdlp
entry point.
Take inspiration from the other nearby syscalls.
Update the extra version tag in the main kernel Makefile so you can tell this kernel that has your syscall apart from the other kernel you compiled.
Update the config using make oldconfig
(this should only take a few seconds, and shouldn't require you to answer any questions).
Compile your new kernel with make -j $(nproc)
If the command finishes, make sure it was successful: echo $?
should output 0.
If it does not, re run make
without -j $(nproc)
to get a better error message.
Fix whatever issue you see, and re run make -j $(nproc)
.
Repeat this process of checking the result and fixing any errors until it compiles successfully.
Install your new kernel and its modules sudo make -j $(nproc) modules_install install
Reboot your vm and pick the new kernel.
If all is well it should boot successfully.
Congrats, you just wrote your first kernel code!
Make a patch out of your kernel code.
Find all untracked files, and files you modified by looking at the output of git status
within your linux kernel repository.
Add them to the staging area with git add
and then run git commit -s
and provide a message.
Format the resulting patch as a .patch
file with git format-patch -1
.
--rfc
or -v1
or --cover-letter
because this .patch
is not getting emailed directlyCopy the .patch
file into your named folder within the P1
folder in the ILKD submissions repo.
You can make your first commit for the assignment at this time.
Write a C program that invokes the new syscall.
Use the syscall(2)
function from the C standard library to invoke your syscall.
Check for any errors and print an appropriate message if they occur.
Otherwise, print the message you received from the kernel using the write(2)
syscall.
You can now make your second commit for the assignment at this time.
Write an assembly language program that behaves exactly the same as your C program with the following minor differences because you are not using the C standard library:
Your program starts at a label named _start
instead of a function named main
You need to explicitly call the exit
system call to terminate your program (trying to return from _start
will segfault)
You explicitly invoke the system calls by following the conventions of the linux syscall ABI (application binary interface)
For aarch64:
Arguments go into specific registers in order (x0
, x1
, x2
, x3
, x4
, x5
)
The syscall number goes into x8
The syscall is invoked using the svc #0
instruction
The return value is in x0
and is either a non-negative return value, or or negative error number (errno variable is a C library concept)
For x86-64:
Arguments go into specific registers in order (%rdi
, %rsi
, %rdx
, %r10
, %r8
, %r9
)
The syscall number goes into %rax
The syscall is invoked using the syscall
instruction
The return value is in %rax
and is either a non-negative return value, or a negative error number (errno variable is a C library concept)
Make sure to update Makefile to assemble and link your program
You can now make your third commit for the assignment at this time.
Test your C program and assembly program.
They should both print a message including your name and the name of executable file when you try them on the kernel you just compiled.
Try running strace
on your assembly program. Capture the output into a file using 2> strace_log
on the end of the command and add it to the repo as your fourth patch.
You can also test the error case by booting into a kernel without your syscall (i.e. the regular upstream kernel you compiled or the one packaged by fedora)
The kernel will return -ENOSYS
to indicate that the syscall is not implemented.
Both programs should print some sort of error message and exit early with a nonzero code.
The following pages will be of interest to a student:
Conventions:
Assembly files that must be pre-processed use the extension .S
Assembly files that are ready to be assembled use the extension .s
Refer to the Linux kernel documentation about adding syscalls for further guidance.
msg = (silence)
whoami = None
singularity v0.5 https://github.com/underground-software/singularity