Linux Kernel Module Development Chapter 7

The Linux Kernel Module Programming Guide

  • Peter Jay Salzman, Michael Burian, Ori Pomerantz, Bob Mottram, Jim Huang
  • Translate WaterCutter ( WaterCutter )
  • SOURCELKMPG _
    insert image description here

7 /proc file system

There is an additional mechanism in Linux - the /proc file system, which is used to support the kernel and modules to send information to processes (processes).

This mechanism was most designed to access process information and is now used by every bit of the kernel which has something interesting to report. These reports include files that provide a list of modules, and files /proc/modulesthat collect memory usage ./proc/meminfo

The usage of the /proc file system is similar to that of a device driver - create a structure containing /proc file information and pointers to multiple handler functions, register with and unregister init_modulewith cleanup_module.

The normal (normal) file system is on the disk (disk), and /proc is in the memory (memory), which inode numberis a pointer to the location of the file on the disk. inodeContains the file's permissions, a pointer to where the file's data resides on disk.

Because such files are not called when they are opened or closed, developers have nowhere to call try_module_get()and module_put()(see Chapter 6 for the description of these two functions), which also means that the module can be removed when the file is opened .

Below is a routine that uses the /proc file, including an initialization function init_module(), a read function that returns a value and a buffer, procfile_read()and /proc/helloworlda function that deletes the file cleanup_module().

proc_create()Files are created when a module is loaded by a function /proc/helloworld. A return value of type struct proc_dir_entrywill be used to configure the file /proc/helloworld, and a return value NULLof means that the file creation failed.

Whenever the file proc/helloworldis read, the function procfile_read()will be called. The second bufferand fourth parameters of this function offsetare very important. bufferThe contents of the file will be passed to the application that reads the file (such as the cat command), offsetthen mark the current reading position of the file. If the return value of the function is not NULL, it will be called endlessly.

$ cat /proc/helloworld
HelloWorld!

/* 
 * procfs1.c 
 */ 
 
#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/proc_fs.h> 
#include <linux/uaccess.h> 
#include <linux/version.h> 
 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 
#define HAVE_PROC_OPS 
#endif 
 
#define procfs_name "helloworld" 
 
static struct proc_dir_entry *our_proc_file; 
 
static ssize_t procfile_read(struct file *file_pointer, char __user *buffer, 
                             size_t buffer_length, loff_t *offset) 
{
    
     
    char s[13] = "HelloWorld!\n"; 
    int len = sizeof(s); 
    ssize_t ret = len; 
 
    if (*offset >= len || copy_to_user(buffer, s, len)) {
    
     
        pr_info("copy_to_user failed\n"); 
        ret = 0; 
    } else {
    
     
        pr_info("procfile read %s\n", file_pointer->f_path.dentry->d_name.name); 
        *offset += len; 
    } 
 
    return ret; 
} 
 
#ifdef HAVE_PROC_OPS 
static const struct proc_ops proc_file_fops = {
    
     
    .proc_read = procfile_read, 
}; 
#else 
static const struct file_operations proc_file_fops = {
    
     
    .read = procfile_read, 
}; 
#endif 
 
static int __init procfs1_init(void) 
{
    
     
    our_proc_file = proc_create(procfs_name, 0644, NULL, &proc_file_fops); 
    if (NULL == our_proc_file) {
    
     
        proc_remove(our_proc_file); 
        pr_alert("Error:Could not initialize /proc/%s\n", procfs_name); 
        return -ENOMEM; 
    } 
 
    pr_info("/proc/%s created\n", procfs_name); 
    return 0; 
} 
 
static void __exit procfs1_exit(void) 
{
    
     
    proc_remove(our_proc_file); 
    pr_info("/proc/%s removed\n", procfs_name); 
} 
 
module_init(procfs1_init); 
module_exit(procfs1_exit); 
 
MODULE_LICENSE("GPL");

7.1 proc_ops structure

The proc_ops structure is defined in the include/linux/proc_fs.h file of the 5.6 and later Linux kernels . Older kernels use file_operations /proc file system user hooks (user hooks). But it contains some members that are unnecessary in VFS, and the /proc code becomes bloated every time VFS expands the file_operations set. Besides that, the proc_ops structure not only saves space, but also saves some operations to improve its performance. For example, files that never disappear in /proc can set proc_flag to PROC_ENTRY_PERMANENT to save 2 atomic operations, 1 allocation, and 1 free in each open/read/close sequence.

7.2 Reading and writing /proc files

Section 7.1 showed a simple /proc file read operation, here we try to write /proc file. The two are very similar, but the data written to /proc comes from the user, so we need to use copy_from_useror get_userto import data from user space (user space) to kernel space (kernel space).

