Hinweise zur Zeitverwaltung des Linux-Kernel-Gerätetreibers

Hinweise zur Zeitverwaltung des Linux-Kernel-Gerätetreibers
/******************
 * Zeitverwaltung des Linux-Kernels *********************/

(1) Konzept der Zeit im Kernel

Zeitmanagement spielt im Linux-Kernel eine sehr wichtige Rolle.

Im Vergleich zu ereignisgesteuerten Funktionen gibt es im Kernel eine große Anzahl zeitgesteuerter Funktionen.

Einige Funktionen werden periodisch ausgeführt, wie beispielsweise das Aktualisieren des Bildschirms alle 10 Millisekunden.

Einige Funktionen werden nach einer bestimmten Zeit ausgeführt, beispielsweise führt der Kernel eine Aufgabe nach 500 Millisekunden aus.

Zur Unterscheidung:

  • *Absolute Zeit und relative Zeit
  • * Periodisch generierte Ereignisse und aufgeschobene Ereignisse

Periodische Ereignisse werden vom Systemtimer gesteuert

(2)HZ-Wert

Der Kernel benötigt zur Berechnung und Verwaltung der Zeit die Hilfe von Hardware-Timern.

Die Häufigkeit, mit der ein Timer Interrupts generiert, wird als Tickrate bezeichnet.

Im Kernel wird eine Variable HZ angegeben und die Taktfrequenz des Timers wird bei der Initialisierung des Kernels auf Grundlage dieses Wertes bestimmt.

HZ ist in <asm/param.h> definiert. Auf der i386-Plattform beträgt der aktuell verwendete HZ-Wert 1000.

Das heißt, die Taktunterbrechung erfolgt 1000 Mal pro Sekunde mit einer Periode von 1 Millisekunde. Im Augenblick:

#define HZ 1000

Beachten! HZ ist kein fester Wert, er kann geändert und bei der Konfiguration des Kernel-Quellcodes eingegeben werden.

Verschiedene Architekturen haben unterschiedliche HZ-Werte, Arm verwendet beispielsweise 100.

Wenn Sie die Systeminterruptfrequenz im Treiber verwenden möchten, verwenden Sie HZ direkt anstelle von 100 oder 1000

a. Idealer HZ-Wert

Der HZ-Wert von i386 war immer 100, bis er nach Version 2.5 auf 1000 geändert wurde.

Eine Erhöhung der Tick-Rate bedeutet, dass Takt-Interrupts häufiger generiert und Interrupt-Handler häufiger ausgeführt werden.

Die Vorteile sind:

  • * Kernel-Timer können mit höheren Frequenzen und höherer Genauigkeit ausgeführt werden
  • * Systemaufrufe, die auf Timern basieren, wie poll() und select(), werden mit höherer Präzision ausgeführt
  • * Verbessern Sie die Genauigkeit der Prozesspräemption

(Gekürzte Planungsverzögerung: Wenn dem Prozess in einem 10-ms-Planungszyklus noch ein Zeitfenster von 2 ms verbleibt, läuft der Prozess 8 ms länger.
Aufgrund der Verzögerung bei der Präemption sind einige zeitkritische Aufgaben betroffen)

Die Nachteile sind:

*Je höher die Schlagfrequenz, desto höher die Systembelastung.

Interrupt-Handler beanspruchen mehr Prozessorzeit.

(3) Augenblicke

Mit der globalen Variable „jiffies“ wird die Gesamtzahl der seit dem Systemstart generierten Beats aufgezeichnet.

Beim Start wird jiffies auf 0 initialisiert und danach jedes Mal durch den Takt-Interrupt-Handler erhöht.

Auf diese Weise beträgt die Laufzeit nach dem Start des Systems Jiffies/HZ Sekunden

Jiffies sind in <linux/jiffies.h> definiert:

extern unsigned long volatile jiffies;

Die Variable „Jiffies“ ist immer vom Typ „unsigned long“.

Bei einer 32-Bit-Architektur sind es also 32 Bit und bei einer 64-Bit-Architektur 64 Bit. Bei 32-Bit-Jiffies kommt es bei einem HZ-Wert von 1000 nach 49,7 Tagen zu einem Überlauf. Obwohl ein Überlauf selten vorkommt, ist es dennoch möglich, dass ein Programm beim Erkennen einer Zeitüberschreitung Fehler aufgrund von Umbruch verursacht. Linux stellt vier Makros zum Vergleichen der Beat-Zählung bereit, die den Beat-Count-Wraparound korrekt handhaben können.

