So erstellen Sie einen eingebetteten Linux-Gerätetreiber (4)

4. Lese- / Schreibimplementierung und Speichergeschichte

Über diese Serie

Ich werde ein HowTo darüber schreiben, wie man ein Kernelmodul (Gerätetreiber) für eingebettetes Linux erstellt. Der gesamte Inhalt dieses Artikels kann auf Razpie ausgeführt werden.

Der gesamte Quellcode, der in diesem Artikel angezeigt wird

https://github.com/take-iwiw/DeviceDriverLesson/tree/master/04_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/04_02

Inhalt dieser Zeit

Bis zum letzten Mal haben wir die Methode durchgeführt, den erstellten Gerätetreiber als Kernelmodul in den Kernel zu integrieren. Dieses Mal werde ich Werte mit diesem Gerätetreiber durch Lesen / Schreiben aus dem Anwenderprogramm austauschen.

Vorausgesetztes Wissen

In Bezug auf Speicherplatz

Computer haben normalerweise einen Speicher wie DDR. Im Fall von Raspberry Pi 2 ist 1 GB SDRAM installiert. Für Raspeye beginnt die physikalische Adresse dieses Speichers bei 0x0000_0000. Wenn das von uns geschriebene Programm jedoch beim Zugriff auf die Daten im Speicher direkt die physikalische Adresse verwendet, ist dies nicht der Fall. Die CPU hat eine Funktion namens MMU, und die MMU konvertiert die physikalische Adresse ⇔ virtuelle Adresse. Der Kernel und jeder Prozess werden in unabhängigen virtuellen Adressräumen ausgeführt. Wenn beispielsweise 50 Prozesse ausgeführt werden, gibt es 51 virtuelle Adressräume (Anzahl der Prozesse + Kernel). Die Adressen in jedem virtuellen Raum sind unabhängig. Beispielsweise unterscheiden sich 0xAAAA_AAAA für Prozess A und 0xAAAA_AAAA für Prozess B. Dies verhindert, dass der Speicher durch andere Prozesse beschädigt wird. Sie können auch mehr Speicherplatz als die tatsächliche physische Speicherkapazität haben. Dies sind die Vorteile der Verwendung von MMU.

Im Fall von Raspeltorte

memorymap.jpg

Das Obige ist die Speicherkarte von Raspeye. Erstens ist das Zentrum die physikalische Adresse, die von der CPU (ARM) aus gesehen wird. Wenn Sie sich das ansehen, können Sie sehen, dass das SDRAM von 0x0000_0000 zugewiesen wird. Die rechte Seite ist die Speicherzuordnung der logischen Adresse nach der Übersetzung durch die ARM-MMU. 0x0000_0000 bis 0xC000_0000 sind virtuelle Adressen für User Space-Prozesse. In diesem virtuellen Speicherbereich werden normale Programme ausgeführt. Zwischen verschiedenen Prozessen treten doppelte virtuelle Adressen auf. Die MMU übersetzt es jedoch so, dass es sich nicht mit der physischen Adresse überschneidet. Ab 0xC000_0000 ist die obere Ebene der virtuelle Speicherbereich für den Kernelbereich.

Ich habe bereits erwähnt, dass die physikalische Adresse des SDRAM aus Sicht des ARM bei 0x0000_0000 beginnt, aber wenn die CPU tatsächlich auf den Speicher zugreift, geht sie über den Bus (wahrscheinlich AXI). Dies ist wahrscheinlich auch Raspais eigene Konfiguration, aber es scheint, dass sie über einen Videoprozessor (VC (Video Core) auf der linken Seite in der Abbildung) mit dem Bus verbunden ist. Daher ist die Konvertierung von VC mit MMU enthalten. In der Abbildung ist 0xC000_0000 am Ende die Busadresse von SDRAM (Nicht-Cache). Daher ist es erforderlich, diese Adresse bei Verwendung von DMA usw. festzulegen. Übrigens scheint es 0x8000_0000 über den L2-Cache zu sein. Wenn die CPU beispielsweise Daten erstellt, die von DMA übertragen werden sollen, wird die Kohärenz nicht beibehalten, wenn Sie an diese Adresse schreiben. Dateninkonsistenz tritt auf, weil die DMA-Übertragung durchgeführt wird, bevor das Zurückschreiben vom Cache zum SDRAM durchgeführt wird. Sie müssen es in eine nicht zwischengespeicherte Adresse konvertieren, bevor Sie auf den Speicher zugreifen können.

