Managing the Linux kernel module

Why does the user application work incorrectly? There are not many ways that would help identify the problem. In most cases, third-party software is required to maintain high availability. In this article, we will tell you how to set up monitoring of a user application through a Linux kernel module, as well as how to establish communication with a socket.

Introduction

Two-way interaction between user application and kernel module:

Application: any user-level application that can interact with a kernel module.

Kernel Module: Contains the definition of system calls that can be used by application and kernel APIs for health monitoring.

Linux Administration. Mega”

Checking the status of a kernel module

Let’s look at commands that are useful when writing and using kernel extensions.

Loading a kernel module

insmod: used to insert the module into the kernel.

example: insmod ‘kernel_ext_binary’

# insmod helloWorld.ko
Welcome to Hello world Module.

Unloading a kernel module

example: rmmod ‘kernel_ext_binary’

# rmmod helloWorld.ko
Goodbye, from Hello world.

List of all running kernel modules

lsmod: Lists all loaded kernel modules.

example:lsmod | grep ‘kernel_ext_binary’

# lsmod | grep hello
helloWorld 12189  1

Detailed information about the kernel module

modinfo: Displays additional information about the module.

example: modinfo hello*.ko

# modinfo helloWorld.ko
filename:       /root/helloWorld.ko
description:    Basic Hello World KE
author:         helloWorld
license:        GPL
rhelversion:    7.3
srcversion:     5F60F86F84D8477986C3A50
depends:
vermagic:       3.10.0-514.el7.ppc64le SMP mod_unload modversions

The listed commands can be run on the console and through a binary application by calling system().

Communication with user space

Userspace must open the file at the specified path using the open() API. This file is used by the user application and the kernel module to communicate with each other. All commands and data from the user application are written to this file, from which the kernel module reads and executes actions. The opposite is also possible.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);

Example:

int fd;
#define DEVICE_FILE_NAME "/dev/char_dev"
fd = open(DEVICE_FILE_NAME, 0);

Return value open() is a file descriptor, a small non-negative integer that is used in subsequent system calls (in this case ioctl).

Using ioctl calls

System call ioctl() can be called from user space to control basic device settings.

#include <sys/ioctl.h>

int ioctl(int fd, int cmd, ...);

fd is the file descriptor returned from open()a cmd – the same as implemented in ioctl() kernel module.

Example:

#define IOCTL_SEND_MSG _IOR(MAJOR_NUM, 0, char *)
int ret_val;
char message[100];
ret_val = ioctl(file_desc, IOCTL_SEND_MSG, message);
if (ret_val < 0) {
printf("ioctl_send_msg failed:%d\n", ret_val);
exit(−1);
}

In the given example IOCTL_SEND_MSG — a command that is sent to the module.

_IOR means that the application generates a command number ioctl to pass information from the user application to the kernel module.

First argument MAJOR_NUMis the main number of the device we are using.

The second argument is the command number (there may be several of them with different values).

The third argument is the type we want to pass from the process to the kernel.

Similarly, a user application can receive a message from the kernel with a slight change in the arguments ioctl.

Handling Threads in a Kernel Module

In the following sections, we’ll look at ways to handle multithreading in the context of the kernel.

Create a thread

We can create multiple threads in a module using the following calls:

#include <linux/kthread.h>
static struct task_struct * sampleThread = NULL;
sampleThread = kthread_run(threadfn, data, namefmt, …)

kthread_run() creates a new thread and tells it to start.

threadfn is the name of the function to run.

data * is a pointer to the function arguments.

namefmt – stream name (in the output of the command ps)

Stopping the flow

We can stop running threads using the call:

kthread_stop(sampleThread)

Establishing a socket connection

It is possible to create a raw socket using the function sock_create(). Through this socket, the kernel module will communicate with other user-level applications inside or outside the host.

struct socket *sock;
struct sockaddr_ll *s1 = kmalloc(sizeof(struct sockaddr_ll),GFP_KERNEL);
result = sock_create(PF_PACKET, SOCK_RAW, htons(ETH_P_IP), &sock);
if(result < 0)
{
printk(KERN_INFO "[vmmKE] unable to create socket");
    return -1;
}

