Skip to content

Linux Attitude

Le libre est un état d'esprit

Archive

Tag: C

Niveau :      
Résumé : chmod +x test.c

Après une technique utilisant du perl, voici une nouvelle façon d'utiliser du C aussi simplement qu'un script.

Cette fois nous évitons l'usage de perl, on se passe donc de ses avantages en terme de parsing. Par contre, on utilise un en-tête court et le code fonctionne avec n'importe quel code valide en C. Comme d'habitude, on fait propre et on renvoie le bon code de retour.

#!/bin/sh
tail -n +4 $0 | gcc -Wall -o /tmp/cscript.$$ -x c - && /tmp/cscript.$$ "$@"
ret=$? ; rm -f /tmp/cscript.$$ ; exit $ret
//
// Code C
//
#include <stdio.h>
int main(int argc, char** argv)
{
        printf( "Appel de %s avec %d arguments\n", argv[0], argc-1 );
        return 0;
}

Et maintenant on teste pour prouver que ça marche :

chmod +x test.c
./test.c 1 2 3 && echo "OK" | | echo "KO"

Niveau :      
Résumé : SSE

Aujourd'hui l'avenir (et un peu le passé aussi). Vous souvenez-vous des cray ? Ces machines surpuissantes qui avaient au moins la puissance de calcul d'une TI92. Comment faisaient-ils pour avoir une telle puissance ? Il faisaient des calculs identiques en parallèle et multipliaient ainsi par n le nombre d'opérations. C'est ce qu'on appelle une architecture vectorielle.

Intel dans sa grande bonté a commencé à mettre en place une telle architecture dans ses processeurs. Tout d'abord avec les instructions MMX. Ces instructions utilisent les registres servant au calcul en virgule flottante (quelques transistors de gagnés), lesquels font 64 bits. On peut ainsi exécuter des instructions identiques simultanément sur 4 fois 1 octets ou sur 2 fois 2 octets. Ce qui est bien, mais pas top.

Le SSE est une amélioration du concept. Tout d'abord on utilise de nouveaux registres (ce qui libère les calculs en virgule flottante) et on leur donne une taille de 128bits. Du coup on peut manipuler deux fois plus d'entiers et cela devient intéressant pour les flottants. On peut caser 4 flottants simple précision et 2 flottants double précision dans 128 bits. Le SSE s'arrête aux 4 flottants et le SSE2 ajoute quasiment toutes les autres combinaisons possibles (flottants et entiers).

Version normale

Comme nous sommes joueurs, nous allons jouer à améliorer notre code de calcul de Mandelbrot. Mais cette fois, nous allons le laisser tourner sous linux. Comme ne sommes plus dans notre monde, mais sur un vrai système, nous allons devoir faire quelques modifications. Nous allons continuer à écrire directement en mémoire, donc on garde nos fonctions d'écriture de pixels, mais cette fois dans une mémoire allouée avec malloc. Ajoutons le support de l'écriture d'un des format d'image les plus simples, le BMP, pour stocker notre image dans un fichier et pouvoir admirer le résultat. Pour cela on prend la première documentation venue.

/* Ecriture de l'image dans un fichier bitmap (on écrit l'en-tête kivabien puis les données) */
void writebitmap(char* filename, char*bitmap, int width, int height);