Es ist mühsam, diese Adressen einzeln in der Speicherzuordnung zu überprüfen, aber es scheint, dass Sie sie mit der folgenden Funktion erhalten können. Der Build lautet "gcc get_memory.c -L / opt / vc / lib -lbcm_host". Für Raspberry Pi2 gab bcm_host_get_sdram_address C0000000 und bcm_host_get_peripheral_address 3F000000 zurück.

get_memory.c


#include <stdio.h>

int main()
{
	/* https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md */
	extern unsigned bcm_host_get_sdram_address(void);
	printf("%08X\n", bcm_host_get_sdram_address());
	extern unsigned bcm_host_get_peripheral_address(void);;
	printf("%08X\n", bcm_host_get_peripheral_address());

	return 0;
}

Weitere Informationen zur Speicherkarte von Raspeye finden Sie unter Datenblatt: BCM2835-ARM-Peripherals.pdf. ..

Das Wichtigste hier

Es ist etwas abseits der ausgetretenen Pfade, aber diesmal ist es wichtig, dass der Aufrufer des Systemaufrufs (User-Space-Prozess) und der virtuelle Adressraum innerhalb des Kernel-Moduls unterschiedlich sind. Das Übergeben eines Zeigers garantiert nicht, dass er auf dieselbe Adresse verweist.

User Space - Sichere Datenkopie im Kernel Space

Letzte Implementierung

Beim letzten Mal habe ich vorerst den folgenden Read System Call Handler in der Implementierung implementiert. Wann immer Sie lesen, wird 1Byte of'A' in buf gespeichert. Dieser Puffer ist der vom Anrufer reservierte Bereich (Benutzerbereich). Daher sollte ein Zugriff außerhalb des Bereichs erfolgen. Es hat jedoch tatsächlich gut funktioniert. Der Wert wurde ebenfalls korrekt gespeichert. Dies liegt daran, dass der virtuelle Adressraum in 1 GB für den Kernel und 3 GB für den Benutzer unterteilt ist, wie in der Speicherzuordnung am Anfang gezeigt. Daher kann der Kernel auf den virtuellen Adressraum von Prozessen zugreifen, die im selben Kontext ausgeführt werden (dh auf den Prozess, der den Systemaufruf aufgerufen hat). In der entgegengesetzten Richtung denke ich, dass eine Verletzung des Speicherschutzes auftreten wird. Dies funktioniert jedoch zufällig, und ich glaube nicht, dass es funktionieren wird, wenn die Zieladresse beispielsweise ausgetauscht wird.

static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");
	buf[0] = 'A';
	return 1;
}

Kopieren Sie die Daten entsprechend der Art und Weise

Die obige Methode funktioniert zufällig, aber Lehrbücher verwenden "copy_to_user" und "copy_from_user", wenn Daten in den User Space-Kernel Space kopiert werden. Im folgenden Code wird die Zeichenkette, die der Benutzer zum Zeitpunkt des Schreibens mit "copy_from_user" gesetzt hat, in der statischen Variablen "saved_value" gespeichert. Der zum Zeitpunkt des Lesens gespeicherte Inhalt wird von "copy_to_user" zurückgegeben.

#define NUM_BUFFER 256
static char stored_value[NUM_BUFFER];