//copy the interface name to ifr.name  and other required information.
strcpy((char *)ifr.ifr_name, InfName);
s1->sll_family = AF_PACKET;
s1->sll_ifindex = ifindex;
s1->sll_halen = ETH_ALEN;
s1->sll_protocol = htons(ETH_P_IP);

result = sock->ops->bind(sock, (struct sockaddr *)s1, sizeof(struct sockaddr_ll));
if(result < 0)
{
printk(KERN_INFO "[vmmKE] unable to bind socket");
    return -1;
}

By using sock_sendmsg() a kernel module can send data using a message structure.

struct msghdr message;
int ret= sock_sendmsg(sock, (struct msghdr *)&message);

Signal generation to a user-space process

Signals can also be generated from a kernel module into a custom application. If the process ID (PID) is known to the kernel using this pid, the module can fill in the required pid structure and pass it to send_sig_info() to start a signal.

struct pid *pid_struct = find_get_pid(pid);
struct task_struct *task = pid_task(pid_struct,PIDTYPE_PID);
int signum = SIGKILL, sig_ret;
struct siginfo info;
memset(&info, '\0', sizeof(struct siginfo));
info.si_signo = signum;
//send a SIGKILL to the daemon
sig_ret = send_sig_info(signum, &info, task);
if (sig_ret < 0)
{
printk(KERN_INFO "error sending signal\n");
return -1;
}

Log rotation

If the user wants to redirect all logs associated with a kernel module to a specific file, an entry must be added to rsyslog (/etc/rsyslog.conf) as follows:

:msg,startswith,"[HelloModule]" /var/log/helloModule.log

This allows rsyslog to redirect all kernel logs starting with [Hello Module]to the module /var/log/helloModule.log file.

Example: Users can write their own rotation script and place it in /etc/logrotate.d.

"/var/log/helloModule.log" {
daily
rotate 4
maxsize 2M
create 0600 root
postrotate
    service rsyslog restart > /dev/null
endscript
}

Scenario daily checks if the size of the log file is larger than 2 MB, and supports four rotation of this file. If the log size exceeds 2 MB, a new file will be created with the same name and file permissions 0600and a date and time stamp will be added to the old file.

After rotation, it will restart the rsyslog service.

File creation

Refer to the contents of the makefile to generate binaries for the program sample:

obj−m += helloWorld.o
all:
make −C /lib/modules/$(shell uname −r)/build M=$(PWD) modules
clean:
make −C /lib/modules/$(shell uname −r)/build M=$(PWD) clean

Note: The example is based on the RHEL variant. Other makefile implementations may differ.

Integration of a kernel module with a user application

The user application uses calls ioctl to send data to the kernel module. In the example below, these calls ioctl can be used to send application details or send updates at a later point in time.

Custom Application Example

The example includes all the concepts described earlier.

# cat helloWorld.h

#ifndef HELLOWORLD_H
#define HELLOWORLD_H
#include <linux/ioctl.h>

// cmd ‘KE_DATA_VAR’ to send the integer type data
#define KE_DATA_VAR _IOR('q', 1, int *)

#endif

# cat helloWorld.c

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "helloWorld.h"

/* @brief: function to load the kernel module */
void load_KE()
{
    printf ("loading KE\n");
    if (system ("insmod /root/helloWorld.ko") == 0)
    {
        printf ("KE loaded successfully");
    }
}

/* @brief: function to unload the kernel module */
void unload_KE()
{
    printf ("unloading KE\n");
    if (system ("rmmod /root/helloWorld.ko") == 0)
    {
        printf ("KE unloaded successfully");
    }
}

/* @brief: method to send data to kernel module */
void send_data(int fd)
{
    int v;

    printf("Enter value: ");
    scanf("%d", &v);
    getchar();
    if (ioctl(fd, KE_DATA_VAR, &v) == -1)
    {
        perror("send data error at ioctl");
    }
}