Et voilà, cela nous donne une première version du code quasiment identique à la précédente et donc non optimisée pour le SSE (disponible à la fin de l'article).


continue reading...

Niveau :      
Résumé : grub ; noyau ; multiboot ; mandelbrot

Suite à de précédentes bidouilles, on veut faire mieux.

Multiboot

Donc grub peut booter plusieurs formats, l'un d'entre eux est le multiboot, un format de fichier bootable défini par les développeurs de grub espérant qu'il soit adopté par différentes distributions. Il souffre d'un certain nombre de faiblesses et n'est pas encore vraiment un standard. D'autant que grub lui-même ne supporte pas encore complètement la spécification pourtant très courte. Si vous ne voulez pas lire toute la spécification, allez directement au chapitre 4 où on trouve un code d'exemple prêt à compiler pour vous lancer dans le développement.

Vous trouverez un autre exemple chez quelqu'un qui s'est amusé à coder un space invaders bootable directement à partir de la spécification multiboot.

Alors c'est parti, reprenons notre projet précédent. Faites attention, le processeur est dans un état particulier lorsqu'on vous laisse la main (lisez le chapitre 3.2), à vous de vous en accommoder.

Mode graphique

Tout d'abord on voudrait changer le mode graphique, et si possible pour mieux que la première fois. Pour cela, on va utiliser le standard vesa 2.0 qui standardise des modes avec une plus grande définition et qui ajoute de nouvelles fonctions au bios pour manipuler la carte.

Choisissons le mode 0x118 (1024x768x24) avec 16 millions de couleurs. Plus besoin de gérer un palette puisqu'on écrit directement les couleurs RGB à utiliser pour le pixel. On aura tout de même une fonction de palette puisqu'on n'utilise qu'un petit nombre de couleurs placées sur une échelle.

Le problème c'est que vesa n'a pas prévu qu'on puisse appeler les fonctions de la carte en mode protégé sans passer au moins une fois par une interruption en mode réel. Multiboot est bien gentil, il a prévu le cas, malheureusement, grub n'implémente pas les fonctions qu'il devrait pour le faire pour nous. Un patch est disponible pour ceux qui voudrait quand même cette fonctionnalité.


continue reading...

Niveau :      
Résumé : C

Le C est plus lisible que le perl, la preuve le code C (test.c) suivant est valide :

%:include <stdio.h>
??=define T 2

int main(void)
??<
        int n<:T:>;

        n??(0??) = 1;
        if(n<:0??) == 1)
        <%
                n??(1:> = ??- n<:0:>;
        %> else
        <%
                n??(1??) = n<:0:> ??! 2;
        %>
        n??(0??) ??'= 3;

        printf( " n0 = %d ??/
                n1 = %d\n", n<:0:>, n<:1:> );

        return 0;
??>

Je le dis et je le prouve :

$ gcc -trigraphs test.c
$ ./a.out
 n0 = 2 		n1 = -2

Hé oui, le C contient quelques spécifications plus ou moins oubliées qui vous permettent ces bizarreries. Il s'agit en fait des trigraphs et des digraphs. Les premiers (3 caractères dont les 2 premiers sont des ?) ont été inventé pour palier un manque dans les tables de caractères pré ASCII (on me souffle qu'en fait c'est post ASCII), dont le petit nom est ISO 646. À l'époque de l'unicode ceci est toujours valable (par une option du compilateur il est vrai).

La deuxième série de caractère spéciaux (basée sur des <, >, % et :) a été introduite en 1999 ! Hé oui, la première écriture était trop peu lisible et devait être simplifiée ...

On peut ainsi créer un bug en C99 sans s'en rendre compte avant longtemps. Exemple :

int a;
a=1;
// Algo a la Kevin ?????????????????/
a++;

Exercice : Pour ceux qui suivent, trouvez la signification de chacun des groupes de caractères. Vous avez le droit à un gcc mais pas au web.

Niveau :      
Résumé : cmsg_type = SCM_RIGHTS

Ne vous-êtes vous jamais posé la question de comment fait apache pour lancer des processus à l'avance tout en continuant de recevoir les connexions sur le processus père ? Les cours de programmation sur les sockets ne proposent que deux façons de coder un serveur. Vous écoutez (listen) sur un processus et à chaque nouvelle connexion d'un client (accept) vous obtenez une nouvelle socket. Ensuite, soit vous créez (fork) un nouveau processus qui communiquera uniquement avec ce client sur la socket ainsi créée, soit vous gérez toutes les sockets dans le même processus (merci select).

Bien, je vous propose de continuer à faire gérer les connexions par des processus fils, tout en lançant ceux-ci à l'avance. Pour cela, il suffit de faire en sorte qu'un processus puisse communiquer un file descriptor (ou une socket) à un autre. La solution est relativement simple si on est habitué à la gestion des sockets en C. Elle se trouve dans man 7 PF_UNIX à la section "Ancillary Messages".

Les socket unix permettent de communiquer certaines informations non communicables par le réseau. Dans notre cas, nous allons communiquer des file descriptor. Il nous faut donc une socket unix pour communiquer entre les différents processus. Vous trouverez en attaché le source pour deux programme. Le premier (listener) écoute les connexions sur le port tcp 2000, dès qu'une connexion est ouverte, il communique le fd résultant au second (worker) qui va simplement afficher un petit message. Notez que la communication passe par une socket unix (/tmp/transmit) et que listener doit être lancé en premier.

Voici un aperçu des fonctions intéressantes :

Transmission du file descriptor

void fd_transmit(int unixfd, int fd)
{
 [ ... ]
        /* on transmet le minimum de données (1 octet) 
         * pour que le message soit pris en compte */
        iov.iov_base    = &one_char;
        iov.iov_len     = 1;
        msg.msg_iov     = &iov;
        msg.msg_iovlen  = 1;

        /* on spéficie un header spécial précisant qu'on transmet une socket */
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_len   = CONTROLLEN;
        cmsg->cmsg_type  = SCM_RIGHTS;
        *(int *)CMSG_DATA(cmsg) = fd;

        msg.msg_control = cmsg;
        msg.msg_controllen = CONTROLLEN;

        /* ce type de message doit être envoyé avec sendmsg */
        rval = sendmsg(unixfd, &msg, 0);
[ ... ]
}

continue reading...