LET début de en revue quelques-uns des concepts clés:
Description du fichier
Dans le noyau du système d'exploitation, chaque fichier, point final de la conduite, point final de la prise, le nœud de l'appareil ouvert, et ainsi de suite, a une description de fichier . Le noyau les utilise pour garder trace de la position dans le fichier, des drapeaux (lire, écrire, ajouter, fermer-sur-exec), des verrous d'enregistrement, et ainsi de suite.
Les descriptions de fichiers sont internes au noyau et n'appartiennent à aucun processus en particulier (dans les implémentations typiques).
descripteur de fichier
Du point de vue des processus, les descripteurs de fichiers sont des nombres entiers qui identifient les fichiers ouverts, des tuyaux, des prises de courant, ou dispositifs FIFOs.
Le noyau du système d'exploitation conserve une table de descripteurs pour chaque processus. Le descripteur de fichier utilisé par le processus est simplement un index de cette table.
Les entrées de la table des descripteurs de fichiers font référence à une description de fichier de noyau.
Chaque fois qu'un processus utilise dup()
or dup2()
pour dupliquer un descripteur de fichier, le noyau ne fait que dupliquer l'entrée dans la table de descripteurs de fichier pour ce processus; il ne duplique pas la description du fichier qu'il garde pour lui-même. Quand un processus forks, le processus enfant obtient sa propre table de descripteur de fichier, mais les entrées pointent toujours vers les mêmes descriptions de fichier noyau. (Ce sont essentiellement des shallow copy, toutes les entrées de la table des descripteurs de fichiers étant des références aux descriptions de fichiers.Les références sont copiées, les cibles visées restent les mêmes.)
Lorsqu'un processus envoie un descripteur de fichier à un autre processus via un Unix Message auxiliaire de socket de domaine, le noyau alloue réellement un nouveau descripteur sur le récepteur, et copie la description du fichier auquel le descripteur transféré fait référence.
Tout fonctionne très bien, même si elle est un peu déroutant que « descripteur de fichier » et « Description du fichier » sont tellement semblables.
Qu'est-ce que tout ce qui a à voir avec les effets de l'OP?
Chaque fois que de nouveaux processus sont créés, il est courant d'ouvrir le périphérique, le canal ou le socket cible et dup2()
le descripteur à l'entrée standard, à la sortie standard et à l'erreur standard. Cela conduit aux trois descripteurs standard faisant référence à la même description de fichier , et donc toute opération qui est valide en utilisant un descripteur de fichier, est également valable en utilisant les autres descripteurs de fichier.
Ceci est le plus courant lors de l'exécution de programmes sur la console, car alors les trois descripteurs se réfèrent tous à la même description de fichier; et cette description de fichier décrit l'extrémité esclave d'un dispositif de caractères pseudoterminal.
Tenir compte du programme suivant, run.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
static void wrerrp(const char *p, const char *q)
{
while (p < q) {
ssize_t n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
return;
}
}
static inline void wrerr(const char *s)
{
if (s)
wrerrp(s, s + strlen(s));
}
int main(int argc, char *argv[])
{
int fd;
if (argc < 3) {
wrerr("\nUsage: ");
wrerr(argv[0]);
wrerr(" FILE-OR-DEVICE COMMAND [ ARGS ... ]\n\n");
return 127;
}
fd = open(argv[1], O_RDWR | O_CREAT, 0666);
if (fd == -1) {
const char *msg = strerror(errno);
wrerr(argv[1]);
wrerr(": Cannot open file: ");
wrerr(msg);
wrerr(".\n");
return 127;
}
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO ||
dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) {
const char *msg = strerror(errno);
wrerr("Cannot duplicate file descriptors: ");
wrerr(msg);
wrerr(".\n");
return 126;
}
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) {
/* We might not have standard error anymore.. */
return 126;
}
/* Close fd, since it is no longer needed. */
if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
close(fd);
/* Execute the command. */
if (strchr(argv[2], '/'))
execv(argv[2], argv + 2); /* Command has /, so it is a path */
else
execvp(argv[2], argv + 2); /* command has no /, so it is a filename */
/* Whoops; failed. But we have no stderr left.. */
return 125;
}
Il prend deux ou plusieurs paramètres. Le premier paramètre est un fichier ou un périphérique, et le second est la commande, avec le reste des paramètres fournis à la commande. La commande est exécutée, avec les trois descripteurs standard redirigés vers le fichier ou le périphérique nommé dans le premier paramètre. Vous pouvez compiler ce qui précède avec gcc en utilisant par exemple.
gcc -Wall -O2 run.c -o run
Écrivons un petit utilitaire testeur, report.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char buffer[16] = { "\n" };
ssize_t result;
FILE *out;
if (argc != 2) {
fprintf(stderr, "\nUsage: %s FILENAME\n\n", argv[0]);
return EXIT_FAILURE;
}
out = fopen(argv[1], "w");
if (!out)
return EXIT_FAILURE;
result = write(STDIN_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "write(STDIN_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "write(STDIN_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDOUT_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDERR_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDERR_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDERR_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
if (ferror(out))
return EXIT_FAILURE;
if (fclose(out))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
Il faut exactement un paramètre, un fichier ou un périphérique à écrire, de faire rapport si l'écriture à l'entrée standard, et la lecture de la sortie standard et le travail d'erreur. (Nous pouvons normalement utiliser $(tty)
dans les shells Bash et POSIX, pour faire référence au terminal actuel, afin que le rapport soit visible sur le terminal.) Compilez celui-ci en utilisant par ex.
gcc -Wall -O2 report.c -o report
Maintenant, nous pouvons vérifier certains appareils:
./run /dev/null ./report $(tty)
./run /dev/zero ./report $(tty)
./run /dev/urandom ./report $(tty)
ou sur ce que nous voulons. Sur ma machine, quand j'exécuter sur un fichier, par exemple
./run some-file ./report $(tty)
écrit à l'entrée standard, et la lecture de la sortie standard et l'erreur standard tous les travaux - qui est comme prévu, les descripteurs de fichiers se réfèrent au même , lisible et accessible en écriture, description du fichier.
La conclusion, après avoir joué avec ce qui précède, est qu'il n'y a pas comportement étrange ici du tout. Tout se comporte exactement comme on s'y attendrait, si les descripteurs de fichier utilisés par les processus sont simplement des références aux descriptions de fichiers du système d'exploitation interne, et les descripteurs d'entrée, de sortie et d'erreur standard sont dup
l'un de l'autre.
Pourquoi dans le monde pensez-vous qu'il écrit quoi que ce soit sur stdout? Il écrit à votre terminal. La sortie standard de votre processus peut être associée à votre terminal, mais ce n'est pas la même chose. Ne confondez pas les deux. Dans votre cas, stdin est également associé au terminal, il n'est donc pas surprenant que les écritures sur stdin apparaissent sur le terminal. –