The reason for copy_from_user or get_user is that Linux memory (on Intel architecture, it may be different under some other processors) is segmented. This means that a pointer, by itself, does not reference a unique location in memory, only a location in a memory segment, and you need to know which memory segment it is to be able to use it. There is one memory segment for the kernel, and one for each of the processes.

The only memory segment accessible to a process is its own, so when writing regular programs to run as processes, there is no need to worry about segments. When you write a kernel module, normally you want to access the kernel memory segment, which is handled automatically by the system. However, when the content of a memory buffer needs to be passed between the currently running process and the kernel, the kernel function receives a pointer to the memory buffer which is in the process segment. The put_user and get_user macros allow you to access that memory. These functions handle only one character, you can handle several characters with copy_to_user and copy_from_user . As the buffer (in read or write function) is in kernel space, for write function you need to import data because it comes from user space, but not for the read function because data is already in kernel space.

/* 
 * procfs2.c -  create a "file" in /proc 
 */ 
 
#include <linux/kernel.h> /* We're doing kernel work */ 
#include <linux/module.h> /* Specifically, a module */ 
#include <linux/proc_fs.h> /* Necessary because we use the proc fs */ 
#include <linux/uaccess.h> /* for copy_from_user */ 
#include <linux/version.h> 
 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 
#define HAVE_PROC_OPS 
#endif 
 
#define PROCFS_MAX_SIZE 1024 
#define PROCFS_NAME "buffer1k" 
 
/* This structure hold information about the /proc file */ 
static struct proc_dir_entry *our_proc_file; 
 
/* The buffer used to store character for this module */ 
static char procfs_buffer[PROCFS_MAX_SIZE]; 
 
/* The size of the buffer */ 
static unsigned long procfs_buffer_size = 0; 
 
/* This function is called then the /proc file is read */ 
static ssize_t procfile_read(struct file *file_pointer, char __user *buffer, 
                             size_t buffer_length, loff_t *offset) 
{
    
     
    char s[13] = "HelloWorld!\n"; 
    int len = sizeof(s); 
    ssize_t ret = len; 
 
    if (*offset >= len || copy_to_user(buffer, s, len)) {
    
     
        pr_info("copy_to_user failed\n"); 
        ret = 0; 
    } else {
    
     
        pr_info("procfile read %s\n", file_pointer->f_path.dentry->d_name.name); 
        *offset += len; 
    } 
 
    return ret; 
} 
 
/* This function is called with the /proc file is written. */ 
static ssize_t procfile_write(struct file *file, const char __user *buff, 
                              size_t len, loff_t *off) 
{
    
     
    procfs_buffer_size = len; 
    if (procfs_buffer_size > PROCFS_MAX_SIZE) 
        procfs_buffer_size = PROCFS_MAX_SIZE; 
 
    if (copy_from_user(procfs_buffer, buff, procfs_buffer_size)) 
        return -EFAULT; 
 
    procfs_buffer[procfs_buffer_size & (PROCFS_MAX_SIZE - 1)] = '\0'; 
    *off += procfs_buffer_size; 
    pr_info("procfile write %s\n", procfs_buffer); 
 
    return procfs_buffer_size; 
} 
 
#ifdef HAVE_PROC_OPS 
static const struct proc_ops proc_file_fops = {
    
     
    .proc_read = procfile_read, 
    .proc_write = procfile_write, 
}; 
#else 
static const struct file_operations proc_file_fops = {
    
     
    .read = procfile_read, 
    .write = procfile_write, 
}; 
#endif 
 
static int __init procfs2_init(void) 
{
    
     
    our_proc_file = proc_create(PROCFS_NAME, 0644, NULL, &proc_file_fops); 
    if (NULL == our_proc_file) {
    
     
        proc_remove(our_proc_file); 
        pr_alert("Error:Could not initialize /proc/%s\n", PROCFS_NAME); 
        return -ENOMEM; 
    } 
 
    pr_info("/proc/%s created\n", PROCFS_NAME); 
    return 0; 
} 
 
static void __exit procfs2_exit(void) 
{
    
     
    proc_remove(our_proc_file); 
    pr_info("/proc/%s removed\n", PROCFS_NAME); 
} 
 
module_init(procfs2_init); 
module_exit(procfs2_exit); 
 
MODULE_LICENSE("GPL");

7.3 Managing /proc files with standard filesystems

We already know how to read and write /proc files using the /proc interface, but is it possible to inode manage /proc files using ? The question focuses on some advanced functions (advance function) - such as rights management.

There is a standard file registration mechanism in Linux. Each file system has inodefunctions for processing and file operations, and there are also some structures containing pointers to these functions—— struct inode_operations, this structure contains a pointer struct proc_opsto .

