Добавьте код в ядро Linux, который создаст процесс ввода-вывода, а затем прочитайте значение по умолчанию в этой записи

#c #linux-kernel #scheduler #readfile #proc

Вопрос:

У меня есть задание, в котором я должен создать proc_entry, который может быть записан(пользователем) и прочитан(ядром).

Мотив заключается в том, что код ядра должен иметь возможность считывать значение в proc_entry и использовать его позже в качестве порогового значения для количества файлов, открытых процессом. Если процесс открыл больше файлов, чем это пороговое значение, он будет оштрафован в планировщике. Поскольку пользователь также может изменять значение внутри этого proc_entry, таким образом, ядро будет использовать этот порог динамически.

Большинство кодов, которые я видел в Интернете, рассказывают, как создать модуль, который создаст такую запись, и другой модуль, который, учитывая путь к этой записи, прочитает присутствующую строку.

Код модуля для создания proc_entry ввода — вывода — (Используя приведенный ниже модуль, я могу создать proc_entry «/proc/my_proc_entry_write», в который я могу записать с помощью — «echo 200 > /proc /my_proc_entry_write» и прочитать это значение через «cat /proc/my_proc_entry_write»>)-

 #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <asm/types.h>
     #define DATA_SIZE 3000000 // We can keep 1024 bytes of data with us.
    #define MY_PROC_ENTRY "my_proc_entry_write"
    #define PROC_FULL_PATH "/proc/my_proc_entry_write"
    
    struct proc_dir_entry *proc;
    int len;
    char *msg = NULL;
    
    /*
     * Function to write to the proc. Here we free get the new value from buffer,
     * count from the buffer and then overwrite the data in our file.
     *
     * Note that - you can have any other implementation as well for this, all you have to
     * ensure that you comply with the expectations of the write() system calls
     * like filling in the buffer, and returning the numbers of character written.
     */
    
    static ssize_t my_proc_write(struct file *filp, const char __user * buffer, size_t count, loff_t *pos)  // buffer, of length count, should be copied to kernel
    {
        int i;
        char *data = PDE_DATA(file_inode(filp));   // gives data pointer of file
    
        if (count > DATA_SIZE) {
            return -EFAULT;
        }
    
        printk(KERN_INFO "Printing the data passed. Count is %lu", (size_t) count);
        for (i=0; i < count; i  ) {
            printk(KERN_INFO "Index: %d . Character: %c Ascii: %d", i, buffer[i], buffer[i]);    
        }
    
        printk(KERN_INFO "Writing to proc");
        if (copy_from_user(data, buffer, count)) {
            return -EFAULT;
        }
    
        data[count-1] = '';
    
        printk(KERN_INFO "msg has been set to %s", msg);   // Due to kmalloc, msg points to this. So, when we write, msg is changed.
        printk(KERN_INFO "Message is: ");
        for (i=0; i < count; i  ) {
            printk(KERN_INFO "n Index: %d . Character: %c", i, msg[i]);
        }
    
        *pos = (int) count;   // length written to be copied to pos at end
        len = count-1;       // len is length of string. count is len 1, to accomodate the .
    
        return count;
    }
    
    /*
     * Function to read the proc entry, here we copy the data from our proc entry 
     * to the buffer passed.
     */
    
    ssize_t my_proc_read(struct file *filp,char *buf, size_t count, loff_t *offp )   // copy to buf,which is in userspace
    {
        char* f_path; int i=0; char f_arr[128]; char* f_path_2;
    
        while(i<128)
        {
            f_arr[i]=0;
            i  ;
        }
    
        f_path=dentry_path_raw(filp->f_path.dentry,f_arr,128);
       
        printk(KERN_ERR"f_path: %sn",f_path);
    
        i=0;
        while(i<128 amp;amp; f_arr[i]==0)
        {
            i  ;
        }
        if(i!=128)
        {
            f_path_2=amp;f_arr[i];
            printk(KERN_ERR"f_path_2: %sn",f_path_2);
        }
        
        int err;
        char *data = PDE_DATA(file_inode(filp));
    
        if ((int) (*offp) > len) {
            return 0;
        }
    
        printk(KERN_INFO "Reading the proc entry, len of the file is %d", len);
        if(!(data)) {
            printk(KERN_INFO "NULL DATA");
            return 0;
        }
    
        if (count == 0) {
            printk(KERN_INFO "Read of size zero, doing nothing.");
            return count;
        } else {
            printk(KERN_INFO "Read of size %d", (int) count);
        }
    
        count = len   1; //  1 to read the    ;  thus we store the previous written length in global variable len
        err = copy_to_user(buf, data, count); //  1 for 
        printk(KERN_INFO "Read data : %s", buf);
        *offp = count;
    
        if (err) {
            printk(KERN_INFO "Error in copying data.");
        } else {
            printk(KERN_INFO "Successfully copied data.");
        }
    
        return count;
    }
    
    
    /*
     * The file_operations structure. This is the glue layer which associates the
     * proc entry to the read and write operations.
     */
    struct file_operations proc_fops = {
        .read = my_proc_read,
        .write = my_proc_write,
    };
    
    
    /*
     * This function will create the proc entry. This function will allocate some
     * data where the data will be written incase of a write to the proc entry. The
     * same memory will be used to serve the reads.  * Initially the function fills
     * the data with DATA which has "100".
    
     * The important function to see here is the proc_create_data, this function
     * will take the proc entry name and create it with the given permissions
     * (0666). We also need to pass the file_operations structure which has the
     * function pointers to the functions which needs to be called when read or
     * write is called on the file. 
        The last argument has the pointer to the data
     * associated with the file. (So, by "char *data = PDE_DATA(file_inode(filp));", the 'data' is actually 'msg')
     */
    
    int create_new_proc_entry(void) {
        int i;
        char *DATA = "100";
        len = strlen(DATA);
        msg = kmalloc((size_t) DATA_SIZE, GFP_KERNEL); //  1 for 
    
        if (msg != NULL) {
            printk(KERN_INFO "Allocated memory for msg");
        } else {
            return -1;
        }
    
        strncpy(msg, DATA, len 1);
        for (i=0; i < len  1 ; i  ) {
            printk(KERN_INFO "%c", msg[i]);
            if (msg[i] == '') {
                printk(KERN_INFO "YES");
            }
        }
        proc = proc_create_data(MY_PROC_ENTRY, 0666, NULL, amp;proc_fops, msg);
        if (proc) {
            return 0;
        }
        return -1;
    }
    
    
    /* The init function of the module. Does nothing other than calling the
     * create_new_proc_entry. */
    
    int proc_init (void) {
        if (create_new_proc_entry()) {
            return -1;
        }
        return 0;
    }
    
    /* Function to remove the proc entry.  Call this when the module unloads. */
    void proc_cleanup(void) {
        remove_proc_entry(MY_PROC_ENTRY, NULL);
    }

