Ein charakteristisches Merkmal der modernen Programmierung ist die Verwendung des globalen Netzwerks als Quelle für Referenzinformationen, insbesondere als Quelle für Muster zur Lösung unbekannter oder wenig bekannter Probleme für einen bestimmten Programmierer. Ein solcher Ansatz spart viel Zeit und liefert oft recht qualitative Ergebnisse. Die im Netzwerk dargelegten Lösungen berücksichtigen zwar normalerweise die richtigen, berücksichtigen jedoch nicht immer alle Feinheiten der Problemlösung, was dazu führt, dass im Quellcode Abschnitte angezeigt werden, die normalerweise korrekt funktionieren, aber unter nicht ganz normalen Umständen werden Quellen unangenehmer Überraschungen.
Betrachten Sie das Thema der Verwendung von Sequenzdateien im Linux-Kernel. Solche Dateien gelten als der bequemste Mechanismus zum Drucken aus dem Kernelmodus. In der Praxis ist es jedoch viel schwieriger, sie richtig zu verwenden, als Sie denken.
Viele Materialien zu diesem Thema sind online verfügbar. Das Beste ist der Quellcode des Kernels selbst, der ziemlich detaillierte Kommentare enthält. Das Problem mit dieser Informationsquelle ist das Volumen. Wenn Sie nicht genau wissen, wonach Sie suchen sollen, ist es besser, wenn Sie nur eine begrenzte Zeit haben und es überhaupt nicht versuchen. Als ich mich für das Thema interessierte, lieferte Google einige scheinbar hervorragende Informationsquellen zu meiner Suche: das berühmte Buch
The Linux Kernel Module Programming Guide und
eine Reihe von Artikeln von Rob Day . Diese Quellen sind nicht neu, aber sehr solide.
Lassen Sie uns zunächst genauer betrachten, wann es natürlich ist, Sequenzdateien zu verwenden. Die häufigste Situation besteht darin, eine eigene Datei im Dateisystem / proc zu erstellen. Durch Lesen der Dateien dieses Systems erhalten Sie eine Vielzahl von Informationen über die verwendeten Geräte, deren Treiber, RAM, Prozesse usw.
Es scheint, dass der Ausdruck von irgendetwas die einfachste Aufgabe bei der Programmierung ist. Wenn das Betriebssystem im Kernelmodus arbeitet, unterliegt es jedoch vielen Einschränkungen, die für den Entwickler der Anwendungssoftware völlig unvorstellbar erscheinen. Im Kernelmodus ist die Größe des Druckpuffers durch die Größe einer virtuellen Speicherseite begrenzt. Für die x86-Architektur sind es vier Kilobyte. Daher muss ein gutes Programm beim Drucken großer Datenmengen zuerst den Puffer maximal füllen, dann drucken und diese Iteration wiederholen, bis die zu druckenden Daten vollständig erschöpft sind. Sie können natürlich Zeichen für Zeichen drucken, was alles erheblich vereinfachen würde, aber wir sprechen von guten Programmen.
Die oben genannten Quellen waren etwas schlechter als erwartet. In dem Buch zum Beispiel erwiesen sich einige der Informationen als allgemein falsch, und das hat mich dazu bewegt, diese Notiz zu schreiben. Es ist üblich zu berücksichtigen, dass Informationen in Form eines Schema-Bildes am einfachsten zu verstehen und zu verwenden sind. In diesem Buch ist das Bild zum Thema jedoch falsch. Die Verwendung eines solchen Schemas kann zu schwerwiegenden Fehlern führen, obwohl das Beispiel im Buch korrekt funktioniert und genau diesem Schema folgt. Dies liegt an der Tatsache, dass in diesem Beispiel nur wenige Bytes gleichzeitig gedruckt werden, wenn auf / proc / iter zugegriffen wird. Wenn Sie es als Vorlage zum Drucken von Texten verwenden, die größer als eine Speicherseite sind, gibt es Überraschungen. Die oben genannte Artikelserie enthält keine offensichtlichen Fehler, berichtet jedoch nicht über einige Details, die für das Verständnis des Themas wichtig sind.
Betrachten wir also zunächst das richtige Schema für die Arbeit mit einer Sequenzdatei.

