File I/O: The Universal I/O model
Le fichier est l’une des abstractions fondamentales dans le domaine des systèmes d’exploitation; L'autre abstraction est le processus. Si le processus résume l'exécution d'une tâche spécifique sur le processeur, le fichier résume les informations persistantes d'un système d'exploitation. Un fichier est utilisé pour stocker les informations nécessaires au fonctionnement du système d'exploitation et aux interactions de l'utilisateur.
Un système de fichiers est un moyen d'organiser les fichiers et de les présenter à l'utilisateur. Du point de vue de l'utilisateur, un système de fichiers a une structure hiérarchique de fichiers et de répertoires, commençant par un répertoire racine. L'emplacement d'une entrée (fichier ou répertoire) est défini par un chemin qui répertorie toutes les entrées jusque-là. Ainsi, pour le chemin /usr/local/file.txt
, le répertoire racine /
contient un sous-répertoire usr
qui comprend le sous-répertoire local
contenant un fichier file.txt.
Chaque fichier est donc associé à un nom identifiant, à un ensemble de droits d'accès et à des zones contenant des informations utiles.
Les systèmes de fichiers pris en charge par les systèmes d'exploitation Unix et Windows sont hiérarchiques. Les systèmes Linux / Unix sont sensibles à la casse (“Données” est différent de “données”) et les systèmes Windows ne font pas la distinction entre les casse.
La hiérarchie du système de fichiers Unix comporte un seul répertoire appelé “racine” et ”” / ”” dans lequel n’importe quel fichier est localisé (à ne pas confondre avec le répertoire ”/ racine”, qui est: patrie de privilegiat, root). La liste des chemins de fichiers Unix est une chaîne de noms de répertoires séparés par '/'
, suivis du nom du fichier. Il existe également des chemins relatifs au répertoire en cours '.'
Ou au répertoire parent '..
'.
Sous Unix, il n'y a pas de différence entre les fichiers du disque local, du CD ou du lecteur réseau. Tous ces fichiers feront partie de la hiérarchie unique du répertoire racine. Cela se fait par 'mount': les systèmes de fichiers seront montés dans l'un des répertoires du système de fichiers racine.
Il existe plusieurs hiérarchies dans Windows, une pour chaque partition et une pour chaque emplacement réseau. Contrairement à Unix, le délimiteur entre les noms de répertoire dans un chemin et pour les chemins absolus, la hiérarchie doit être spécifiée sous la forme C: \
, E: \
ou \\FILESERVER\myFile
(pour le réseau). Comme Unix, Windows utilise .
Pour le répertoire actuel et '..'
pour le répertoire parent.
Sous Unix, un descripteur de fichier est un ensemble qui indexe une table avec des pointeurs sur des structures décrivant les fichiers ouverts par un processus. Si un programme est exécuté dans un shell Unix, le processus parent (shell) ouvre pour le processus enfant (le programme) 3 fichiers standard avec des descripteurs de fichier de valeur spéciaux:
* standard input (0) - lecture à partir d'une entrée standard (clavier)
Un fichier associe le pointeur de fichier qui indique la position actuelle dans le fichier. Le curseur de fichier est un entier représentant le déplacement (décalage) du début du fichier.
Opérations spécifiques pour travailler avec des fichiers:
fopen
(ANSI C), open
, creat
(POSIX), CreateFile
(Win32 API))). ( Linux)fclose
(ANSI C), close
(POSIX), CloseHandle
(Win32 API)). ( Linux)fread
(ANSI C), read
(POSIX), ReadFile
(Win32 API)). ( Linux)fwrite
(ISO C), write
(POSIX), WriteFile
(Win32 API)). ( Linux)fseek
(ANSI C), lseek
(POSIX), SetFilePointer
(Win32 API)). ( Linux) fcntl
(POSIX), SetFileAttributes (Win32 API)). ( Linux)Pour pouvoir utiliser des fonctions pour la gestion des fichiers en Python, on va importer la bibliotheque os.
import os
Pour ouvrir / créer un fichier, utilisez la fonction open.
os.open(path, flags); # ouverture os.open(path, os.O_CREAT|flags, mode); # creation
Les valeurs possibles pour flags sont:
Les valeurs des indicateurs sont représentées par des bits, car elles peuvent être combinées par l'opérateur | (ou sur des bits).
# open a file in write only and delete all its contents (truncate to 0) os. open(pathname, os.O_WRONLY | os.O_TRUNC, mode);
La valeur de mode est représentée par les droits du nouveau fichier créé (en bits). Généralement, un nombre de base de 8 est utilisé. Il a trois chiffres, chacun avec trois bits.
r w x | r - x | r - - |
---|---|---|
1 1 1 | 1 0 1 | 1 0 0 |
7 | 5 | 4 |
Chaque chiffre se réfère à:
La fermeture des fichiers se fait avec close:
os.close(int fd)
La suppression effective d'un fichier du disque se fait avec unlink:
os.unlink(pathname);
Si, par exemple, nous voulons ouvrir le fichier in.txt
pour la lecture et l'écriture, éventuellement le créer, et le fichier out.txt
pour l'écriture, avec des troncatures, nous pouvons utiliser la séquence de code suivante:
import os try: fd1 = os.open("in.txt", os.O_RDWR | os.O_CREAT, 0644) # will fail if out.txt does not exist fd2 = os,open("out.txt", os.O_WRONLY | os.O_TRUNC) os.close(fd1) os.close(fd2) except Exception as e: print ("Error: {}".format (e))
Attention Une erreur courante est l'omission des droits de création de fichier (0644 dans l'exemple ci-dessus) lorsque l'indicateur O_CREAT ouvert est appelé.
La fonction read est utilisée pour lire maximum count
octets du fichier:
os.read(fd, count)
La fonction read renvoie le nombre d'octets réellement lus, tout au plus “count”. Quand elle atteint la fin du fichier, elle renvoieun bytestring vide.
La fonction write est utilisée pour écrire dans un ficher les données stockées en str:
os.write(fd, str)
La valeur de retour est le nombre d'octets réellement écrits. Par défaut, il n'est pas garanti que le retour de write soit terminé. Pour forcer la mise à jour, vous pouvez utiliser fsync ou le fichier peut être ouvert à l'aide de l'indicateur O_FSYNC
. Dans ce cas, il est garanti qu'après chaque écriture le fichier a été mis à jour.
Note : Pour read, write, il y a les versions pread, pwrite, qui vous permettent de spécifier un décalage de fichier à partir duquel effectuer l'opération de lecture / écriture.
La fonction lseek permet le déplacement du curseur du fichier vers une position absolue ou relative.
os.lseek(fd, offset, whence)
Le paramètre “wheece” représente la position relative à partir de laquelle le déplacement est effectué:
os.SEEK_SET
- contre la position de départ os.SEEK_CUR
- à la position actuelle os.SEEK_END
- vers la position finaleRemarque lseek permet également le positionnement à la fin du fichier. L'écriture dans de telles zones n'est pas perdue, ce qui est obtenu en étant un fichier void , une zone ignorée - n'est pas allouée au disque.
Outre la troncature à 0, ce qui peut être fait en appelant open
avec l'indicateur O_TRUNC
, vous pouvez spécifier de tronquer un fichier à une taille spécifiée par des appels système ftruncate et truncate:
os.ftruncate(fd, length) os.truncate(path, length)
Dans le cas de ftrunched, le paramètre fd
est le fichier descripteur obtenu avec un appel ouvert fournissant l'autorisation d'écriture. Dans le cas de truncate, le fichier représenté par chemin
doit avoir le droit d'écriture.
import os # Print the last 100 bytes from a file try: # open file fd = os.open("file.txt", os.O_RDONLY) # set file pointer at 100 characters _before_ the end of the file rc = os.lseek(fd, -100, os.SEEK_END) # read the last 100 characthers msg = os.read(fd, 100) bytes_read = len(msg) print ("the last " + bytes_read + " bytes") print (buf.decode("utf-8")) # close file os.close(fd) except Exception as e: print ("Error: {}".format (e))
Sous Linux, les redirections sont effectuées à l'aide des fonctions de duplication des descripteurs de fichier dup et dup2 (notez la différence entre 2 dans les liens précédents):
os.dup(oldfd); os.dup2(oldfd, newfd, inhertable=True)
Par exemple, pour rediriger la sortie vers le fichier output.txt
, deux lignes de code sont requises:
try: fd = os.open("output.txt", os.O_RDWR|os.O_CREAT|os.O_TRUNC, 0600) os.dup2(fd, 1) except Exception as e: print ("Error: {}".format (e))
Pour résoudre le laboratoire, utilisez des clones repository.
Allez dans le répertoire 1-redirect
et regardez le contenu du fichier redirect.py
.
Exécutez le programme à l'aide de la commande 'python3 redirect.py'.
Ouvrez un autre terminal et lancez la commande:
watch -d lsof -p $(pidof redirect)
lsof est un utilitaire qui affiche des informations sur les fichiers ouverts (fichiers ouverts dans le système, fichiers ouverts par un utilisateur particulier, etc.). Regardez dans le manuel ( man 8 lsof
) pour identifier la signification de la colonne FD et de la colonne TYPE.
Utilisez la commande ENTER pour continuer le programme. En parallèle, observez l’évolution de la table des descripteurs.
Dans le code, notez les paramètres de redirection avec dup2 (après 2 (fd2, 2)). Remarquez ce qui se passe si les paramètres sont dans l'ordre inverse.
Allez dans le répertoire 2-read-write
et regardez le contenu du fichier read-write.py
.
Mettez votre nom dans la variable name. Écrivez la variable à l'écran en utilisant uniquement la fonction write. Suivez les lignes avec TODO 1.
Lisez votre nom depuis le clavier dans la variable name en utilisant uniquement la fonction read.
Ecrivez le nouveau nom sur l'écran en utilisant uniquement les fonctions write.
Suivez les lignes avec TODO 2.
Ecrivez votre nom dans le fichier output.txt en utilisant la fonction print.
Lors de la création d'un nouveau fichier, vous devez définir le mode du fichier (le troisième paramètre de la fonction open ). Le mode le plus commun est 0644.
Suivez les lignes avec TODO 3.
Allez dans le répertoire 3-lseek
et regardez le code source dans lseek.py
.
Quelle valeur va retourner le deuxieme appel de “lseek”? Décommentez la ligne d'affichage, compilez et exécutez pour vérification.
La source ne ferme que le descripteur de fichier fd1
. Avez-vous besoin de fermer et de classer le descripteur fd2
? Pourquoi
Allez dans le répertoire 4-mcat
.
Remplissez le fichier pour que le programme mcat
résultant ait une fonctionnalité similaire à celle de 'cat' (suivez les commentaires avec TODO 1
)
Le programme 'mcat' recevra comme argument de ligne de commande le nom d'un fichier dont il affichera le contenu à la sortie standard. Vous ne pouvez pas lire le fichier entier en mémoire. Vous ne pouvez lire que des morceaux de taille maximale BUFSIZE.
Vérifiez le code d'erreur renvoyé par les appels système. Passez en revue les sections Création, ouverture et fermeture de fichiers et Écriture et lecture de fichiers.
Testez avec une commande comme:
python3 mcat.py Makefile
Étendez la fonctionnalité de sorte que la sortie soit redirigée vers un fichier reçu en tant que deuxième argument - une fonctionnalité similaire à l'utilitaire cp
. (suivez les commentaires avec TODO 2
)
Consultez la section redirections.
Tester la fonctionnalité:
python3 mcat.py Makefile out ; python3 mcat.py out
make
/dev/nasty
, dans le directoire 4-mcat:
chmod u+x ./set_nasty.sh
Essayez de copier les fonctionnalités sur /dev/nasty
:
python3 mcat.py /dev/nasty python3 mcat.py /dev/nasty out ; python3 mcat.py out
Si des différences se produisent, veillez à ce que les fonctions de lecture et d'écriture (affichent éventuellement ces valeurs) et résolvez le problème.
python3 mcat.py Makefile /dev/nasty ; cat /dev/nasty
Testez l'écriture avec:
./mcat Makefile /dev/nasty ; cat /dev/nasty
read
/write
renvoie une valeur inférieure au troisième paramètre.