les entrées-sorties
les entrées-sorties
introduction
il y a 2 types d'entrée-sortie:
les opérations bas niveau. un fichier y est
repéré par un entier positif de 0 à
(getdtablesize(2) ou mieux) getrlimit(rlimit_nofile).
exemple: on demande à lire n octets.
les opérations haut niveau, d'interface plus
simple. mais passant par un buffer intermédiaire!
un fichier y est repéré par un file *,
structure dont l'intérieur importe peu.
exemple: on demande à lire une ligne.
librairies bas niveau
le schéma de principe est:
un processus a une table de fichiers ouverts qui pointe vers
une table des fichiers ouverts qui pointe vers
la table des v-nodes, interface avec le disque physique.
plusieurs processus peuvent donc référencer le
même fichier; et même un processus peut référencer
le même fichier plusieurs fois avec des positions différentes.
on peut aussi sous plusieurs descripteurs de fichiers référencer
le même fichier avec la même position (2 fd -> 1 entrée de
la table des fichiers ouverts).
un descripteur de fichier est simplement un entier positif.
appel : open(2)
c'est l'appel de base pour ouvrir un fichier:
#include <fcntl.h>
int open(const char *path, int oflag, mode_t mode);
les principaux flags sont:
o_rdonly, o_wronly, o_rdwr : mode d'ouverture
o_ndelay : a un sens pour les fifos et les lignes asynchrones
o_noctty : empêche de devenir terminal de contrôle du
processus
o_sync : les écritures sont forcées sur disque
o_append : ouvrir en mode append
o_creat : si le fichier n'existe pas, le créer:
son propriétaire sera l'effective-uid du processus
son groupe sera le groupe du répertoire (bsd
et répertoire en set-gid) ou l'effective-gid du processus
ses droits seront mode and complement(umask)
o_trunc : tronquer le fichier s'il existe déjà
o_excl : si l'on a demandé la création et que le
fichier existait, alors erreur.
les modes usuels sont donc:
open("file",o_rdonly);
open("file",o_wronly | o_creat | o_trunc, 0666);
open("file",o_wronly | o_creat | o_append, 0666);
appel : creat(2)
n'a plus de sens réel car c'est un déguisement de:
open(path, o_wronly | o_creat | o_trunc, mode);
appels: access(2)
permet de tester simplement si on aura le droit de lire, d'écrire,
d'exécuter:
int access(char *path, r_ok);
int access(char *path, w_ok);
int access(char *path, x_ok);
appel : lseek(2)
pour se positionner dans le fichier:
off_t lseek(int fd, off_t offset, int whence)
avec comme troisième champ : seek_set, seek_cur
ou seek_end.
l'appel perl est toujours seek.
appel : read(2) et write(2)
lire et écrire:
int read(int fd, char *buf, int nbyte);
int write(int fd, char *buf, int nbyte);
le résultat de ces fonctions est le nombre d'octets
lus ou écrits, -1 en cas d'erreur.
appel : dup(2) et dup2(2)
int dup(int fd);
int dup2(int fd1, int fd2);
duplique une entrée dans la process control block. i.e.
on obtient 2 pointeurs vers la même position dans le
même fichier:
dup prend le premier descripteur libre
dup2 ferme fd2 s'il est ouvert et le prend
application : on ouvre un fichier, on le réouvre
sous un numéro déterminé pour le donner
à un fils (cas des shells).
appel : fcntl(2)
permet des opérations spéciales:
dupliquer avec une contrainte sur la borne inférieure
faut-il ou non fermer le fichier lors d'un exec(2)?
positionner ou vérifier les flags d'ouverture du fichier
(fndelay, fappend, fasync).
gérer les verrous
gérer le process-group
appel : ioctl(2)
permet de gérer tout ce qui peut dépendre
du type de périphérique associé.
exemple: rembobiner une bande, définir le
caractère type control-c.
appel: mknod(2)
pour créer les descripteurs de périphériques.
il y a aussi la commande mknod(8).
exemple:
mknod /dev/null c 3 2
nb : c'est très lié à l'intérieur du noyau!
librairies haut niveau
un descripteur de fichier est ici une structure file *.
extern struct _iobuf {
int _cnt;
unsigned char *_ptr;
unsigned char *_base;
int _bufsiz;
short _flag;
char _file; /* should be short */
} _iob[];
#define file struct _iobuf
i.e. on accède aux données du fichier
via un buffer intermédiaire.
appel fopen(3)
#include <stdio.h>
file *fopen(char *filename, char *type);
le type est r, w, a,
r+, w+ ou a+.
cette fonction rend un pointeur null en cas d'erreur.
appel fclose(3)
vider les buffers, fermer le fichier:
int fclose(file *f);
appels fseek(3), ftell(3)
int fseek(file *f,long offset,ptrname /* 0, 1 ou 2 */);
int ftell(file *f);
ftell est nécessaire car fseek rend -1 ou 0...
appels [f]getc(3) et [f]putc(3)
lire et écrire par caractère:
int getc(file *f); # may be a macro
int fgetc(file *f); # function
int putc(char c, file *f);
int fputc(char c, file *f);
appels fgets(3) et fputs(3)
char *fgets(char *s, int n, file *f);
char *fputs(char *s, file *f);
lire une ligne (ou le nombre de caractères), écrire
une ligne.
nb : il existe aussi gets(char *line), implicitement
sur l'entrée standard (stdin), mais qui ne teste
pas les débordements (donc très déconseillé!).
nb : il existe aussi puts, implicitement sur la sortie
standard. mais puts ajoute un passage à la ligne donc
puts(line) et puts(line,stdout) ne sont pas synonymes!
appels fprintf(3), fscanf(3)
similaire à printf() et scanf()
avec en premier argument le fichier:
fprintf(stderr,"error message %d\n",errno);
/* very bad example: use perror() */
appel fflush(3)
cet appel
int fflush(file *f);
force l'écriture de ce qui peut être dans les buffers file *.
appels setbuf(3), setbuffer(3)
ces fonctions permettent de contrôler la bufferisation
ou non, la taille des buffers.
nb : par défaut sur stdout, les sorties sont bufferisés;
et elles ne le sont pas sur stderr.
correspondance haut et bas niveau
int fileno(file *f);
file *fdopen(int fd,char *type);
assurent la correspondance entre haut et bas niveau.
lecture des répertoires
un répertoire est lu à travers des primitives
spéciales:
/* system v */
#include <dirent.h>
dir *opendir(char *dirname);
struct dirent *readdir(dir *dirp); # d_namlen
&& strncmp(name,directp->d_name,strlen(name)) == 0) { /* found */ }
en perl, c'est très simple:
#!/usr/local/bin/perl
dir = opendir("/my/directory");
while ($file = readdir(dir)) { ... }
# or
@files = readdir(dir);
closedir(dir);
cas particulier des terminaux
les caractères passent du programme (shell ou utilisateur)
vers le couple clavier/écran via une gestion de terminal
et un driver.
le gestionnaire de ligne a les rôles suivants:
echo des caractères tapés
assemblage en lignes, avec gestion des effacements
(control-h, control-w, control-u, control-r),
du flot (control-s, control-q)
gestion des signaux (control-c, control-z, etc.)
fin de fichier (control-d)
conversion de caractères : tabulation,
retour-chariot; gestion de l'affichage
(délai après certains caractères)
gestion physique: parité, nombre de stop,
raccrochage, etc.
tous ces paramètres sont contrôlés par
la commande stty:
en berkeley
stty all > /dev/ttyp0
en system v
stty -a < /dev/ttyp0
programmation berkeley
la structure de référence est:
struct sgttyb mode;
/*
char sg_ispeed;
char sg_ospeed;
char sg_erase;
char sg_kill;
short sg_flags;
*/
if (ioctl(fd,tiocgetp,&mode) < 0) { /* erreur */ }
mode.sg_flags |= raw;
mode.sg_flags &= ~(echo | crmod | tandem | anyp);
if (ioctl(fd,tiocsetn,&mode) < 0) { /* erreur */ }
programmation system v
la structure de référence est ici:
struct termios param;
/*
tcflag_t c_iflag; /* input modes */
tcflag_t c_oflag; /* output modes */
tcflag_t c_cflag; /* control modes */
tcflag_t c_lflag; /* local modes */
cc_t c_cc[nccs]; /* control chars */
*/
if (ioctl(fd,tiocsetn,¶m) == -1) { /* erreur */ }
param.c_iflag &= ~icrnl;
param.c_iflag &= ~inlcr;
param.c_lflag &= ~echo;
param.c_lflag &= ~icanon;
param.c_lflag &= ~isig;
param.c_lflag &= ~iexten;
param.c_cc[vmin] = 1;
param.c_cc[vtime] = 0;
if (ioctl(fd,tcsanow,¶m) == -1) { /* erreur */ }
les paramètres de temps et de minimum
de caractères font référence aux rafales
de caractères, typiquement utilisés par les
éditeurs de texte pour reconnaître les
flèches et autres clés de fonctions.
cf infra.
les gestions d'écran
en berkeley, les terminaux sont décrits par un grand
fichier texte /etc/termcap.
en system v, il y a un fichier /usr/lib/terminfo/x/xyz
par terminal.
il existe des conversions entre les 2 formats, mais la
sémantique sous-jacente diffère par
endroits.
essentiellement, on trouve dans ces descriptions:
taille de l'écran
séquence à envoyer pour effacer l'écran,
la fin de ligne, insérer ou détruire une ligne ou des lignes,
passer en écran alterné, initialiser le terminal, etc.
séquence de caractères émises par les
flèches et autres clés de fonction, etc.
l'accès berkeley est via la librairie -ltermcap:
char term[256], bp[1024], filled[500], *area = filled;
strcpy(term,(char *)getenv("term"));
if (tgetent(bp,term) != 1) { /* erreur */ }
co = tgetnum("co");
up = tgetstr("ku",&area);
en system v, il faut y préférer curs_terminfo(3).
cas particulier: taille de l'écran
en berkeley, elle est gérée au niveau du
terminal directement, à l'aide du signal sigwinch:
stty rows 34 cols 80
struct winsize winsize;
if (ioctl(1, tiocgwinsz, &winsize) == 0
&& winsize.ws_col > 0
&& winsize.ws_row > 0) {
en system v, ce sont les variables d'environnement lines
et columns qui doivent être mises à jour
en cas de changement de taille.
les entrées-sorties Précédent 666 Précédent 665 Précédent 664 Précédent 663 Précédent 662 Précédent 661 Précédent 660 Précédent 659 Précédent 658 Précédent 657 Précédent 656 Précédent 655 Précédent 654 Précédent 653 Précédent 652 Précédent 651 Précédent 650 Précédent 649 Précédent 648 Précédent 647 Précédent 646 Précédent 645 Précédent 644 Précédent 643 Précédent 642 Précédent 641 Précédent 640 Précédent 639 Précédent 638 Précédent 637 Suivant 668 Suivant 669 Suivant 670 Suivant 671 Suivant 672 Suivant 673 Suivant 674 Suivant 675 Suivant 676 Suivant 677 Suivant 678 Suivant 679 Suivant 680 Suivant 681 Suivant 682 Suivant 683 Suivant 684 Suivant 685 Suivant 686 Suivant 687 Suivant 688 Suivant 689 Suivant 690 Suivant 691 Suivant 692 Suivant 693 Suivant 694 Suivant 695 Suivant 696 Suivant 697