User Tools

Site Tools


progsys:index

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…

Commande ls

myls.c
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
 
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

forkn.c
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
 
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”.

faux-pipe.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
 
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.c
/* log file cmd args... */
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
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.

log2.c
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;
}
Générer/afficher un fichier de k double

on utilise les entrées et sorties standard pour faire simple…

generer.c
/* $ ./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.c
/* $ ./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.c
/* calculer < in > out */
 
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
 
#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-1] et tubes[i] */
    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

consprod.c
/* Exo 2.2 et 2.3 */
/* compilation: gcc -std=c99 consprod.c -o consprod */
/* execution: ./consprod */
 
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
 
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;
}
consprodN.c
/* Exo 2.5 */
/* compilation: gcc -std=c99 consprodN.c -o consprodN */
/* execution: ./consprodN 4 */
 
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
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()

loop.c
#include <setjmp.h>
#include <stdio.h>
 
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.

foreground.c
#define _XOPEN_SOURCE
#define _XOPEN_SOURCE_EXTENDED
 
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
 
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… ?

write2g.c
#define _BSD_SOURCE
 
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
 
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 !!!

nreines-threads.c
/* gcc -std=c99 -pthread nreines-threads.c  */
 
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
 
#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 <n ; i++)
    pthread_create(tids + i, NULL, start, (void*)i);
 
  for(int i = 0; i <n ; i++)
    pthread_join(tids[i],NULL);  
 
 printf ("%d\n", cpt);
 
  return EXIT_SUCCESS;
}

Essayer

essayer.c
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
 
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).

signaux-pere-fils-v0.c
// compilation: gcc -std=c99 signaux-pere-fils.c
// test: ./a.out 100 1 2 3 4 5 6
 
#define _GNU_SOURCE
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
 
#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 = 1 ; 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 = 2 ; 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 !

signaux-pere-fils.c
// compilation: gcc -std=c99 signaux-pere-fils.c
// test: ./a.out 100 1 2 3 4 5 6
 
#define _GNU_SOURCE
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
 
#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;
}
progsys/index.txt · Last modified: 2016/03/05 06:47 by orel