Les basiques de la programmation système. Comment lire et écrire en binaire correctement sur des flux de fichier.
La commande man(1)
permet de localiser les pages de manuel par
mots-clés, indépendamment des sections dans lesquelles elles se
trouvent, grâce à l’option -k
(NB: Dans tout le cours de
prog. système, on notera cmd(k)
pour spécifier la commande cmd
appartenant à la section k
du man).
Lorsqu’on cherche précisément une page dont on connaît le nom, on pourra
s’aider de grep(1)
pour sélectionner les pages dont le nom commence
exactement par celui que l’on souhaite. Ex.:
man -k write | grep ^write
Le système d’exploitation propose parfois plusieurs version d’une même
section pour une commande. Quelle est la différence entre les sections
3
et 3p
ou 3posix
? Dans quelle section se trouve la
documentation de la fonction de bibliothèque standard printf()
? A
quoi correspond(ent) l’(es) autre(s) page(s) de manuel que vous avez
trouvée(s) sur printf
?
Un appel système requiert une instruction assembleur spécifique, par exemple
ta
sur Sparc, int
sur x86, trap
sur m68k,
syscall
sur x86_64…
Une implémentation de la bibliothèque C standard peut être trouvée grâce aux commandes:
locate libc.so
ou
locate libc.a
A l’ENSEIRB, vous pouvez prendre /usr/lib64/libc.so.6
. Sur vos machines personnelles, par exemple sous Ubuntu, le fichier à prendre est /usr/lib/x86_64-linux-gnu/libc.so.6
.
Désassemblez une des bibliothèques trouvées avec l’utilitaire
objdump(1) -d
et trouvez des fonctions où des appels système ont
lieu. Sans forcément connaître ce langage assembleur, trouvez comment
le système d’exploitation reconnaît quel appel système est demandé.
Tip: Rediriger le fichier désassemblé dans less
et utiliser /
pour
rechercher les mots-clés.
Une fois que vous pensez avoir compris comment fonctionnent les appels système, jetez un oeil à cette liste
write(2)
L’appel système write(2)
permet d'écrire dans un fichier. Lisez-en la
documentation.
En partant du squelette donné en introduction. Écrivez un
programme echo
qui recopie ses arguments sur la sortie standard
en utilisant write(2)
.
Votre programme devra être écrit dans les règles de l’art : testez les erreurs
possibles de write(2)
, et affichez le cas échéant un message avec la
fonction fournie exit_if()
. On en profitera pour regarder le code de
cette fonction et l’expliquer (cf perror(3)
).
Pour tester si votre programme gère correctement les erreurs, exécutez la commande:
./echo blabla < . 1>&0
write(2)
Modifiez rapidement le programme précédent pour créer un programme
write_number
qui écrit sur sa sortie standard:
long
) dont la
valeur sera 5write(2)
.Que se passe-t-il lorsque vous exécutez votre programme ?
./write_number
Observez la sortie grâce à l’outil od
:
./write_number | od -x
perror()
est-elle un appel système~? Pourquoi~?
read(2)
En partant du squelette donné en introduction, et du
programme echo
. Écrivez un
programme read
qui lit du texte sur
son entrée standard avec l’appel système read(2)
et écrit sur
sa sortie standard avec write(2)
le texte lu.
Le programme se termine lorsque
son entrée standard est fermée (utilisez Ctrl-d
dans le terminal
pour fermer l’entrée standard d’un processus).
Pour la lecture, on utilisera un tableau de caractères dont la taille sera fixée par une constante:
#define BUFFER_SIZE ...
Testez votre programme de différentes manières:
./read
Lecture de fichier (cat
):
./read < input_file
Copie de fichier:
./read < input_file > output_file
Gestion d’erreur:
./read < .
read(2)
write_number
, et l’affiche avec printf(3)
.Reprenez les exercices write_number.c
et read_number.c
en
write_struct.c
et read_struct.c
Modifiez les deux nouveaux pour écrire et lire au lieu du
long
une structure du type suivant, où c1
contiendra a,l
contiendra 5, et c2
contiendra b:
struct nopad {
char c1;
long l;
char c2;
};
On déclarera la structure dans utils.h
pour qu’elle soit partagée
dans les deux programmes.
make CFLAGS+=-O3
, cette taille
va changer ?