Page suivante Page précédente Table des matières QNX Système temps réel : Le micro-noyau   

2. Le micro-noyau

2.1 Introduction

Le microkernel de QNX dirige :

A l'interieur du microkernel

2.2 La communication interprocess

Le microkernel de QNX supporte 3 type d'IPC (communication inter process) : messages, proxies et signaux

2.3 IPC via les messages

Dans QNX un message est un packet d'ocets qui transmis d'une manière synchrone entre deux process. QNX n'attache aucune importance au contenu du message. Le contenu du message à une importance pour l'éméteur et pour le receveur mais pour personne d'autre.

Les fonctions de transmition de messages

Pour communiquer directement avec un autre, un process utilise ces fonctions C :

Ces fonctions peuvent être utilisée localement ou le long d'un réseau.

Notez que a moins que les process veulent communiquer directement entre eux, ils n'ont pas besoin d'utiliser Send(), Receive() ou Reply(). La librairie C QNX est construite sur le principe de ces messages. Mais les process peuvent communiquer indirectement.

schéma d'une communication entre deux process au travers des messages


Le schéma ci-dessus montre une séquence d'événements dans laquelle 2 processus A et B utilisents Send(), Receive(), et Reply() pour communiquer entre eux :
  1. Le process A envois un message to process B par l'intermédiaire de la fonction Send() envoyée au microkernel. A cet instant le process A devient "SEND-blocked" jusqu'à ce que le process B revoie un Receive() pour indiquer sa bonne réception du message.
  2. Le process B renvoit un Receive() et prend connaissance de l'attente de du process A qui attend une réponse (Reply). Le process A change alors d'état pour entrer dans celui de "REPLAY-blocked". le process B lui continue le traitement de ses instructions.
  3. (il est à noter que si le process B avait émit un Receive, il aurait pris l'état "RECEIVE-blocked" jusqu'à la réception d'un message. Dans ce cas l'émetteur aurait tout de suite pris l'état "REPLAY-blocked" en attendant la réponse de B à son message).
  4. Le process B complete l'analyse et la travail éffectu avec les données recues par le message envoyé par A et génére un Reply() à destination de A qui ne sera alors plus bloqué. Un Reply() ne bloque pas l'émetteur donc la process B est lui aussi prêt a continuer à executer des instructions. Le processus qui fonctionnera sera alors celui qui aura la priorité la plus haute.

La synchronisation de processus

La transmission de messages ne permet pas seulement aux processus de s'echanger des données, mais fournit aussi une synchronisation au niveau de leur eecution (j(envois un message j'attend de recevoir une réponse, je renvois ....). Regardons lillustration ci-dessus encore une fois. Une fois que le process A génère un Send() il se bloque jusqu'à la réceptiond'une réponse à son envois. Cela permet de s'assurer que le travail que doit effectuer le process B sera complet lorsque le process A s'eécutera à nouveau. De plus lorsque le process B renvoit un Receive() il peut continuer a travailler jusqu'à la reception d'un autre message.

Etats bloquants

Quand un process ne peut continuer a s'executer -parcequ'il doit attendre un message- on dit de lui qu'il est bloqué. Le tableau suivant résume les étant bloquant auquels peuvent être sujet les process.

Si un process a généré un : le process est :
Send() et qu'il n'a pas recu de réponseSEND-blocked
Send() qu'il a recu un accusé de reception mais qu'il n'a pas eu de réponseReply-blocked
Receive() et qu'il n'a pas encore recu de messageReceive-blocked


Un process subi toujours des changement d'état dans des échange du type send-receive-reply

Utiliser Send(), Receive(), and Reply()

Regardons maintenant plus en details les fonctions Send(), Receive(), and Reply(). Nous garderons notre exemple avec le process A et le process B.

Send()

Le process A envois un message par l'intermédiaired'un send par l'intermédiaire d'un appelle de la fonction :


Send( pid, smsg, rmsg, smsg_len, rmsg_len );