#include <linux/jiffies.h>
#define time_after(unbekannt, bekannt) // unbekannt > bekannt
#define time_before(unbekannt, bekannt) // unbekannt < bekannt
#define time_after_eq(unbekannt, bekannt) // unbekannt >= bekannt
#define time_before_eq(unbekannt, bekannt) // unbekannt <= bekannt

Unbekannt bezieht sich normalerweise auf Jiffies, und bekannt ist der zu vergleichende Wert (normalerweise ein relativer Wert, der durch Addieren oder Subtrahieren von Jiffies berechnet wird). Beispiel:

unsigned long timeout = jiffies + HZ/2; /* Timeout nach 0,5 Sekunden */
...
wenn (Zeit_vor(jiffies, Timeout)) {
/* Kein Timeout, gut */
}anders{
/* Zeitüberschreitung, Fehler aufgetreten */

time_before kann so verstanden werden, als ob es vor dem Timeout (vorher) abgeschlossen wäre.

*Im System wird auch ein 64-Bit-Wert jiffies_64 deklariert. In einem 64-Bit-System haben jiffies_64 und jiffies denselben Wert.

Dieser Wert kann über get_jiffies_64() abgerufen werden.

*verwenden

u64 j2;
j2 = get_jiffies_64();

(4) Aktuelle Uhrzeit abrufen

Fahrer müssen die aktuelle Uhrzeit (also die Uhrzeit des Jahres, des Monats und des Tages) im Allgemeinen nicht kennen. Der Fahrer muss sich jedoch möglicherweise mit absoluten Zeiten auseinandersetzen.
Zu diesem Zweck stellt der Kernel zwei Strukturen bereit, die beide in <linux/time.h> definiert sind:

Struktur timeval {
 time_t tv_sec; /* Sekunden */
 suseconds_t tv_usec; ​​​​/* Mikrosekunden */
};
// Älter, aber beliebt. Es speichert in Sekunden und Millisekunden die Anzahl der Sekunden seit 0:00 am 1. Januar 1970. struct timespec {
 time_t tv_sec; /* Sekunden */
 long tv_nsec; /* Nanosekunden */
};
// Neuer, verwendet Sekunden und Nanosekunden, um Zeit zu sparen.

do_gettimeofday() Diese Funktion füllt eine Zeigervariable, die auf die Struktur timeval zeigt, mit den üblichen Sekunden oder Mikrosekunden. Der Prototyp sieht wie folgt aus:

#include <linux/time.h>
void do_gettimeofday(Struktur timeval *tv);

current_kernel_time() Mit dieser Funktion können Sie die Zeitspezifikation abrufen

#include <linux/time.h>
Struktur Zeitspezifikation current_kernel_time(void);
/********************
 *Verzögerte Ausführung der festgelegten Zeit********************/

Gerätetreiber müssen die Ausführung bestimmten Codes häufig für einen bestimmten Zeitraum verzögern, normalerweise, um der Hardware die Fertigstellung einer bestimmten Aufgabe zu ermöglichen.

Verzögerungen, die länger als die Timerperiode (auch als Taktimpuls bezeichnet) sind, können durch die Verwendung der Systemuhr erreicht werden, während sehr kurze Verzögerungen durch Softwareschleifen erreicht werden können.

(1) Kurze Verzögerung

Bei Verzögerungen von bis zu einigen zehn Millisekunden gibt es keine Möglichkeit, den System-Timer zu verwenden.

Das System bietet die folgenden Verzögerungsfunktionen über Softwareschleifen:

#include <linux/delay.h> 
/* Tatsächlich in <asm/delay.h> */
void ndelay(unsigned long nsecs); /*Verzögerung Nanosekunden*/
void udelay(unsigned long usecs); /*Verzögerung in Mikrosekunden*/
void mdelay(unsigned long msecs); /*Verzögerung in Millisekunden*/

Bei diesen drei Verzögerungsfunktionen handelt es sich um Wartefunktionen, und während des Verzögerungsvorgangs können keine anderen Aufgaben ausgeführt werden.

Tatsächlich ist eine Nanosekundenpräzision derzeit nicht auf allen Plattformen möglich.

(2) Lange Verzögerung

