Wie anderen Kernel-Bestandteile haben sich auch das Einblenden von Speicher und DMA über die Jahre verändert. Dieser Abschnitt beschreibt, was ein Autor von Gerätetreibern beachten muß, um portablen Code zu schreiben.
In der 2.3-Entwicklungsserie wurden massive Veränderungen an der Speicherverwaltung vorgenommen. Der 2.2-Kernel war hinsichtlich der Menge verwendbaren Speichers sehr eingeschränkt, insbesondere auf 32-Bit-Prozessoren. In 2.4 sind diese Einschränkungen aufgehoben worden; Linux kann nun sämtlichen Speicher ansprechen, den der Prozessor adressieren kann. Manches mußte verändert werden, um dies möglich zu machen; insgesamt gesehen ist die Anzahl der Änderungen auf API-Ebene aber überraschend klein.
Wie Sie gesehen haben, macht der 2.4-Kernel intensiven Gebrauch von Zeigern auf struct page, um auf bestimmte Seiten im Speicher zu verweisen. Diese Struktur hat es in Linux schon lange gegeben, aber bisher ist sie nicht dazu verwendet worden, um auf die Seiten selbst zu verweisen; der Kernel verwendete vielmehr logische Adressen.
Daher gab etwa pte_page einen unsigned long-Wert anstelle eines struct page* zurück. Das Makro virt_to_page existierte überhaupt nicht; wenn Sie den struct page-Eintrag herausfinden mußten, mußten Sie sich diesen direkt aus der Speichertabelle holen. Das Makro MAP_NR wandelte eine logische Adresse in einen Eintrag in mem_map um. Daher könnte das aktuelle Makro virt_to_page folgendermaßen definiert werden (und in sysdep.h im Beispiel-Code wird es auch so definiert):
#ifdefMAP_NR #definevirt_to_page(page)(mem_map+MAP_NR(page)) #endif |
Das Makro MAP_NR verschwand, als virt_to_page eingeführt wurde. get_page existierte vor 2.4 nicht, weswegen sysdep.h es folgendermaßen definiert:
#ifndefget_page #defineget_page(p)atomic_inc(&(p)->count) #endif |
struct page hat sich also mit der Zeit geändert, insbesondere gibt es das Feld virtual erst ab Linux 2.4.
page_table_lock wurde in 2.3.10 eingeführt. Davor holte sich der Code die “große Kernel-Sperre” (durch Aufrufen von lock_kernel und unlock_kernel), bevor Seitentabellen traversiert wurden.
Die vm_area_struct erfuhr in der 2.3-Entwicklungsserie eine Reihe von Änderungen und in 2.1 sogar noch mehr. Dazu gehören:
Das Feld vm_pgoff hieß in 2.2 und davor vm_offset und war ein Offset in Bytes, nicht in Seiten.
Das Feld vm_private_data existierte in Linux 2.2 noch nicht, so daß Treiber keine Möglichkeiten hatten, ihre eigenen Informationen in der VMA unterzubringen. Einige taten das trotzdem und verwendeten dafür das Feld vm_pte, aber es wäre sicherer gewesen, sich die Minor-Nummer aus vm_file zu holen und anhand dieser die gewünschte Information aufzufinden.
Der 2.4-Kernel initialisiert den vm_file-Zeiger vor dem Aufruf der Methode mmap. In 2.2 mußten Treiber diesen Wert unter Verwendung der als Argument übergebenen file-Struktur selbst zuweisen.
Der Zeiger vm_file existierte in den 2.0-Kerneln überhaupt nicht; statt dessen gab es einen vm_inode-Zeiger, der auf eine inode-Struktur verwies. Dieses Feld mußte vom Treiber zugewiesen werden; es war außerdem notwendig, inode->i_count in mmap zu inkrementieren.
Das Flag VM_RESERVED wurde im Kernel 2.4.0-test10 hinzugefügt.
Es hat auch Änderungen an den diversen in der VMA gespeicherten vm_ops-Methoden gegeben:
2.2 und frühere Kernel hatten eine Methode namens advise, die nie vom Kernel verwendet wurde. Außerdem gab es eine Methode namens swapin, mit der Speicher aus der Auslagerung zurückgeholt wurde; diese war für Treiber-Autoren nicht interessant.
Die Methoden nopage und wppage gaben in 2.2 unsigned long (also eine logische Adresse) anstelle von struct page* zurück.
Die Rückgabe-Codes NOPAGE_SIGBUS und NOPAGE_OOM von nopage existierten nicht. nopage gab einfach 0 zurück, um ein Problem anzuzeigen und ein Bus-Signal an den betroffenen Prozeß zu schicken.
Weil nopage unsigned long zurückzugeben pflegte, wurde die logische Adresse der betroffenen Seite anstelle von deren mem_map-Eintrag zurückgegeben.
Natürlich gab es in älteren Kerneln keine Unterstützung für hohen Speicher. Sämtlicher Speicher hatte logische Adressen, und die Funktionen kmap und kunmap gab es nicht.
Im 2.0-Kernel wurde die Struktur init_mm nicht an Module exportiert. Daher mußte ein Modul, das auf init_mm zugreifen wollte, die Task-Tabelle durchsuchen, um die Struktur zu finden (als Bestandteil des init-Prozesses). Auf einem 2.0-Kernel findet scullp init_mm mit folgendem Stückchen Code:
staticstructmm_struct*init_mm_ptr; #defineinit_mm(*init_mm_ptr)/*spaetereifdefsvermeiden*/ staticvoidretrieve_init_mm_ptr(void) { structtask_struct*p; for(p=current;(p=p->next_task)!=current;) if(p->pid==0) break; init_mm_ptr=p->mm; } |
Die 2.0-Kernel unterschieden nicht zwischen logischen und physikalischen Adressen, weswegen es die Makros __va und __pa nicht gab — man brauchte sie damals einfach nicht.
Die 2.0-Kernel pflegten auch den Verwendungszähler von Modulen im Zusammenhang mit in den Speicher eingeblendeten Bereichen nicht. Treiber, die mmap unter 2.0 implementierten, mußten open- und close-VMA-Operationen implementieren, um den Verwendungszähler selbst anzupassen. Die Beispiel-Module, die mmap implementieren, stellen diese Operationen bereit.
> > Schließlich hatte die Methode mmap in den 2.0-Versionen wie die meisten anderen ein struct inode-Argument und damit folgenden Prototyp:
int(*mmap)(structinode*inode,structfile*filp, structvm_area_struct*vma); |
Die oben beschriebene PCI-DMA-Schnittstelle gibt es erst seit Kernel 2.3.41. Vorher wurde DMA direkter und damit auch systemabhängiger durchgeführt. Puffer wurden durch Aufruf von virt_to_bus “eingeblendet”; es gab keine allgemeine Schnittstelle für Bus-Einblendungsregister.
Für diejenigen von Ihnen, die portable PCI-Treiber schreiben müssen, enthält sysdep.h im Beispiel-Code eine einfache Implementation der 2.4-DMA-Schnittstelle, die auf älteren Kerneln verwendet werden kann.
Die ISA-Schnittstelle ist dagegen seit Linux 2.0 fast unverändert geblieben. ISA ist schließlich eine alte Architektur, bei der es nicht viele Änderungen mehr gegeben hat, um die man sich hätte kümmern müssen. Die einzige Änderung war das DMA-Spinlock in 2.2; vorher gab es keine Möglichkeit, sich gegen nebenläufige Zugriffe auf den DMA-Controller zu schützen. Versionen dieser Funktionen sind in sysdep.h definiert; sie schalten Interrupts ein und aus, machen aber nichts anderes.