Qui possède les arguments suivants :
pid
l'identificateur du processus (process ID) a qui est destiné le message (comparable à une adresse d'expédition), dans notre exemple ce serait l'ID du process B. Un PID est l'identificateur qui permet au système de différencier un process d'un autre process.

smsg le buffer du message (le message qui doit être envoyé)

rmsg le buffer qui doit contenir la réponse

smsg_len la longueur du message qui sera envoyé

rmsg_len la taille maximum de la réponse que le process A acceptera

Il est à noter qu'aucun buffer plus grand que smsg_len ne sera envoyé qu'un message contenant un buffer plus grand que rmsg_len ne sera accepté en réponse. Ce la permet de s'assurer qu'aucun buffer pourra être écrasé accidentellement.

Receive()

Le process B peut recevoir un Send() du process A grâce à la fonction Recive dont voici le prototype :


pid = Receive( 0, msg, msg_len );

La fonction Receive() contient les arguments suivants :

pid l'identificateur du process ayant emit le message (ici le process A) est renvoyé par la fonction 0 (zero) le 0 spécifie que le process B acceptera les messages de n'importe quel process.

msg le buffer dans lequel le message sera reçu.

msg_len la taille maximum du buffer recevant le message

Si la taille du message envoyé (smsg_len) différe en taille avec la taille souhaité pour le message reçu (msg_len ), le plus petit des deux déterminera la quantité de donnée qui sera transférée.

Reply()

Apres la reception d'un message du process A, le process B devra renvoyer au process A une reponse en utilisant la fonction Reply() :


Reply( pid, reply, reply_len );

La fonction Reply() contient les arguments suivants :

pid L'identificateur du processus a qui est destiné la réponse ( dans notre exemple le Process A)

reply buffer qui doit etre renvoyé

reply_len la taille des données qui doivent être renvoyées

Si la taille reply_len dans le Reply() et la taille rmsg_len dans le Send() diffère ent aille, la plus petit des deux determinera la quantité de données qui sera transférée.

Les réponses

L'exemple d'échange de messages que nous avons étudié illustre la manière la plus répandue de ce type d'IPC dans lequel un processus serveur est normalement dans l'état RECEIVE-blocked en attendant une requête d'un client afin de la traiter. Cet exemple montre un échange que l'on pourrait appeller send-driven : l'action débute avec l'envois par un client d'un message et ce termine par la réponse du serveur à ce message.

Bien que moins répendue que la méthode du send-driven, une autre forme d'échanges de messages est possible et souvent préférée à l'utilisation le reply-driven dans lequel l'action est initialisée avec un Reply().
Dans cette méthode un process envoit un message au serveur en lui indiquant qu'il est prêt à "travailler". Le serveur ne repond pas tout de suite mais prend acte de la demande du process libre. Ainsi il pourra plus tard decider de lui donner des actions à effectuer. Le process aura alors du travail dont il rendra alors les résultats par l'indermédiaire d'un message renvoyé au serveur.

Autres points importants

Les données d'un message sont maintenue dans le processus d'envois jusqu'à ce que le receveur soit paré à le recevoir. Il n'y a pas de sauvegarde de ce message dans le microkernel. Les données du messages sont maintenanues et ne peuvent être changées par inadvertance tant que le process emetteur est dans l'état SEND-blocked .
Le message renvoyé est directement copié du process renvoyant au process REPLY-blocked lorsque la fonction Reply() est appellée. La fonction Reply() ne bloque en aucun cas le processus de reponse du message. L'état REPLY-blocked prend fin lorsque les données sont reçues par le recepteur.
Le process émetteur n'a pas besoin de savoir l'état du process a qui est destiné son message avant l'envois. Si le recepteur ne peut accepter au moment de l'envois le message , l'emetteur passe simmplement dans l'état SEND-blocked.
Des messages d'une longeur de 0 peuvent être échangés si neccessaire.
Il peut y avoir plusieurs messages en attente de réception envoyés par plusieurs process à destination d'un seul. Normalement le process recepteur recoit les messages dans l'ordre de leur émission ; mais l'ordre peut être adapté à la priorité des processus émetteurs.