a. Geben Sie den Prozessor auf, bevor die Verzögerung abläuft

während(Zeit_vor(jiffies, j1))
Zeitplan();

Während der Wartezeit kann der Prozessor freigegeben werden, das System kann jedoch nicht in den Leerlaufmodus wechseln (da dieser Vorgang immer geplant wird), was der Energieeinsparung nicht förderlich ist.

b. Timeout-Funktion

#include <linux/sched.h>
signiertes langes Schedule_Timeout (signiertes langes Timeout);

Anwendung:

setze_aktuellen_Zustand(TASK_UNTERBRECHUNG);
schedule_timeout(2*HZ); /* 2 Sekunden schlafen*/

Der Prozess wird nach 2 Sekunden aufgeweckt. Wenn Sie nicht durch den Benutzerbereich unterbrochen werden möchten, können Sie den Prozessstatus auf TASK_UNINTERRUPTIBLE setzen.

msleep
ssleep // Sekunden

(3) Warteschlange

Lange Verzögerungen können auch durch Warteschlangen erreicht werden.

Während der Verzögerung schläft der aktuelle Prozess in der Warteschlange.

Wenn ein Prozess schläft, muss er basierend auf dem Ereignis, auf das er wartet, mit einer Warteschlange verknüpft werden.

a. Warteschlange bekannt geben

Die Warteschlange ist eigentlich eine prozessverknüpfte Liste, die alle Prozesse enthält, die auf ein bestimmtes Ereignis warten.

#include <linux/wait.h>
Struktur __wait_queue_head {
    Spinlock_t-Sperre;
    Struktur list_head task_list;
};
Typdefinitionsstruktur __wait_queue_head wait_queue_head_t;

Um einen Prozess zur Warteschlange hinzuzufügen, muss der Treiber zunächst einen Warteschlangenkopf im Modul deklarieren und initialisieren.

Statische Initialisierung

DECLARE_WAIT_QUEUE_HEAD(Name);

Dynamische Initialisierung

wait_queue_head_t meine_Warteschlange;
init_waitqueue_head(&meine_Warteschlange);

b. Wartefunktion

Ein Prozess kann für eine festgelegte Zeit in einer Warteschlange schlafen, indem er die folgende Funktion aufruft:

#include <linux/wait.h>
langes Wait_event_Timeout (Wait_Queue_Head_t q, Bedingung, langes Timeout);
langes wait_event_interruptible_timeout(wait_queue_head_t q, Bedingung, langes Timeout);

Nach dem Aufruf dieser beiden Funktionen schläft der Prozess in der angegebenen Warteschlange q, kehrt jedoch zurück, wenn das Zeitlimit abgelaufen ist.

Wenn das Zeitlimit abgelaufen ist, wird 0 zurückgegeben. Wenn der Prozess durch ein anderes Ereignis aufgeweckt wird, wird die verbleibende Zeit zurückgegeben.

Wenn keine Wartebedingung vorliegt, setze Bedingung auf 0

Anwendung:

wait_queue_head_t warte;
init_waitqueue_head(&warten);
wait_event_interruptible_timeout(warten, 0, 2*HZ); 
/*Der aktuelle Prozess schläft 2 Sekunden in der Warteschlange */

(4) Kernel-Timer

Eine andere Möglichkeit, die Ausführung einer Aufgabe zu verzögern, ist die Verwendung eines Kernel-Timers. Im Gegensatz zu den vorherigen Verzögerungsmethoden blockiert der Kernel-Timer den aktuellen Prozess nicht. Das Starten eines Kernel-Timers erklärt lediglich, dass eine Aufgabe irgendwann in der Zukunft ausgeführt wird, und der aktuelle Prozess wird weiterhin ausgeführt. Verwenden Sie keine Timer für anspruchsvolle Echtzeitaufgaben

Der Timer wird durch die Struktur timer_list dargestellt, die in <linux/timer.h> definiert ist.

Struktur timer_list{
Struktur list_head entry; /* Timer-verknüpfte Liste */
unsigned long expires; /* Zeitwert in Augenblicken*/
Spinlock_t-Sperre;
void(*function)(unsigned long); /* Timer-Verarbeitungsfunktion*/
unsigned long data; /* An die Timer-Verarbeitungsfunktion übergebene Parameter*/
}

