Assignment 3: System Information Fetching Kernel Module
===
- Assignment 3: System Information Fetching Kernel Module
- Linux Kernel Module
- Descriptions
- Kernel Module: kfetch_mod
- Kfetch information mask
- Device operations
- Requirements
- Default logo
In this assignment, you are going to implement a kernel module that fetches the system information from the kernel, like below screenshot:

Implement a kernel module fetches system info from kernel
---
**Step 0: Create new program**
```
vim kfetch_mod_312551002.c
```
**Step 1: Implement the fetches system information function**
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mm.h> // For si_meminfo
#include <linux/utsname.h>
#include <linux/timekeeping.h>
#include <linux/sched.h>
#include <linux/sched/stat.h>
#include <linux/cpu.h>
#include <linux/sched/signal.h>
#include <linux/utsname.h>
#define DEVICE_NAME "kfetch_mod_312551002"
#define CLASS_NAME "kfetch"
#define BUFFER_SIZE 1024
// Information mask definitions
#define KFETCH_NUM_INFO 6
#define KFETCH_RELEASE (1 << 0)
#define KFETCH_NUM_CPUS (1 << 1)
#define KFETCH_CPU_MODEL (1 << 2)
#define KFETCH_MEM (1 << 3)
#define KFETCH_UPTIME (1 << 4)
#define KFETCH_NUM_PROCS (1 << 5)
#define KFETCH_FULL_INFO ((1 << KFETCH_NUM_INFO) - 1)
static int majorNumber;
static struct class *kfetchClass = NULL;
static struct cdev kfetchCdev;
static int mask_info = KFETCH_FULL_INFO;
static char data_buffer[BUFFER_SIZE];
static int dev_open(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t dev_write(struct file *, const char __user *, size_t, loff_t *);
static int dev_release(struct inode *, struct file *);
static struct file_operations fops = {
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
};
static int __init kfetch_init(void) {
printk(KERN_INFO "Kfetch: Initializing the Kfetch Module\n");
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber < 0) {
printk(KERN_ALERT "Kfetch failed to register a major number\n");
return majorNumber;
}
printk(KERN_INFO "Kfetch: Registered with major number %d\n", majorNumber);
kfetchClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(kfetchClass)) {
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(kfetchClass);
}
if (IS_ERR(device_create(kfetchClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME))) {
class_destroy(kfetchClass);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(device_create(kfetchClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME));
}
cdev_init(&kfetchCdev, &fops);
if (cdev_add(&kfetchCdev, MKDEV(majorNumber, 0), 1) < 0) {
device_destroy(kfetchClass, MKDEV(majorNumber, 0));
class_destroy(kfetchClass);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to add cdev\n");
return -1;
}
printk(KERN_INFO "Kfetch: Device class created correctly\n");
return 0;
}
static void __exit kfetch_exit(void) {
cdev_del(&kfetchCdev);
device_destroy(kfetchClass, MKDEV(majorNumber, 0));
class_destroy(kfetchClass);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_INFO "Kfetch: Module successfully unloaded\n");
}
static int dev_open(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "Kfetch: Device has been opened\n");
return 0;
}
static int dev_release(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "Kfetch: Device successfully closed\n");
return 0;
}
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
struct sysinfo si;
struct timespec64 uptime;
unsigned long mem_free_mb, mem_total_mb, uptime_seconds;
int num_procs = 0;
struct task_struct *task;
char *info = NULL;
char *formatted_output = NULL;
char *info_ptr;
char *line;
char *next_info;
char *next_line;
int i;
char hostname[65]; // HOST_NAME_MAX typically is 64
char separator_line[65]; // Same size as hostname for simplicity
const char *logo_lines[] = {
" \e[33mLinux\e[0m ",
" .-. ",
" (.. | ",
" \e[33m<>\e[0m | ",
" / --- \\ ",
" ( | | | ",
" \e[33m|\\\e[0m\\_)___/\\)\e[33m/\\\e[0m ",
" \e[33m<__)\e[0m------\e[33m(__/\e[0m "
};
size_t hostname_len;
// Initialize the hostname and separator_line
memset(hostname, 0, sizeof(hostname));
strncpy(hostname, init_uts_ns.name.nodename, sizeof(hostname) - 1);
hostname_len = strlen(hostname);
memset(separator_line, '-', hostname_len);
separator_line[hostname_len] = '\0';
// Clear the buffer
memset(data_buffer, 0, BUFFER_SIZE);
// Gather system information
si_meminfo(&si);
ktime_get_boottime_ts64(&uptime);
for_each_process(task) if (task->mm) num_procs++;
mem_free_mb = (si.freeram * si.mem_unit) / 1024 / 1024;
mem_total_mb = (si.totalram * si.mem_unit) / 1024 / 1024;
uptime_seconds = uptime.tv_sec;
// Allocate info buffer dynamically
info = kmalloc(BUFFER_SIZE, GFP_KERNEL);
formatted_output = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!info || !formatted_output) {
kfree(info); // It's safe to call kfree with NULL
kfree(formatted_output);
return -ENOMEM;
}
// Prepare the info string with proper padding for each line
info_ptr = info;
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33m%s\e[0m\n%s\n", hostname, separator_line);
// Concatenate information based on the mask_info
if (mask_info & KFETCH_RELEASE || mask_info == KFETCH_FULL_INFO) {
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33mKernel:\e[0m %-20s\n", utsname()->release);
}
if (mask_info & KFETCH_CPU_MODEL || mask_info == KFETCH_FULL_INFO) {
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33mCPU:\e[0m %-20s\n", boot_cpu_data.x86_model_id);
}
if (mask_info & KFETCH_NUM_CPUS || mask_info == KFETCH_FULL_INFO) {
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33mCPUs:\e[0m %d / %-14d\n", num_online_cpus(), num_possible_cpus());
}
if (mask_info & KFETCH_MEM || mask_info == KFETCH_FULL_INFO) {
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33mMem:\e[0m %lu MB / %lu MB\n", mem_free_mb, mem_total_mb);
}
if (mask_info & KFETCH_NUM_PROCS || mask_info == KFETCH_FULL_INFO) {
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33mProcs:\e[0m %-d\n", num_procs);
}
if (mask_info & KFETCH_UPTIME || mask_info == KFETCH_FULL_INFO) {
info_ptr += snprintf(info_ptr, BUFFER_SIZE - (info_ptr - info), "\e[33mUptime:\e[0m %-lu mins\n", uptime_seconds / 60);
}
// Formatting the output to align side by side with the logo
next_info = info;
line = formatted_output;
for (i = 0; i < ARRAY_SIZE(logo_lines) || next_info; ++i) {
if (i < ARRAY_SIZE(logo_lines)) {
line += sprintf(line, "%-20s", logo_lines[i]);
} else {
line += sprintf(line, "%-20s", "");
}
if (next_info) {
next_line = strchr(next_info, '\n');
if (next_line) *next_line = '\0'; // Terminate the current line
line += sprintf(line, " %s\n", next_info);
next_info = next_line ? next_line + 1 : NULL;
} else {
line += sprintf(line, "\n");
}
}
// Copy the formatted buffer to user space
if (copy_to_user(buffer, formatted_output, strlen(formatted_output))) {
kfree(info);
kfree(formatted_output);
return -EFAULT; // Failed to copy to user space
}
// Free the dynamically allocated buffers
kfree(info);
kfree(formatted_output);
// Return the number of characters sent
return strlen(formatted_output);
}
static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset) {
// Update mask_info based on user input
if (len == sizeof(int)) {
if (copy_from_user(&mask_info, buffer, sizeof(int))) {
printk(KERN_ERR "Kfetch: Error setting mask_info\n");
return -EFAULT;
}
printk(KERN_INFO "Kfetch: Mask set to %d\n", mask_info);
return sizeof(int);
} else {
printk(KERN_ERR "Kfetch: Incorrect mask size\n");
return -EINVAL;
}
}
module_init(kfetch_init);
module_exit(kfetch_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benedict");
MODULE_DESCRIPTION("System Information Fetching Kernel Module");
MODULE_VERSION("0.1");
```
**Step 3: Create new Makefile**
```
vim Makefile
```
**Step 4: Implement a Makefile**
```c
obj-m += kfetch_mod_312551002.o
DEVICE_NAME := kfetch_mod_312551002
# Use shell to grab the major number dynamically. This will be empty if the device is not loaded.
MAJOR_NUMBER := $(shell grep $(DEVICE_NAME) /proc/devices | cut -d ' ' -f 1)
all: build unload remove_device_node load create_device_node
build:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
load:
# Check if module is already loaded, if yes, remove it
@if lsmod | grep $(DEVICE_NAME) &> /dev/null ; then \
echo "Module $(DEVICE_NAME) is already loaded, unloading first..."; \
sudo rmmod $(DEVICE_NAME) || true; \
fi
# Insert the module
sudo insmod $(DEVICE_NAME).ko
create_device_node:
# Check if device node already exists, if not, create it
@if [ ! -e /dev/$(DEVICE_NAME) ] ; then \
echo "Creating device node /dev/$(DEVICE_NAME)..."; \
sudo mknod /dev/$(DEVICE_NAME) c $(MAJOR_NUMBER) 0 ; \
sudo chmod 666 /dev/$(DEVICE_NAME) ; \
else \
echo "Device node /dev/$(DEVICE_NAME) already exists."; \
fi
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
unload:
# Remove the module if it is loaded
@if lsmod | grep $(DEVICE_NAME) &> /dev/null ; then \
echo "Unloading module $(DEVICE_NAME)..."; \
sudo rmmod $(DEVICE_NAME) || true; \
else \
echo "Module $(DEVICE_NAME) is not loaded."; \
fi
remove_device_node:
# Remove the device node if it exists
@if [ -e /dev/$(DEVICE_NAME) ] ; then \
echo "Removing device node /dev/$(DEVICE_NAME)..."; \
sudo rm -f /dev/$(DEVICE_NAME) ; \
else \
echo "Device node /dev/$(DEVICE_NAME) does not exist."; \
fi
.PHONY: all build load create_device_node clean unload remove_device_node
```
**Step 5: Compile written program**
```
make
```
**Step 6: Compile test program**
We have prepared the user-space program kfetch for you. Source code `kfetch.c` and the header file (shared with the kernel module) `kfetch.h` to test your module.
```
cc 。、kfetch.c -o kfetch
```
**Step 7: Test the program**
```
sudo ./kfetch -h
```

**Step 8: Test the program**
Initially, when the module is loaded, the first invocation without any options will display all the information. If the options -c and m are specified, only the information about the CPU model name and the memory will be displayed.
```
sudo ./kfetch -a
```

```
sudo ./kfetch -m -c
```
