Hallo an alle. In diesem Artikel möchte ich die Erfahrung beim Erstellen eines einfachen Linux-Kernelmoduls teilen. Dieser Artikel ist nützlich für diejenigen, die verstehen möchten, wie Kernelmodule geschrieben werden, aber nicht wissen, wo sie anfangen sollen.
Ich wollte dieses Thema schon lange verstehen, wusste aber bis vor kurzem nicht, wie ich es angehen sollte. Ich wollte, dass das Modul einfach genug ist, aber komplizierter als die Meldung „Hallo Welt!“ Ausgabe in einer Protokolldatei. Am Ende habe ich beschlossen, eine LED zu blinken. Ein weiteres Ziel war es, den Parameter abzuleiten, der für die Blinkfrequenz in sysfs verantwortlich ist.
Für das Experiment habe ich das Orange Pi One Board mit Ubuntu Linux an Bord (Kernel Version 3.4.113) verwendet. Um das Kernelmodul zu erstellen, benötigen Sie den gcc-Compiler, das Dienstprogramm make und die Kernel-Header-Dateien. Führen Sie den folgenden Befehl aus, um die Header-Dateien zu installieren:
sudo apt-get install linux-headers-$(uname -r)
Als nächstes werde ich meiner Meinung nach die interessantesten Teile des Moduls analysieren. Um Platz zu sparen, werde ich hier nicht den gesamten Code
angeben , er ist zusammen mit der make-Datei auf
github verfügbar.
Im Modul habe ich die Header-Dateien verwendet:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/hrtimer.h> #include <linux/moduleparam.h>
kernel.h und module.h sollten beim Schreiben des Kernelmoduls immer aktiviert sein. Gpio.h ist tatsächlich für die Arbeit mit GPIO verantwortlich. hrtimer.h (hochauflösender Timer) ist die Header-Datei des Timers. moduleparam.h wird benötigt, um die Parameter in sysfs anzuzeigen.
Um ihre Variablen und Funktionen nicht im Kernel des Systems zu erstrahlen, sollten sie alle als statisch beschrieben werden. Nur für den Fall, ich stelle fest, dass der Kernel in C und statisch geschrieben ist, bedeutet dies im Gegensatz zu C ++, dass das Objekt nur in der ausführbaren Datei verfügbar ist.
Der Einstiegspunkt ist:
static int blink_module_init(void)
Hier initialisiere ich die Variablen, die ich in Zukunft verwenden werde, einschließlich:
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
ktime_set initialisiert den Datentyp ktime_t, indem ihm die gewünschte Anzahl von Sekunden (gpio_blink_interval_s) und Nanosekunden (0) zugewiesen wird. In Zukunft wird diese Variable vom Timer verwendet.
Das Folgende ist eine Anforderung zur Verwendung von GPIO:
err = gpio_request(BLINK_PIN_NR, "blink_led");
Diese Funktion gibt bei Erfolg 0 zurück, daher überprüfe ich in Zukunft, ob sie zurückgegeben wurde. Als nächstes muss der ausgewählte Pin auf den Signalausgang gesetzt und der Standardwert angegeben werden.
err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW);
Wenn keine Fehler aufgetreten sind, initialisieren Sie den Timer und starten Sie ihn
hrtimer_init(&gpio_blink_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); gpio_blink_timer.function = &gpio_blink_timer_callback; hrtimer_start(&gpio_blink_timer, gpio_timer_interval, HRTIMER_MODE_REL);
Die Timer-Rückruffunktion lautet gpio_blink_timer_callback. In dieser Funktion ändere ich den Pin-Wert auf das Gegenteil
gpio_value ^= 0x01; gpio_set_value(BLINK_PIN_NR, gpio_value);
Ich frage, wann der Timer das nächste Mal funktionieren soll
hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval);
und geben Sie HRTIMER_RESTART zurück.
Schauen wir uns nun an, wie eine Variable in sysfs angezeigt wird. Dafür benutze ich ein Makro
module_param_cb(gpio_blink_interval_s, &kp_ops, &gpio_blink_interval_s, 0660);
Der erste Parameter dieses Makros ist der Dateiname in sysfs. Die zweite ist eine Datenstruktur, die Rückruffunktionen enthält. Der dritte Parameter ist ein Zeiger auf die reale Variable und die vierten Dateiberechtigungen in sysfs.
Funktionen von kp_ops werden aufgerufen, wenn der Benutzer die Werte der sysfs-Datei ändert oder ihren Wert liest. So habe ich sie initialisiert:
static const struct kernel_param_ops kp_ops = { .set = &set_blink_interval, .get = &get_blink_interval };
In diesem Fall ist set von Interesse, da es einen neuen Wert setzt, gpio_timer_interval.
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
Am Ausgang lösche ich alle verwendeten Ressourcen
static void blink_module_exit(void) { hrtimer_cancel(&gpio_blink_timer); gpio_set_value(BLINK_PIN_NR, 0); gpio_free(BLINK_PIN_NR); printk(KERN_ALERT "Blink module unloaded\n"); }
Die Ein- und Ausstiegspunkte müssen in den entsprechenden Makros angegeben werden
module_init(blink_module_init); module_exit(blink_module_exit);
Es scheint alle wichtigen Punkte zu beschreiben. Wenn Leser Fragen haben, beantworte ich diese gerne in den Kommentaren.