En informatique, ioctl, raccourci signifiant input-output control (contrôle d'entrée-sortie), est un appel système pour des opérations d'entrée-sortie spécifiques à un périphérique qui ne peuvent être exécutées par un appel système classique. Il reçoit un paramètre spécifiant un code-requête à exécuter ; l'effet de cet appel dépend complètement du code-requête. Les codes-requêtes sont souvent spécifiques au périphérique. Par exemple, un pilote de CD-ROM qui souhaite éjecter un disque d'un lecteur doit fournir un code-requête à un ioctl pour faire cela. Des codes-requêtes indépendants du périphérique sont quelquefois utilisés pour donner un accès, depuis l'espace utilisateur, à des fonctions du noyau utilisées uniquement par le cœur du système ou encore en développement.

L'appel système ioctl est apparu pour la première fois dans la version 7 d'Unix sous ce nom. il est supporté par la plupart des systèmes Unix et apparentés, dont Linux et OS X, bien que les codes-requêtes proposés diffèrent d'un système d'exploitation à l'autre. Microsoft Windows fournit une fonction similaire, nommée DeviceIoControl, dans son API Win32.

Fonctionnement

modifier

Les systèmes d'exploitation conventionnels divisent la mémoire en 2 espaces : l'espace utilisateur et l'espace noyau. Le code d'une application telle qu'un éditeur de texte réside dans l'espace utilisateur, tandis que les fonctionnalités sous-jacentes du système d'exploitation, telles que la pile réseau, résident dans le noyau. Le code du noyau gère les ressources sensibles et met en œuvre les barrières de sécurité et de fiabilité entre les applications. Pour cette raison, les applications dans l'espace utilisateur ne peuvent pas accéder directement aux ressources du noyau.

Les applications en espace utilisateur font généralement des requêtes au noyau par l'intermédiaire des appels système, dont le code se trouve dans l'espace noyau. Un appel système prend généralement la forme d'un « vecteur d'appels système », dans lequel l'appel système désiré est indiqué par un numéro d'index. Par exemple, exit() peut être l'appel système numéro 1, et write() l'appel système numéro 4. Le vecteur d'appels système est utilisé pour trouver la fonction du noyau désirée pour la requête. C'est pourquoi les systèmes d'exploitation classiques offrent généralement plusieurs centaines d'appels système à l'espace utilisateur.

Bien qu'étant un moyen facile d'accéder aux fonctions de l'OS, les appels système peuvent ne pas être adaptés pour l'accès à des périphériques physiques non standard. Les périphériques ne sont adressables directement que depuis l'espace noyau. Mais souvent, le code en espace utilisateur a besoin d'accéder directement à un périphérique (par exemple, configurer le media type d'une interface Ethernet). Les OS modernes supportent un grand nombre de périphériques avec eux-mêmes un grand nombre de fonctions. Beaucoup de ces fonctions ne peuvent pas être prévues à l'avance et ne sont donc pas forcément implémentables par des appels système.

Pour résoudre ce problème, les noyaux sont conçus pour être extensibles et acceptent donc des modules appelés pilotes de périphérique qui s'exécutent en espace noyau et peuvent adresser directement des périphériques. Une interface de type ioctl se caractérise par un seul appel système par lequel le programme en espace utilisateur peut communiquer avec le périphérique. Comme vu plus haut, les requêtes vers le périphérique sont vectorisées à partir de cet appel système en utilisant typiquement un identifiant unique et un code de requête. Le noyau permet, par ce moyen, au programme utilisateur de communiquer avec le pilote de périphérique sans connaitre les fonctions de celui-ci et sans implémenter un nombre important d'appels système.

Exemples d'utilisations

modifier

Configuration de périphériques

modifier

L'utilisation la plus courante des ioctls concerne le contrôle des périphériques physiques. Par exemple, sur les systèmes d'exploitation Win32 (Windows), on peut communiquer avec les périphériques USB à l'aide d'ioctls. On peut aussi obtenir des informations sur la géométrie des disques durs.

Les OS de type Unix (Linux, FreeBSD, OS X) utilisent les ioctls pour configurer les interfaces réseau. Par exemple, sur les systèmes dérivés de BSD, on peut configurer le masque de sous-réseau en ouvrant une socket puis en utilisant l'ioctl SIOCSIFNETMASK sur celle-ci.

Terminaux

modifier

Une utilisation visible des ioctls est le contrôle des terminaux.

Les systèmes d'exploitation de type Unix ont traditionnellement comme interface un interpréteur de commandes. L'interpréteur de commandes Unix est basé sur des pseudo-terminaux (TTY) qui émulent les anciens terminaux physiques en mode texte comme des VT100. Ces pseudo-terminaux sont configurés comme s'ils étaient des périphériques logiques à l'aide d'ioctls. Par exemple, la taille de la fenêtre d'un pseudo-terminal peut être configurée en utilisant l'ioctl TIOCSWINSZ.

Extensions du noyau

modifier

Quand une application veut étendre les possibilités du noyau, pour accélérer les traitements réseau par exemple, les ioctls fournissent un pont entre le code en espace utilisateur et les extensions, ou modules. L'interface de ces modules est souvent constituée d'entrées dans le système de fichiers au travers desquelles un certain nombre d'ioctls peuvent être appliqués. Par exemple, sur le système d'exploitation Solaris, la couche de filtrage des paquets IP peut être configurée grâce aux ioctls de la famille SIOCIPF*.

Implémentations

modifier

Un ioctl Unix prend comme paramètres :

  1. un descripteur de fichier ouvert ;
  2. le code spécifique de la requête ;
  3. soit une valeur discrète (entier ou autre), soit un pointeur sur une structure de données vers le pilote de périphérique ou depuis le pilote de périphérique, ou bien les deux.

Le noyau transfère directement la requête au pilote de périphérique. Charge à celui-ci d'interpréter la requête. Les développeurs du pilote de périphérique fournissent la liste documentée des ioctls supportés par ce pilote sous forme de constantes dans un en-tête (fichier .h).

Les OS de type Unix, y compris Linux, suivent une convention qui consiste à encoder à l'intérieur de la requête :

  1. la taille des données à transférer ;
  2. la direction du transfert ;
  3. l'identité du pilote de périphérique.

Si le pilote de périphérique ne reconnait pas la requête, un code d'erreur unique est renvoyé (code ENOTTY).

Note : le code ENOTTY (dérivé du message « Not a typewriter ») vient du fait que dans les anciens systèmes seuls les télétypes (TTY) pouvaient renvoyer cette erreur. Bien qu'utilisé pour des raisons de compatibilité, les systèmes plus récents renvoient plutôt un message du type « Inappropriate device control operation ».

Par exemple, l'ioctl TCSETS est utilisé par les ports série. Le transfert de données est géré par les appels read() et write() alors que l'appel ioctl(fd, TCSETS, data) permet de contrôler divers états du port.

Un ioctl Win32 prend comme paramètres :

  1. un handle ouvert sur un objet (l'équivalent Win32 d'un descripteur de fichier) ;
  2. un code de requête (le control code) ;
  3. un tampon pour les paramètres d'entrée ;
  4. la taille du tampon d'entrée ;
  5. un tampon pour les paramètres de sortie ;
  6. la taille du tampon de sortie.
  7. une structure OVERLAPPED si le mode overlapped est utilisé.

Le code de requête prend en compte le mode de l'opération (impactant pour la sécurité du pilote) :

  1. METHOD_IN_DIRECT — l'adresse du tampon doit être autorisée en lecture pour l'appelant en mode utilisateur ;
  2. METHOD_OUT_DIRECT — l'adresse du tampon doit être autorisée en écriture pour l'appelant en mode utilisateur ;
  3. METHOD_NEITHER — l'adresse virtuelle du tampon est passé sans vérification ni mapping ;
  4. METHOD_BUFFERED — les tampons partagés du IO Manager sont utilisés pour transférer des données de et vers l'espace utilisateur.

Alternatives

modifier

Autres interfaces d'appel à base de vecteurs

modifier

Les périphériques et modules peuvent s'interfacer avec l'espace utilisateur grâce à des nouveaux appels système. Cependant, cette approche est rarement utilisée.

Sur des OS de type Unix, deux autres interfaces sont couramment utilisées :

  1. l'appel fcntl (file control) permet de configurer des fichiers préalablement ouverts (permet, par exemple, d'activer les I/O non bloquantes) ;
  2. l'appel setsockopt (set socket option) permet de configurer des sockets préalablement ouvertes (permet, par exemple, de configurer le pare-feu ipfw sur les systèmes BSD).

Mapping mémoire

modifier

Références

modifier