The difference between file operations and inodeinode operations is that the former deals with the file itself, while the latter deals with things related to file indexing—such as creating links to it.

Here is another interesting thing to mention - module_permissionfunctions. This function is called when the process tries to operate the /proc file, and determines whether to allow the corresponding operation. Currently permission depends only on the current user's uid, but it can actually depend on any criteria we specify, such as other processes' operations on the file, dates, or received messages.

It should be noted that the direction of reading and writing in the kernel is reversed, use the read function to write, and use the write function to read. Because reading and writing are described from the user's perspective, when the user needs to read data from the kernel, the kernel should output the corresponding data.

It is important to note that the standard roles of read and write are reversed in the kernel. Read functions are used for output, whereas write functions are used for input. The reason for that is that read and write refer to the user’s point of view — if a process reads something from the kernel, then the kernel needs to output it, and if a process writes something to the kernel, then the kernel receives it as input.

/* 
 * procfs3.c 
 */ 
 
#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/proc_fs.h> 
#include <linux/sched.h> 
#include <linux/uaccess.h> 
#include <linux/version.h> 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) 
#include <linux/minmax.h> 
#endif 
 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 
#define HAVE_PROC_OPS 
#endif 
 
#define PROCFS_MAX_SIZE 2048UL 
#define PROCFS_ENTRY_FILENAME "buffer2k" 
 
static struct proc_dir_entry *our_proc_file; 
static char procfs_buffer[PROCFS_MAX_SIZE]; 
static unsigned long procfs_buffer_size = 0; 
 
static ssize_t procfs_read(struct file *filp, char __user *buffer, 
                           size_t length, loff_t *offset) 
{
    
     
    if (*offset || procfs_buffer_size == 0) {
    
     
        pr_debug("procfs_read: END\n"); 
        *offset = 0; 
        return 0; 
    } 
    procfs_buffer_size = min(procfs_buffer_size, length); 
    if (copy_to_user(buffer, procfs_buffer, procfs_buffer_size)) 
        return -EFAULT; 
    *offset += procfs_buffer_size; 
 
    pr_debug("procfs_read: read %lu bytes\n", procfs_buffer_size); 
    return procfs_buffer_size; 
} 
static ssize_t procfs_write(struct file *file, const char __user *buffer, 
                            size_t len, loff_t *off) 
{
    
     
    procfs_buffer_size = min(PROCFS_MAX_SIZE, len); 
    if (copy_from_user(procfs_buffer, buffer, procfs_buffer_size)) 
        return -EFAULT; 
    *off += procfs_buffer_size; 
 
    pr_debug("procfs_write: write %lu bytes\n", procfs_buffer_size); 
    return procfs_buffer_size; 
} 
static int procfs_open(struct inode *inode, struct file *file) 
{
    
     
    try_module_get(THIS_MODULE); 
    return 0; 
} 
static int procfs_close(struct inode *inode, struct file *file) 
{
    
     
    module_put(THIS_MODULE); 
    return 0; 
} 
 
#ifdef HAVE_PROC_OPS 
static struct proc_ops file_ops_4_our_proc_file = {
    
     
    .proc_read = procfs_read, 
    .proc_write = procfs_write, 
    .proc_open = procfs_open, 
    .proc_release = procfs_close, 
}; 
#else 
static const struct file_operations file_ops_4_our_proc_file = {
    
     
    .read = procfs_read, 
    .write = procfs_write, 
    .open = procfs_open, 
    .release = procfs_close, 
}; 
#endif 
 
static int __init procfs3_init(void) 
{
    
     
    our_proc_file = proc_create(PROCFS_ENTRY_FILENAME, 0644, NULL, 
                                &file_ops_4_our_proc_file); 
    if (our_proc_file == NULL) {
    
     
        remove_proc_entry(PROCFS_ENTRY_FILENAME, NULL); 
        pr_debug("Error: Could not initialize /proc/%s\n", 
                 PROCFS_ENTRY_FILENAME); 
        return -ENOMEM; 
    } 
    proc_set_size(our_proc_file, 80); 
    proc_set_user(our_proc_file, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID); 
 
    pr_debug("/proc/%s created\n", PROCFS_ENTRY_FILENAME); 
    return 0; 
} 
 
static void __exit procfs3_exit(void) 
{
    
     
    remove_proc_entry(PROCFS_ENTRY_FILENAME, NULL); 
    pr_debug("/proc/%s removed\n", PROCFS_ENTRY_FILENAME); 
} 
 
module_init(procfs3_init); 
module_exit(procfs3_exit); 
 
MODULE_LICENSE("GPL");

Still feel that the routines are not rich enough? There are rumors that procfs is about to be phased out and it is recommended to consider using sysfs instead. If you want to record some kernel-related content yourself, then consider using this mechanism.