Um mit einer solchen Datei arbeiten zu können, müssen Sie die Funktionen start (), stop (), next () und show () erstellen. Die Namen dieser Funktionen können beliebig sein. Ich habe die kürzesten Wörter ausgewählt, deren Bedeutung den Aktionen der Funktionen entspricht. Wenn solche Funktionen vorhanden und ordnungsgemäß mit Kernelsystemen verbunden sind, funktionieren sie automatisch, wenn sie auf die ihnen zugeordnete Datei im Verzeichnis / proc zugreifen. Am verwirrendsten ist die Verwendung der Funktion stop (), die in drei verschiedenen Kontexten aufgerufen werden kann. Wenn Sie es nach start () aufrufen, wird der Druckauftrag beendet. Wenn Sie es nach show () aufrufen, bedeutet dies, dass der letzte Druckvorgang im Puffer (normalerweise wird hierfür die Funktion seq_printf verwendet) den Seitenpuffer übergelaufen ist und dieser Druckvorgang abgebrochen wurde. Der Aufruf nach next () ist der interessanteste Fall, der auftritt, wenn einige Daten in den Puffer gedruckt werden und Sie entweder den Job beenden oder neue Daten verwenden müssen. Angenommen, unsere Datei im Verzeichnis / proc erstellt beim Zugriff zuerst einige Informationen zu Blockgeräten und dann zu Zeichengeräten. Erstens initialisiert die Funktion start () das Drucken für Blockgeräte, und die Funktionen next () und möglicherweise show () verwenden diese Initialisierungsdaten, um schrittweise Informationen über die Blockgeräte zu drucken. Wenn alles fertig ist, wird nach dem letzten Aufruf von next () der betrachtete Aufruf von stop () ausgeführt, wonach start () aufgerufen wird, der diesmal bereits das weitere Drucken für Zeichengeräte einleiten sollte.
Ich gebe ein leicht modifiziertes Beispiel (den Inhalt der Datei evens.c) aus dem Artikel von Rob Day. Ich musste den Aufruf einer Funktion, die in modernen Kerneln fehlt, durch das eigentliche Äquivalent ersetzen. Die Kommentare sind ebenfalls leicht verändert.
#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; //default value, it can be changed here or module_param(limit, int, S_IRUGO); //transfered as a module parameter static int* even_ptr; //we will work with dynamic memory /** * 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; } //Allocate an integer to hold our increasing even value even_ptr = kmalloc(sizeof(int), GFP_KERNEL); if (!even_ptr) // fatal kernel allocation error 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)++; //increase my position counter if (*pos >= limit) //are we done? return NULL; *(int*)v += 2; //to the next even value 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"); } /** * This structure gathers functions which control the sequential reading */ static struct seq_operations ct_seq_ops = { .start = ct_seq_start, .next = ct_seq_next, .stop = ct_seq_stop, .show = ct_seq_show }; /** * This function is called when a file from /proc is opened */ static int ct_open(struct inode *inode, struct file *file) { return seq_open(file, &ct_seq_ops); }; /** * This structure gathers functions for a /proc-file operations */ static struct file_operations ct_file_ops = { .owner = THIS_MODULE, .open = ct_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /** * This function is called when this module is loaded into the kernel */ static int __init ct_init(void) { proc_create("evens", 0, NULL, &ct_file_ops); return 0; } /** * This function is called when this module is removed from the kernel */ static void __exit ct_exit(void) { remove_proc_entry("evens", NULL); } module_init(ct_init); module_exit(ct_exit); MODULE_LICENSE("GPL");
Funktionen zum Arbeiten mit einer Sequenzdatei verwenden zwei Zeiger mit überlappenden Funktionen (dies ist auch etwas verwirrend). Einer von ihnen sollte auf das aktuelle Objekt zeigen, das mit show () in den Puffer gedruckt werden soll - es ist ein `v'-Zeiger im Programm. Der andere Zeiger "pos" wird normalerweise verwendet, um auf den Zähler zu zeigen.
Für diejenigen, die ihr Programm zum ersten Mal im Kernelmodus ausführen möchten, gebe ich ein Beispiel für ein Makefile für einen erfolgreichen Build. Für einen erfolgreichen Build müssen natürlich Linux-Kernel-Quellheader im System vorhanden sein.
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
Die Verbindung zum Kernel wird mit dem Befehl
sudo insmod evens.ko
, wobei die Funktionalität der danach angezeigten Datei / proc / evens-file mit dem Befehl
cat /proc/evens
und das Ereignisprotokoll
cat /proc/evens
, mit dem die Systemvorgänge erläutert werden der Befehl
sudo cat /var/log/messages
.
Um den Seitenpuffer zu überlaufen, setzen Sie den Parameter limit auf einen größeren Wert, z. B. 200. Dieser Wert kann in den Programmtext eingegeben oder beim Laden des Moduls mit dem Befehl
sudo insmod events.ko limit=200
.
Die Protokollanalyse kann die verbleibenden unklaren Punkte erklären. Beispielsweise können Sie feststellen, dass das System vor dem Aufruf von stop () nach next () oder start () die Variable "v" auf Null setzt. Möglicherweise stellen Sie auch fest, dass das System vor dem Aufruf von start () nach stop () den Inhalt des Puffers druckt.
Ich wäre dankbar, wenn jemand Ungenauigkeiten in meiner Notiz oder irgendetwas anderes, das erwähnt werden sollte, melden würde.
Der Quellcode ist auch
hier verfügbar.