MODULE_LICENSE("GPL");
module_init(proc_init);
module_exit(proc_cleanup);
 

Файл Makefile для вышеуказанного модуля(при условии, что код модуля записан в файле proc_write_read.c):-

 MYPROC=proc_write_read
obj-m  = $(MYPROC).o

export KROOT=/lib/modules/$(shell uname -r)/build
#export KROOT=/lib/modules/$(uname)3.2.0-23-generic/build

allofit: modules

modules: clean
    @$(MAKE) -C $(KROOT) M=$(PWD) modules

modules_install:
    @$(MAKE) -C $(KROOT) M=$(PWD) modules_install

kernel_clean:
    @$(MAKE) -C $(KROOT) M=$(PWD) clean

clean: kernel_clean
    rm -rf   Module.symvers modules.order

insert: modules
    dmesg -c
    insmod proc_write_read.ko

remove: clean
    rmmod proc_write_read
 

The module that reads this proc_entry, like a file is

     #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/syscalls.h>
    #include <linux/fcntl.h>
    #include <asm/uaccess.h>
    #include <linux/slab.h>
    #include <linux/string.h>
    
    #include <linux/kernel.h>
    #include <linux/proc_fs.h>
    #include <linux/sched.h>
    #include <linux/mm.h>
    #include <linux/uaccess.h>
    #include <asm/types.h>
    
    int proc_init(void)
    {
    struct file *f; ssize_t ret = -EBADF;int i;
    char* f_path; 
    i=0;

    f=NULL;
    char buf[128]; char f_arr[128];
    mm_segment_t fs;
    
    i=0;

    while(i<128)
    {
        buf[i] = 0; 
        f_arr[i]=0;
        i  ;
    }

    // To see in /var/log/messages that the module is operating
    //printk(KERN_INFO "read_file- my module is loadedn");

    f = filp_open("/proc/my_proc_entry_write", O_RDONLY, 0);

    if (IS_ERR(f)) {
        printk(KERN_ERR "Error in filp_open: %p ; %dn", f,PTR_ERR(f));
        return 100000;
    }

    f_path=dentry_path_raw(f->f_path.dentry,f_arr,128);
    printk(KERN_ERR"f_path_in_proc_entry_read: %sn",f_path);

    printk(KERN_ERR"kernel Below1!!!n");
    // Get current segment descriptor
    fs = get_fs();
    printk(KERN_ERR"kernel Below2!!!n");
    // Set segment descriptor associated to kernel space
    set_fs(get_ds());
    printk(KERN_ERR"kernel Below3!!!n");
    // Read the file
    if (f!=NULL) {
                if ((f->f_op)!=NULL amp;amp; (f->f_op->read)!=NULL){
                    ret=f->f_op->read(f, buf, 128, amp;f->f_pos);
                    printk(KERN_ERR"kernel Below4!!!n");
                }
                else
                   return 100000;
            
        }
        else
            return 100000;

    // Restore segment descriptor
    set_fs(fs);
    // See what we read from file
    printk(KERN_ERR "kernel Read my_proc_entry_write buf:%sn",buf);

    int val=0;
    sscanf(buf, "%d", amp;val);

    printk(KERN_ERR"kernel val: %dn",val);
   
    filp_close(f,NULL);

    return val;

    }
    
    void proc_cleanup(void)
    {
        printk(KERN_INFO "My module is unloadedn");
    }
    
    
    module_init(proc_init);
    module_exit(proc_cleanup);
 

