Hallo Leute,
ich möchte zur Verwendung in einem RTOS ein Filesystem programmieren,
damit ich auf SD-Karten und MMC-Karten zugreifen kann. Zum
Datenaustausch mit dem PC wird es wohl unumgänglich sein, FAT zu
verwenden :-(
Ich hab nun also mal begonnen mit der Implementierung; vorerst mache ich
alles im MS Visual Studio, weil da schon eine funktionierende Umgebung
vorhanden ist; die SD-Karte fake ich mit einem Image, das ich mittels
Hexeditor gemacht habe.
Ich frage mich jetzt, ob meine Architektur, die ich mir überlegt habe,
klug ist. Ich mache es wie folgt:
es gibt 2 interne Buffer, einen für die FAT, und einen für die Directory
Entries. Wenn ich ein File suche, dann lade ich den Root Cluster, oder
halt das Root Directory, in den Directory Buffer, um es dort zu
traversieren; ausserdem gibt es ein "dirty"-Flag, das gesetzt wird, wenn
irgendwas am Buffer modifiziert wurde (Datei erstellt, gelöscht etc.).
Jedes mal, wenn der Buffer neu geladen werden muss, weil ein File
gesucht wird oder dergleichen, wird das dirty-Bit dann abgefragt und der
Buffer ggf. noch auf die Karte geschrieben. Beim FAT-Buffer mache ich es
genauso.
Jetzt ist die Sache die, ich möchte das ganze reentrant machen, damit in
meiner OS-Umgebung ggf. auch mehrere Tasks ein File öffnen können. Könnt
ihr mir ein paar Tipps geben, wie man das richtig macht?
Meine Buffer habe ich der Einfachheit halber 512 Bytes gross gewählt,
damit passt jeweils exakt 1 Sektor rein. Zudem habe ich um Files
öffnen/lesen/schreiben zu können ähnich wie in der C-Library eine struct
file_t definiert, dort drin ist nochmal ein 512 Bytes Buffer für jedes
offene File; beim öffnen wird dann dieser Buffer gefüllt, bzw. wenn man
fwrite aufruft wird immer in diesen Buffer geschrieben, und erst wenn er
voll ist/fflush/fclose aufgerufen wird, wird er geschrieben.
Darf man das so machen, oder verschwende ich zu viel Memory? Eleganter
Ansatz? was könnte ich besser machen?
Ich möchte das auf einem ARM Prozessor einsetzen; prinzipiell steht also
schon einigermassen viel RAM zur Verfügung, aber wenn ich mir die
anderen Implementierungen anschaue, z.B. von Holger Klabunde, der mit
100 Bytes RAM auskommt, dann bin ich schon recht verschwenderisch :-)
Wäre mal gespannt auf eure Statements.
Ach ja: ich weiss, es gibt auch CHaNs FatFS, das toll funktioniert; ich
möchte es aber wirklich selber machen, wegen des Lerneffekts :-) Ich
komme auch gut voran, möchte aber mal wissen, was ihr von meinem Ansatz
haltet.
Gruss
Um Reentranz zu erreichen brauchst Du für jeden Vorgang eigene Puffer.
Andernfalls müsstest Du bei jedem einzelnen Zugriff den Puffer immer
wieder umladen. Wenn Du beispielsweise drei Dateien offen hast und drei
Threads schreiben nun jeweils ein Byte in die Datei, dann müsstest Du
drei mal den aktuellen Block in den Puffer laden, das eine Byte ändern
und dann den Puffer wieder auf die Karte schreiben. Denn einzelne Bytes
kann man bei SD Karten ja nicht lesen/schreiben.
Die Performance dürfte entsprechend lahm sein.
>aber wenn ich mir die>anderen Implementierungen anschaue, z.B. von Holger Klabunde, der mit>100 Bytes RAM auskommt, dann bin ich schon recht verschwenderisch :-)
Das ist ein Sonderfall den du in deine Betrachtungen nicht
einbeziehen kannst. Dort wird von Vorraussetzungen ausgegangen
die du nicht einhalten kannst und auch nicht willst.
>Um Reentranz zu erreichen brauchst Du für jeden Vorgang eigene Puffer.
Und eine Absicherung das beim lesen/schreiben eines Sektors kein anderer
Task Zugriff auf die Karte bekommt. Paralleler Zugriff ist nicht
möglich.
>>Um Reentranz zu erreichen brauchst Du für jeden Vorgang eigene Puffer.>>Und eine Absicherung das beim lesen/schreiben eines Sektors kein anderer>Task Zugriff auf die Karte bekommt. Paralleler Zugriff ist nicht>möglich.
Nachtrag: Was dann bedeuten kann das ein Task bis zu 500ms warten muss
bis er auf die Karte zugreifen kann. SD Karten sind groß, aber
nicht unbedingt schnell.
Hallo,
danke für eure Posts.
Dass ich, wegen der Reentranz, eigene Buffer brauche, ist mir klar. Das
heisst also: ich muss in der Funktion, die die FAT traversiert, jeweils
512 Bytes für die FAT und nochmal 512 Bytes für die Directory Entries
reservieren, plus eventuell nochmal 512 Bytes für die geöffnete Datei.
Korrekt? Das liegt dann alles auf dem Stack, sprich: 1.5 KB sind erst
mal schon allein für die Verwaltung reserviert. Kommen dann u.U. nochmal
256 Bytes für die Langen Dateinamen hinzu. Darf man das so machen?
Mein bisheriger Ansatz war, einen einzigen, globalen Buffer für die FAT
vorzusehen; auf diesen wird mittels Semaphor zugegruffen.
Wie machen es die Profis?
Ich würde mich nicht wundern, wenn im professionellen Umfeld, so etwas
in eigene Instanz ausgelagert wird. Dann erübrigen sich die meisten
Probleme mit der Reentranz.
Ich kann mir zumindest nicht vorstellen, das eine Datei- und
Belegtverwaltung funktionieren kann, bei der zwei oder mehr Teilnehmer
Zugriff haben, die in letzter Konsequenz nichts voneinander wissen.
>Wie machen es die Profis?
Sie meiden die kleinen Mistdinger.
Schon mal über eine Notstromversorgung nachgedacht
damit deine ganzen Buffer auch noch geschrieben werden können
wenn der Strom ausfällt?
Hallo,
meiner Ansicht nach ist Reentrance hier der falsche Begriff, das
File-System muss zunächst mal Transaktionen ausführen, d.h. wenn es
Lesen oder Schreiben soll, dann muss es diese Aktion ganz durchführen
oder alles angefangene rückgängig machen, ganz egal was sonst passiert.
Reentrance geht an dieser Stelle nicht. Andere Tasks müssen bis zum
Abschluss der Transaktion warten, Realtime hin oder her. Man muss also
drauf achten, dass Dateioperationen eine Task nicht blockieren können!
In Windows gibt es dafür non-blocking Aufrufe, die fordern die Aktion
nur an, bei Abschluss wird die Task benachrichtigt.
Natürlich brauchst du einen Sector Buffer für eine Task. Wozu eine Task
aber einen eigenen Directory Buffer haben soll, erschliesst sich mir so
einfach nicht, im Gegenteil, die Verwaltung des Directories darf nur in
einer Instanz geschehen. Aber das ist nur so der erste Eindruck, File
Systeme müssen sorgfältig durchdacht sein.
Gruss Reinhard
Hallo,
ich glaube ihr habt mich ein wenig missverstanden :-)
es gibt in meinem FAT-Modul zwei globale Variablen, einmal ein Buffer
für die FAT sowie ein Buffer für irgendwelche beliebigen Directory
Entries. Beim Initialisieren wird erstmal der 1. Teil der FAT in den
FAT-Buffer gelesen, und das Root Directory wird in den Directory Buffer
gelesen.
Wenn jetzt irgend ein Task ein File öffnen möchte, muss man natürlich
immer beim Root Directory zu suchen beginnen; beispielsweise möchte ich
ein File /ordner/file.ext öffnen, dann muss ich erst Sektor für Sektor
das Root directory in den Directory Buffer laden, diesen durchsuchen
nach "ordner", und wenn gefunden, dessen Startcluster dann in den
Directory Buffer laden und dann nach "file.ext" suchen. Das Problem, das
sich mir aber stellt ist, dass, weil der Directory und der FAT Buffer
global sind, in der Zwischenzeit ein anderer Task auch versuchen könnte,
ein File zu öffnen, und mir die Buffer überschreibt.
Aber so wie ich den Tenor hier verstehe, kann man das gar nicht anders
lösen, als die Buffer mit Semaphoren oder Mutexen zu schützen, auf die
der jeweilige Task dann halt warten muss.
Andere Frage:
Der Einfachheit halber möchte ich meine Buffer nicht mit malloc()
alloziieren; sprich, sie haben demnach auch eine feste Grösse. Ich
finde, 512 Bytes, also 1 Sektor, ist eine gute Grösse. Die Frage ist
allerdings: darf ich davon ausgehen, dass käufliche SD/MMC-Karten 512
Bytes Sektorgrösse haben? Wie sieht es mit SDHC aus? Ich habe bereits
danach gesucht, aber ich bin mir nicht sicher; man findet
widersprüchliche Angaben; die einen sagen, es sei immer 512 Bytes,
andere sagen, man müsse im CID-Register nachschauen, dort würde die
Sektorgrösse stehen.
Das Problem wäre halt, dass wenn die Sektoren andere Grössen hätte, ich
meine Buffer nicht mehr statisch alloziieren könnte und zudem (wenn sie
grösser als 512 Bytes sind) mehr Speicher "verschwendet" würde.
igor schrieb:> Das Problem wäre halt, dass wenn die Sektoren andere Grössen hätte, ich> meine Buffer nicht mehr statisch alloziieren könnte
Buffer = Sektor ist kein Naturgesetz. Schon beim Urvater CP/M war der
BS-Sektor 128 Byte, auf den späteren Disketten aber 512, dafür gab es
das Blocking/Deblocking. Das macht die Software natürlich komplizierter,
dafür kann man im BS mit einer festen Grösse arbeiten. Abgesehen davon
kann man den grössten vorkommenden Sektor allozieren, das ist ja eh der
worst case, dan man berücksichtigen muss.
Gruss Reinhard
igor schrieb:> Das Problem, das> sich mir aber stellt ist, dass, weil der Directory und der FAT Buffer> global sind, in der Zwischenzeit ein anderer Task auch versuchen könnte,> ein File zu öffnen, und mir die Buffer überschreibt.
Das ist eben das, was ich mit Transaktion meine: das Öffnen ist eine
solche Transaktion, die nicht unterbrochen werden darf (genau: nicht von
einer anderen Dateioperation - sonst schon, sonst wäre die Realtime im
Eimer). Bei Abschluss liefert sie einen Pointer auf den ersten Sektor
der Datei oder eine Fehlermeldung, erst dann steht das Filesystem für
andere Transaktionen wieder zur Verfügung - gern auch im Directory.
Gruss Reinhard
>Die Frage ist>allerdings: darf ich davon ausgehen, dass käufliche SD/MMC-Karten 512>Bytes Sektorgrösse haben? Wie sieht es mit SDHC aus?
Nimm keine 2GB Karten. Nimm keine 4GB Karten wo nicht SDHC drauf steht.
Ich hatte auch mit denen bisher keine Probleme, andere aber wohl schon
wenn man mal so rumliest.
SDHC hat laut Spezifikation 512 Byte Sektoren.
Hallo,
danke nochmals für eure Bemühungen. Ich habe es jetzt wie folgt
implementiert:
Alle Funktionen, wo der FAT oder Directory Buffer modifiziert wird, sind
mit einem Semaphor geschützt. Das scheint ziemlich gut zu funktionieren
:-)
Mein Filesystem kann jetzt jedenfalls schon verschiedenste Karten
erkennen, FAT16 und FAT32 unterscheiden, anhand eines angegebenen Pfades
eine Datei öffnen, sowie Attribute lesen und verändern.
Ich habe mir in meinem Code mit printf() ein paar Debugmeldungen
eingebaut, und mein ARM Board mit dem Terminal verbunden. Macht schon
Spass, wenns da rattert, und man sieht, wie da Zeug auf der Karte
gesucht (und gefunden :-) ) wird. Es scheint bisher also zu
funktionieren :-) auch mit Langen Dateinamen. Ich muss nur noch
Funktionen zum Schreiben implementieren und das Lesen noch ein wenig
Effizienter machen.