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.sigchld
comme suggéré par l'énoncé.
Toutefois, il y a un certain nombres de précautions à prendre.
sigchld
).sigchld
peuvent être confondues
(mais un au moins sera reçu). Lorsqu'on reçoit un signal sigchld
d'un
fils, il faut donc délivrer ce fils mais également d'autres fils
éventuels qui auraient envoyés le même signal presque simultanément. process
et on garde à jour la liste des processus en
tâche de fond.
type process = { pid : int; cmd : string; args : 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;; |
let unwind_protect f x g y = try let v = f x in g y; v with z -> g y; raise z let with_blocked_sigchld f x = let mask = sigprocmask SIG_BLOCK [ Sys.sigchld ] in unwind_protect f x (sigprocmask SIG_SETMASK) mask;; |
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 | p, s when p = proc.pid -> s | p, s -> set_status p s; wait () with Unix_error (EINTR, _, _) -> wait () | Unix_error (ECHILD, _, _) -> assert false in wait();; |
ECHILD
ne doit pas arriver car on a bien un fils proc.pid
.
let rec free_bg_sons q = try match waitpid [ WUNTRACED; WNOHANG ] (-1) with | 0, _ -> () (* il n'y a plus que des fils vivant *) | p, s -> set_status p s; free_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);; |
EINTR
ne peut pas arriver car l'appel waitpid
avec l'option
WNOHANG
n'est pas bloquant dont n'est pas interruptible.
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.stderr; false | Some (WSIGNALED s) -> eprintf "Process %d exited with signal %d\n" p.pid s; flush Pervasives.stderr; false | _ -> true in process_table := List.filter keep !process_table;; |
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 = k; cmd = cmd; args = args; status = None } in unwind_protect launch () (sigprocmask SIG_SETMASK) mask;; 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;; |
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 |