0

I am really new to this Kernel stuff. I went here and I found this code that outputs process information like its ID.

main.c:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/sched.h>
 
 
struct task_struct *task;        /*    Structure defined in sched.h for tasks/processes    */
struct task_struct *task_child;        /*    Structure needed to iterate through task children    */
struct list_head *list;            /*    Structure needed to iterate through the list in each task->children struct    */
 
int iterate_init(void)                    /*    Init Module    */
{
    printk(KERN_INFO "%s","LOADING MODULE\n");    /*    good practice to log when loading/removing modules    */
     
    for_each_process( task ){            /*    for_each_process() MACRO for iterating through each task in the os located in linux\sched\signal.h    */
        printk(KERN_INFO "\nPARENT PID: %d PROCESS: %s STATE: %ld",task->pid, task->comm, task->state);/*    log parent id/executable name/state    */
        list_for_each(list, &task->children){                        /*    list_for_each MACRO to iterate through task->children    */
 
            task_child = list_entry( list, struct task_struct, sibling );    /*    using list_entry to declare all vars in task_child struct    */
     
            printk(KERN_INFO "\nCHILD OF %s[%d] PID: %d PROCESS: %s STATE: %ld",task->comm, task->pid, /*    log child of and child pid/name/state    */
                task_child->pid, task_child->comm, task_child->state);
        }
        printk("-----------------------------------------------------");    /*for aesthetics*/
    }    
     
 
    return 0;
 
}                /*    End of Init Module    */
     
void cleanup_exit(void)        /*    Exit Module    */
{
 
 
    printk(KERN_INFO "%s","REMOVING MODULE\n");
 
}                /*    End of Exit Module    */
 
module_init(iterate_init);    /*    Load Module MACRO    */
module_exit(cleanup_exit);    /*    Remove Module MACRO    */
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ITERATE THROUGH ALL PROCESSES/CHILD PROCESSES IN THE OS");
MODULE_AUTHOR("Laerehte");

Makefile:

obj-m += main.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

Executed with ./ins (with chmod +x)

Code:

sudo insmod main.ko
sudo rmmod main
sudo dmesg -c

I looked up how to find how much memory a process uses, and I found this question: Memory usage of current process in C.

Correct me if I'm wrong here, but I'm thinking that you can read the current RAM usage of these processes by looking in /proc/[process_id]/status. I found out from another place(forgot where) that within this file, there is something called VmRSS that would hold the current RAM usage of the process.

You can apparently use:

ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos);

to read a file, but I have not been able to modify main.c with this successfully. I need to find the size of the file, but I also have not been able to use vfs_stat correctly. When I just try some constant integer, I get all 0s in the buffer anyway. I don't know how to use these functions properly. I'm trying to modify main.c so that I will see the RAM usage of these processes along with the other information. Much of the information I found was outdated. Can anyone help?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
fas117
  • 3
  • 5

1 Answers1

2

While you can technically open and read files from kernel space, it's usually a bad idea for multiple reasons. Whatever information is provided under /proc is already available to the kernel, you just need to figure out where it is and how to obtain it, and you will be able to do everything in your module without reading any file.

You are interested in knowing how much RAM is a particular process using given its PID, and you are correct that this statistic is available in /proc/<PID>/status: in fact, it is labeled as "VmRSS" which means "virtual memory resident set size". If we take a look at the kernel source code, we can see exactly how that number is calculated:

The kernel function called when reading /proc/<PID>/status is proc_pid_status() in fs/proc/array.c, which then calls (among the other things) task_mem().

    // ...
    anon = get_mm_counter(mm, MM_ANONPAGES);
    file = get_mm_counter(mm, MM_FILEPAGES);
    shmem = get_mm_counter(mm, MM_SHMEMPAGES);
    // ...
    hiwater_rss = total_rss = anon + file + shmem;
    // ...
    SEQ_PUT_DEC(" kB\nVmHWM:\t", hiwater_rss);
    SEQ_PUT_DEC(" kB\nVmRSS:\t", total_rss); // <===== here it is
    SEQ_PUT_DEC(" kB\nRssAnon:\t", anon);
    // ...

