Durée 2 heures |
Les appendices B et C du cours, éventuellement annotées, sont la seule documentation autorisée.
Dans les solutions, on pourra utiliser les fonctions du moduleMisc
décrites dans l'appendice B.7 en les appelant explicitement avec le préfixeMisc
.
On s'efforcera de donner des explications à la fois précises et concises.
Les quatre parties sont indépendantes. Les questions soulignées peuvent être plus difficiles ou plus longues que les autres de la même partie.
execv
file argv?
argv
(justifier très brièvement)?
execv
.
execv
file argv?
execv
? Quel est l'intérêt de ce choix?
9
fork()
(justifier brièvement)?
fork
partage toute la mémoire entre les deux processus,
sauf le numéro de processus, la valeur de retour, et quelques variables
système, donc il n'y a
pas de raison de changer le comportement des signaux (bien que
techniquement on pourrait les remettre à leur comportement par défaut).
fork
.
fork
. De
plus, pour chaque descripteur, celui du fils et celui du père pointent vers
la même entrée dans la table des fichiers: ce que l'un lit, l'autre ne le
lira pas: si le fils puis le père écrivent dans un fichier de log
ouvert avant le fork, leurs écritures ne s'écrasent pas (au pire, elles seront
entrelacées).
ENOENT
lorsqu'on leur demande d'accéder à un
chemin qui n'existe pas.mkpath
de type string -> int -> unit
telle que l'appel mkpath
path perm crée le répertoire path ainsi que
tous les répertoires sur le chemin path qui n'existent pas. Tous les
répertoires créés le seront avec les permissions perm. Le chemin path est
absolu ou relatif.
Si la commande mkpath
échoue, on ne cherchera pas à retirer les
répertoires qui auraient déjà pu être créés.
Si un chemin intermédiaire de p existe mais n'est pas un répertoire, on
déclenchera une erreur ENOTDIR
comme le ferait la commande mkdir
(et la
plupart des commandes qui prennent des chemins en arguments).
open Unix;; let rec mkpath path perm = try let st = stat path in if st.st_kind <> S_DIR then raise (Unix_error (ENOTDIR, "mkpath", path)); with Unix_error (ENOENT, _, _) -> let parent = Filename.dirname path in if parent <> path then mkpath parent perm; mkdir path perm;; |
./A
existe avec les droits 0740
(ie.
rwxr-----
en notation symbolique). Décrire une valeur de perm (en
notation octale ou symbolique, ou en français) avec laquelle le
répertoire A/B
a été créé mais le répertoire A/B/C
n'a pas pu l'être.
A/B/C
peut
échouer alors que celle du répertoire A/B
a réussi.
mkpath
(en effaçant un répertoire
intermédiaire déjà créé...)
mkpath_plus
de mkpath
réalisant cela.
let mkpath_plus path perm = let user = 0o700 in let finish dirs = List.iter (fun file -> chmod file perm) dirs in let rec mkpath path = try let st = stat path in if st.st_kind <> S_DIR then raise (Unix_error (ENOTDIR, "mkdirp", path)); [] with Unix_error (ENOENT, _, _) -> let parent = Filename.dirname path in let dirs = if parent <> path then mkpath parent else [] in let () = try mkdir path user with exn -> finish dirs; raise exn in path :: dirs in finish (mkpath path);; |
lseek
lève l'exception ESPIPE
si le
descripteur auquel il est appliqué n'est pas en accès direct (i.e. ne
supporte pas lseek
). seekable
de type Unix.file_descr -> bool
qui
teste si le descripteur reçu en argument est en accès direct.
open Unix let seekable fd = try let _ = lseek fd 0 SEEK_CUR in true with Unix_error (ESPIPE, _, _) -> false |
unlinkf
qui se comporte comme unlink
, mais ne lève pas
d'erreur s'il n'est pas possible d'effectuer l'opération.
let unlinkf filename = try unlink filename with _ -> ();; |
let rec create_file_descr() = let tmpdir = "/tmp" in let rec open_tmp n = try let name = Filename.concat tmpdir (string_of_int n) in let descr = openfile name [ O_CREAT; O_RDWR; O_EXCL ] 0 in unlinkf name; descr with Unix_error (EEXIST, _, _) -> open_tmp (n+1) in open_tmp (getpid());; |
create_file_descr
, un fichier est à
nouveau créé avec le même nom que celui qui a servi à créer descr
?
inode
et sera donc
complètement indépendant.
create_file_descr
donne un accès privé dans le sens où ce qui est lu ou écrit dans desc
n'a
pu être écrit ou lu que par le processus courant ou un de ses descendants?
prog
prend un seul argument qui est un
nom de fichier ou bien zéro argument et dans ce cas utilise l'entrée
standard en supposant qu'elle est en accès direct. wrap_prog
de telle façon que wrap_prog
se comporte
comme prog
, mais fonctionne également lorsque qu'il est appelé sans
argument et que l'entrée standard n'est pas en accès direct. Pour cela, dans
ce cas, il recopie l'entrée standard dans un descripteur auxiliaire en
accès direct et fait ce qu'il faut pour que la commande prog
lise dans ce
descripteur.prog
se trouve dans le chemin d'exécution
(variable d'environnement PATH
). On supposera que le code des trois
premières questions (énoncés ou réponses) a déjà été placé dans un fichier
seek.ml
.
open Unix;; let main () = let () = if Array.length Sys.argv < 2 && not (Seek.seekable stdin) then let descr = Seek.create_file_descr () in Misc.retransmit stdin descr; ignore (lseek descr 0 SEEK_SET); dup2 descr stdin; close descr in execvp "prog" Sys.argv in handle_unix_error main();; |
select
et un seul processus. Dans tout cet
exercice, on suppose que la machine est mono-processeur.select
permet d'augmenter l'efficacité du
serveur par rapport au serveur séquentiel.
select
permet d'examiner les connexions prêtes à recevoir ou à émettre et
donc au serveur de ne jamais attendre après un client lorsque d'autres
clients sont prêts à communiquer.
read
ou
write
et qu'on écrit les réponses en mode non bloquant et que l'écriture
n'échoue jamais. select
. L'appel système
select
est un appel système cher et son coût est nettement supérieur au
coût minimal d'un appel système mais reste aussi inférieur à celui d'un
changement de contexte. Pour simplifier on fera l'approximation par excès
que sont coût est celui S d'un changement de contexte, S.select
, quelle est la valeur Nc de n pour
laquelle le serveur passe en régime saturé?
ii)
En régime saturé, on note k le nombre de clients qui sont en moyenne en
attente après le serveur. Quel est le temps nécessaire pour traiter ces k
clients?
iii)
En déduire la valeur N'c de n lorsque le serveur passe en situation
critique.
select
il ne retourne qu'un seul client sur lequel il peut lire une question.
Il faut donc compter un appel select
par question par client dont le coût
est (par hypothèse) celui d'un changement de contexte.
D'où Nc = Nb = c/(q+S). select
retourne immédiatement avec k clients.
Le temps pour traiter les k clients est donc kq + S. select
se comporte
mieux, puisque
N'c / N'b = |
|
/ |
|
= 1 + |
|
= 1 + |
|
⎛ ⎜ ⎜ ⎝ |
1 − |
|
⎞ ⎟ ⎟ ⎠ |
≈ 1 + |
|
select
ne fera pas la différence entre
les lectures/écritures qui sont prêtes (les caches sont en mémoire) et
celles qui demandent un accès disque (avec un temps de latence). La solution
avec select se comporterait alors comme une solution séquentielle. Par
contre une solution avec coprocessus pourra demander en avance plusieurs
accès disques en parallèle donc solliciter le disque en permanence.
Ce document a été traduit de LATEX par HEVEA