Der Kernel stellt eine Reihe von Schnittstellen zur Verwaltung von Timern in <linux/timer.h> bereit.

a. Erstellen Sie einen Timer

struct timer_list my_timer;

b. Initialisieren Sie den Timer

init_timer(&mein_timer);
/* Datenstruktur füllen */
my_timer.expires = Augenblicke + Verzögerung;
mein_timer.data = 0;
my_timer.function = my_function; /*Funktion, die aufgerufen wird, wenn der Timer abläuft*/

c. Timer-Ausführungsfunktion

Der Prototyp der Timeout-Verarbeitungsfunktion sieht wie folgt aus:

void meine_timer_funktion(vorzeichenlose lange Daten);

Mit dem Datenparameter können Sie mehrere Timer mit einer Verarbeitungsfunktion verwalten. Sie können die Daten auf 0 setzen

d. Aktivieren Sie den Timer

add_timer(&my_timer);

Sobald der Timer aktiviert wird, beginnt er zu laufen.

e. Ändern der Timeout-Periode eines aktivierten Timers

mod_timer(&mein_timer,
    jiffies+ney_delay);

Es kann für Timer verwendet werden, die initialisiert, aber noch nicht aktiviert wurden. Wenn der Timer beim Aufruf nicht aktiviert ist, gibt es 0 zurück, andernfalls 1. Sobald mod_timer zurückkehrt, wird der Timer aktiviert.

f. Timer löschen

del_timer(&my_timer);

Es können sowohl aktivierte als auch inaktivierte Timer verwendet werden. Ist der Timer beim Aufruf inaktiv, gibt er 0 zurück, andernfalls 1. Kein Anruf bei abgelaufenen Timern nötig, sie werden automatisch gelöscht

g. Synchrones Löschen

del_time_sync(&my_timer);

Stellen Sie in SMP-Systemen sicher, dass alle Timer-Handler bei der Rückkehr beendet werden. Kann nicht im Interrupt-Kontext verwendet werden.

/********************
 *Verzögerte Ausführung mit ungewisser Zeit********************/

(1) Was ist eine unbestimmte Verzögerung?

Im vorherigen Abschnitt wurde die verzögerte Ausführung für eine bestimmte Zeit vorgestellt. Diese Situation tritt jedoch häufig beim Schreiben von Treibern auf: Das Benutzerbereichsprogramm ruft die Lesefunktion auf, um Daten vom Gerät zu lesen, aber derzeit werden im Gerät keine Daten generiert. An diesem Punkt besteht die Standardoperation der Lesefunktion des Treibers darin, in den Ruhemodus zu wechseln und zu warten, bis Daten im Gerät vorhanden sind.

Bei dieser Art des Wartens handelt es sich um eine Verzögerung auf unbestimmte Zeit, die normalerweise durch die Verwendung eines Schlafmechanismus erreicht wird.

(2) Schlaf

Das Schlafen basiert auf Warteschlangen. Wir haben die wait_event-Funktionsreihe bereits eingeführt, aber jetzt haben wir keine feste Schlafzeit mehr.

Wenn ein Prozess in den Ruhezustand versetzt wird, wird er mit einem speziellen Status gekennzeichnet und aus der Ausführungswarteschlange des Schedulers entfernt.

Bis bestimmte Ereignisse eintreten, z. B. das Gerät Daten empfängt, wird der Prozess in den Ausführungszustand zurückgesetzt und gelangt zur Planung in die Ausführungswarteschlange.

Die Header-Datei der Sleep-Funktion ist <linux/wait.h> und die spezifische Implementierungsfunktion befindet sich in kernel/wait.c.

a. Ruheregeln

  • * Schlafen Sie niemals in einem atomaren Kontext
  • * Wenn wir aufwachen, haben wir keine Ahnung, wie viel Zeit wir geschlafen haben oder ob wir nach dem Aufwachen über die Ressourcen verfügen, die wir brauchen
  • * Ein Prozess kann nicht schlafen, wenn er nicht weiß, dass ihn ein anderer Prozess woanders aufwecken wird

b. Initialisierung der Warteschlange

Siehe vorherigen Artikel

c. Schlaffunktion

Der einfachste Ruhemodus in Linux ist das Makro wait_event. Dieses Makro prüft die Bedingung, auf die der Prozess während der Implementierung des Ruhezustands wartet.

1. void warte_ereignis(
   warte_warteschlangenkopf_t q, 
   int-Bedingung);

