📜 ⬆️ ⬇️

Using Linux Kernel Sequence Files

A characteristic feature of modern programming is in using the global network as a source of reference information, in particular, a source of patterns for solving unknown or little-known problems to a specific programmer. Such an approach saves a lot of time and often gives quite qualitative results. However, the solutions laid out in the network, although usually correct, do not always take into account all the subtleties of solving a particular problem, which leads to the appearance of sections in codes that usually work correctly, but under not quite standard circumstances become sources of unpleasant surprises.

Consider the topic of using sequence files in the Linux kernel. Such files are considered the most convenient mechanism for printing from kernel mode. But in practice, using them correctly is much more difficult than you can think about.

In the network there are many materials on this topic. The best is the source code of the kernel itself, with fairly detailed comments. The problem of this source of information in its scope. If you do not know exactly what to look for, then it is better to have only a limited time, and not try. When I became interested in the topic, Google found several seemingly excellent sources of information: the famous book The Linux Kernel Module Programming Guide and a series of articles by Rob Day (Rob Day) on the topic of interest. Sources are not new, but very solid.

Consider first in more detail when it is natural to use sequence files. The most typical situation is to create your own file in the / proc file system. Reading the files of this system, you can get a wide variety of information about the hardware used, its drivers, RAM, processes, etc.
')
It would seem that a printout of anything is the simplest of all that is in programming. But working in the kernel mode of the OS imposes many limitations that may seem completely unimaginable to the developer of application software. In kernel mode, the print buffer size is limited by the virtual memory page size. For architecture, x86 is four kilobytes. Therefore, a good program when printing out data of a larger volume must first achieve maximum filling of this buffer, then print it out, and then repeat this iteration again until complete printing data is exhausted. You can, of course, type character by character, which would make everything much simpler, but we are talking about good programs.

The above sources were slightly worse than expected. In the book, for example, some information turned out to be altogether incorrect, and this is what motivated me to write this note. Incorrect was the easiest for perception and use of the information given in the form of a diagram. Using such a scheme can lead to serious errors. Although the example given in the book works correctly and follows this very scheme. This is due to the fact that in this example, when accessing / proc / iter, only a few bytes are printed at a time. If you use it as a template for printing texts that are larger than a page of memory, then surprises will arise. The series of articles mentioned above does not contain obvious errors, but does not report on some details that are important for understanding the topic.

So, we first consider the correct scheme of how to work with the file sequence.


To work with such a file you need to create functions start, stop, next and show. The names of these functions can be any, chose the shortest and corresponding in meaning. When these functions are present and properly connected to the kernel systems, they start working automatically when accessing the file associated with them in the / proc directory. The most confusing is the use of the stop function, which can be called in three different contexts. Calling it after start means ending the print job. Calling it after show means that when the last print operation was performed to the buffer (usually the seq_printf function is used for this), the page buffer overflow occurred and this print operation was canceled. And its call after next is the most interesting case that occurs when printing some data to the buffer ends and you need to either shut down or use new data. For example, suppose that our file in the / proc directory, when accessing it, first produces some information on block devices, and then on character ones. First, the start function initializes printing by block devices, and the next function and, possibly, show use this initialization data for the subsequent printing step by step of the necessary information on block devices. When everything is ready, after the last call to next, the considered case of calling stop will be made, after which start is called, which this time should already initiate further printing on character devices.

I will give a slightly modified example (the contents of the file evens.c) from the article by Rob Day. It had to replace the call of a function, which is absent in modern kernels, with its actual equivalent. Comments in English replaced by in Russian.

#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/proc_fs.h> #include <linux/fs.h> #include <linux/seq_file.h> #include <linux/slab.h> static int limit = 10; //  ,     module_param(limit, int, S_IRUGO); //,  ,    static int* even_ptr; //  , ,   //   /** * start */ static void *ct_seq_start(struct seq_file *s, loff_t *pos) { printk(KERN_INFO "Entering start(), pos = %Ld, seq-file pos = %lu.\n", *pos, s->count); if (*pos >= limit) { // are we done? printk(KERN_INFO "Apparently, we're done.\n"); return NULL; } //   ,        even_ptr = kmalloc(sizeof(int), GFP_KERNEL); if (!even_ptr) //  return NULL; printk(KERN_INFO "In start(), even_ptr = %pX.\n", even_ptr); *even_ptr = (*pos)*2; return even_ptr; } /** * show */ static int ct_seq_show(struct seq_file *s, void *v) { printk(KERN_INFO "In show(), even = %d.\n", *(int*)v); seq_printf(s, "The current value of the even number is %d\n", *(int*)v); return 0; } /** * next */ static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos) { printk(KERN_INFO "In next(), v = %pX, pos = %Ld, seq-file pos = %lu.\n", v, *pos, s->count); (*pos)++; //  if (*pos >= limit) //? return NULL; *(int*)v += 2; //    return v; } /** * stop */ static void ct_seq_stop(struct seq_file *s, void *v) { printk(KERN_INFO "Entering stop().\n"); if (v) printk(KERN_INFO "v is %pX.\n", v); else printk(KERN_INFO "v is null.\n"); printk(KERN_INFO "In stop(), even_ptr = %pX.\n", even_ptr); if (even_ptr) { printk(KERN_INFO "Freeing and clearing even_ptr.\n"); kfree(even_ptr); even_ptr = NULL; } else printk(KERN_INFO "even_ptr is already null.\n"); } /** *         */ static struct seq_operations ct_seq_ops = { .start = ct_seq_start, .next = ct_seq_next, .stop = ct_seq_stop, .show = ct_seq_show }; /** *        /proc */ static int ct_open(struct inode *inode, struct file *file) { return seq_open(file, &ct_seq_ops); }; /** *         /proc */ static struct file_operations ct_file_ops = { .owner = THIS_MODULE, .open = ct_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /** *   ,       */ static int __init ct_init(void) { proc_create("evens", 0, NULL, &ct_file_ops); return 0; } /** *   ,       */ static void __exit ct_exit(void) { remove_proc_entry("evens", NULL); } module_init(ct_init); module_exit(ct_exit); MODULE_LICENSE("GPL"); 

Functions for working with sequence files use two pointers with overlapping functionality (this is also a somewhat confusing moment). One of them should point to the current object to be printed into the buffer by the show-v function in the program, and the other (pos) is usually used to point to the counter.

For those who may for the first time want to run their program in kernel mode, I give an example Makefile for a successful build. Of course, for a successful build, you must have Linux kernel source headers in the system.

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

Connecting to the kernel is done with the sudo insmod evens.ko command, checking the functionality of the / proc / evens file that appeared after this with the cat / proc / evens command, reading the event log explaining the system operation with the sudo cat / var / log / messages command.

In order for the page buffer to overflow, you need to set the limit parameter to a larger value, for example, 200. This value can be entered both in the program text and used when loading the module - sudo insmod evens.ko limit = 200.

Log analysis may clarify the remaining unclear points. For example, you may notice that before calling stop after next or start, the system zeros v. You may also notice that before calling start after stop, the system prints the contents of the buffer.

I would be grateful if someone would report any inaccuracies found in my note or that should be mentioned.

Sources can also be found here .

Source: https://habr.com/ru/post/358184/


All Articles