Operating systems project
This project is aimed to cover the following features:
-
Develop a function that determines if an arithmetic expression is found correctly closed. The function should return true if the expression is correctly closed and false otherwise
It is understood by closed the fact that each symbol of parentheses and / or open bracket must have its corresponding closing. Example (5 + 5), [(1 + 1) * (2 + 2)], (((([1]))))
-
This function must be implemented in a system call that must receive as a parameter a pointer to the string containing the expression
-
The student must use C++ (or C) and the Linux kernel to add the system call
-
You will need to compile the operating system kernel to add the custom system call
-
You must create a program that receives as command line input the string to use and make use of the system call implemented in the operating system
For further reference, we included the files we manipulated (in their respective directories) to compile the linux kernel
There are 5 main components:
-
BIOS: When you turn on your machine, this program executes a hardware check; it also loads the operating system
-
Boot Loader
a. MRB: Loads and executes GRUB
b. GRUB: Allows you to choose the kernel image and it executes it
-
Kernel: Mounts the file system specifie3d in the GRUB; this file is compressed
-
Init: Decompress the kernel
-
Runlevel: Loads the file system and finishes the boot
Linux memory management subsystem is responsible, as the name implies, for managing the memory in the system. Dealing directly with physical memory is quite complex and to avoid this complexity a concept of virtual memory was developed.
The virtual memory abstracts the details of physical memory from the application software and with virtual memory, each memory access uses a virtual address.
Since the physical system memory is divided into page frames, or pages, every physical memory page can be mapped as one or more virtual pages. These mappings are described by page tables that allow translation from a virtual address used by programs to the physical memory address and you can access them with a pointer that is in a register.
- Ubuntu (64 bit) system
In this project we used Ubuntu VM (Virtualbox)
Tip: type sudo -s in your terminal before executing the upcoming commands so you will have a super user terminal session
-
Type
uname -r
to know your current kernel version. Remember this information because you'll use it to compare to the installed one -
Download kernel source
wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.17.4.tar.xz
-
Extract the kernel source code
tar: stores and extracts files from a tape or disk archivesudo tar -xvf linux-4.17.4.tar.xz -C/usr/src/
-x: extract files from an archive
-v: requested using the –verbose option, when extracting archives
-f: file archive; use archive file or device archive
-C: extract to the directory specified after it.(in this case /usr/src/)
Switch to directory
cd /usr/src/linux-4.17.4/
-
Define a new system call
a. Create a directory named balancedp/ and change the directory to balancedp/:
mkdir balancedp cd balancedp
b. Create a file balancedp.c
gedit balancedp.c
c. Write the following code:
#include <linux/kernel.h> #include <linux/linkage.h> #include <linux/syscalls.h> int areBracketsBalanced(char *s); SYSCALL_DEFINE1(balancedp, char *, src) { printk("%s\n", src); printk("Memory address of input %p\n", &src); if (areBracketsBalanced(src)) printk("Balanced \n"); else printk("Not Balanced \n"); return 0; } int areBracketsBalanced(char *s) { char *q=s; char *p; for (p=s; *p; p++) switch(*p) { case '(': *q++ = ')'; continue; case '{': *q++ = '}'; continue; case '[': *q++ = ']'; continue; case '0': continue; case '1': continue; case '2': continue; case '3': continue; case '4': continue; case '5': continue; case '6': continue; case '7': continue; case '8': continue; case '9': continue; case '+': continue; case '*': continue; case '-': continue; case '/': continue; default: if (q==s || *p != *--q) return 0; } return q==s; }
To understand better about this syscall macro you can check the detailed explanations section
d. Create a “Makefile” in the balancedp directory:
gedit Makefile
e. Add the following line to it:
obj-y := balancedp.o
This is to ensure that the balancedp.c file is compiled and included in the kernel source code
-
Adding balancedp/ to the kernel's Makefile:
a. Go to parent dir (
cd ../
) and open "Makefile"gedit Makefile
Search for core-y in the document, you’ll find this line as the second instance of your search:
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
Add "balancedp/" to the end of this line:
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ balancedp/
This is to tell the compiler that the source files of our new system call (sys_balancedp()) are in present in the balancedp directory
-
Add the new system call to the system call table:
a. Go to directory inside linux-4.17.4/:
cd arch/x86/entry/syscalls/
Edit the table
gedit syscall_64.tbl
b. Go to the last line of the first chunk and write:
333 64 balancedp __x64_sys_balancedp
- We wrote 333 because in the previous line the number entry was 332. This number it will be used in later steps
- We wrote 64 because our system is 64 bit
- The third column indicates the name of the syscall
- The last column refers to the entrypoint
-
Add new system call to the system call header file:
a. Go to the linux-4.17.4/ directory and type:
cd include/linux/ gedit syscalls.h
b. Add the following line to the end of the document before the #endif statement:
asmlinkage long sys_balancedp(char *);
This defines the prototype of the function of our system call. "asmlinkage" is a key word used to indicate that all parameters of the function would be available on the stack.
-
Compile the kernel:
a. Install necessary packages
apt-get install gcc apt-get install libncurses5-dev apt-get install bison apt-get install flex apt-get install libssl-dev apt-get install libelf-dev apt-get update apt-get upgrade
b. To configure your kernel use the following command in your linux-4.17.4/ directory:
make menuconfig
You will get a pop up window with the list of menus and you can select the items for the new configuration. If your unfamiliar with the configuration just check for the file systems menu and check whether “ext4” is chosen or not, if not select it and save the configuration.
c. Compile the kernel
make
-
Install / update Kernel:
a. Run the following command in your terminal:
make modules_install install
b. To update the kernel in your system you will need to reboot
shutdown -r now
After rebooting you can verify the kernel version:
uname -r
You can compare it to the one you saw before and it must be different
-
Go to any directory and create a <file_name>.c file
To keep simpicity we did it in the Desktop and we named the file "test.c"
-
Write the following code in the file:
#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/syscall.h> #include <string.h> int main(void){ char st[256]; printf("Enter an arithmetic operation: "); gets(st); printf("Memory address of input (outside syscall): %p\n", &st); long sta = syscall(333, st); printf("return value from syscall: %ld\n", sta); return 0; }
-
Compile and run the program:
gcc <file_name>.c ./a.out
Enter an expression to check parentheses. The address of the input you entered will be displayed as well as the return of the program
-
Check the message of your kernel run:
dmesg
This will display the input you entered before and the address that the kernel used to handle it. Down below you will see if your input has balanced parentheses or not
The purpose of macros is to ensure that the proper compiler pragmas1 are applied to the function
It's syntax is:
SYSCALL_DEFINEn(name_of_the_syscall, type_of_argument1, name_of_argument1, type_of_argument2, name_of_argument2, ...)
n stands for the number of arguments the syscall is going to receive
This macro generates two functions:
a. SYSCALL_METADATA() Basic tracer to catch the syscall entry and exit events. Builds a boilerplate function to receive the arguments and match their types
b. __SYSCALL_DEFINEx() It involves another read function and also implements several instances to validate the types of the entered arguments
1 In computer programming, a directive or pragma (from "pragmatic") is a language construct that specifies how a compiler (or other translator) should process its input.