/*Funktion beim Lesen aufgerufen*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");
	if(count > NUM_BUFFER) count = NUM_BUFFER;
	if (copy_to_user(buf, stored_value, count) != 0) {
		return -EFAULT;
	}
	return count;
}

/*Funktion zum Zeitpunkt des Schreibens aufgerufen*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_write");
	if (copy_from_user(stored_value, buf, count) != 0) {
		return -EFAULT;
	}
	printk("%s\n", stored_value);
	return count;
}

Für jedes Öffnen anders verwalten (Dateideskriptor)

Der obige Code hält die Daten statisch. Selbst wenn verschiedene Benutzer sie separat öffnen, greifen sie daher auf dieselbe Variable zu. Außerdem greifen Sie mit einem anderen Gerät auf dieselben Variablen zu. Reservieren Sie beim Öffnen einen Datenbereich, damit Sie ihn individuell verwalten können.

Verarbeitung zum Zeitpunkt des Öffnens / Schließens

Die Dateistruktur wird im offenen Argument angegeben. In dieser Dateistruktur befindet sich ein Mitglied namens "private_data", und Sie können den Zeiger frei speichern. Diese Dateistruktur selbst wird vom Benutzer als Dateideskriptor gespeichert / verwaltet. Verwenden Sie "kmalloc", um Speicher zuzuweisen. Im geschlossenen Zustand mit "kfree" loslassen. Nehmen wir diesmal an, dass die Struktur _mydevice_file_data der Typ ist, der die Daten enthält.

/***Jede Datei(Dateideskriptor für jedes Öffnen erstellt)Informationen im Zusammenhang mit***/
#define NUM_BUFFER 256
struct _mydevice_file_data {
	unsigned char buffer[NUM_BUFFER];
};

/*Funktion beim Öffnen aufgerufen*/
static int mydevice_open(struct inode *inode, struct file *file)
{
	printk("mydevice_open");

	/*Reservieren Sie einen Bereich zum Speichern von Daten, die für jede Datei eindeutig sind*/
	struct _mydevice_file_data *p = kmalloc(sizeof(struct _mydevice_file_data), GFP_KERNEL);
	if (p == NULL) {
		printk(KERN_ERR  "kmalloc\n");
		return -ENOMEM;
	}

	/*Dateispezifische Daten initialisieren*/
	strlcat(p->buffer, "dummy", 5);
	
	/*Lassen Sie den gesicherten Zeiger von fd auf der Benutzerseite halten*/
	file->private_data = p;

	return 0;
}

/*Funktion wird beim Schließen aufgerufen*/
static int mydevice_close(struct inode *inode, struct file *file)
{
	printk("mydevice_close");

	if (file->private_data) {
		/*Geben Sie den Datenbereich frei, der für jede zum Zeitpunkt des Öffnens reservierte Datei eindeutig ist*/
		kfree(file->private_data);
		file->private_data = NULL;
	}

	return 0;
}

Verarbeitung zum Zeitpunkt des Lesens / Schreibens

Wenn der Benutzer liest oder schreibt, wird der durch open erhaltene Dateideskriptor als Argument festgelegt. Bei der Implementierung auf der Gerätetreiberseite kann auf den zum Zeitpunkt des Öffnens zugewiesenen Datenbereich zugegriffen werden, indem auf das als Argument übergebene Element "private_data" in der Datei "file" verwiesen wird. Auf diese Weise können Sie Daten für jedes Öffnen einzeln verwalten (Dateideskriptor).