Le serveur a recu (mais pas repondu) les messages des clients A et B, mais n'a pas encore reçu les messages des clients C, D et E

Aller plus loin

QNX fournit des possibilités de transmition de messages avancés :

La reception conditionnelle de messages

Généralement lorsqu'un process veut recevoir un message, il utilise la fonction Receive() afin d'attendre l'arrivée d'un message. C'est le moyen usuel pour recevoir des données et le plus approprié dans bien des cas.
Dans certains cas un process peut avoir besoin de prendre connaissance des messages en attente d'être reçu afin de ne pas avoir à entrer en RECEIVE-blocked en l'absence de messages. Par exemple un process a besoin d'executer un programme à une grande vitesse sans avoir à se bloquer. Malheureusement le process doit répondre aux messages des autres process. Dans ce cas le process peut utiliser la fonction Creceive() afin de lire le message si un se présente et retourner immédiatement si aucune donnée n'est disponible ( a la différence de receive() qui attend qu'un message se présente et ne rend pas la main sinon).
Il est tout de même déconseillé d'utiliser Creceive() car le processus utiliserait le processeur continuellement en recherchant constamment un message.

Lire ou écrire dans une partie d'un message

Il arrive qu'on ai besoin d'ecrire ou de lire seulement une partie d'un message. Il est possible d'utiliser pour cela directement le buffer alloué pour le message plutôt qu'un buffer séparé.
Prenons un exemple : un manager entrée/sortie accepte des messages qui consistent en une entête de taille fixe suivie par des datas d'une taille variable. L'entête contient le comptage des octets (0 à 64 octets).
Le manager peut choisir de ne recevoir que l'entête et va utiliser la fonction Readmsg() pour lire les données directement dans un buffer. S'il la taille du buffer à lire est inferieur à la taille totale, le manager pourra utiliser de nouveau la fonction Readmsg jusqu'à ce qu'il l'y ai plus de données. De même la fonction Writemsg peut être utilisée pour rassembler des donnée et pour ensuite les inscrires dans le buffer du reply.

Messages en plusieurs blocs

Jusqu'à maintenant les messages ont été géré comme une seule et même entité. Toutefois ils consistent souvent en 2 ou plusieurs parties. Par exemple un message peut avoir une entête de taille fixe suivit par des données dune taille variable. Pour s'assurer que le message sera bien envoyé et reçu sans être copié dans un buffer temporaire, un message en plusieurs parties peut être créé a partir d'un ou plusieurs messages. Ce genre de possibilités aide les gestionnaires d'entrées sorties (I/O) comme Dev et Fsys en augmentant leur performances. Les fonctions suivantes permettent de gerer ces messages divisés en plusieurs parties :



Les messages en plusieurs parties peuvent être créés avec une structure de contrôle de type mx.
Le microkernel assemble alors ces parties en une seule entité.

Structure d'un message

Bien que vous ne soyez pas obligé de la faire, QNX commence tous ses messages par un mot de 16 bits appellé code. Les processus système de QNX utilisent les code en les ordonnants ainsi :

bits :Description:
0x0000 à 0x00FFmessages du gestionnaire de processus
0x0100 à 0x01FFmessagesI/O -entrées sorties- (identique pour tous les servers I/O )
0x0200 à 0x02FFmessages du gestionnaire de fichier
0x0300 à 0x03FFmessages du gestionnaire de materiel (Device Manager)
0x0400 à 0x04FFmessages du gestionnaire réseau (Network Manager)
0x0500 à 0x0FFFnon utilisé pour l'instant (réservés au futurs process système)

2.4 IPC au travers des proxies

Un proxy est une forme de message non blocant spécialement efficace pour les événements ou le process emetteur n'a pas besoin d'interragir avec le recepteur. La seue fonction d'un proxy est d'envoyer un message à un processus spécifique qui connait le proxy.
Tout comme les messages un proxy peut conctionner le long d'un réseau.
En utilisant un proxy, un processys ou une interruption peut envoyer un message à un autre processus sans avoir a se bloquer ou avoir à attendre une réponse.
Voici quelques exemples d'utilisation de proxy :


Les proxies sont crées avec la fonction qnx_proxy_attach(). Tout les processus ou interruptions qui connaissent le proxy peuvent alors par l'intermédiaire de celui-ci envoyer un message en utilisant la fonction Trigger(). Le microkernel prend alors en charge la requête de la fonction Trigger().
Un proxy peut être utilisé plus d'une fois - il envoit un message a chaque fois qu'il est déclenché. Un proxy peut aisi garder jusqu'à 65 535 messages à envoyer.

Un processus client qui utilise un proxy 3 fois implique que le serveur recevra trois messages du proxy

2.5 IPC au travers des signaux

Les signaux permettent une communication asynchrone qui sont présents depuis un bon nombre d'années dans bien des OS.

QNX supporte un grand nombre de signaux compatible POSIX, des signaux UNIX, mais aussi des signaux propres à QNX.

Créer des signaux

Un signal est considéré comme envoyé à un processus lorsque le process qui l'a defini est créé. Un processus peut cré un signal pour lui même.

Si vous voulez :utilisez :
Générer un signal à partir du shellles programmes kill ou slay
Generer un signal à l'interieur d'un processles fonctions kill() or raise()

Recevoir des signaux

Un processus peut recevoir des signaux de trois manières différentes suivant de quel manière il a défini sa gestion des signaux :



Entre le moment ou le signal est généré et et le moment ou il est envoyé on dit du signal qui est en attente (pending). Plusieurs signaux distincts peuvent être en attente d'un processus à un instant t. Les signaux sont envoyés à un processus lorsque celui-ci devient opérationnel grâce au scheduler du microkernel. Un processus ne peut pas connaître l'ordre dans lequel les signaux sont envoyés.


Si une second erreur apparaît alors que le processus est confronté à ce signal, il se termine.
Résumé des signaux:

Définir les signaux

Pour definir le type d'action à réaliser pour chaques signaux, vous devez utiliser la fonction C ANSI : signal() ou son équivalent POSIX sigaction().
La fonction sigaction() vous donnera un plus grand control sur la gestions des signaux.
Vous pouvez changer votre facon de gerer un signal a tout moment. Si vous changez la gestion d'un signal en demandant d'ignorer celui-ci, tout signal de ce type en attente sera écarté.

Attraper des signaux

Revenons sur les processus qui attrappent des signaux.
Une fonction qui manipule un signal peut être comparée à une interruption software. Elle est executée d'une manière asynchrone par rapport aux autres process. Il est possible par consequent pour un signal d'être prit en compte alors qu'une fonction dans le programme est en cours d'execution (quelque soit le type de la fonction).
Si votre processus reste dans le manipulateur et ne retourne pas, il est possible d'utiliser les fonctions siglongjmp() ou longjmp(), mais il est preferable d'utiliser la fonction siglongjmp() parcequ'avec longjmp() le signal reste bloqué.

Signaux bloquants

Il peut arriver que vous ayez besoin d'empecher un signal d'être intercepté sans pour autant avoir a changer la méthode qui manipule ce signal.
QNX fournit un jeu de fonctions qui vous permet de bloquer l'émission de signaux. Un signaux qui est bloqué, reste en attente ; une fois que qu'il est débloqué votre programme peut le recevoir.
Pendant que votre programme traite un signal, QNX bloque automatiquement ce signal. Cela veut dire que vous n'avez pas à vous en soucier .
Chaque appelle à un manipuleur de signal est une instruction simple qui respecte l'arrivée de d'autres signaux de ce type.
Si le manipuleur de sognal de votre processus renvois normalement, le signal est alros automatiquement débloqué. Certains système Unix on une implémentation défectueuse des manipuleurs de signaux dans le fait qu'ils reinitient le signal à son action par défaut plutôt que de le bloquer. Certaines applications Unix appellent ainsi la fonction signal() avec le manipuleur de signaux afin de réarmer celui-ci. Cela peut provoquer deux types de problèmes ; premièrement si un signal arrive alors que notre programme est dans le manipulateur mais avant que la fonction signal() soit apellée votre programme sera terminé.
Secondement si un signal arrive juste apres l'appelle de la fonction signal() il vous faudra appeller le manipulateur "manuellement".
QNX permet de bloquer les signaux et résout ainsi ce genre de problèmes.

Les Signaux et les messages

Il y'a de nombreuses interractions entre les signaux et les messages. Si votre processus est SEND-blocked ou RECEIVE-blocked alors qu'un signal est généré et que vous avez un manipulateur de signaux, il peut arriver plusieurs choses :

  1. Le processus est débloqué
  2. Le manipulateur de signal est executé
  3. La fonction Send() ou Receive() renvoit une erreur
Si votre processus etait dans l'état SEND-blocked, cela ne représente pas de problemes parceque le récepteur n'a pas recu le message. Par contre si le processus etait dans l'état REPLY-blocked, vous ne pouvez pas savoir si le message a été recu ou pas, et donc dans l'impossibilit de savoir s'il faut renvoyer ou non le message.
Il est possible pour les programmes fonctionnant sur le modèle des serveurs (c'est à dire dans notre exemple plus haut ceux qui recoivent des messages) d'être notifié lorsque qu'un client recoit un signal alors qu'il est dans l'état REPLY-blocked. Dans ce cas le processus client rentre dans l'état SIGNAL-blocked, le signal en attente, et le serveur recoit un messsages spécial lui indiquant le type du signal. Le serveur peut alors :

2.6 IPC le long d'un reseau

Cicuits virtuels

Une application QNX peut parler à un processus situé sur un autre ordinnateur sur le réseau comme si il parlait à un processus situé sur la meme machine. Pour l'application il n'a aucune différence entre une ressource locale et une ressource exterieure.
Ce remarque degré de transparence est du au circuits virtuels (VCs) qui sont des chemins que le gestionnaire de réseau fournit pour transmettre les mesages, proxies et signaux le long du réseau.
Les VCs permettent une utilisation optimisée des ressources réseau pour plusieurs raisons :

  1. lorsqu'un VC est créé il peut manipuler les messages jusqu'à une certaine taille, cela veut dire qu'il est possible d'allouer en avance les ressources pour la manipulation de ces messages. Néanmoins si vous avez à envoyer un message plus grand que la taille maximum spécifiée, le VC sera automatiquement agrandit pour s'adapter à la taille de ce message.
  2. Si deux proccess résidant sur des points différent d'un réseau communiquent ensemble par l'intermédiaire de plusieurs VC, ceux-ci seront partagés -seulement un circuit viruel réel existe entre les process. Cette situation arrive souvent lorsque qu'un process accède à plusieurs fichiers sur un sytème exterieur.
  3. Si un process utilise un VC partagé et demande un buffer d'une taille plus grande que celui autorisé par le VC, la taille du bufer de ce dernier est automatiquement augmenté.
  4. Quand un process prend fin, les VCs qui lui étaient associés sont automatiquement libérés.

processus virutels

Un processus émetteur est responsable de la configurayion du VC entre lui-même et le processus avec qui il veut communiquer. Pour ce faire il doit appeller la fonction qnx_vc_attach(). En plus de créer un VC, cet appel créé un identificateur de processus virtuel ou VID (virtual ID) a chaque bout du circuit. Pour le process situé à des bouts, le VID situé à l'autre bout apparait pour lui comme l'ID l'autre machine avec laquelle il veut communiquer situé sur le même VC. Les processus communiquent ainsi entre eux par l'intermédiaire de ces VID.

Par exemple dans l'illustration qui suit, un circuit virtuel connecte PID 1 (Process IDentificateur 1 )à PID 2. Sur le noeud 20 -sur lequel PID 1 réside-, un VID représente PID 2. Sur le noeud 40 (pù se trouve PID 2) un VID représente PID 1.
PID 1 et PID 2 peuvent se referer au VID de leur noeud comme si c'était un autre processus local (envoyer des messages, en recevoir...). Ainsi PID 1 peut envoyer un message au VID de son noeud qui lui va le relayer le long du réseau par l'intermédiaire du VC au VID représentant PID 1 à l'autre bout. Ce VID va alors donner le message à PID 2.

Chaques VID contient les informations suivantes : Vous n'allez pas souvent être en contact direct avec les VC ; Lorsqu'une application veut créer un acces I/O à une ressource en un point du réseau, un VC est créé par l'appel de la fonction open() de cette application. L'application n'a pas de rôle direct dans la création ou l'utilisation de ce VC. De même lorsqu'une application établit la location d'un serveur à l'aide de la fonction qnx_name_locate(), un VC est automatiquement créé. Pour l'application le VC apparrait simplement comme un PID.

Les proxies Virtuels

Un proxy virutel pemet à un proxy d'etre déclenché à distance, tout comme les circuits virtuels permettent à un processus d'echanger des messages avec un noeud'éloigné.
Mais à la différence d'un VC qui lie deux processus ensemble, un proxy virtuel permet à un process sur un noeud éloigné de le déclancher.
Les proxies virtuels sont créés par l'appel de la fonction qnx_proxy_rem_attach(), qui prend un noeud (nid_t) et un proxy (pid_t) comme arguments. Un proxy virtuel est créé sur le noeud éloigné et représentera le proxy sur le noeud appellant.

Un proxy virtuel est créé sur le noeud éloigné qui représente le proxy du noeud appellelant
Nous pouvons noter que le circuit virtuel est créé automatiquement à partir du noeud appellant grâce à la fonction qnx_proxy_rem_attach().

Pour terminer avec les circuits virtuels

Un processu peut devenir incapable de communiquer avec un VC existant pour plusieurs raisons :

Toutes ces conditions peuvent empecher les messages d'être envoyés par l'intermédiaire d'un VC. Il est ainsi neccessaire de détecter ces situations afin que les applications prennent les mesures qui s'imposent ou se terminent elles-même d'une manière convenable. Sans cela, les ressources ne peuvent être libérée.
Le gestionnaire de processus sur chaque noeuds vérifie l'intégrité des VCs sur son noeud de la manière suivante :
  1. A chaque fois qu'une transmition se déroule sans problèmes, une sauvegarde de l'horaire est faite pour indiquer l'heure de la dernière activitée.
  2. A intervals de temps définit le gestionnaire de processus regarde chaque VC. S'il n'y a pas d'activités sur le circuit, le gestionnaire de process envois un packet au gestionnaire situé sur le noeud à l'autre bout du circuit.
  3. Si aucune réponse n'est détectée ou si un problème se présente, le VC est signalé comme problèmatique. Un nombre defini d'essais sont alors effectués pour réétablir un contact.
  4. Si les essais echouent, le VC est libéré; tout processus bloqué sur le VC est mit à ready (le processus revoit un code d'echec comme retour du VC).
Pour controler les parâmetres que nous venons de décrire, utilisez l'utilitaire netpoll.

2.7 IPC via les sémaphores

Les sémaphores sont une autre forme tres puissante de la synchonization qui permet aux processus de "poster" (sem_post()) et "d'attendre" (sem_wait()) un sautre émaphore pour controler lorsqu'un processus se reveille ou dort.
L'opération "post" incrémente le sémaphore tandis que l'opération "wait" le décrémente.
Attendre un semaphore avec une valeur positif n'est pas bloquant. Attendre après un sémaphore non positif sera bloquant jusqu'à ce qu'un autre process execute un post une ou plusieurs fois (afin d'obtenir une valeur positive)avant un wait. Cela va permettre à un ou plusieur processus d'executer le wait sans bloquer. Une différence significative des sémaphore et des autres formes de synchronization et que les sémaphore sont dans l'impossibilité de tomber dans un mode asynchrone et peuvent être manipulée par des manipulateurs de signaux. Si la volonté est d'avoir un manipulateur de signaux qui reveille un processus, alors les sémaphore sont un meilleur choix.

2.8 Gestion des processus

Le scheduler du microkernel entre action lorsque :

Priorité d'un processus

Dans QNX chaque processus possède une prioritée. Le scheduler selectionne le prochain process qui doit être executé en regardant la priorité de chaque process qui sont dans l'état READY (un processus READY est capable d'utiliser la CPU). Le processus avec la plus haute priorité sera alors choisi.

La queue possède 6 processus (A-F) qui sont READY. Les autres (G-Z) sont bloqués. Le process A est actuellement en train de s'executer. Les processus A, B, et C ont la plus haut priorité, ils vont donc se partager la CPU
Les priorité assignées à un processus oscille entre 0 (la plus basse) à 31 (la plus haute). La priorité par défaut d'un nouveau processus est hérité de son parent ; elle normalement de 10 pour les applications démarrées par le shell.

Si vous voulez :Utilisez la fonction :
Determiner la priorité d'un processgetprio()
Donner une priorité à un processsetprio()

Methodes de scheduling

Pour répondre aux demandent des applications QNX fournis 3 méthodes de scheduling :

Tous les processus dans le système utiliserons une de ces trois méthodes, mais deux processus différents peuvent utiliser ue méthode différente.
Souvenez vous que ces méthodes de sheduling s'appliquent uniquement lorsque 2 ou plusieurs processus qui partagent la même priorité sont READY ( les processus sont prêt à utliser la CPU). Si une priorité superieure se présente elle gagne sur toutes les priorités plus faible.
Dans le diagramme suivant trois processus d'égale priorité sont READY.

Le process A bloque, le process B s'execute
Bien qu'un process hérite sa gestion d'un parent, vous pouvez changer cette gestion.
Si vous voulez :utilisez la fonction :
determiner la méthode de scheduling pour unprocessusgetscheduler()
donner une méthode de scheduling pour un processsetscheduler()

FIFO scheduling

Dans le principe du FIFO, un processus selectionné pour la CPU continue à s'executer jusqu'à ce que :

Le Process A s'execute jusqu'à ce qu'il bloque
Deux processus qui ont la même priorité peuvent utiliser le FIFO scheduling pour assurer une exclusion propre lors de l'utilisation d'une ressource partagée. Ces deux process ne pourront se deranger mutuellement par une action de préemption alors qu'il seront en train de s'executer.
Par exemple s'ils partagent une partie de la mémoire, chacun de ces deux processus pourrons mettre à jour la mémoire sans avoir à utiliser une quelconque forme de sémaphores.

Le scheduling Round-robin

Dans le principe du round-robin, un process selectionné pour la CPU jusqu'à ce que :



Le Process A s'execute jusqu'à ce qu'il ai utlisé son timeslice; Le prochain processus prêt (READY) -ici le process B- s'executera alors


Un timeslice est une unité de temps assignée à chaque processus. Une fois qu'il consume sont timeslice, un processus est préempté et le prochain processus READY avec la même priorité prend le controle. Un timeslice est de 50 milliseconds.
Mis à part le "time slicing" le principe du round-robin est identique à la méthode FIFO.

Le scheduling Adaptif

Dans le scheduling adaptif un processus se comporte ainsi :



le Process A consomme tout son timeslice; sa priorité se trouve alors réduite de 1 Le prochain processus READY (process B) s'execute

Vous pouvez utliser un scheduling adaptif dans les evironnement possédant un grand nombre de processus en fond. Cette mthode donne un accès suffisant au CPU tout en donnant souvent "la parole" aux autres processus. Le scheduling adaptif est la méthode par défaut employée avec les programmes dushell.

Les priorités influées (client-driven)

Dans QNX la plus part des échanges entre processus suivent le modèle du client/serveur.
Les serveurs fournissent des services et les clients envoient des messages à ces serveurs pour utiliser ces services. En général les serveurs sont plus actifs que les clients.
Les clients sont normallement plus nombreux que les serveurs. Ainsi un serveur tournera toujours avec une priorité superieure à celle de ses clients. La méthode de schedulling employée sera l'une des trois énnoncées ci-dessus, mais la round-robin est sans doute la plus utilisée.
Si un client avec une basse priorité envoit un message au serveur, alors sa requête sera prise en charge par la priorité superieure du serveur. Ce la à indirectement augmnté la priorité su client car celui ci à obligé le serveur à s'executer.
Aussi longtemps que le process tourne pour de petite période de temps cela ne pose pas de problèmes. Mais si le serveur tourne un bon moment, alors on pourra voir un client d'une basse priorité (à l'origine de l'appel du serveur) qui concurencera d'autres process d'une priorité superieure à lui mais inferieure à celle du serveur.
Pour résoudre ce probleme le serveur prendra la priorité de ses clients. Lorsqu'il recevra un message, sa priorité prendra alors la valeur de celle de son client. Il est à noter que seule la priorité change, la méthode de schedulling reste. Si un autre message arrive alors que le serveur s'execute, la priorité du serveur sera augmentée si la priorité du client emetteur est superieure à celle du serveur. Cela permet au serveur de pouvoir ensuite prendre la nouvelle requete que vient de faire le client emetteur. Si ce n'est pas possible, c'est la priorité du client qui sera abaissée.
Si vous selectionnez une gestion des priorités comme celle que nous venons de décrire pour votre serveur (client-driven priorities) vous pouvez demander à ce queles messages vous arrivent dans l'ordre de leur priorité (et non pas dans l'ordre de leur arrivée).
Pour utiliser le sytème client-driven vous devez utiliser les masques de la fonction qnx_pflags() ainsi :


 qnx_pflags(~0, _PPF_PRIORITY_FLOAT | _PPF_PRIORITY_REC, 0, 0);

