Tafelübung zu BS 5. Dateioperationen Olaf Spinczyk Arbeitsgruppe Eingebettete Systemsoftware Lehrstuhl für Informatik 12 TU Dortmund olaf.spinczyk@tu-dortmund.de http://ess.cs.uni-dortmund.de/~os/ http://ess.cs.tu-dortmund.de/de/teaching/ss2010/bs/ 1
Agenda Besprechung Aufgabe 4: Speicherverwaltung Aufgabe 5: Dateioperationen Öffnen/Lesen/Schreiben von Dateien/Verzeichnissen - Syscall-Wrapper (open/close/write/read) - libc-abstraktion - Dateien (fopen/fclose/fwrite/fread) - Verzeichnisse (opendir/readdir/closedir) Aufbau des Rohdateiformats Big- und Little-Endian PGM-Bildformat (ASCII-Variante) BS: U5 Dateioperationen 2
Besprechung Aufgabe 4 Foliensatz Besprechung BS: U5 Dateioperationen 3
Arbeiten mit Dateien Alles unter Linux/Unix wird auf Dateien abgebildet viele verschiedene Dateitypen (Geräte, Sockets,...) Typische Dateioperationen sind Öffnen/Lesen/Schreiben/Schließen Unter Linux mehrere Möglichkeiten Syscall-Wrapper der C-Bibliothek ( low-level ), z.b. open(2) int open(const char *path, int flags) syscall( NR_open, path, flags) - keine Abstraktion, sondern direkte Umsetzung in Systemcall Abstrakte Stream Schnittstelle der C-Bibliothek ( high-level ), z.b. fopen(3) FILE* fopen(const char *path, const char *mode) Abstraktion von Filedeskriptor Stream (FILE*) open(path, flags) BS: U5 Dateioperationen 4
Low-Level Dateioperationen Öffnen von Dateien mit open(2) int open(const char *path, int flags) gibt einen Dateideskriptor zurück, der für die anderen Funktionen verwendet wird Lesen aus Dateien mit read(2) ssize_t read(int fd, void *buf, size_t count) Schreiben in Dateien mit write(2) ssize_t write(int fd, void *buf, size_t count) Schließen von offenen Dateien mit close(2) int close(int fd) Standardisiert u.a. in POSIX.1 2001 BS: U5 Dateioperationen 5
Beispiel low-level #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define SIZE 64 int main(void) { int r_fd = 0, w_fd = 0; ssize_t r_bytes = 0, w_bytes = 0; char buf[size] = {0}; /* Datei zum Lesen öffnen */ r_fd = open( /tmp/inputfile, O_RDONLY); if (r_fd == -1) { /*... Fehler... */ }; r_bytes = read(r_fd, buf, SIZE); /* SIZE Bytes aus der Datei lesen */ /* Ausgabedatei öffnen und leeren; nicht vorhanden? -> erzeugen! */ w_fd = open( /tmp/outputfile, O_CREAT O_WRONLY O_TRUNC); if (w_fd == -1) { /*... Fehler... */ }; w_bytes = write(w_fd, buf, r_bytes); /* gelesene Bytes schreiben */ } close(r_fd); close(w_fd); return 0; BS: U5 Dateioperationen 6
C-Streams Abstrakter, plattformunabhängiger Kommunikationskanal zu einem Gerät einer einfachen Datei einem Prozess,... Interne Pufferung Blockweises Lesen höhere Verarbeitungsgeschwindigkeit Abstraktion vom eigentlichen Datenstrom Lesen und Zurückschreiben von Daten (fgetc(3), ungetc(3)) Unabhängiges Lesen und Schreiben im Datenstrom (an verschiedenen Positionen) Stream-Repräsentation durch struct FILE enthält Zeiger auf Lese-/Schreibpuffer, aktuelle Positionen, Undo- Informationen (in stdio.h definiert) definierte Standardstreams: stdin, stdout & stderr BS: U5 Dateioperationen 7
High-Level Dateioperationen Öffnen von Dateien mit fopen(3) FILE *fopen(const char *path, const char *mode) Modi: r/r+ (lesen/+schreiben), w/w+ (schreiben/+lesen), a/a+ (anhängen/+lesen) (siehe Manpage) Lesen aus Dateien mit fread(3) size_t fread(void *buf, size_t size, size_t count, FILE *stream) Schreiben in Dateien mit fwrite(3) size_t fwrite(void *buf, size_t size, size_t count, FILE *stream) Schließen von offenen Dateien mit fclose(3) int fclose(file *stream) Im C-Standard enthalten, plattformunabhängig! BS: U5 Dateioperationen 8
Beispiel High-Level #include <stdio.h> #define SIZE 64 int main(void) { FILE *r_file = NULL, *w_file = NULL; size_t r_bytes = 0, w_bytes = 0; char buf[size] = {0}; /* Datei zum Lesen öffnen */ r_file = fopen("/tmp/inputfilef", "r"); if (r_file == NULL) { /*... Fehler... */ } r_bytes = fread(buf, sizeof(char), SIZE, r_file); /* Ausgabedatei öffnen und leeren; nicht vorhanden? -> erzeugen! */ w_file = fopen("/tmp/outputfilef", "w"); if (w_file == NULL) { /*... Fehler... */ } /* gelesene Bytes schreiben */ w_bytes = fwrite(buf, sizeof(char), r_bytes, w_file); } fclose(r_file); fclose(w_file); return 0; BS: U5 Dateioperationen 9
Dateieigenschaften und stat(2) Dateien unter Linux haben verschiedenste Eigenschaften Besitzer/Gruppe Größe Anzahl der belegten Blöcke Anlegezeitpunkt, letzter Zugriffs- und Änderungszeitpunkt Typ: Verzeichnis, Gerätedatei, (Symbolischer) Link, Named Pipe,... Abfrage dieser Eigenschaften mit stat(2) int stat(const char *path, struct stat *buf) Rückgabe der Werte in eine Struktur, Typ struct stat Dateityp kann über Makros abgefragt werden, z.b. S_ISREG(st_mode) = reguläre Datei S_ISDIR(st_mode) = Verzeichnis Benötigte Header: <sys/types.h>, <sys/stat.h>, <unistd.h> BS: U5 Dateioperationen 10
Beispiel stat(2) #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> int main(void) { struct stat fileinfo = {0}; int retval = 0; retval = stat("/etc/passwd", &fileinfo); if (retval == -1) { /*... Fehler... */ }; printf("normale Datei: %d\n", S_ISREG(fileinfo.st_mode)); printf("verzeichnis : %d\n", S_ISDIR(fileinfo.st_mode)); printf("größe : %d\n", (int) fileinfo.st_size); /* Extraktion der Rechteinformationen aus st_mode (siehe manpage) */ printf("rechte : %o\n", fileinfo.st_mode & 0777); } return 0; BS: U5 Dateioperationen 11
High-Level Verzeichnisoperationen Öffnen von Verzeichnissen mit opendir(3) DIR *opendir(const char *path) Auslesen der Verzeichnisinhalte mit readdir(3) struct dirent *readdir(dir *dir) Rückgabe = NULL alle Verzeichniseinträge gelesen, keine weiteren vorhanden oder Fehler sonst: in dirent.d_name steht der Name des Verzeichniseintrages Schließen von geöffneten Verzeichnissen mit closedir(3) int closedir(dir *dir) Benötigte Header: <sys/types.h>, <dirent.h> BS: U5 Dateioperationen 12
Beispiel: Verzeichnis Auslesen #include <sys/types.h> #include <dirent.h> #include <stdio.h> int main(void) { int retval = 0, count = 0; DIR *dir = NULL; struct dirent *entry = NULL; dir = opendir("/tmp"); /* Verzeichnis öffnen, DIR-Zeiger erstellen */ if (!dir) { /*... Fehler... */ } /* jeder Aufruf liefert einen Eintrag, NULL -> Listenende */ while ((entry = readdir(dir))) { printf("eintrag %d: %s\n", ++count, entry->d_name); } printf("anzahl: %d\n", count); retval = closedir(dir); /* Verzeichnis wieder schließen */ if (retval == -1) { /*... Fehler... */ }; } return 0; BS: U5 Dateioperationen 13
Hilfreiche Stringfunktionen Notwendiger Header #include <string.h> Kopieren eines Strings char *strncpy( char *to, const char *from, size_t count ); Konkatenieren zweier Strings char *strncat( char *str1, const char *str2, size_t count ); Länge eines Strings (ohne terminierende Null) size_t strlen( char *str ); BS: U5 Dateioperationen 14
Bin2PGM Aufgabe: Umwandlung eines Spezialkamera-Bildformats Datenquelle: TOF-Kamera (Tiefenbild) Umgang mit Dateioperationen soll geübt werden a) Verarbeitung von Kommandozeilenparametern studi@bs:~$./bin2pgm datei_01 datei_02 b) Einlesen aller Dateien, Prüfen auf Validität - Überprüfen der Dateigröße - Auswerten des Headers c) Konvertierung in ein Standardformat, das mit einem normalen Bildbetrachtungsprogramm angezeigt werden kann BS: U5 Dateioperationen 15
Rohdateiformat im Hex-Editor studi@bs:~$ od -f 000 head 0000000 1,280000e+04 4,120000e+02 1,000000e+01 1,000000e+00 0000020 4,000000e+00 5,000000e+03 2,250000e+02 1,603000e+03 0000040 3,052600e+05 0,000000e+00 0,000000e+00 0,000000e+00 0000060 0,000000e+00 1,000000e+00 2,500000e+01-1,000000e+00 0000100 0,000000e+00 3,840000e+02 3,000000e+00 7,006703e+02 0000120 3,000000e+00 0,000000e+00 0,000000e+00 0,000000e+00 0000140 6,300000e+01 1,000000e+00 4,800000e+01 8,100000e-03 0000160 1,000000e-04 0,000000e+00 0,000000e+00 1,000000e+00 0000200 0,000000e+00 5,000000e+00 5,000000e+02 1,000000e+02 0000220 0,000000e+00 0,000000e+00 0,000000e+00 0,000000e+00 an dieser Stelle steht die Größe des Datenbereichs in Byte: 12800 an dieser Stelle steht die Größe des Headers in Byte: 412 DATA_SIZE + HEADER_SIZE = FILE_SIZE = 13212 Die restlichen Einträge des Headers sind für unseren Zweck nicht weiter interessant. Aus wie vielen Bytes besteht ein float normalerweise? BS: U5 Dateioperationen 16
Rohdateiformat im Hex-Editor Ansehen des Datenbereichs ( -j 412 überspringt die ersten 412 Bytes): studi@bs:~$ od -j 412 -f 000 head 0000634 4,638455e-01 4,692418e-01 4,585017e-01 4,670429e-01 0000654 4,682699e-01 4,758938e-01 4,944439e-01 4,922463e-01 0000674 4,900948e-01 5,011191e-01 5,014700e-01 5,097792e-01 0000714 5,150690e-01 5,320356e-01 5,312724e-01 5,482447e-01 0000734 5,531824e-01 5,635117e-01 5,674478e-01 6,804598e-01 0000754 5,835271e-01 5,883580e-01 5,979524e-01 6,003090e-01 0000774 6,514618e-01 6,582504e-01 6,266403e-01 7,016214e-01 0001014 6,458892e-01 6,376296e-01 6,567788e-01 6,596068e-01 0001034 6,724393e-01 6,784100e-01 6,763144e-01 6,971168e-01 0001054 6,971825e-01 6,857606e-01 6,735934e-01 6,766129e-01 zur Erinnerung: Größe des Datenbereichs ist 12800 Bytes, jeder float (jeweils 4 Bytes) steht für einen Bildpunkt Die Werte sind bereits normalisiert, d.h. es kommen nur Fließkommazahlen zwischen 0.0 und 1.0 vor. BS: U5 Dateioperationen 17
Rohdateiformat im Hex-Editor studi@bs:~$ od -j 412 -f 000 head 0000634 4,638455e-01 4,692418e-01 4,585017e-01 4,670429e-01 0000654 4,682699e-01 4,758938e-01 4,944439e-01 4,922463e-01 [ ] (i = Zeile, j = Spalte) studi@bs:~$ od -j 412 -f 000 head 0000634 (0,0) (0,1) (0,2) (0,3) 0000654 (0,4) (0,5) (0,6) (0,7) [ ] Wichtig: Das Rohdateiformat ist spaltenweise aufgebaut. int start_img = (int)get_float(bin[1])/4; (0,0) (1,0) (0,1) (1,1) for (i = 0; i < 64; i++) /* iteriere durch alle Bildzeilen */ { for (j = 0; j < 50; j++) /* und alle Bildspalten */ { /* Gebe die Werte zeilenweise aus */... (0,64)... (1,64)... (2,64)...... fprintf(output_file, "%03d ", (int)(get_float(bin[start_img+j*64+i])* 255)); } (0,2) (1,2) 64x50 (2,0) (2,1) (2,2)......... (50,0) (50,1) (50,2)... (50,64) } fprintf(output_file, "\n"); get_float wichtig für alle Zugriffe auf das Rohdateiformat (Big- und Little-Endian-Problematik) BS: U5 Dateioperationen 18
Big- und Little-Endian Gullivers Reisen (Jonathan Swift) Bewohner des Landes Lilliput teilen sich auf zwei verfeindete Gruppen auf - die einen schlagen ihre Eier am großen Ende auf (Big Ender) - die anderen schlagen ihre Eier am kleinen Ende auf (Little Ender) Big- und Little-Endian im Kontext der Informatik studi@bs:~$ od -x -N 4 000 0000000 0000 4648 studi@bs:~$ od -x -N 4 000 0000000 4846 0000 So würde ein float als Little-Endian aussehen... und so als Big-Endian. Ein Dateiformat jedoch möglicherweise verschiedene Architekturen. Was tun? Klar: Architektur feststellen und übersetzen, falls notwendig. (Keine Sorge, das ist in der Vorgabe bereits erledigt!) BS: U5 Dateioperationen 19
PGM-Bilddateiformat studi@bs:~$ cat 000.pgm P2 50 64 255 118 115 116 115 119 119 118 122 121 123 124 126 125 128 125 125 125 123 124 124 119 118 115 118 121 119 119 121 123 123 124 123 126 126 127 126 115 104 098 099 116 119 119 120 119 123 123 121 123 125 128 128 126 127 121 104 081 053 043 044 119 121 120 121 122 122 123 123 124 128 127 128 128 121 100 079 039 023 024 021 119 120 122 123 123 124 124 128 127 127 128 130 125 105 056 026 020 014 014 010 121 123 123 123 123 127 125 126 129 128 130 131 113 081 027 019 016 010 008 007 126 123 123 123 125 126 126 131 130 132 132 119 089 038 016 016 012 008 004 000 Salvador Dalí P2: ASCII-basierte Variante des PGM-Formates P5 wäre die binäre darauf folgt die Bildbreite (50) und Höhe (64) 255 bezeichnet den maximalen Grauwert Grauwerte von 0-255 erlaubt Danach folgen Bildpunkt für Bildpunkt die jeweiligen Farbwerte (getrennt durch Whitespace, also: Leerzeichen, Newlines oder Tabulatoren) BS: U5 Dateioperationen 20
Falls noch Zeit ist...... eine kleine Vorstellung von GDB. BS: U5 Dateioperationen 21
Optional: GDB GDB GNU Debugger kommandozeilenbasiert grafische Front-ends verfügbar (z.b. DDD, Insight)... oder integriert (z.b. Eclipse, NetBeans) Starten mit: home:~> gdb malloctest Programm normal ausführen: (gdb) run GDB fängt Signale an das gestartete Programm ab (z.b. ungültige Speicherzugriffe) und erlaubt Einsicht und Manipulation von Variablenwerten und Kontrollfluss. BS: U5 Dateioperationen 22
Optional: GDB Segfault Beispiel: int main(){ int i = 43; int *p = (int*)0; *p = ++i; // Versucht Wert an Adresse 0 zu schreiben. return 0; } # Verwende u.a. keine Register als Zwischenspeicher, + Metainfo Home> gcc -O0 -g -Wall -o main main.c Home> gdb main (gdb) run Starting program: /home/kleinsor/temp/main Program received signal SIGSEGV, Segmentation fault. 0x08048378 in main () at main.c:4 5 *p = ++i; // Versucht Wert an Adresse 0 zu schreiben. (gdb) BS: U5 Dateioperationen 23
Optional: GDB Segfault entdecken Beispiel: int main(){ int i = 43; int *p = (int*)0; *p = ++i; // Versucht Wert an Adresse 0 zu schreiben. return 0; } Starting program: /home/kleinsor/temp/main Program received signal SIGSEGV, Segmentation fault. 0x08048378 in main () at main.c:4 4 *((int*)0) = ++i; // Versucht Wert an Adresse 0 zu schreiben. (gdb)print i $1 = 44 (gdb)info locals i = 44 p = (int *) 0x0 (gdb)info variables [Symboltabelle!] BS: U5 Dateioperationen 24
Optional: GDB Breakpoints Halten Ausführung an vordefinieren Punkten an. Erlaubt also auch das punktuelle Auswerten von Variablen. #include <stdio.h> int segfault() { int i = 43; int *p = (int*)0; *p = ++i; return *p; } int main() { printf("%d\n", segfault()); return 0; } Home> gdb main (gdb) break main Breakpoint 1 at 0x80483bc: file main.c, line 9. (gdb) run Starting program: /home/kleinsor/temp/a.out Breakpoint 1, main () at main.c:9 9 printf("%d\n", segfault()); (gdb) BS: U5 Dateioperationen 25
Optional: GDB Breakpoints #include <stdio.h> int segfault() { int i = 43; int *p = (int*)0; *p = ++i; // Der Segfault-Kandidat return *p; } int main() { printf("%d\n", segfault()); return 0; } (gdb) list segfault [...] 5 *p = ++i; // Der Segfault-Kandidat [...] (gdb) break 4 Breakpoint 1 at 0x8048391: file main.c, line 4. (gdb) cont Breakpoint 1, segfault () at main.c:4 4 int *p = (int*)0; (gdb) backtrace #0 0x080483a2 in segfault () at main.c:4 #1 0x080483c1 in main () at main.c:9 (gdb) set variable p = &i (gdb) cont Continuing. 44 BS: U5 Dateioperationen 26
Optional: GDB Even more Kann sowohl auf Sourcecodeebene (-00 -g) als auch auf Instruktionsebene arbeiten. Also auch direkt mit Adressen im Speicher des überwachten Prozesses. Kann noch erheblich mehr: Ausdrücke überwachen Umgebung manipulieren (freier Zugriff!) Kontrollfluss manipulieren: z.b. Funktionen 'interaktiv' aufrufen und sehr viel mehr! Hilfe: Allgemein: z. B. Spezifisch: z. B. (gdb) help (gdb) help break BS: U5 Dateioperationen 27