Forum: Mikrocontroller und Digitale Elektronik Embedded File System


von igor (Gast)


Lesenswert?

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

von Stefan F. (sfrings)


Lesenswert?

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.

von holger (Gast)


Lesenswert?

>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.

von holger (Gast)


Lesenswert?

>>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.

von igor (Gast)


Lesenswert?

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?

von amateur (Gast)


Lesenswert?

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.

von holger (Gast)


Lesenswert?

>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?

von Reinhard Kern (Gast)


Lesenswert?

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

von igor (Gast)


Lesenswert?

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.

von Reinhard Kern (Gast)


Lesenswert?

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

von Reinhard Kern (Gast)


Lesenswert?

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

von holger (Gast)


Lesenswert?

>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.

von igor (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.