Makefile for the above module(name=read_file_in_kernel) is-

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

Мой вопрос- как эти модули прекрасно работают в качестве пользовательских модулей, с помощью insmod, но если я скопировать и вставить этот код в исходный код ядра 4.19.200 (не точная копия-паста, только основные части как функции), и использовать 2-й модуль,я.e, код read_file_in_kernel, чтобы получить доступ к значению труды, я получаю сообщение об ошибке, я.е ядро не может загрузиться.
При запуске gdb через ядро я обнаружил, что код в read_file_in_kernel трижды проходил через приведенный ниже код (т. е. когда функция update_curr в ядре linux вызывала эту функцию 3 раза), т. е. f каждый раз возвращается как ошибка

 if (IS_ERR(f)) {
    printk(KERN_ERR "Error in filp_open: %p ; %dn", f,PTR_ERR(f));
    return 100000;
}
 

При 4-м вызове read_file_in_kernel ядро замерло на

 f = filp_open("/proc/my_proc_entry_write", O_RDONLY, 0);
 

Тот же случай с gdb.

Я не знаю, что я здесь делаю не так. Дело в том, что /proc/my_proc_entry_write не создается во время загрузки при его чтении, и поэтому filp_open не может открыть этот proc_entry для чтения.
Я даже попытался полностью удалить первый модуль из ядра, запустив его отдельно от пользовательского пространства, чтобы заранее создать proc_entry(my_proc_entry_write), который будет загружаться по умолчанию каждый раз при загрузке ядра. Но все равно, происходит та же ошибка.

Какое исправление я должен внести в это? Если это не способ создания динамического proc_entry, который может быть записан пользователем и готов ядром, что это такое?

Комментарии:

1. В общем, не рекомендуется осуществлять доступ к файлам пользовательского пространства из кода ядра. Если вам нужно получить доступ к содержимому буфера из другого модуля, либо экспортируйте переменную, указывающую непосредственно на буфер, либо экспортируйте функцию для копирования данных из буфера. Для этого EXPORT_SYMBOL() и EXPORT_SYMBOL_GPL() предназначены макросы.

2. Привет @IanAbbott . Мотив заключается в том, что ядро должно читать то, что пишет пользователь, чтобы существовало какое-то динамическое поведение. Мне нужно иметь возможность считывать значение в буфере в методе update_curr (), который является частью основного кода ядра. Поскольку EXPORT_SYMBOL() позволяет считывать переменные из одного модуля в другой, это не будет выполняться там(я попытался получить доступ к такой переменной в основном коде ядра, и это дало ошибку во время компиляции).

3. /proc будет смонтирован из пользовательского пространства, поэтому он не будет смонтирован до тех пор, пока ядро не создаст init процесс.

4. @IanAbbott , я немного изменил метод update_curr (), так что метод «read_file_in_kernel ()» (который считывает значение буфера/процесса) будет вызываться из update_curr () через некоторое время после загрузки ядра. Когда read_file_in_kernel еще не был вызван, мое ядро загружается, и я вижу созданную запись proc, в которую я могу читать и писать. Но по прошествии этого времени, когда update_curr вызывает read_file_in_kernel, он выдал ошибку, и ядро замерло. Итак, /proc был смонтирован, но все равно filp_open() застыл при чтении этой записи proc.

5. Ну, вам, вероятно, не следует пытаться делать сложные вещи, такие как чтение файлов в середине кода планирования.