int main(int argc, char *argv[])
{
    const char *file_name = "/dev/char_device"; //used by ioctl
    int fd;
    enum
    {
        e_load, //load the kernel module
        e_unload, //unload the kernel module
        e_send, //send a HB from test binary to kernel module
    } option;

    if (argc == 2)
    {
        if (strcmp(argv[1], "-l") == 0)
        {
            option = e_load;
        }
        else if (strcmp(argv[1], "-u") == 0)
        {
            option = e_unload;
        }
                }
        else if (strcmp(argv[1], "-s") == 0)
        {
            option = e_send;
        }
        else
        {
            fprintf(stderr, "Usage: %s [-l | -u | -s ]\n", argv[0]);
            return 1;
        }
    }
    else
    {
        fprintf(stderr, "Usage: %s [-l | -u | -s ]\n", argv[0]);
        return 1;
    }

    if ((option != e_load) && (option != e_unload))
    {
        fd = open(file_name, O_RDWR);
        if (fd == -1)
        {
            perror("KE ioctl file open");
            return 2;
        }
    }
    switch (option)
    {
        case e_load:
            load_KE();
            break;
        case e_unload:
            unload_KE();
            break;
        case e_send:
            send_data(fd);
            break;
        default:
            break;
    }

    if ((option != e_load) && (option != e_unload))
    {
        close (fd);
    }
return 0;
}

Sample kernel module
# cat helloWorld.c
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/socket.h>
#include <linux/ioctl.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/kmod.h>
#include <linux/if.h>
#include <linux/net.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/time.h>
#include <linux/delay.h>

typedef struct
{
    char ethInfName[8];
    char srcMacAdr[15];
    char destMacAdr[15];
    int ifindex;
}KEConfig_t;

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Owner Name");
MODULE_DESCRIPTION("Sample Hello world");
MODULE_VERSION("0.1");

static char *name = "world";
static struct task_struct *ke_thread;
static struct KEConfig_t KECfg;
module_param(name, charp, S_IRUGO);
MODULE_PARM_DESC(name, "The name to display in /var/log/kern.log");


/* @brief: create socket and send required data to HM
 * creates the socket and binds on to it.
 * This method will also send event notification
 * to HM.
 * */
static int createSocketandSendData(char *data)
{
    int ret_l =0;
    mm_segment_t oldfs;
    struct msghdr message;
    struct iovec ioVector;

    int result;
    struct ifreq ifr;
    struct socket *sock;
    struct sockaddr_ll *s1 = kmalloc(sizeof(struct sockaddr_ll),GFP_KERNEL);
    if (!s1)
    {
       printk(KERN_INFO "failed to allocate memory");
       return -1;
    }
    printk(KERN_INFO "inside configureSocket");
    memset(s1, '\0', sizeof(struct sockaddr_ll));
    memset(픦, '\0', sizeof(ifr));

    result = sock_create(PF_PACKET, SOCK_RAW, htons(ETH_P_IP), &sock);
    if(result < 0)
    {
        printk(KERN_INFO "unable to create socket");
        return -1;
    }
    printk(KERN_INFO "interface: %s", KECfg.ethInfName);
    printk(KERN_INFO "ifr index: %d", KECfg.ifindex);
    strcpy((char *)ifr.ifr_name, KECfg.ethInfName);

    s1->sll_family = AF_PACKET;
    s1->sll_ifindex = KECfg.ifindex;
    s1->sll_halen = ETH_ALEN;
    s1->sll_protocol = htons(ETH_P_IP);
result = sock->ops->bind(sock, (struct sockaddr *)s1, sizeof(struct sockaddr_ll));
    if(result < 0)
    {
        printk(KERN_INFO "Unable to bind socket");
        return -1;
    }

    //create the message header
    memset(&message, 0, sizeof(message));
    message.msg_name = sockData->sock_ll;
    message.msg_namelen = sizeof(*(sock_ll));

    ioVector.iov_base = data;
    ioVector.iov_len  = sizeof(data);
    message.msg_iov = &ioVector;
    message.msg_iovlen = 1;
    message.msg_control = NULL;
    message.msg_controllen = 0;
    oldfs = get_fs();
    set_fs(KERNEL_DS);
    ret_l = sock_sendmsg(sockData->sock, &message, sizeof(data));


    return 0;
}