/*Funktion beim Lesen aufgerufen*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");
	if(count > NUM_BUFFER) count = NUM_BUFFER;

	struct _mydevice_file_data *p = filp->private_data;
	if (copy_to_user(buf, p->buffer, count) != 0) {
		return -EFAULT;
	}
	return count;
}

/*Funktion zum Zeitpunkt des Schreibens aufgerufen*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_write");

	struct _mydevice_file_data *p = filp->private_data;
	if (copy_from_user(p->buffer, buf, count) != 0) {
		return -EFAULT;
	}
	return count;
}

Versuchen Sie, vom Anwenderprogramm aus aufzurufen

Über Fehlercode

Es ist ein wenig abseits des Themas, aber der Kernel-Rückgabewert ist normalerweise 0. Das Lese- / Schreibsystem ist die Anzahl der tatsächlich verarbeiteten Bytes. Im Fehlerfall wird ein negativer Wert zurückgegeben.

Auf der Benutzerprogrammseite wird der Fehlercode durch Einfügen von "#include <errno.h>" in der Variablen "errno" gespeichert. Sie können es direkt überprüfen, aber mit einer Funktion namens "perror" wird der Fehlercode in einen leicht verständlichen Satz umgewandelt. Gehen Sie zum Beispiel folgendermaßen vor: if ((fd = open (" / dev / mydevice0 ", O_RDWR)) <0) perror (" open ");

Testcode

test.c


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main()
{
	char buff[256];
	int fd0_A, fd0_B, fd1_A;

	printf("%08X\n", buff);

	if ((fd0_A = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
	if ((fd0_B = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
	if ((fd1_A = open("/dev/mydevice1", O_RDWR)) < 0) perror("open");

	if (write(fd0_A, "0_A", 4) < 0) perror("write");
	if (write(fd0_B, "0_B", 4) < 0) perror("write");
	if (write(fd1_A, "1_A", 4) < 0) perror("write");

	if (read(fd0_A, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	if (read(fd0_B, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	if (read(fd1_A, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	
	if (close(fd0_A) != 0) perror("close");
	if (close(fd0_B) != 0) perror("close");
	if (close(fd1_A) != 0) perror("close");

	return 0;
}

Kompilieren Sie den Testcode und führen Sie ihn wie oben aus.

gcc test.c
./a.out
0_A
0_B
1_A

Das Ergebnis sieht folgendermaßen aus, und Sie können sehen, dass Sie auch dann, wenn Sie auf dasselbe Gerät zugreifen, unterschiedliche Werte beibehalten können, wenn Sie sie separat öffnen.

Recommended Posts

So erstellen Sie einen eingebetteten Linux-Gerätetreiber (11)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (8)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (1)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (4)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (7)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (2)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (3)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (6)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (5)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (10)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (9)
So erstellen Sie einen eingebetteten Linux-Gerätetreiber (12) (vollständig)
Wie man Yubico Yubikey von Manjaro Linux erkennt
So erstellen Sie ein interaktives CLI-Tool mit Golang
So erstellen Sie einen HTTPS-Server mit Go / Gin
[Python] Erstellen einer Adjazenzmatrix / Adjazenzliste [Graphentheorie]
So erstellen Sie ein Hacking-Labor - Kali Linux (2020.1) VirtualBox 64-Bit Teil 2-
So erstellen Sie ein Hacking-Labor - Kali Linux (2020.1) VirtualBox 64-Bit-Edition -
Wie erstelle ich ein Python-Paket (geschrieben für Praktikanten)
So erstellen Sie eine ISO-Datei (CD-Image) unter Linux
Wie erstelle ich eine japanisch-englische Übersetzung?
Wie man einen lockeren Bot macht
So installieren Sie VMware-Tools unter Linux
Wie erstelle ich einen Crawler?
So erstellen Sie eine rekursive Funktion
So installieren Sie MBDyn (Linux Ubuntu)
[Blender] So erstellen Sie ein Blender-Plug-In
[Blender] So erstellen Sie Blender-Skripte mehrsprachig
Wie erstelle ich einen Crawler?
So erstellen Sie den MongoDB C-Sprachtreiber
So überprüfen Sie die Linux-Betriebssystemversion
So machen Sie einen String in Python zu einem Array oder ein Array zu einem String
So bringen Sie den Druckertreiber für Oki Mac in Linux
So machen Sie Word Cloud-Zeichen monochromatisch
Wie baue ich meinen eigenen Linux-Server?
Wie man Selen so leicht wie möglich macht
[Linux] Unterteilen von Dateien und Ordnern
So erstellen Sie einen LINE-Bot mit künstlicher Intelligenz mit der Flask + LINE Messaging-API
So installieren Sie das aws-session-manager-Plugin unter Manajro Linux
[Python] Wie man eine Klasse iterierbar macht
python3 So installieren Sie ein externes Modul
So erstellen Sie eine NVIDIA Docker-Umgebung
So konvertieren Sie Python in eine exe-Datei
Ich möchte wissen, wie LINUX funktioniert!
[Linux] Verwendung des Befehls echo
So aktualisieren Sie PHP unter Amazon Linux 2
So zeigen Sie Piktogramme unter Manjaro Linux an
So installieren Sie Pakete unter Alpine Linux
[Cocos2d-x] So erstellen Sie eine Skriptbindung (Teil 2)
So bedienen Sie Linux von der Konsole aus
So installieren Sie das Windows-Subsystem für Linux
So schalten Sie Linux unter Ultra96-V2 aus
So aktualisieren Sie die Sicherheit unter CentOS Linux 8
Ich möchte ein Automatisierungsprogramm erstellen!