2. int wait_event_interruptible(
   warte_warteschlangenkopf_t q, 
   int-Bedingung);
  • q: ist der Kopf der Warteschlange. Beachten Sie, dass es als Wert übergeben wird.
  • Bedingung: Jeder Boolesche Ausdruck. Der Prozess bleibt inaktiv, bis die Bedingung erfüllt ist.
  • Beachten! Der Prozess kann nur durch die Weckfunktion geweckt werden und die Bedingungen müssen zu diesem Zeitpunkt erkannt werden.
  • Wenn die Bedingung erfüllt ist, wacht der aufgeweckte Prozess tatsächlich auf;
  • Wenn die Bedingung nicht erfüllt ist, bleibt der Prozess im Ruhezustand.

Weckfunktion

Wenn unser Prozess in den Ruhezustand wechselt, muss er von einem anderen Ausführungsthread (das kann ein anderer Prozess oder eine Routine zur Interrupt-Behandlung sein) geweckt werden. Weckfunktion:

#include <linux/wait.h>
1. void wake_up(
  wait_queue_head_t *Warteschlange);

2. void wake_up_interruptible(
  wait_queue_head_t *Warteschlange);

wake_up weckt alle Prozesse, die in der angegebenen Warteschlange warten. Und wake_up_interruptible weckt Prozesse, die sich im unterbrechbaren Ruhezustand befinden. In der Praxis besteht die Konvention darin, wake_up zu verwenden, wenn wait_event verwendet wird, und wake_up_interruptible zu verwenden, wenn wait_event_interruptible verwendet wird.

Zusammenfassen

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Lernwert für Ihr Studium oder Ihre Arbeit hat. Vielen Dank für Ihre Unterstützung von 123WORDPRESS.COM. Wenn Sie mehr darüber erfahren möchten, schauen Sie sich bitte die folgenden Links an

Das könnte Sie auch interessieren:
  • Hinweise zur Speicherverwaltung von Linux-Kernel-Gerätetreibern
  • Hinweise zum Zeichengerätetreiber des Linux-Kernel-Gerätetreibers
  • Hinweise zum virtuellen Dateisystem des Linux-Kernel-Gerätetreibers
  • Sortierung der technischen Hinweise zum Linux-Kernel-Gerätetreiber-Kernel-Debugging
  • Hinweise zur Verwendung der verknüpften Liste des Linux-Kernel-Gerätetreibers
  • Hinweise zum Proc-Dateisystem des Linux-Kernel-Gerätetreibers
  • Detaillierte Erklärung zum Schreiben von Linux-Kameratreibern
  • Analyse des Parameterübertragungsprozesses des Treibermoduls in Linux

<<:  MySQL-Installationstutorial unter Centos7

>>:  Beispiel für die Implementierung eines dynamischen Überprüfungscodes auf einer Seite mithilfe von JavaScript

Artikel empfehlen

So simulieren Sie eine Aufzählung mit JS

Vorwort Im aktuellen JavaScript gibt es kein Konz...

Vollständige Schritte zum Erstellen eines Passwortgenerators mit Node.js

Inhaltsverzeichnis 1. Vorbereitung 2. Befehlszeil...

MySQL-Einstellungscode für die grüne Version und Details zum Fehler 1067

MySQL-Einstellungscode für grüne Version und Fehl...

Detaillierte Erläuterung des Speichermodells der JVM-Serie

Inhaltsverzeichnis 1. Speichermodell und Laufzeit...

Zusammenfassung der Ereignisbehandlung im Vue.js-Frontend-Framework

1. v-on-Ereignisüberwachung Um DOM-Ereignisse abz...

CSS realisiert den Maskeneffekt, wenn die Maus über das Bild bewegt wird

1. Setzen Sie den HTML-Code der Maskenebene und d...

element-ui Markieren Sie die Koordinatenpunkte nach dem Hochladen des Bildes

Was ist Element-UI element-ui ist eine auf Vue.js...

Beispiele für die Verwendung von HTML-Metadaten

Beispielverwendung Code kopieren Der Code lautet w...

Die v-for-Direktive in Vue vervollständigt die Listendarstellung

Inhaltsverzeichnis 1. Listendurchlauf 2. Die Roll...

CSS3-Randeffekte

Was ist CSS? CSS (Abkürzung für Cascading Style S...