1. Was ist copy_{to,from}_user()Es ist eine Brücke für die Kommunikation zwischen Kernel- und Benutzerbereich. Alle Dateninteraktionen sollten eine solche Schnittstelle verwenden. Aber was genau ist seine Rolle? Wir stellen folgende Fragen:
Herzliche Erinnerung: Die Codeanalyse in diesem Artikel basiert auf Linux-4.18.0 und einige architekturbezogene Codes werden durch ARM64 dargestellt. 1. copy_{to,from}_user() vs. memcpy()
In verschiedenen Blogs konzentrieren sich die Meinungen hauptsächlich auf den ersten Punkt. Es scheint, dass der erste Punkt allgemein anerkannt ist. Wer jedoch auf die Praxis setzt, gelangt zu einer zweiten Ansicht, denn Übung macht den Meister. Liegt die Wahrheit in den Händen einiger weniger Menschen? Oder sind die Augen der Menschen schärfer? Selbstverständlich bestreite ich keine der oben genannten Ansichten. Auch können wir Ihnen nicht garantieren, welche Ansicht richtig ist. Denn ich bin davon überzeugt, dass selbst eine einstmals einwandfreie Theorie im Laufe der Zeit oder aufgrund veränderter Umstände ihre Richtigkeit verlieren kann. Beispielsweise Newtons Theorie der klassischen Mechanik (das scheint etwas weit hergeholt). Wenn ich es in menschlichen Worten ausdrücken müsste, wäre es Folgendes: Die Linux-Codebasis ändert sich im Laufe der Zeit ständig. Vielleicht war die obige Ansicht einmal richtig. Natürlich kann es auch heute noch richtig sein. Die folgende Analyse stellt meine Meinung dar. Ebenso müssen wir skeptisch bleiben. 2. FunktionsdefinitionSehen wir uns zunächst die Funktionsdefinitionen von memcpy() und copy_{to,from}_user() an. Die Parameter sind fast gleich, sie enthalten alle die Zieladresse, die Quelladresse und die Größe der zu kopierenden Bytes. statisch __always_inline unsigned long __must_check kopiere_nach_Benutzer(void __user *nach, const void *von, unsigned long n); statisch __always_inline unsigned long __must_check kopiere_von_Benutzer(void *nach, const void __user *von, unsigned long n); void *memcpy(void *dest, const void *src, size_t len); Eines wissen wir jedoch mit Sicherheit. Das heißt, memcpy() überprüft nicht die Legitimität der übergebenen Adresse. Und copy_{to,from}_user() führt eine Gültigkeitsprüfung ähnlich der folgenden an der eingehenden Adresse durch (um es einfacher auszudrücken: Weitere Einzelheiten zur Überprüfung finden Sie im Code).
Schauen wir uns nach diesem kurzen Vergleich weitere Unterschiede an und diskutieren wir die beiden oben genannten Punkte. Beginnen wir mit dem zweiten Punkt. Wenn es ums Üben geht, glaube ich immer noch, dass Übung den Meister macht. Aus den Ergebnissen meines Tests lassen sich zwei Implementierungsergebnisse ableiten. Das Ergebnis des ersten Falls ist: Beim Testen mit memcpy() gibt es kein Problem und der Code wird normal ausgeführt. Der Testcode lautet wie folgt (es wird nur die Leseschnittstellenfunktion angezeigt, die den Dateioperationen unter dem Proc-Dateisystem entspricht): statische ssize_t test_read(Struktur Datei *Datei, char __user *buf, size_t Länge, loff_t *Offset) { memcpy(buf, "test\n", 5); /* Kopiere_in_Benutzer(buf, "test\n", 5) */ Rückgabe 5; } Wir verwenden den Befehl cat, um den Dateiinhalt zu lesen. cat ruft test_read über den Systemaufruf read auf und die übergebene Puffergröße beträgt 4 KB. Der Test verlief reibungslos und die Ergebnisse waren vielversprechend. Die Zeichenfolge „Test“ wurde erfolgreich gelesen. Es scheint, dass der zweite Punkt richtig ist. Wir müssen jedoch weiterhin Überprüfungen und Untersuchungen durchführen. Denn der erste erwähnte Punkt: „Diese Seitenfehlerausnahme muss explizit im Kernelbereich repariert werden.“ Daher müssen wir auch die folgende Situation überprüfen: Wenn buf virtueller Adressraum im Benutzerraum zugewiesen wurde, aber keine spezifische Zuordnungsbeziehung mit dem physischen Speicher hergestellt wurde, tritt in diesem Fall ein Seitenfehler im Kernelmodus auf. Wir müssen zuerst diese Bedingung erstellen, den passenden Puffer finden und ihn dann testen. Getestet habe ich das natürlich nicht. Weil es Testschlussfolgerungen gibt (hauptsächlich, weil ich faul bin und es mühsam finde, diese Bedingung zu konstruieren). Dieser Test wurde von einem Freund von mir durchgeführt, der auch als „Assistenzlehrer“ von Teacher Song, Ackerman, bekannt ist. Er hat dieses Experiment einmal durchgeführt und ist zu folgendem Schluss gekommen: Auch wenn keine spezielle Zuordnungsbeziehung zwischen Puffer und physischem Speicher besteht, kann der Code normal ausgeführt werden. Ein Seitenfehler tritt im Kernelzustand auf und wird von diesem repariert (Zuweisen eines bestimmten physischen Speichers, Füllen der Seitentabelle und Herstellen einer Zuordnungsbeziehung). Gleichzeitig habe ich es aus der Perspektive des Codes analysiert und bin zu dem gleichen Schluss gekommen. Nach der obigen Analyse scheint es, dass memcpy () auch normal verwendet werden kann. Aus Sicherheitsgründen wird empfohlen, Schnittstellen wie copy_{to,from}_user () zu verwenden. Das Ergebnis des zweiten Falls ist, dass der obige Testcode nicht ordnungsgemäß ausgeführt wird und einen Kernel-Oops auslöst. Natürlich unterscheiden sich die Kernelkonfigurationsoptionen für diesen Test von denen für den letzten Test. Dieses Konfigurationselement ist CONFIG_ARM64_SW_TTBR0_PAN oder CONFIG_ARM64_PAN (für ARM64-Plattform). Die Funktion beider Konfigurationsoptionen besteht darin, den direkten Zugriff des Kernelmodus auf den Benutzeradressraum zu verhindern. Der einzige Unterschied besteht darin, dass Nachdem Sie Warum brauchen wir die PAN-Funktion (Privileged Access Never)? Der Grund dafür kann sein, dass die Dateninteraktion zwischen Benutzerbereich und Kernelbereich leicht zu Sicherheitsproblemen führen kann. Daher erlauben wir dem Kernelbereich nicht, einfach auf den Benutzerbereich zuzugreifen. Wenn dies erforderlich ist, müssen wir PAN über eine bestimmte Schnittstelle schließen. Andererseits kann die PAN-Funktion die Verwendung von Schnittstellen für die Dateninteraktion im Kernelmodus und Benutzermodus weiter standardisieren. Wenn die PAN-Funktion aktiviert ist, können die Kernel- oder Treiberentwickler gezwungen werden, Sicherheitsschnittstellen wie copy_{to,from}_user() zu verwenden, um die Sicherheit des Systems zu verbessern. Bei nicht standardmäßigen Vorgängen wie memcpy() gibt der Kernel ein Hoppla aus. Durch unsachgemäße Programmierung entstehen Sicherheitslücken. Beispiel: Die Linux-Kernel-Sicherheitslücke CVE-2017-5123 kann zu einer Privilegienerhöhung führen. Der Grund für die Einführung dieser Sicherheitsanfälligkeit ist das Fehlen von access_ok(), um die Legitimität der vom Benutzer übergebenen Adresse zu überprüfen. Um Sicherheitsprobleme zu vermeiden, die durch unseren eigenen Code verursacht werden, müssen wir daher bei der Interaktion zwischen Daten im Kernelbereich und im Benutzerbereich besonders vorsichtig sein. 2. CONFIG_ARM64_SW_TTBR0_PAN-PrinzipCONFIG_ARM64_SW_TTBR0_PAN Das Prinzip hinter dem Design. Aufgrund des speziellen Hardwaredesigns von ARM64 verwenden wir zwei Seitentabellen-Basisadressregister ttbr0_el1 und ttbr1_el1. Der Prozessor bestimmt anhand der oberen 16 Bits der 64-Bit-Adresse, ob die aufgerufene Adresse zum Benutzerbereich oder zum Kernelbereich gehört. Wenn es sich um eine Benutzerbereichsadresse handelt, verwenden Sie ttbr0_el1, andernfalls verwenden Sie ttbr1_el1. Daher müssen Sie beim Wechseln des ARM64-Prozesses nur den Wert von ttbr0_el1 ändern. ttbr1_el1 muss sich möglicherweise nicht ändern, da alle Prozesse die gleiche Kernelspace-Adresse gemeinsam nutzen. Wie können wir verhindern, dass der Kernelzustand auf den Adressraum des Benutzerzustands zugreift, wenn ein Prozess in den Kernelzustand wechselt (Unterbrechung, Ausnahme, Systemaufruf usw.)? Tatsächlich ist es nicht schwer herauszufinden, dass wir nur den Wert von ttbr0_el1 ändern müssen, damit er auf eine ungültige Zuordnung verweist. Daher bereiten wir zu diesem Zweck eine spezielle Seitentabelle vor. Die Seitentabellengröße beträgt 4 KB Speicher und ihre Werte sind alle 0. Wenn der Prozess in den Kernelmodus wechselt, kann das Ändern des Werts von ttbr0_el1 in die Adresse der Seitentabelle sicherstellen, dass der Zugriff auf die Benutzerbereichsadresse unzulässig ist. Weil der Wert der Seitentabelle unzulässig ist. Dieser spezielle Seitentabellenspeicher wird vom Linker-Skript zugewiesen. #define RESERVED_TTBR0_SIZE (SEITENGRÖSSE) ABSCHNITTE { reserviert_ttbr0 = .; . += RESERVIERT_TTBR0_SIZE; swapper_pg_dir = .; . += SWAPPER_DIR_SIZE; swapper_pg_end = .; } Diese spezielle Seitentabelle befindet sich zusammen mit der Kernel-Seitentabelle. Der Größenunterschied zu swapper_pg_dir beträgt nur 4 KB. Der Inhalt des 4K-Speicherplatzes ab der Adresse reserved_ttbr0 wird gelöscht. Wenn wir in den Kernelstatus wechseln, schalten wir ttbr0_el1 über __uaccess_ttbr0_disable um, um den Benutzerbereich-Adresszugriff zu deaktivieren, und aktivieren den Benutzerbereich-Adresszugriff über _uaccess_ttbr0_enable, wenn Zugriff erforderlich ist. Die beiden Makrodefinitionen sind nicht kompliziert. Nehmen wir _uaccess_ttbr0_disable als Beispiel, um das Prinzip zu verdeutlichen. Die Definition lautet wie folgt: Makro __uaccess_ttbr0_disable, tmp1 mrs \tmp1, ttbr1_el1 // swapper_pg_dir (1) bic \tmp1, \tmp1, #TTBR_ASID_MASK sub \tmp1, \tmp1, #RESERVED_TTBR0_SIZE // reserved_ttbr0 kurz vor // swapper_pg_dir (2) msr ttbr0_el1, \tmp1 // setze reserviertes TTBR0_EL1 (3) isb füge \tmp1, \tmp1, #RESERVED_TTBR0_SIZE hinzu msr ttbr1_el1, \tmp1 // reservierte ASID festlegen isb .endm
Die C-Sprachimplementierung, die __uaccess_ttbr0_disable entspricht, finden Sie hier. Wie kann dem Kernelmodus der Zugriff auf Benutzerbereichsadressen gestattet werden? Es ist auch sehr einfach, nämlich die umgekehrte Operation von __uaccess_ttbr0_disable, wodurch ttbr0_el1 eine gültige Basisadresse für die Seitentabelle erhält. Es ist nicht nötig, es hier zu wiederholen. Was wir jetzt wissen müssen, ist, dass, wenn CONFIG_ARM64_SW_TTBR0_PAN konfiguriert ist, die Schnittstelle copy_{to,from}_user() dem Kernelmodus den Zugriff auf den Benutzerbereich vor dem Kopieren ermöglicht und die Möglichkeit des Kernelmodus, auf den Benutzerbereich zuzugreifen, nachdem der Kopiervorgang abgeschlossen ist, deaktiviert. Daher ist die Verwendung von copy_{to,from}_user() der konventionelle Ansatz. Dies spiegelt sich hauptsächlich in Sicherheitsüberprüfungen und der Sicherheitszugriffsverarbeitung wider. Dies ist die erste Funktion, die es gegenüber memcpy() hat, und eine weitere wichtige Funktion wird später eingeführt. Wir können nun die Fragen beantworten, die im vorigen Abschnitt noch offen geblieben sind. Wie kann ich memcpy() weiterhin verwenden? Jetzt ist es ganz einfach. Erlauben Sie dem Kernelmodus vor dem Aufruf von memcpy() den Zugriff auf die Benutzerbereichsadresse über uaccess_enable_not_uao(), rufen Sie memcpy() auf und deaktivieren Sie schließlich die Möglichkeit des Kernelmodus, über uaccess_disable_not_uao() auf den Benutzerbereich zuzugreifen. 3. TestenDie oben genannten Testfälle basieren alle auf dem Test der Weitergabe legaler Adressen im Benutzerbereich. Was ist eine legale Benutzerbereichsadresse? Der im virtuellen Adressraum enthaltene Adressbereich, der vom Benutzerraum über den Systemaufruf angefordert wurde, ist eine gültige Adresse (unabhängig davon, ob physische Seiten zugewiesen werden, um eine Zuordnungsbeziehung herzustellen). Da wir ein Schnittstellenprogramm schreiben, müssen wir auch die Robustheit des Programms berücksichtigen. Wir können nicht davon ausgehen, dass alle von Benutzern übergebenen Parameter zulässig sind. Wir sollten das Auftreten illegaler Übertragungen von Teilnehmern vorhersehen und uns im Voraus darauf vorbereiten, das heißt, uns auf schlechte Zeiten vorbereiten. Wir verwenden zuerst den Testfall von memcpy() und übergeben eine zufällige ungültige Adresse. Beim Testen stellte sich heraus, dass es Kernel-Oops auslösen würde. Verwenden Sie weiterhin copy_{to,from}_user() anstelle des memcpy()-Tests. Der Test ergab, dass read() lediglich einen Fehler zurückgibt, aber keinen Kernel-Oops auslöst. Dies ist das gewünschte Ergebnis. Schließlich sollte eine Anwendung nicht in der Lage sein, einen Kernel-Oops auszulösen. Was ist das Umsetzungsprinzip dieses Mechanismus? Nehmen wir copy_to_user() als Beispiel. Der Funktionsaufrufablauf ist: _arch_copy_to_user() ist auf der ARM64-Plattform in Assemblercode implementiert und dieser Teil des Codes ist kritisch. Ende .req x5 EINTRAG(__arch_copy_to_user) uaccess_enable_not_uao x3, x4, x5 Ende hinzufügen, x0, x2 #include "Kopiervorlage.S" uaccess_disable_not_uao x3, x4 mov x0, #0 zurück ENDPROC(__arch_copy_to_user) .Abschnitt .Fixup,"ax" .ausrichten 2 9998: sub x0, end, dst // Bytes nicht kopiert zurück .vorherige
Verglichen mit den Ergebnissen der vorherigen Analyse kann _arch_copy_to_user() tatsächlich ungefähr der folgenden Beziehung entsprechen. uaccess_enable_not_uao(); memcpy(ubuf, kbuf, Größe); == __arch_copy_to_user(ubuf, kbuf, Größe); uaccess_disable_not_uao(); Lassen Sie mich zunächst eine Nachricht einfügen, um zu erklären, warum copy_template.S memcpy() ist. memcpy() wird durch Assemblercode auf der ARM64-Plattform implementiert. Es ist in der Datei arch/arm64/lib/memcpy.S definiert. .schwaches memcpy EINTRAG(__memcpy) EINTRAG(memcpy) #include "Kopiervorlage.S" zurück ENDPIPROC(memcpy) ENDPROC(__memcpy) Daher sind die Funktionsdefinitionen von memcpy() und __memcpy() offensichtlich identisch. Und die memcpy()-Funktion wird als schwach deklariert, sodass die memcpy()-Funktion neu geschrieben werden kann (etwas weit hergeholt). Lassen Sie mich noch etwas weiter gehen. Warum Assembly verwenden? Warum nicht die Funktion memcpy() in der Datei lib/string.c verwenden? Dies dient natürlich dazu, die Ausführungsgeschwindigkeit von memcpy() zu optimieren. Die Funktion memcpy() in der Datei lib/string.c kopiert Bytes (selbst die beste Hardware kann durch schlechten Code ruiniert werden). Die meisten Prozessoren heutzutage sind jedoch 32- oder 64-Bit-Prozessoren, es ist also möglich, 4 Bytes, 8 Bytes oder sogar 16 Bytes zu kopieren (unter Berücksichtigung der Adressausrichtung). Kann die Ausführungsgeschwindigkeit erheblich verbessern. Daher verwendet die ARM64-Plattform eine Assemblerimplementierung. Weitere Informationen hierzu finden Sie in diesem Blog „Memcpy-Optimierung und Implementierung von ARM64“. Kommen wir zum Punkt zurück und wiederholen: Wenn der Kernelstatus auf eine Benutzerbereichsadresse zugreift und ein Seitenfehler ausgelöst wird, behebt der Kernelstatus die Ausnahme, als wäre nichts passiert (er weist physischen Speicher zu und stellt eine Seitentabellenzuordnungsbeziehung her), solange die Benutzerbereichsadresse zulässig ist. Wenn Sie jedoch auf eine illegale Benutzerbereichsadresse zugreifen, wählen Sie Pfad 2 und versuchen Sie, sich zu rehabilitieren. Diese Möglichkeit besteht darin, die Abschnitte statisches void __do_kernel_fault(unsignierte lange Adresse, unsignierte int esr, Struktur pt_regs *regs) { /* * Sind wir darauf vorbereitet, diesen Kernelfehler zu beheben? * Wir sind mit ziemlicher Sicherheit nicht darauf vorbereitet, Anweisungsfehler zu beheben. */ wenn (!is_el1_instruction_abort(esr) && fixup_exception(regs)) zurückkehren; /* … */ } fixup_exception() ruft anschließend search_exception_tables() auf, das nach dem Abschnitt _extable sucht. Das Segment __extable speichert die Ausnahmetabelle und jeder Eintrag speichert die Ausnahmeadresse und die entsprechende Reparaturadresse. Beispielsweise wird die Adresse der oben erwähnten Anweisung 9998:subx0,end,dst gefunden und die Rücksprungadresse der Funktion do_page_fault() geändert, um die Sprungreparaturfunktion zu erreichen. Tatsächlich besteht der Suchvorgang darin, basierend auf der Adressadresse des Problems herauszufinden, ob im Segment _extable (Ausnahmetabelle) ein entsprechender Ausnahmetabelleneintrag vorhanden ist. Wenn dies der Fall ist, bedeutet dies, dass es repariert werden kann. Da die Implementierungsmethoden von 32-Bit-Prozessoren und 64-Bit-Prozessoren unterschiedlich sind, beginnen wir zunächst mit dem Implementierungsprinzip der Ausnahmetabelle für 32-Bit-Prozessoren. Die erste und letzte Adresse des _extable-Segments sind __start___ex_table und __stop___ex_table (definiert in include/asm-generic/vmlinux.lds.h). Dieses Speichersegment kann als Array betrachtet werden, dessen jedes Element vom Typ struct exception_table_entry ist, das die Adresse aufzeichnet, an der die Ausnahme aufgetreten ist, und die entsprechende Reparaturadresse. Ausnahmetabellen __start___ex_table --> +---------------+ | Eintrag | +-----------------+ | Eintrag | +-----------------+ | ... | +-----------------+ | Eintrag | +-----------------+ | Eintrag | __stop___ex_table --> +---------------+ Auf einem 32-Bit-Prozessor wird die Struktur exception_table_entry wie folgt definiert: Struktur Ausnahmetabelleneintrag { unsignierte lange Inserate, Fixup; }; Eines muss klargestellt werden: Auf einem 32-Bit-Prozessor beträgt „unsigned long“ 4 Bytes. insn und fixup speichern die Adresse des Ausnahmeauftretens bzw. die entsprechende Fixup-Adresse. Suche nach der entsprechenden Reparaturadresse gemäß der Ausnahmeadresse ex_addr (bei Nichtgefunden wird 0 zurückgegeben). Der schematische Code lautet wie folgt: unsigned long search_fixup_addr32(unsigned long ex_addr) { const struct Ausnahmetabelleneintrag *e; für (e = __start___ex_table; e < __stop___ex_table; e++) wenn (ex_addr == e->insn) Rückgabewert e->fixup; gebe 0 zurück; }
Wenn wir mit der Entwicklung von 64-Bit-Prozessoren fortfahren und diese Methode weiterhin verwenden, werden wir zwangsläufig doppelt so viel Speicher wie bei 32-Bit-Prozessoren benötigen, um die Ausnahmetabelle zu speichern (da zum Speichern einer Adresse 8 Bytes erforderlich sind). Daher verwendet der Kernel eine andere Methode zur Implementierung. Auf 64-Prozessoren wird Struktur Ausnahmetabelleneintrag { int insn, fixup; }; Der von jedem Ausnahmetabelleneintrag belegte Speicher entspricht dem eines 32-Bit-Prozessors, die Speichernutzung bleibt also unverändert. Allerdings hat sich die Bedeutung von „insn“ und „fixup“ geändert. insn und fixup speichern jeweils die Adresse, an der die Ausnahme aufgetreten ist, und den Offset der Reparaturadresse relativ zur aktuellen Adresse des Strukturmitglieds (etwas verwirrend). Beispielsweise wird gemäß der Ausnahmeadresse ex_addr die entsprechende Reparaturadresse gesucht (wenn sie nicht gefunden wird, wird 0 zurückgegeben), und der schematische Code lautet wie folgt: unsigned long search_fixup_addr64(unsigned long ex_addr) { const struct Ausnahmetabelleneintrag *e; für (e = __start___ex_table; e < __stop___ex_table; e++) wenn (ex_addr == (unsigned long)&e->insn + e->insn) Rückgabewert (vorzeichenloser Long-Wert) und e->fixup + e->fixup; gebe 0 zurück; } Daher konzentrieren wir uns auf die Konstruktion von exception_table_entry. Wir müssen für jeden Speicherzugriff auf eine Benutzerbereichsadresse einen Ausnahmetabelleneintrag erstellen und ihn in das _extable-Segment einfügen. Beispielsweise die folgenden Montageanweisungen (die den Montageanweisungen entsprechenden Adressen sind willkürlich geschrieben, machen Sie sich also keine Gedanken darüber, ob sie richtig oder falsch sind. Das Verständnis der Prinzipien ist der Schlüssel). 0xffff000000000000: ldr x1, [x0] 0xffff000000000004: füge x1, x1, #0x10 hinzu 0xffff000000000008: ldr x2, [x0, #0x10] /* … */ 0xffff000040000000: mov x0, #0xffffffffffffffff2 // -14 0xffff000040000004: ret Angenommen, das x0-Register enthält die Benutzerbereichsadresse, sodass wir einen Ausnahmetabelleneintrag für den Assemblerbefehl an der Adresse 0xffff000000000000 erstellen müssen. Wir erwarten, dass, wenn x0 eine ungültige Benutzerbereichsadresse ist, die vom Sprung zurückgegebene Reparaturadresse 0xffff000040000000 ist. Der Einfachheit halber nehmen wir an, dass dies die Erstellung des ersten Eintrags ist und der Wert von __start___ex_table 0xffff000080000000 ist. Dann lauten die Werte der Insn- und Fixup-Mitglieder des ersten Ausnahmetabelleneintrags: 0x80000000 und 0xbffffffc (beide Werte sind negativ). Daher wird für jede Anweisung zum Zugriff auf eine Userspace-Adresse im Assemblercode copy{to,from}user() ein Eintrag erstellt. Daher muss die Assembleranweisung an der Adresse 0xffff000000000008 auch einen Ausnahmetabelleneintrag erstellen. Was genau passiert also, wenn der Kernelmodus auf eine ungültige Benutzerbereichsadresse zugreift? Der obige Analyseprozess kann wie folgt zusammengefasst werden:
IV. FazitJetzt ist es Zeit zur Überprüfung und Zusammenfassung, und die Überlegungen zu copy_{to,from}_user() enden hier. Lassen Sie uns diesen Artikel mit einer Zusammenfassung beenden. Unabhängig davon, ob im Kernelmodus oder im Benutzermodus auf eine legitime Benutzerbereichsadresse zugegriffen wird und die virtuelle Adresse keine Zuordnungsbeziehung zur physischen Adresse herstellt, ist der Seitenfehlerprozess nahezu derselbe. Er hilft uns dabei, den physischen Speicher zu beantragen und eine Zuordnungsbeziehung herzustellen. In diesem Fall sind memcpy() und copy_{to,from}_user() ähnlich. Wenn der Kernelstatus auf eine ungültige Benutzerbereichsadresse zugreift, wird die Reparaturadresse basierend auf der Ausnahmeadresse gefunden. Diese Methode zum Beheben der Ausnahme stellt keine Adresszuordnungsbeziehung her, ändert jedoch die Rücksprungadresse von do_page_fault(). memcpy() kann dies nicht. Wenn Abschließend möchte ich sagen, dass memcpy() in einigen Fällen sogar einwandfrei funktionieren kann. Dies wird jedoch ebenfalls nicht empfohlen und stellt keine gute Programmierpraxis dar. Bei der Dateninteraktion im Benutzerbereich und im Kernelbereich müssen wir eine Schnittstelle verwenden, die copy_{to,from}_user() ähnelt. Warum sind sie ähnlich? Denn es gibt zwar noch andere Schnittstellen für die Dateninteraktion im Kernel- und Benutzerbereich, diese sind aber nicht so bekannt wie copy_{to,from}_user(). Beispiel: {get,put}_user(). Dies ist das Ende dieses Artikels über copy_{to, from}_user(). Weitere relevante Kopier- und Benutzerinhalte finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird! Das könnte Sie auch interessieren:
|
<<: Beispielcode zur Implementierung des wellenförmigen Wasserballeffekts mit CSS
>>: Detaillierte Erklärung der vier Arten von MySQL-Verbindungen und Multi-Table-Abfragen
Jetzt ist .net Core plattformübergreifend und jed...
1. Installation von Windows Server 2019 Installie...
Inhaltsverzeichnis 1. mixin.scss 2. Einzeldateinu...
Mac-Knoten löschen und neu installieren löschen K...
Überblick Ich verwende Docker seit über einem Jah...
In diesem Artikelbeispiel wird der spezifische Co...
Lassen Sie mich zunächst eine interessante Eigens...
Inhaltsverzeichnis einführen Unterstützt Intel-CP...
<br />Beim Hochladen auf manchen Websites wi...
Browserkompatibilität ist nichts anderes als Stil...
Wenn die oben genannten Einstellungen in IE8 und C...
Kaskadierung und kaskadierende Ebenen HTML-Elemen...
Hinweis: Die Nginx-Version muss 1.9 oder höher se...
Um einen String in ein Array aufzuteilen, müssen ...
Hintergrund Ein spezielles Gerät wird verwendet, ...