La solution la plus simple serait de ne pas utiliser le signal sigchld et d'éliminer les processus mort simplement lorsqu'on à l'occasion d'un wait sur un processus séquentiel ainsi qu'à chaque lancement d'un processus en tâche de fond (donc si on alloue beaucoup de processus en tâche de fond, on les récupère également plus souvent). Dans ce cas, le risque est d'allouer beaucoup de processus en tâche de fond puis de se mettre à calculer sans allouer de processsus, auxquel cas les processus en tâche de fond ne seraient plus récupérés jusqu'à la fin du programme.

Pour être plus précis, nous allons libérer les processus morts dès que possible en utilisant le signal sigchld comme suggéré par l'énoncé. Toutefois, il y a un certain nombres de précautions à prendre. On mémorise l'information sur les processus en cours d'exécution dans un enregistrement process et on garde à jour la liste des processus en tâche de fond.
      
type process =
    { pid : intcmd : stringargs : string array;
      mutable status : process_status option; }

let process_table = ref [];;
let add_process p = process_table := p :: !process_table;;
let last_process p =
  match !process_table with h::t -> h | [] -> raise Not_found
let find_process pid =
  try List.find (fun p -> p.pid = pid) !process_table
  with Not_found -> assert false;;
let remove_process pid =
  process_table := List.filter (fun p -> p.pid <> pid) !process_table;;
let set_status pid s = (find_process pid).status <- Some s;;
On utilise les fonctions suivantes pour protéger l'évaluation d'erreurs éventuelles:
      
let unwind_protect f x g y =
  try let v = f x in g yv with z -> g yraise z
let with_blocked_sigchld f x =
  let mask = sigprocmask SIG_BLOCK [ Sys.sigchld ] in
  unwind_protect f x (sigprocmask SIG_SETMASKmask;;
L'attente de la terminaison d'un processus proc doit bloquer le signal sigchld, car le traitement de ce signal pourrait récupérer le processus qu'on est en train d'attendre. En contrepartie, on libére les zombis éventuels qui apparaissent pendant l'attente (qui peut être longue). Un fils dont le pid n'est pas celui recherché est une tâche de fond qui doit apparaître dans la table des processus.
      
let wait_fg_son proc =
  let rec wait () =
    (* on suppose que sigchld est bloqué *)
    try
      match waitpid [ WUNTRACED ] (-1) with
      | ps when p = proc.pid -> s
      | ps -> set_status p swait ()
    with
      Unix_error (EINTR__) -> wait ()
    | Unix_error (ECHILD__) -> assert false in
  wait();;
L'erreur ECHILD ne doit pas arriver car on a bien un fils proc.pid.

Une variante de la fonction précédente permet de délivrer les processus en tâche de fond qui ont déjà terminé. À la différence de cette dernière, si aucun processus n'a terminé, on retourne immédiatement. Cette fonction ne doit pas être appelée pendant l'attente de la terminaison d'un processus car son pid n'est pas dans la table des processus.
      
let rec free_bg_sons q =
  try
    match waitpid [ WUNTRACEDWNOHANG ] (-1) with
    | 0, _ -> () (* il n'y a plus que des fils vivant *)
    | ps -> set_status p sfree_bg_sons q
  with
  | Unix_error (ECHILD__) -> () (* il n'y a plus de fils *);;

Sys.set_signal Sys.sigchld (Sys.Signal_handle free_bg_sons);;
L'erreur EINTR ne peut pas arriver car l'appel waitpid avec l'option WNOHANG n'est pas bloquant dont n'est pas interruptible.

La table des processus sera nettoyée sur demande, typiquement à chaque interaction avec l'utilisateur. Cela pourrait aussi se faire automatiquement.
      
let clear_process_table () =
  let keep p =
    match p.status with
      Some (WEXITED c) ->
        eprintf "Process %d exited with code %d\n" p.pid c;
        flush Pervasives.stderrfalse
    | Some (WSIGNALED s) ->
        eprintf "Process %d exited with signal %d\n" p.pid s;
        flush Pervasives.stderrfalse
    | _ -> true in
  process_table := List.filter keep !process_table;;
La commande launch lance une commande. Elle prend en argument une fonction finish qui permettra de différencier le cas d'une tâche de fond de celle d'une commande frontale. Ici, on bloque le signal sigchld avant l'appel à fork afin d'être sûr que la terminaison du processus fils ne puisse pas être prise en compte par free_bg_sons avant l'appel à finish, ce qui assure en cas de tâche frontal que la terminaison du processus sera bien prise en compte par wait_fg_son.
      
let launch_command finish cmd args =
  let mask = sigprocmask SIG_BLOCK [ Sys.sigchld ] in
  (* on bloque le signal, car on va manipuler la table des processus *)
  let launch() =
    match Unix.fork () with
    | 0 ->
        (* on remet le masque pas défaut pour le fils, le signal
           sera remis à  sa valeur par défaut par "exec" *)
        ignore (sigprocmask SIG_SETMASK mask);
        mon_exec cmd args
    | k ->
        finish { pid = kcmd = cmdargs = argsstatus = None } in
  unwind_protect launch () (sigprocmask SIG_SETMASKmask;;

let finish_fg p = return_status (wait_fg_son p)
let command_wait = launch_command finish_fg;;

let finish_bg p =
  eprintf "Backgrounded process %d\n" p.pid;
  flush Pervasives.stderr;
  add_process p; 0;;
let command_bg = launch_command finish_bg;;
L'affichage des processus est un simple parcours de la table des processus.
      
let jobs() =
  (* pour être sûr que la table soit à jour *)
  clear_process_table();
  List.iter
    (fun p -> printf "[%d] %s" p.pid p.cmd)
    !process_table;
  flush Pervasives.stdout