Consider using this mechanism, in case you want to document something kernel related yourself.

7.4 Using seq_file to manage /proc files

As you can see, the /proc file can be a bit complicated, so there is a set of seq_fileAPIs called to help format the output. This set of APIs is based on a sequence of operations consisting of 3 functions start(), next(), , and so on. This sequence of operations is initiated stop()when the user reads the /proc file, which reads as follows:seq_file

  • call function start().
  • If start()the return value is not NULL, the function is called next(). This function is an iterator (iterator), used to traverse the data (go through all the data). Every next()time is called, the function is also called show()to write the data to be read by the user into the buffer.
  • next()If the return value is not NULL, repeat the call next().
  • next()The return value is NULL, calling the function stop().

Note: stop()After calling the function start(), the function will be called again until start()the return value of the function is NULL

sqe_fileproc_opsProvides basic functions such as seq_read, , etc., but does not provide functions for writing /proc files. seq_lseekThe following is the usage routine:

/* 
 * procfs4.c -  create a "file" in /proc 
 * This program uses the seq_file library to manage the /proc file. 
 */ 
 
#include <linux/kernel.h> /* We are doing kernel work */ 
#include <linux/module.h> /* Specifically, a module */ 
#include <linux/proc_fs.h> /* Necessary because we use proc fs */ 
#include <linux/seq_file.h> /* for seq_file */ 
#include <linux/version.h> 
 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 
#define HAVE_PROC_OPS 
#endif 
 
#define PROC_NAME "iter" 
 
/* This function is called at the beginning of a sequence. 
 * ie, when: 
 *   - the /proc file is read (first time) 
 *   - after the function stop (end of sequence) 
 */ 
static void *my_seq_start(struct seq_file *s, loff_t *pos) 
{
    
     
    static unsigned long counter = 0; 
 
    /* beginning a new sequence? */ 
    if (*pos == 0) {
    
     
        /* yes => return a non null value to begin the sequence */ 
        return &counter; 
    } 
 
    /* no => it is the end of the sequence, return end to stop reading */ 
    *pos = 0; 
    return NULL; 
} 
 
/* This function is called after the beginning of a sequence. 
 * It is called untill the return is NULL (this ends the sequence). 
 */ 
static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos) 
{
    
     
    unsigned long *tmp_v = (unsigned long *)v; 
    (*tmp_v)++; 
    (*pos)++; 
    return NULL; 
} 
 
/* This function is called at the end of a sequence. */ 
static void my_seq_stop(struct seq_file *s, void *v) 
{
    
     
    /* nothing to do, we use a static value in start() */ 
} 
 
/* This function is called for each "step" of a sequence. */ 
static int my_seq_show(struct seq_file *s, void *v) 
{
    
     
    loff_t *spos = (loff_t *)v; 
 
    seq_printf(s, "%Ld\n", *spos); 
    return 0; 
} 
 
/* This structure gather "function" to manage the sequence */ 
static struct seq_operations my_seq_ops = {
    
     
    .start = my_seq_start, 
    .next = my_seq_next, 
    .stop = my_seq_stop, 
    .show = my_seq_show, 
}; 
 
/* This function is called when the /proc file is open. */ 
static int my_open(struct inode *inode, struct file *file) 
{
    
     
    return seq_open(file, &my_seq_ops); 
}; 
 
/* This structure gather "function" that manage the /proc file */ 
#ifdef HAVE_PROC_OPS 
static const struct proc_ops my_file_ops = {
    
     
    .proc_open = my_open, 
    .proc_read = seq_read, 
    .proc_lseek = seq_lseek, 
    .proc_release = seq_release, 
}; 
#else 
static const struct file_operations my_file_ops = {
    
     
    .open = my_open, 
    .read = seq_read, 
    .llseek = seq_lseek, 
    .release = seq_release, 
}; 
#endif 
 
static int __init procfs4_init(void) 
{
    
     
    struct proc_dir_entry *entry; 
 
    entry = proc_create(PROC_NAME, 0, NULL, &my_file_ops); 
    if (entry == NULL) {
    
     
        remove_proc_entry(PROC_NAME, NULL); 
        pr_debug("Error: Could not initialize /proc/%s\n", PROC_NAME); 
        return -ENOMEM; 
    } 
 
    return 0; 
} 
 
static void __exit procfs4_exit(void) 
{
    
     
    remove_proc_entry(PROC_NAME, NULL); 
    pr_debug("/proc/%s removed\n", PROC_NAME); 
} 
 
module_init(procfs4_init); 
module_exit(procfs4_exit); 
 
MODULE_LICENSE("GPL");

Find more information on the following pages:

Guess you like

Origin blog.csdn.net/qq_33904382/article/details/131586504