The system call provides an interface to the operating system services.
Application developers often do not have direct access to the system calls, but can access them through an application programming interface (API). The functions that are included in the API invoke the actual system calls. By using the API, certain benefits can be gained:
•Portability: As long a system supports an API, any program using that API can compile and run.
•Ease of Use: Using the API can be significantly easier, than using the actual system call.
Fig1. How System Call works between the User Space and the Kernel Space. |
Consider, the example of a simple system call, write(), called from an User Process. The User Process calls 'write()'. This invokes the Kernel through an INT, interrupt instruction, where the write operation is conveyed as an index into the syscall[] table. The file descriptor argument contains, among other things, a MAJOR Device number so that the Kernel's write function can access the driver's file operations structure. The Kernel calls the driver's write function, which copies the user space buffer into the kernel space.
An Example: Invoking a given System Call
Now, coming to Raspberry Pi, which is a Broadcom SOC,BCM 2835,based on ARM Processor. Every System Call is having an Index in the System Call Table. The Index is an Integer value which is passed to the Register R7, in case of ARM Platform. The registers, R0, R1 and R2 are used to pass the arguments of the System Call. The instruction, SWI, which is now being used as SVC, which is a Supervisor Call, used to jump to the Privileged Mode, to invoke the Kernel. The number embedded with SVC #num, is used to refer to the Handler.
svc #0
Hence, as an example, say, we want to invoke a System Call to print "Hello World\n". The System Call Index to invoke 'Write' is #4. Thus, the code will be something like,
.arch armv6
.section .rodata
.align 2
.data
HelloWorldString:
.ascii "Hello World\n"
.LC0:
.text
.align 2
.global main
.type main, %function
main:
mov r7, #4
mov r0, #1
ldr r1,=HelloWorldString
mov r2, #12
svc #0
@ Need to exit the program
mov r7, #1
mov r0, #0
svc #0
.L3:
.align 3
.L2:
.size main, .-main
.ident "GCC: (Debian 4.6.3-14+rpi1) 4.6.3"
.section .note.GNU-stack,"",%progbits
In, the above example, #4 is passed to register R7, which will invoke the System Call corresponding to index 4 of the System Call Table. Compile the above program on your RaspberryPi, using,
#gcc -o executable_name file_name.s
And execute the executable,
./executable_name
Now, check 'dmesg' using,
#dmesg | tail
The above command will display the message 'Hello World', which means, that we are able to successfully invoke the System Call.
Using the above example, we can Invoke a given System Call, from the User Process. In the next part, below, we will see, how we can register a System Call with the Kernel. And, later, we will invoke the registered System Call, from the User Process.
Registering a new System Call with the Kernel
In this section, we will see, how we can register a new System Call with the Kernel, for RaspberryPi. For, this we must know, how to cross-compile the Linux Kernel for our RaspberryPi Platform. The Kernel version can be found by executing the command, uname -a. Now, the version of the Kernel shown as the output of the command #uname -a must match with the version of the Kernel source that we are trying to cross compile to Register our System Call. By looking into the 'Makefile' inside the Kernel Source directory, we can get that info, as shown below:
Kernel Makefile through which we can know kernel version, here for me the RPi was having a version 3.6.11, and the same can be found here. |
- /arch/arm/kernel/calls.S
- /arch/arm/include/asm/unistd.h
- /arch/arm/kernel/sys_arm.c
- /include/linux/syscalls.h
CALL(sys_ni_syscall)
and change it to,
CALL(sys_MyHelloWorld)
I choose, to give the name, 'MyHelloWorld', any other name would be fine too.
Fig2. Add the System Call entry in 'calls.S' |
In the second file, '/arch/arm/include/asm/unistd.h', contains the mapping with respect to the System Table Base, which is a memory mapped address value. The Index that we chose in calls.S, would act as Offset for the same in the Kernel Address Space of the System Call.
Here, at Index 59, we see that, it is commented as something follows:
/* 59 was sys_olduname */
which will be modified as below:
#define __NR_MyHelloWorldKernel (__NR_SYSCALL_BASE+ 59)
Fig 3. Add Offset Entry corresponding to the Index in 'unistd.h' file |
In the third file, '/arch/arm/kernel/sys_arm.c', add the system call handler code.
asmlinkage long sys_MyHelloWorld()
{
printk("My Hello World system call\n");
return 0;
}
Fig 4. Add the System Call Handler code in 'sys_arm.c' file |
And, in the last file, '/include/linux/syscalls.h', add the following above as System Call Handler prototype.
asmlinkage long sys_MyHelloWorld(void);
Fig 5. Add the prototype of the System Call handler in 'syscalls.h' file |
After,working with these four files and adding the System Call Handler, the System Call has to be registered with the Kernel, for which the Kernel has to be cross-compiled, since i am trying to compile it on my x86 machine, with Ubuntu installed, and the target is ARM Based, Raspberry Pi.
- Get the tarball,from GIT Hub, https://github.com/raspberrypi/linux/tree/rpi-3.6.y, and download it in the form of ZIP. Make the changes in the above four files, mentioned above.
- Get the tools tarball.
- Extract the Toolchain and the Linux Source, #tar -xvzf rpi-3.6.y.tar.gz and #tar -xvzf master.tar.gz (tools). These tools are used as the cross compile toolchain.
- Set the environment variable CCPREFIX:
- export CCPREFIX= /home/rajiv/RPI/tools-master/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
- Set the environment variable KERNEL_SRC:
- export KERNEL_SRC= /home/rajiv/RPI/linux-rpi-3.6.y
- First, clean the source:
- #make ARCH=arm CROSS_COMPILE=${CCPREFIX} distclean
- Then set the correct config file for the Board, there are lots of config file that one can find inside '/arch/arm/configs/' folder.
- #make ARCH=arm CROSS_COMPILE=${CCPREFIX} RPI_defconfig
- Otherwise, place the following config file that i have attached in the form of .txt here(https://drive.google.com/file/d/0B6Cu_2GN5atpMzUxOXJCSmlzOEk/edit?usp=sharing), rename it as RPI_defcofig, copy and place it inside '/arch/arm/configs' directory of the Linux Kernel Source.
- After, this start the compilation
- #make ARCH=arm CROSS_COMPILE=${CCPREFIX}
- #make ARCH=arm CROSS_COMPILE=${CCPREFIX} modules_install
- #make ARCH=arm CROSS_COMPILE=${CCPREFIX} install
- Finally, the kernel image will be built in the following path, 'arch/arm/boot/' as 'zimage', rename it as 'kernel.img'. Place it inside the Boot Partition of the RaspberryPi.
Fig 6. The User Space Program to invoke our added System Call |
After, this we will compile the above user space program.
Fig 7. Compiling the User Space Program |
After, this we execute the Binary executable.
Fig 8. Executing the Binary Executable |
After, this we check the 'dmesg' using dmesg | tail command.
Fig 9. The 'dmesg' shows that our added System Call is being executed. The 'printk' statements are present |
So, this ends our discussion on Registering a Simple System Call with RaspberryPi.
Comments and thoughts most welcomed.
I'm seeing an undefined reference error to my newly defined system call in entry-common.S
ReplyDeleteI'm seeing an undefined reference error to my newly defined system call in entry-common.S
ReplyDeleteHi. Thank you for your great post. I found a typo error in one of your file path, so I wanted to bring it to your attention. Then second file path is
ReplyDeletearch/arm/include/asm/unistd.h
In your post you accidentally typed arch/arm/include/arm/unistd.h
Thanks for the Typo error...yep, i corrected it...Regards, Rajiv.
ReplyDeleteHi Rajiv, Thank you for the post. I have a doubt, in which file do we set environmental variable?
ReplyDelete# Consider adding this at the end of your $HOME/.bashrc file to set environment variable
Deleteexport PATH="$PATH:/home/PACKAGE/bin"
After this run in the cmdline terminal,
source $HOME/.bashrc
You can also set environment variables in $HOME/.bash_aliases
say as an example, run the following in your cmdline terminal,
echo "export SOURCE_FOLDER=$HOME/sources" >> $HOME/.bash_aliases
echo "export LOCAL_BUILD=$HOME/local-builds" >> $HOME/.bash_aliases
echo "export LD_LIBRARY_PATH=$HOME/local-builds/lib:$LD_LIBRARY_PATH" >> $HOME/.bash_aliases
echo "export PATH=$HOME/local-builds/bin:$PATH" >> $HOME/.bash_aliases
echo "export PKG_CONFIG_PATH=$HOME/local-builds/lib/pkgconfig:$PKG_CONFIG_PATH" >> $HOME/.bash_aliases
And after this run,
source $HOME/.bash_aliases
which will set your Env variables too.