====== Programmation Système ====== __Site Web__ : http://dept-info.labri.fr/ENSEIGNEMENT/prs //Quelques exemples et corrections des TP de Programmation Système en L3 Info...// ==== Redirection de la sortie d'erreur ==== #include #include #include #include #include #include int main(int argc, char *argv[]) { int fd = open("error", O_WRONLY|O_CREAT|O_APPEND, 0644); dup2(fd, 2); close(fd); printf("hello on stdout\n"); fprintf(stderr, "hello on stderr\n"); return EXIT_SUCCESS; } ==== Commande ls ==== #include #include #include #include int main(int argc, char **argv) { DIR * dir = opendir("."); if(dir == NULL) return EXIT_FAILURE; struct dirent * entry; while((entry = readdir(dir)) != NULL) printf("%s\n", entry->d_name); return EXIT_SUCCESS; } ==== Création de N processus fils ==== #include #include #include #include int main (int argc, char *argv[]) { int n = atoi(argv[1]); for(int i = 0 ; i < n ; i++) { if( (fork() == 0) { printf("fils %d (pid = %d)\n",i, getpid()); return EXIT_SUCCESS; } } for(int i = 0 ; i < n ; i++) wait(NULL); return EXIT_SUCCESS; } ====Faux Pipe ==== On code "ls | wc -w" à la MS-DOS, en passant par un fichier temporaire, i.e. "ls > /tmp/toto ; wc -w < /tmp/toto". #include #include #include #include #include #include #include int main (int argc, char *argv[]) { // création d'un nouveau processus fils... int pid = fork(); if(pid == 0) { // fils int fd = open("/tmp/toto", O_WRONLY|O_CREAT|O_TRUNC, 0644); if(fd < 0) { perror("open"); exit(EXIT_FAILURE);} dup2(fd,STDOUT_FILENO); // redirection de la sortie standard dans le fichier close(fd); execlp(argv[1],argv[1],NULL); // cmd1 remplace le processus fils perror("exec cmd1"); exit(EXIT_FAILURE); } else { // pére wait(NULL); // j'attend la fin de mon fils... int fd = open("/tmp/toto", O_RDONLY); if(fd < 0) { perror("open"); exit(EXIT_FAILURE);} dup2(fd,STDIN_FILENO); // redirection de l'entrée du programme close(fd); unlink("/tmp/toto"); // suppression du fichier à la fin du programme... execvp(argv[2],argv+2); // cmd2 remplace le processus principal perror("exec cmd2"); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } ==== Commande log ==== /* log file cmd args... */ #include #include #include #include #include int main(int argc, char* argv[]) { int p[2]; pipe(p); char c; if(fork()) { /* père */ int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644); if(fd < 0) exit(EXIT_FAILURE); close(p[1]); /* ici, sinon read() bloquera à la fin ! */ while(read(p[0], &c, 1) > 0) { write(1, &c, 1); write(fd, &c, 1); } close(fd); close(p[0]); } else { /* fils */ close(p[0]); dup2(p[1], 1); close(p[1]); execvp(argv[2], argv+2); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } Si l'on dispose de la commande //tee// qui duplique son entrée standard (0) sur la sortie standard (1) et dans un fichier passé un argument, on peut simplifier notre programme de la façon suivante. int main(int argc, char* argv[]) { int p[2]; pipe(p); if(fork()) { /* père */ close(p[1]); dup2(p[0], 0); close(p[0]); execlp("tee", "tee", argv[1], NULL); exit(EXIT_FAILURE); } else { /* fils */ close(p[0]); dup2(p[1], 1); close(p[1]); execvp(argv[2], argv+2); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } ==== Une chaîne de commandes ==== On souhaite écrire un programme //./chaine cmd0 cmd1 cmd2 ... cmdN// qui exécute une chaîne de commandes de la façon suivante : ./cmd1 | ./cmd2 | ./cmd3 | ... | cmdN Dans cette première version, chaque fils crée un nouveau fils en cascade : Père ==> Fils0 ==> Fils1 ==> ... ==> FilsN-1 (cmd0) (cmd1) (cmdN-1) cmdN Le pipe est partagé naturellement entre FilsK-1 et FilsK. #include #include #include int main(int argc, char *argv[]) { int nb = argc-1; int tube[2]; int i; for(i=0; i < nb-1; i++) { pipe(tube); if(fork() != 0) { /* père */ dup2(tube[1],1); close(tube[0]); close(tube[1]); break; } else { /* fils */ dup2(tube[0],0); close(tube[0]); close(tube[1]); } } execlp(argv[i+1], argv[i+1], NULL); perror("execlp"); return EXIT_SUCCESS; } Pour tester ce programme, nous pouvons exécuter les commandes suivantes : $ echo bbb > file.txt $ echo aaa >> file.txt $ echo aaa >> file.txt $ echo ccc >> file.txt $ ./chaine cat sort uniq < file.txt aaa bbb ccc Examinons maintenant une autre version... Nous allons juste inverser le code du père et du fils dans la boucle for ! Expliquer par quel miracle ce code peut marcher ? #include #include #include int main(int argc, char *argv[]) { int nb = argc-1; int tube[2]; int i; for(i=0; i < nb-1; i++) { pipe(tube); if(fork() == 0) { /* fils */ dup2(tube[1],1); close(tube[0]); close(tube[1]); break; } else { /* père */ dup2(tube[0],0); close(tube[0]); close(tube[1]); } } execlp(argv[i+1], argv[i+1], NULL); perror("execlp"); return EXIT_SUCCESS; } __Indice__ : Dans ce cas, un même père crée tous les fils de la façon suivante... mais cela n'explique pas tout ! Père (cmdN) ==> Fils0 (cmd0) ==> Fils1 (cmd1) ==> ... ==> FilsN-1 (cmdN-1) ==== Calculer en Pipeline ==== ==Générer/afficher un fichier de k double== //on utilise les entrées et sorties standard pour faire simple...// /* $ ./generer k > fichier */ int main(int argc, char* argv[]) { int k = atoi(argv[1]); double x; for(int i = 0 ; i < k ; i++) { x = (double)i; write(1, &x, sizeof(double)); } return EXIT_SUCCESS; } /* $ ./afficher k < fichier */ int main(int argc, char* argv[]) { int k = atoi(argv[1]); double x; for(int i = 0 ; i < k ; i++) { read(0, &x, sizeof(double)); printf("%f ", x); } printf("\n"); return EXIT_SUCCESS; } ==Calculer Pipeline (correction partielle à compléter)== /* calculer < in > out */ #include #include #include #include #include #define N 4 typedef double (*funtab_t)(double); double ajouter_un_dizieme(double d){return d+0.1;} double ajouter_un_centieme(double d){return d+0.01;} double ajouter_un_millieme(double d){return d+0.001;} funtab_t funtab[]={ajouter_un_millieme,ajouter_un_centieme,ajouter_un_dizieme,ajouter_un_millieme}; int nombre_suivant(double *d) { if(read(0,d,sizeof(double)) == sizeof(double)) return 0; return -1; } void executer(int i) { double x = 0.0d; while(nombre_suivant(&x) != -1) { double y = funtab[i](x); write(1, &y, sizeof(double)); } } void fermer_tubes(int* tubes) { for(int i = 0 ; i < N-1 ; i++) {close(tubes[i*2+0]); close(tubes[i*2+1]); } } int main() { /* création de N-1 tubes */ int tubes[N-1][2]; for(int i = 0 ; i < N-1 ; i++) { /* TODO: création tube */ } /* création de N fils */ for(int i = 0 ; i < N ; i++) { /* fils i qui utilise tubes[i] et tubes[i+1] */ if(fork() == 0) { /* TODO: redirection tube */ fermer_tubes((int*)tubes); executer(i); return 0; } } fermer_tubes((int*)tubes); /* attente de tous les fils */ for(int i = 0 ; i < N ; i++) wait(NULL); return 0; } ==== DS 2014-2015 : Correction Exo 2 ==== /* Exo 2.2 et 2.3 */ /* compilation: gcc -std=c99 consprod.c -o consprod */ /* execution: ./consprod */ #define _POSIX_SOURCE #include #include #include #include #include void producteur(FILE* output) { for(int i = 0 ; i < 4 ; i++) fprintf(output, "coucou\n"); } void consommateur(FILE* input) { char c; while(fread(&c, 1, 1, input) > 0) printf("%c",c); } int main() { int fd[2]; FILE* fp[2]; pipe(fd); fp[0] = fdopen(fd[0], "r"); fp[1] = fdopen(fd[1], "w"); /* Exo 2.3: En principe, il est mieux que le processus qui interagit avec le terminal rende la main en dernier et soit donc le père. */ /* FILS */ if(fork() == 0) { fclose(fp[0]); producteur(fp[1]); fclose(fp[1]); return EXIT_SUCCESS; } /* PAPA */ fclose(fp[1]); consommateur(fp[0]); fclose(fp[0]); wait(NULL); return EXIT_SUCCESS; } /* Exo 2.5 */ /* compilation: gcc -std=c99 consprodN.c -o consprodN */ /* execution: ./consprodN 4 */ #define _POSIX_SOURCE #include #include #include #include #include void producteur(FILE* output) { for(int i = 0 ; i < 4 ; i++) fprintf(output, "coucou\n"); } void consommateur(FILE* input) { char c; while(fread(&c, 1, 1, input) > 0) printf("%c",c); } int main(int argc, char* argv[]) { int N = atoi(argv[1]); int fd[2]; FILE* fp[2]; pipe(fd); fp[0] = fdopen(fd[0], "r"); fp[1] = fdopen(fd[1], "w"); for(int i = 0 ; i < N ; i++) { if(fork() == 0) { /* ième FILS */ fclose(fp[0]); producteur(fp[1]); fclose(fp[1]); return EXIT_SUCCESS; } } /* PAPA */ fclose(fp[1]); consommateur(fp[0]); fclose(fp[0]); for(int i = 0 ; i < N ; i++) wait(NULL); /* elimination zombies */ return EXIT_SUCCESS; } ==== Ecrire un boucle for avec setjmp()/longjmp() ==== #include #include int main() { jmp_buf env; int i = 0; setjmp(env); printf("%d\n",i); i++; if(i < 10) longjmp(env, 1); return 0; } ==== Groupe de processus en avant-plan dans un terminal ==== Voici un code montrant comment il est possible de faire en sorte que les signaux (ctrl-c ou SIGINT dans cet exemple) soient envoyés au fils (exécutant ici la commande //cat//) et non au groupe père+fils (comportement par défaut). Pour ce faire, il est nécessaire que le fils dispose de sont propre groupe (1) et que ce groupe soit mis en avant-plan (2). A la fin de l'excution du fils, le père doit repasser en avant-plan (3). Si vous commentez les lignes (1), (2) et (3), alors le père et fils recevront tous les deux le signal SIGINT. #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED #include #include #include #include #include #include void myhandler(int s) { printf("[pid %d] signal %d received\n", getpid(), s); } int main() { printf("papa = %d\n", getpid()); struct sigaction act; act.sa_handler = myhandler; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGINT, &act, NULL); // mise en place d'un handler pour ctrl-c (hérité par le fils) signal(SIGTTOU, SIG_IGN); // ignorer ce signal qui survient quand papa repasse en foreground... int fils = fork(); if(fils == 0) { /* FILS */ printf("fils = %d\n", getpid()); char c; while(read(0, &c, 1) > 0) { write(1, &c, 1); } // commande "cat" return EXIT_SUCCESS; } else { /* PAPA */ setpgid(fils, 0); // (1) le père crée un nouveau groupe pour son fils tcsetpgrp(0, fils); // (2) le groupe du fils devient le "terminal foreground process group" wait(NULL); tcsetpgrp(0, getpgid(0)); // (3) le pére redevient le "terminal foreground process group" et reçoit un SIGTTOU return EXIT_SUCCESS; } return EXIT_FAILURE; } ==== Appel système interruptible (ou non) ===== Les appels systèmes sont par défaut non interruptibles, sauf les appels systèmes "arbitrairement long" comme par exemple une lecture sur l'entrée standard ou sur un pipe ! Mais qu'en est-il par exemple de l'écriture de plusieurs Go dans un fichier, un appel système qui peut durer longtemps... ? #define _BSD_SOURCE #include #include #include #include #include #include #include void handler(int s) { fprintf(stderr,"signal %d received!\n",s); } int main(int argc, char* argv[]) { printf("pid = %d\n", getpid()); signal(SIGINT, handler); int fd = open("/tmp/toto", O_WRONLY | O_TRUNC | O_CREAT | O_SYNC, 0644); size_t size = 2*1000*1000*1000; // ~2GB char * buffer = malloc(size); perror("malloc"); if(!buffer) { return 0; } printf("writing ~2G in /tmp/toto... try to interrupt me with ctrl-c?\n"); ssize_t w = write(fd, buffer, size); perror("write"); printf("write %zd / %zd\n", w, size); close(fd); } ==== Les n reines (version multi-threads) ==== Attention, ce code n'est pas fini, car il faut protéger l'accès concurrent à la variable cpt !!! /* gcc -std=c99 -pthread nreines-threads.c */ #include #include #include #include #include #include #define MAX 16 typedef bool echiquier[MAX][MAX]; // variables globales int cpt = 0; int n; static bool ok (int n, int ligne, int colonne, echiquier e); void nreines (int n, int ligne, echiquier e, int *cpt) { for (int col = 0; col < n; col++) if (ok (n, ligne, col, e)) { if (ligne == n - 1) (*cpt)++; // 1 solution trouvée en plus ! else { e[ligne][col] = true; nreines (n, ligne + 1, e, cpt); e[ligne][col] = false; } } } static bool ok (int n, int ligne, int colonne, echiquier e) { int l, c; for (l = 0; l < ligne; l++) if (e[l][colonne]) return false; for (l = ligne - 1, c = colonne - 1; l >= 0 && c >= 0; l--, c--) if (e[l][c]) return false; for (l = ligne - 1, c = colonne + 1; l >= 0 && c <= n; l--, c++) if (e[l][c]) return false; return true; } void usage (char *s) { fprintf (stderr, "%s entier", s); exit (EXIT_FAILURE); } void * start(void * arg) { size_t i = (size_t)arg; echiquier e; // ne doit pas être globale ! memset (e, 0, sizeof (e)); e[0][i] = true; // on place la première reine sur la ligne 0, ième colonne nreines (n, 1, e, &cpt); // on explore à partir de la ligne 1 } int main (int argc, char *argv[]) { if (argc < 2) usage (argv[0]); n = atoi(argv[1]); pthread_t tids[n]; assert(n > 0); assert(sizeof(size_t) == sizeof(void*)); for(size_t i = 0; i ==== Essayer ==== #define _GNU_SOURCE #include #include #include static sigjmp_buf buf; static void handler(int sig) { siglongjmp(buf, 1); } int essayer(void (*f)(), int sig) { int r = 0; struct sigaction sa, old; sa.sa_handler = handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(sig, &sa, &old); if(sigsetjmp(buf, 1) == 0) f(); else r = 1; sigaction(sig, &old, NULL); return r; } void toto() { *(int *)0L = 12; // SEGV } int main(int argc, char *argv[]) { printf("essai de toto : %s\n", essayer(toto, SIGSEGV) == 0 ? "ok" : "echec"); return 0; } ==== Envoyer & Recevoir Signaux ==== //Le fils bombarde son père de signaux. Voici une version incomplète à modifier, car on perd des signaux !!! Il faut mettre en place un système d'acquitement des signaux du père vers le fils (signal SIGUSR1).// // compilation: gcc -std=c99 signaux-pere-fils.c // test: ./a.out 100 1 2 3 4 5 6 #define _GNU_SOURCE #include #include #include #include #include #include #include #define NSIGNORT 32 int tab[NSIGNORT]; void myhandler(int sig) { printf("on vient de recevoir le signal %d (%s) %d fois\n", sig, strsignal(sig), ++tab[sig]); } int main(int argc, char * argv[]) { printf("pid: %d\n", getpid()); int fpid = fork(); if(fpid == 0) { /* FILS: emettre signaux */ int ppid = getppid(); // mon père int k = atoi(argv[1]); for(int s = 2 ; s < argc ; s++) for(int i = 0 ; i < k ; i++) { kill(ppid, atoi(argv[s])); // envoi signal } // end it printf("I will kill daddy in 3 sec...\n"); sleep(3); kill(ppid, 9); } else { /* PÈRE : recevoir signaux */ // installation du handler pour tous les signaux non RT struct sigaction act; act.sa_handler = myhandler; act.sa_flags = 0; sigemptyset(&act.sa_mask); for(int sig = 1 ; sig < NSIGNORT ; sig++) { sigaction(sig, &act, NULL); tab[sig] = 0; } while(1) { pause(); // attente de signaux... } wait(NULL); } return 0; } Voici une proposition de correction qui met en place l'acquittement des signaux et utilise sigprocmask()/sigsuspend() plutôt que pause() pour ne pas perdre de signaux ! // compilation: gcc -std=c99 signaux-pere-fils.c // test: ./a.out 100 1 2 3 4 5 6 #define _GNU_SOURCE #include #include #include #include #include #include #include #define NSIGNORT 32 int tab[NSIGNORT]; void myack(int sig) { } void myhandler(int sig) { printf("on vient de recevoir le signal %d (%s) %d fois\n", sig, strsignal(sig), ++tab[sig]); } int main(int argc, char * argv[]) { printf("pid: %d\n", getpid()); // par défaut, on bloque tous les signaux (hérité par le fils) sigset_t fullmask; sigfillset(&fullmask); sigprocmask(SIG_SETMASK, &fullmask, NULL); int fpid = fork(); if(fpid == 0) { /* FILS: emettre signaux */ struct sigaction act; act.sa_handler = myack; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGUSR1, &act, NULL); int ppid = getppid(); // mon père int k = atoi(argv[1]); // on débloque SIGUSR1 uniquement sigset_t ackmask; sigfillset(&ackmask); sigdelset(&ackmask, SIGUSR1); for(int s = 2 ; s < argc ; s++) for(int i = 0 ; i < k ; i++) { kill(ppid, atoi(argv[s])); // envoi signal sigsuspend(&ackmask); // attente ack } // I will kill daddy in 3 sec... sleep(3); kill(ppid, 9); } else { /* PÈRE : recevoir signaux */ // sleep(1); // debug // installation du handler pour tous les signaux non RT struct sigaction act; act.sa_handler = myhandler; act.sa_flags = 0; sigemptyset(&act.sa_mask); for(int sig = 1 ; sig < NSIGNORT ; sig++) { sigaction(sig, &act, NULL); tab[sig] = 0; } sigset_t emptymask; sigemptyset(&emptymask); while(1) { sigsuspend(&emptymask); // attente de signaux (en les débloquant tous)... kill(fpid, SIGUSR1); // envoie ack } wait(NULL); } return 0; } ==== Open Pipe Command (exo 3, DS 2017-2018) ==== On souhaite disposer d’une fonction //open_pipe_command// dont le profil est : int open_pipe_command (int *fd, char *cmd, char **argv) Cette fonction crée un processus chargé d’exécuter la commande //cmd// avec les arguments //argv//, et renvoie deux descripteurs (dans le tableau fd) permettant respectivement d’écrire vers l’entrée standard de la commande (descripteur fd[1]) et de lire depuis la sortie standard de la commande (descripteur fd[0]). On utilisera pour cela deux tubes permettant d’établir une connexion bidirectionnelle avec le processus exécutant la commande. La fonction retournera le //pid// du processus fils exécutant la commande. /* gcc -Wall -std=c99 open_pipe_command.c && ./a.out */ #include #include #include #include #include #include #define HELLO "hello world!" // 12 int open_pipe_command(int * fd, char * cmd, char **argv) { int pr[2]; pipe(pr); int pw[2]; pipe(pw); /* user reads in fd[0]=pr[0] & writes in fd[1]=pw[1] */ fd[0] = pr[0]; fd[1] = pw[1]; int pid = fork(); if(pid == 0) { /* child */ close(pw[1]); close(pr[0]); /* useless */ /* cmd writes in pr[1]=1 & reads in pw[0]=0 */ dup2(pw[0],0); dup2(pr[1],1); close(pw[0]); close(pr[1]); execvp(cmd, argv); perror("Error execvp"); exit(EXIT_FAILURE); } close(pw[0]); close(pr[1]); /* useless */ return pid; /* success > 0*/ } int main(int argc, char * argv[]) { int fd[2]; char* cmd[] = {"tr", "a-z", "A-Z", NULL}; int pid = open_pipe_command(fd, *cmd, cmd); if(pid <= 0) return EXIT_FAILURE; write(fd[1], HELLO, strlen(HELLO)+1); close(fd[1]); char msg[128]; int r = read(fd[0], msg, 128); printf("msg[%d]: %s -> %s\n", r, HELLO, msg); close(fd[0]); waitpid(pid, NULL, 0); return EXIT_SUCCESS; } ==== Ctrl-C ==== Un processus fils est dans le même PGID (process group ID) que son père par defaut (pgid père = pgid fils = pid père). Un kill sur -pid du père envoie le signal à tous le groupe, donc au fils également... #include #include #include #include #include void handler(int sig) { printf("paf\n"); } int main(void) { signal(SIGINT, handler); // TODO: utiliser sigaction() plutôt que signal() ! if (fork() == 0) { printf("child: %d %d\n", getpid(), getpgid(0)); pause(); printf("child: bye bye!\n"); exit(EXIT_SUCCESS); } printf("father: %d %d\n", getpid(), getpgid(0)); pause(); printf("father: bye bye!\n"); return EXIT_SUCCESS; } ==== Timeout ==== #define _GNU_SOURCE #include #include #include #include static struct sigaction sa, old; static sigjmp_buf env; static void myalarm(int sig) { printf("alarm!\n"); siglongjmp(env,1); } int executer_avant_delai( void (*fun)(void *), void *parametre, int delai_en_seconde) { int ret = 1; sa.sa_handler = myalarm; sa.sa_flags = 0; // SA_RESETHAND; sigemptyset(&sa.sa_mask); sigaction(SIGALRM, &sa, &old); alarm(delai_en_seconde); if(sigsetjmp(env,1) == 0) fun(parametre); else ret = 0; // alarm alarm(0); sigaction(SIGALRM, &old, NULL); return ret; }