static long ke_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
    int b;

    switch (cmd)
    {
        case KE_DATA_VAR:
        if (get_user(b, (int *)arg))
        {
        return -EACCES;
        }
        //set the time of HB here
        mutex_lock(&dataLock);
        do_gettimeofday(&hbTv);
        printk(KERN_INFO "time of day is %ld:%lu \n", hbTv.tv_sec, hbTv.tv_usec);
        printk(KERN_INFO "data %d\n", b);
        //send data out
        createSocketandSendData(&b);
        mutex_unlock(&dataLock);
        break;
        default:
            return -EINVAL;
    }

    return 0;
}

/* @brief: method to register the ioctl call */
static struct file_operations ke_fops =
{
    .owner = THIS_MODULE,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
    .ioctl = ke_ioctl
#else
    .unlocked_ioctl = ke_ioctl
#endif
};

/* @brief The thread function */
int ke_init()
{
    printk(KERN_INFO "Inside function");
    return 0;
}

/* @brief The LKM initialization function */
static int __init module_init(void)
{
   printk(KERN_INFO "module_init initialized\n");
   if ((ret = alloc_chrdev_region(&dev, FIRST_MINOR, MINOR_CNT, "KE_ioctl")) < 0)
   {
       return ret;
   }

   cdev_init(&c_dev, &ke_fops);

   if ((ret = cdev_add(&c_dev, dev, MINOR_CNT)) < 0)
   {
       return ret;
   }

   if (IS_ERR(cl = class_create(THIS_MODULE, "char")))
   {
       cdev_del(&c_dev);
       unregister_chrdev_region(dev, MINOR_CNT);
       return PTR_ERR(cl);
   }
   if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, "KEDevice")))
   {
       class_destroy(cl);
       cdev_del(&c_dev);
       unregister_chrdev_region(dev, MINOR_CNT);
       return PTR_ERR(dev_ret);
   }

   //create related threads
   mutex_init(&dataLock); //initialize the lock
   KEThread = kthread_run(ke_init,"KE thread","KEThread");

  return 0;
}

void thread_cleanup(void)
{
    int ret = 0;

    if (ke_thread)
    ret = kthread_stop(ke_thread);
    if (!ret)
        printk(KERN_INFO "Kernel thread stopped");
}

/* @brief The LKM cleanup function */
static void __exit module_exit(void)
{
   device_destroy(cl, dev);
   class_destroy(cl);
   cdev_del(&c_dev);
   unregister_chrdev_region(dev, MINOR_CNT);

   thread_cleanup();
   printk(KERN_INFO "Exit %s from the Hello world!\n", name);
}

module_init(module_init);
module_exit(module_exit);

A module always starts with either init_moduleor from a function that you specify with a call module_init(). This function tells the kernel what functionality the module provides and configures the kernel to run the module’s functions when they are needed.

All modules end with a call to either cleanup_module(), or a function that you specify with a call module_exit(). This is an exit function for modules – it undoes everything that the input function has done.

Note: suppose the file is opened in user space with the function open()which is used to communicate with the kernel module.

If any call execve() is executed by a user process, you need to set the socket option FD_CLOEXEC in fd (file descriptor).

fd = open(“/dev/char_device”, O_RDWR);
fcntl(fd, F_SETFD, FD_CLOEXEC);

If the parameter FD_CLOEXEC not installed for this fdthe file descriptor must remain open when calling execve().

Briefly about the main

The article looked at ways to monitor user applications and restart them in case of a crash or freeze. We also analyzed ways to rotate the kernel logs and establish a second-level socket connection with user applications.

Linux Administration. Mega”

Similar Posts

Leave a Reply