Abwärtskompatibilität

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.

Änderungen in der Speicherverwaltung

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):


#ifdef MAP_NR
#define virt_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:


#ifndef get_page
#  define get_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:

Es hat auch Änderungen an den diversen in der VMA gespeicherten vm_ops-Methoden gegeben:

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:


static struct mm_struct *init_mm_ptr;
#define init_mm (*init_mm_ptr) /* spaetere ifdefs vermeiden */

static void retrieve_init_mm_ptr(void)
{
    struct task_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)(struct inode *inode, struct file *filp,
            struct vm_area_struct *vma);

DMA-Änderungen

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.