2.9 Développement

Bien sûr les langage C et C++ sont les principales utilisés, mais d'autre langages peuvent être utilisé s'ils sont compilable sous QNX. En effet la plupart des applications venant du libre, de Linux, de BSD ou d'Unix, développés en C ou C++ sont recompilable sous QNX, moyennant peut-etre une retouche des sources.
Voici une liste d'exemples d'applications fonctionnant parfaitement sous QNX: bash, bison, yacc, apache, PHP, Netscape, Doom, Quake, Perl ...

2.10 A propos des performances temps réel

Contrairement a cequ'on voudrait, les ordinateurs ne sont pas infiniment rapide.
Dans un système temps reel, il est absolument crucial que les cycles du CPU ne soit pas dépensés n'importe comment.
Il est donc crucial que vous minimisiez le temps qui se passe entre l'apparition d'un événement jusqu'à son traitement par un programme.

Ce temps est appellé temps de latence.

Plusieurs forme de latence peuvent etre rencontrée dans le système QNX.

Latence d'interruption

Le temps de latence d'une interruption est le temps passé entre la reception d'une interruption materielle jusqu'à l'execution de la premiere instruction d'un manipulateur d'interruption.
Les temps de latence des interruptions sont tres insignifiants sous QNX. Mais certainnes sections de code critiques demandent à ce que les interruptions soient temporairement inhibées.

Le diagramme suivant montre le cas ou une interruption materielle materiel est gérée par un manipulateur.

Les Til (temps de lantence des interruptions) sur différents CPUs.
Les tableaux suivant montre les différents Til pour certains processeurs.
Til: Processeur:
3.3 microsec 166 MHz Pentium
4.4 microsec 100 MHz Pentium
5.6 microsec 100 MHz 486DX4
22.5 microsec 33 MHz 386EX

le temps de latence d'un scheduling.

Dans certains cas une interruption materielle doit demander l'execution d'un processus. Dans ce scenario, le manipulateur d'interruption va indiquer dans la valeur qu'il va retourner le proxy à valider. On appelle cela le temps de latence d'un scheduling.
C'est le tmeps passé entre la fin du manipulateur d'interruption et l'execution de la première intruction du processus driver. C'est en fait le tmeps neccessaire pour sauvegarder le contexte du processus alors en place auquel on ajoute le temps de restauration du processus driver. Bien que plus grand que le temps de lance d'interruption ce temps demeure tres faible.