You can therefore obtain this information in the same way. First get ahold of the task_struct of the process you want to check, then inspect the ->mm field like get_mm_counter() does, or simply using get_mm_rss() from include/linux/mm.h, which does exactly what we want.

Note that:

  1. The value obtained from get_mm_counter() or get_mm_rss() is the number of pages, you will have to multiply this by the page size (simply shift left by PAGE_SHIFT).
  2. You will only be able to do this if your kernel was compiled with MMU support (CONFIG_MMU=y), you can check this in your module code with a simple #ifdef or #ifndef.

Here's a working example module to do this for all processes:

// SPDX-License-Identifier: GPL-3.0
#include <linux/kernel.h>       // printk(), pr_*()
#include <linux/module.h>       // THIS_MODULE, MODULE_VERSION, ...
#include <linux/init.h>         // module_{init,exit}
#include <linux/sched/task.h>   // struct task_struct, {get,put}_task_struct()
#include <linux/sched/signal.h> // for_each_process()
#include <linux/sched/mm.h>     // get_task_mm(), mmput()
#include <linux/mm.h>           // get_mm_rss()

/* Tested on kernel 5.6, qemu-system-aarch64
 * Usage: sudo insmod task_rss
 *        sudo modprobe task_rss
 */

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

static int __init modinit(void)
{
    struct task_struct *tsk;
    struct mm_struct *mm;
    unsigned long rss;
    char comm[TASK_COMM_LEN];

#ifndef CONFIG_MMU
    pr_err("No MMU, cannot calculate RSS.\n");
    return -EINVAL;
#endif

    for_each_process(tsk) {
        get_task_struct(tsk);
        get_task_comm(comm, tsk);
        mm = get_task_mm(tsk);

        // https://www.kernel.org/doc/Documentation/vm/active_mm.rst
        if (mm) {
            rss = get_mm_rss(mm) << PAGE_SHIFT;
            mmput(mm);
            pr_info("PID %d (\"%s\") VmRSS = %lu bytes\n", tsk->pid, comm, rss);
        } else {
            pr_info("PID %d (\"%s\") is an anonymous process\n", tsk->pid, comm);
        }

        put_task_struct(tsk);
    }

    return 0;
}

static void __exit modexit(void)
{
    // This function is only needed to be able to unload the module.
}

module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Silly test module to calculare task RSS of all running tasks.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");

Output on my machine (qemu-system-aarch64):

/ # insmod task_rss.ko
[    7.306284] task_rss: loading out-of-tree module taints kernel.
[    7.312912] task_rss: PID 1 ("init") VmRSS = 4096 bytes
[    7.313295] task_rss: PID 2 ("kthreadd") is an anonymous process
[    7.314039] task_rss: PID 3 ("rcu_gp") is an anonymous process
...
[    7.330169] task_rss: PID 146 ("sh") VmRSS = 1363968 bytes
[    7.330554] task_rss: PID 147 ("insmod") VmRSS = 1339392 bytes
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • I tried using " rss = get_mm_rss(task->mm);" right under "for_each_process( task )", but for some reason all I get is "Killed" right after typing in the insmod command, and then main.c seems to be stuck running and then I'm forced to restart the virtual machine. Your example is great, but I need to make it so it outputs the VmRSS for every process in that main.c loop. I need to be able to loop it and I don't know how to end the loop. – fas117 Apr 23 '21 at 19:38
  • @fas117 I've updated the code in my example, the issue is that you need to check that `->mm` is not `NULL` before actually calculating the RSS. Some tasks do not have a mm at all, so in that case you can ignore them. – Marco Bonelli Apr 23 '21 at 22:05
  • I just want some clarification. You say that some of these processes do not have an mm at all. Does that mean they take up 0 bytes of RAM? – fas117 Apr 23 '21 at 22:20
  • @fas117 yes, they do not have any memory of their own, this is true for example for kernel threads (as you can see in my example output above with `kthreadd` and `rcu_gp`). In fact, if you try to `cat /proc/PID/status` for one of those you will get no `VmRSS` in the output. See [here](https://www.kernel.org/doc/Documentation/vm/active_mm.rst) for more info. – Marco Bonelli Apr 23 '21 at 22:22