Atelier 4 : Programmation syst` eme en C
Syst`emes embarqu´es temps r´eel – GIF-3004 Professeur : Christian Gagn´e
Semaine 4 : 7 f´evrier 2020
Laboratoire 2
Vous devriez avoir termin´e la phase exploratoire et comprendre ce que vous devez faire de mani`ere g´en´erale
I C’est le temps de plonger dans le code !
Nous allons voir ensemble certaines constructions utiles, mais pas forc´ement ´evidentes, `a la r´ealisation du laboratoire 2
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 2 / 14
Lectures et ´ ecritures
Extrait du manuel de read() (mˆeme chose pour write())
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf. [...] On success, the number of bytes read is returned [...] It is not an error if this number is smaller than the number of bytes requested.
Qu’est-ce que cela implique ?
I Ce n’est pas parce que vous vous attendez `a recevoir (ou envoyer)noctets que vous allez tous les recevoir (ou envoyer) d’un seul coup !
Cela s’applique `a la majorit´e des lectures et ´ecritures : fichiers, sockets, pipes, etc.
Lectures et ´ ecritures
Quelle est l’erreur dans le code suivant ?
void lireFichier(char* buffer, size_t* size) {
*size = 43894;
char* buffer = malloc(*size);
read(descripteur, buffer, *size);
}
Comment le corriger ? En ajoutant une boucle de lecture
void lireFichier(char* buffer, size_t* size) {
*size = 43894;
char* buffer = malloc(*size);
size_t octetsLus = 0;
while(octetsLus < *size) {
octetsLus += read(descripteur, buffer + octetsLus, *size - octetsLus);
} }
En bonus : pourquoi size_t?
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 4 / 14
Allocation m´ emoire
C’est bien beau de lire, mais encore faut-il pouvoir allouer !
I Vous connaissez probablement d´ej`avoid *malloc(size_t size);
I Consid´erez utilisercalloc, tr`es similaire, mais qui initialise la m´emoire `a 0 La taille doit ˆetre connue lors de l’appel `amalloc()
Petit probl`eme dans un contexte de communication
I Le message est compos´e democtets d’en-tˆete et denoctets de contenu
I mest connu, mais pasn: il faut d’abord lire l’en-tˆete pour le savoir !
I Mais pour lire l’en-tˆete, il faut d’abord allouer un buffer !
I Tout cela n’est pas tr`es productif
Allocation m´ emoire
Premi`ere solution : allouer 1 Go de m´emoire,«au cas o`u» Pas tr`es efficace...
I Et le jour o`u le message d´epasse 1 Go ? Meilleure solution : utiliser realloc()
struct msgReq req;
char* buffer = malloc(sizeof(req));
/* Lire seulement l'en-tete */
octetsTraites = read(reqList[i].fdSocket, buffer, sizeof(req));
memcpy(&req, buffer, sizeof(req));
/* Reallouer selon taille specifiee dans en-tete */
buffer = realloc(buffer, sizeof(req) + req.sizePayload);
/* Lire les donnees */
octetsTraites = read(reqList[i].fdSocket, buffer + sizeof(req), req.sizePayload);
Question pi`ege : combien d’octets doit-on allouer pour une chaˆıne de 8 caract`eres ?
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 6 / 14
Utilisation de select
Supposons plusieurs sockets ouverts et qui ´ecoutent les requˆetes, avec certains pouvant ˆ
etre prˆets `a ˆetre lus
I Autrement dit, les clients attach´es `a certains sockets ont envoy´e une requˆete Mais vous ne savez pas lesquels, comment proc´eder ?
I Solution simple : faire une boucle
struct msgReq req;
char* buffer = malloc(sizeof(req));
for(int i=0; i<NOMBRE_SOCKETS; i++) {
int octetsLus = read(tableauSockets[i], buffer, sizeof(req));
if(octetsLus > 0) {
/* On a recu un message, on peut le traiter */
} }
I Petit«d´etail»:read()(etopen()etwrite()) sont bloquants
I Donc tant que le premier client n’aura pas envoy´e une requˆete, aucun client ne sera trait´e
Utilisation de select (2
eoption)
Pour les petits malins qui vont lire le manuel de open()
O NONBLOCK or O NDELAY When possible, the file is opened in nonblocking mode.
Neither the open() nor any subsequent operations on the file descriptor which is re- turned will cause the calling process to wait.
Pas fou, mais il faut prendre en consid´eration queread() est un appel syst`eme.
I Chaqueread()coˆute en moyenne 12 000 cycles processeur (≈3800 instructions), sans compter les autres pertes (´evictions du cache, du TLB, etc.)
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 8 / 14
Utilisation de select (3
eoption)
La«bonne» solution : demander au syst`eme de faire le travail pour nous.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
I readfds,writefdsetexceptfds sont des structures similaires qui surveillent diff´erents
´ev´enements apparaissant sur un ensemble de descripteurs
F Ces structures peuvent ˆetre initialis´ees et utilis´ees en utilisant les macrosFD_ZERO,FD_SETet FD_ISSET
I timeoutest une structure permettant d’informerselectdu temps que l’on est prˆet `a attendre avant qu’un ´ev´enement se produise
I nfdsest particulier : valeur du plus haut descripteur de fichier donn´e dans les ensembles readfds,writefdsetexceptfds, plus 1
Exemple d’utilisation de select
fd_set setSockets;
/* Indiquer le temps pret a attendre */
structtimeval tvS;
tvS.tv_set= 0;
tvS.tv_usec=SLEEP_TIME;
intnfdsSockets= 0;
FD_ZERO(&setSockets);
for(inti=0; i<maxlen; i++) {
/* Verifie si le socket peut recevoir des requetes */
if(reqList[i].status==REQ_STATUS_LISTEN) { /* Si oui, ajout a l'ensemble */
FD_SET(reqList[i].fdSocket,&setSockets);
/* Verifie si le descripteur est plus haut que le maximum actuel */
nfdsSockets=(nfdsSockets<=reqList[i].fdSocket)?reqList[i].fdSocket+1 :nfdsSockets;
} }
if(nfdsSockets> 0) {
/* Au moins un socket en attente d'une requete, on fait select en lecture */
ints=select(nfdsSockets,&setSockets,NULL,NULL,&tvS);
if(s> 0) {
for(inti=0; i<maxlen;++i) {
/* Trouver sockets prets a etre lus */
if(reqList[i].status==REQ_STATUS_LISTEN&&FD_ISSET(reqList[i].fdSocket,&setSockets)) { /* On traite la lecture ici */
} } } }
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 10 / 14
Utilisation de select
select peut ˆetre utilis´e sur autre chose que des sockets (par exemple les pipes !) Des alternatives plus modernes comme poll ouepollexistent, mais le principe est similaire, etselect est la solution la plus portable
I Si vous n’ˆetes pas Google ou Facebook et que vous n’op´erez pas de millions de serveurs, l’´economie d’´energie est n´egligeable et selectest un bon choix
Copier des donn´ ees
En C, pas de constructeurs de copie ou d’autres fonctions de«haut niveau», tout se passe via des fonctions de copie
I Fonction g´en´erale
void *memcpy(void *dest, const void *src, size_t n);
F Copie litt´eralement n’importe quoi : n’a aucune notion de ce qu’il copie
F Pratique, mais dangereux...
I Pour une chaˆıne de caract`eres
char *strcpy(char *dest, const char *src);
F S’arrˆete lorsqu’elle rencontre un caract`ere nul (0x00)
I Mieux
char *strncpy(char *dest, const char *src, size_t n);
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 12 / 14
Copier des donn´ ees
En C, pas de notion de type pour la copie, tout est une suite d’octets A quelques d´` etails pr`es, les codes suivants font la mˆeme chose
I Approche la plus«sˆure», mais ne pas oublier de d´esallouer le buffer
struct msgReq req;
char* buffer = malloc(sizeof(req));
octetsTraites = read(reqList[i].fdSocket, buffer, sizeof(req));
memcpy(&req, buffer, sizeof(req));
I Assignation d’un buffer comme contenu : valide mais plus propice `a bogues (copier pointeur au lieu du contenu)
struct msgReq req;
char* buffer = malloc(sizeof(req));
octetsTraites = read(reqList[i].fdSocket, buffer, sizeof(req));
req = *((struct msgReq*)buffer);
I Utilisation directe, valable si valeur lue non utilis´ee apr`es fin de la fonction
struct msgReq req;
octetsTraites = read(reqList[i].fdSocket, (char*)&req, sizeof(req));
Objectifs de la s´ eance
C’est le moment de poser vos questions reli´ees au laboratoire 2
I Vous devriez avoir un environnement op´erationnel
I Et une certaine id´ee de ce qu’il faut faire
Nous sommes disponibles pour r´epondre `a vos questions techniques
I Mais pas pour faire le travail `a votre place !
SETR – GIF-3004 (U. Laval) Programmation syst`eme C. Gagn´e 14 / 14