Mon cher journal,
il y a bien longtemps que je n'ai pas écrit de programme en C, aussi ai-je besoin de tes conseils éclairés et avisés.
Voici mon problème :
nous disposons d'une imprimante à aiguilles qui nous sert plusieurs fois par semaine pour effectuer de grosses éditions, il est donc hors de question d'envisager de nous en séparer.
Puisque nous sommes passés sous Linux, nous utilisons CUPS qui fait fonctionner sans broncher nos 50 imprimantes réseaux.... Mais cette antiquité est loin de comprendre le langage hp/gl ou ps level 2 ou 3, loin s'en faut.
Pour pallier ce problème, j'ai donc connecté notre vieille berta au port parallèle de notre serveur, et configuré une file en mode 'raw' sous cups spécialement pour elle.
Comme elle est en mode raw, pas moyen d'envoyer des paramètres pour l'édition à lancer depuis la ligne de commande (lp ou lpr). Voici donc le noeud du problème :
Il m'est absolument impossible de modifier les fichiers textes que nous envoyons à cette imprimante, j'ai donc codé un petit programme en C qui effectue dans l'ordre les tâches suivantes :
- ouverture du fichier /dev/lp0 en écriture
- écriture dans ce "fichier" des caractères de contrôles correspondants aux paramètres de l'édition
- fermeture du fichier /dev/lp0
-Appel système de la commande lp avec pour paramètres le nom de cette file définie en mode raw
- Ouverture du fichier /dev/lp0
- Ecriture du caractère Ctrl-L (pour sauter une page)
- Fermeture de ce foutu fichier
Je dis foutu, car comme la commande lp est lancée depuis un appel à system(), mon petit prog en C ne peut d'aucune manière savoir que CUPS a fini son travail sur le périphérique lp0 et donc mon second appel à fopen échoue avec un beau core-dumped !
Comment puis-je coder un truc un tant soit peu élégant qui attende gentiment que le périphérique /dev/lp0 puisse être ouvert en écriture ?
J'avais bien pensé à :
while (f=fopen("/dev/lp0", "wb+") == null);
mais je trouve ça gore, non ?
On m'a soufflé dans l'oreille de faire un appel à sleep () juste après l'appel à system(), mais j'ai déjà eu des éditions qui ont duré lus de 12 Heures... sans commentaires !
Donc ma question, c'est comment coder de manière élégante ce truc là.
Merci mon cher journal d'éclairer ma lanterne sur ce point !
# Ne pas utiliser system, mais fork+exec+handler de signal
Posté par Epsos . Évalué à 2.
- fork
- exec
pour remplacer l'appel a system(), et la tu vas me dire oui mais c'est quoi l'interet ? Et bien avec l'appel a fork, tu recuperes le pid de ton processus fils, et ensuite tu attend que ton processus fils soit mort (fin de lp).
Pour faire ca, il faut si je me rappelle bien enregistrer un handler de signal.
[^] # Re: Ne pas utiliser system, mais fork+exec+handler de signal
Posté par Epsos . Évalué à 3.
ce qui permet d'attendre la fin d'un processus dont tu connais le pid.
Donc fork+exec+wait ...
[^] # Re: Ne pas utiliser system, mais fork+exec+handler de signal
Posté par gc (site web personnel) . Évalué à 3.
[^] # Re: Ne pas utiliser system, mais fork+exec+handler de signal
Posté par Gyro Gearllose . Évalué à 1.
# Alors la je repond rapide..
Posté par TheBreton . Évalué à 2.
Cerveau deja plus trop au boulot donc voila grosso modo une piste en pseudo algo et C.
fin=false;
while(fin!=true)
{
sleep(100);
system("lpstat -p >/home/toto.log");
fopen("/home/toto.log");
je_read_le_nom( de mon imprimante et sont status);
fclose();
elle est ready ?
fin=true;
}
le sleep 100 sert a ne pas consommer de ressource cpu pour rien
# popen?
Posté par Colin Leroy (site web personnel) . Évalué à 2.
retry:
if ((f = fopen("/dev/lp0", "wb")) == NULL) {
sleep(5);
goto retry;
}
/* continuer */
[^] # Re: popen?
Posté par TheBreton . Évalué à 1.
vrai
mais le goto me fais vomir en C
alors que simplement
while((f = fopen("/dev/lp0", "wb")) == NULL)
sleep(5);
aurait suffit
[^] # Re: popen?
Posté par Epsos . Évalué à 3.
Le sleep() est une commande a banir. (utile dans tres tres peu de cas en fait)
Faire un sleep, c'est faire un poll et faire un poll c'est prendre du CPU, c'est mal.
Quitte a essayer de lire un descripteur de fichier, autant faire un select().
[^] # Re: popen?
Posté par ckyl . Évalué à 2.
[^] # Re: popen?
Posté par 007 . Évalué à 1.
et "info libc" node "Waiting for I/O"
[^] # Re: popen?
Posté par ckyl . Évalué à 2.
[^] # Re: popen?
Posté par 007 . Évalué à 1.
Puis une foi que tu as fait le sleep() il faut refaire un (ou des) read() pour voir s'il y a des données. Et là ça prend aussi du temp.
Avec select(), ton programme est "réveillé" lorsqu'il y a quelque chose à faire et tu sais quel descripteur de fichier a des données.
Puis c'est plus propre. Lorsqu'on lit le code, on comprend ce que tout de suite l'intention.
[^] # Re: popen?
Posté par 007 . Évalué à 1.
Mais pour ton applis, c'est pas bien grave et pour être honnète je crois que tu vas rien remarquer :-)
[^] # Re: popen?
Posté par ckyl . Évalué à 2.
Au pire usleep utilise un select().
Un signal c'est pas très rapide mais pas très lent non plus, mettre un bit a 1 c'est encore honnete (bon j'abuse la :-).
[^] # Re: popen?
Posté par 007 . Évalué à 1.
Non non. Voir :
http://linuxfr.org/comments/424791,1.html(...)
Il y a appel à sigprocmask() et sigaction().
> mettre un bit a 1 c'est encore honnete (bon j'abuse la :-).
C'est pas de mettre à 1 qui prend du temp. C'est le passage mode user à noyau et le retour.
Puis, on chipote. Par contre entre un select() et un (while(1) { sleep() ; if (read()) ...} ) il n'y a pas photo. La méthode sleep(), c'est 3 fois rien mais la méthode select() c'est 1 centième de 3 fois rien :-)
[^] # Re: popen?
Posté par ckyl . Évalué à 2.
/* We are going to use the `nanosleep' syscall of the kernel. But the
kernel does not implement the stupid SysV SIGCHLD vs. SIG_IGN
behaviour for this syscall. Therefore we have to emulate it here. */
Les signaux ne sont la que par ce que le noyau fait sa tembouille. Dans l'absolu si nanosleep est dispo y'a aucune raison d'avoir des signaux qui se baladent (cf les libc BSD). Après oui sous _linux_ tu as besoin de faire mumuse avec les sig*() mais ca n'a rien d'inherent à sleep().
[^] # Re: popen?
Posté par 007 . Évalué à 1.
Mais explique moi comment fait un programme sous freebsd pour faire une "pause" sans faire d'appels système vers le noyau ?
Comment fait un programme pour se "réveiller" si ce n'est pas le noyau qui le fait ?
Comment demander au noyau de "réveiller" un programme dans x secondes si tu ne fais pas d'appel système ?
J'aimerai connaitre cette magie...
Avec select(), il y a un appel noyau, puis le noyau "réveille" le programme si nécessaire. C'est tout. Si rien arrive durant 3 mois de suite, le programme ne va rien faire.
[^] # Re: popen?
Posté par ckyl . Évalué à 2.
[^] # Re: popen?
Posté par gc (site web personnel) . Évalué à 2.
[gc@meuh /tmp] echo "main() { sleep(2); }" > t.c
[gc@meuh /tmp] gcc -static[1] t.c
[gc@meuh /tmp] strace ./a.out
execve("./a.out", ["./a.out"], [/* 59 vars */]) = 0
uname({sys="Linux", node="meuh", ...}) = 0
brk(0) = 0x80aa000
brk(0x80cb000) = 0x80cb000
open("/dev/urandom", O_RDONLY) = 3
read(3, "\216R5\334$n\262\343\324\345r\177\302\224\254\373!\242"..., 32) = 32
close(3) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({2, 0}, {2, 0}) = 0
exit_group(0) = ?
Il bloque deux secondes dans l'appel système nanosleep, qui ne consomme bien sûr aucun CPU.
[1] -static afin que strace génère moins de bruit dû à ld et la recherche des shlibs
[^] # Re: popen?
Posté par 007 . Évalué à 1.
Mais il y a :
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({2, 0}, {2, 0}) = 0
Fix les signaux puis un signal est envoyé par le noyau pour arrêter le nanosleep. Il y a plusieur aller/retour userland/noyau. Comme après il fait des read()/write() qui dans la majorité des cas vont retourner 0...
Certe, je chipote. Mais on est loin du "ne consomme bien sûr aucun CPU".
[^] # Re: popen?
Posté par ckyl . Évalué à 2.
glibc
http://sources.redhat.com/cgi-bin/cvsweb.cgi/libc/sysdeps/unix/sysv(...)
FreeBSD libc
http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/gen/sleep.c?rev=(...)
Enfin bref c'etait pas le sujet en fait :-)
[^] # Re: popen?
Posté par gc (site web personnel) . Évalué à 3.
[gc@meuh /tmp] echo 'main() { printf("hello world\n"); }' > t.c
[gc@meuh /tmp] gcc t.c
[gc@meuh /tmp] strace -o c.out ./a.out
hello world
[gc@meuh /tmp] strace -f -o bash.out bash -c 'echo "hello world"'
hello world
[gc@meuh /tmp] strace -f -o python.out python -c 'print "hello world"'
hello world
[gc@meuh /tmp] strace -f -o perl.out perl -e 'print "hello world\n"'
hello world
[gc@meuh /tmp] echo 'public class t { public static void main( String args[] ) { System.out.println("Hello world"); } }' > t.java
[gc@meuh /tmp] javac t.java
[gc@meuh /tmp] strace -f -o java.out java t
Hello world
[gc@meuh /tmp] echo 'print_string "Hello World\n"' > t.ml
[gc@meuh /tmp] strace -f -o ocaml.out ocaml t.ml
Hello World
[gc@meuh /tmp] ocamlc t.ml
[gc@meuh /tmp] strace -f -o ocamlc.out ./a.out
Hello World
[gc@meuh /tmp] wc -l *.out | sort -n
25 c.out
81 ocamlc.out
84 bash.out
100 perl.out
159 ocaml.out
522 python.out
2990 java.out
On notera la belle performance de notre ami Java. Et que vraiment Python, par rapport à Perl, ça sux grave.
Disclémeur : ok je mesure essentiellement le startup là :/
# et dans un script ?
Posté par adibou . Évalué à 1.
- ouverture du fichier /dev/lp0 en écriture
- écriture dans ce "fichier" des caractères (arguments, a lire ds un autre fichier, ...)
- fermeture du fichier /dev/lp0
puis un simple script :
char_send "caractères de contrôles correspondants aux paramètres de l'édition" &&
commande lp avec pour paramètres le nom de cette file définie en mode raw &&
char_send "Ctrl-L (pour sauter une page)"
voir meme pour etre encore plus generaliste, demander de passer le fichier a ouvrir...
et alors on tombe sur une simple redirection shell :)
echo "caractères de contrôles correspondants aux paramètres de l'édition" > /dev/lp0 &&
commande lp avec pour paramètres le nom de cette file définie en mode raw &&
echo ""Ctrl-L (pour sauter une page)" > /dev/lp0
ce qui resoud pas mal de pb de gestion des processus...
# pourquoi pas un simple script
Posté par gc (site web personnel) . Évalué à 2.
C'est bien plus bourrin que ton fopen ou que le select proposé ailleurs, mais c'est bien plus court : a priori un simple "echo -n" redirigé n'écrit rien dans la redirection (j'ai vérifié avec strace) mais tente tout de même d'ouvrir la redirection en écriture c'est justement ce qu'il te faut.
#!/bin/sh
cat /etc/parametres-vielle-imprimante-pourrie > /dev/lp0
lp fichier...
while ! echo -n > /dev/lp0; do
echo "Attente..."
sleep 2
done
cat /etc/ctrl-l-vielle-imprimante-pourrie > /dev/lp0
[^] # Re: pourquoi pas un simple script
Posté par Gyro Gearllose . Évalué à 1.
Un script... En fait, je voulais me remettre à faire du C, et puis, les caractères de contrôle de l'imprimante doivent être préfixées avec des séquences d'échapement spéciales... Bref, tout un bouzin...
Je trouvais ça rigolo en C, j'ai même pensé à écrire un "filttre" pour cups... Puis même un daemon pour ma vieille_imprimante_pourrie_konaimebienquandmeme !
Bref, c'était juste pour comprendre plusieurs choses sur comment fonctionne tout ce micmac !
C'est vrai quoi ! En cours, on m'a juste appris comment ouvrir/remplirOUvider/fermer un fichier, jamais comment faire pour savoir si quelqu'un l'utilise, et/ou, comment être notifié de la {,non}utilisation de celui-ci.
Enfin, je dis fichier, mais là, il s'agit d'un périphérique, même si sous Linux/Unix c'est sensiblement la même notion pour ce que j'en sais.
Il semblerait que l'appel à select soit la solution à mon problème. Faut que je regarde ça de plus près.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.