J'essaie de comprendre les détails inférieurs des serveurs Web. Je me demande si un serveur, par exemple Apache, recherche continuellement de nouvelles demandes ou s’il fonctionne avec une sorte de système d’interruption. S'il s'agit d'une interruption, qu'est-ce qui déclenche l'interruption, s'agit-il du pilote de la carte réseau?
La réponse courte est: une sorte de système d'interruption. Ils utilisent essentiellement des E/S bloquantes, ce qui signifie qu'ils dorment (bloquent) en attendant de nouvelles données.
Le serveur crée un socket en écoute, puis se bloque en attendant de nouvelles connexions. Pendant ce temps, le noyau place le processus dans un état de veille interruptible et exécute d'autres processus. C’est un point important: avoir l’enquête de processus continuellement gaspillerait la CPU. Le noyau peut utiliser les ressources système plus efficacement en bloquant le processus jusqu'à ce qu'il y ait du travail à faire.
Lorsque de nouvelles données arrivent sur le réseau, la carte réseau émet une interruption.
Voyant qu'il y a une interruption de la carte réseau, le noyau, via le pilote de carte réseau, lit les nouvelles données de la carte réseau et les stocke en mémoire. (Cela doit être fait rapidement et est généralement géré à l'intérieur du gestionnaire d'interruptions.)
Le noyau traite les données nouvellement arrivées et les associe à un socket. Un processus bloquant sur ce socket sera marqué comme exécutable, ce qui signifie qu'il est maintenant éligible pour être exécuté. Il ne s'exécute pas nécessairement immédiatement (le noyau peut décider de lancer d'autres processus).
À son aise, le noyau réveillera le processus de serveur Web bloqué. (Depuis, il est maintenant exécutable.)
Le processus du serveur Web continue de s'exécuter comme si aucun temps ne s'était écoulé. Son appel système bloquant est renvoyé et toutes les nouvelles données sont traitées. Ensuite ... allez à l'étape 1.
Il y a beaucoup de détails "inférieurs".
Premièrement, considérons que le noyau a une liste de processus et qu’à un moment donné, certains de ces processus sont en cours d’exécution et d’autres pas. Le noyau accorde à chaque processus en cours une tranche de temps CPU, puis l'interrompt et passe au suivant. S'il n'y a pas de processus exécutables, le noyau émettra probablement une instruction du type HLT à la CPU qui suspend la CPU jusqu'à ce qu'une interruption matérielle se produise.
Quelque part sur le serveur se trouve un appel système qui indique "donne-moi quelque chose à faire". Il existe deux grandes catégories de façons de procéder. Dans le cas d’Apache, il appelle accept
sur un socket déjà ouvert par Apache, probablement sur le port 80. Le noyau conserve une file d’attentes de connexion et s’ajoute à cette file chaque fois TCP SYN est reçu. La façon dont le noyau sait que TCP SYN a été reçu dépend du pilote de périphérique. pour de nombreuses cartes réseau, il y a probablement une interruption matérielle lorsque les données du réseau sont reçues.
accept
demande au noyau de me renvoyer la prochaine connexion initiée. Si la file d'attente n'était pas vide, alors accept
revient immédiatement. Si la file d'attente est vide, le processus (Apache) est supprimé de la liste des processus en cours d'exécution. Lorsqu'une connexion est établie ultérieurement, le processus est repris. Cela s'appelle "bloquer", parce que, pour le processus qui l'appelle, accept()
ressemble à une fonction qui ne retourne pas avant d'avoir un résultat, ce qui pourrait être dans un certain temps. Pendant ce temps, le processus ne peut rien faire d’autre.
Une fois que accept
est revenu, Apache sait que quelqu'un tente d'établir une connexion. Il appelle ensuite fork pour scinder le processus Apache en deux processus identiques. L'un de ces processus traite ensuite la demande HTTP, l'autre appelle à nouveau accept
pour obtenir la prochaine connexion. Ainsi, il y a toujours un processus maître qui ne fait qu'appeler accept
et générer des sous-processus, puis il y a un sous-processus pour chaque demande.
Ceci est une simplification: il est possible de faire cela avec des threads au lieu de processus, et il est également possible de fork
à l'avance afin qu'un processus de travail soit prêt à démarrer lorsqu'une demande est reçue, réduisant ainsi le temps système nécessaire au démarrage. En fonction de la configuration d’Apache, il est possible qu’il opère de la sorte.
C'est la première grande catégorie de la façon de le faire, et cela s'appelle bloquer IO parce que les appels système comme accept
et read
et write
qui fonctionnent sur des sockets vont suspendre le processus jusqu'à ce qu'ils aient quelque chose revenir.
L’autre façon générale de le faire est appelée E/S asynchrone non bloquante ou événementielle. Ceci est implémenté avec des appels système tels que select
ou epoll
. Ils font chacun la même chose: vous leur donnez une liste de sockets (ou en général, des descripteurs de fichier) et ce que vous voulez en faire, et le noyau se bloque jusqu'à ce qu'il soit prêt à faire l'une de ces choses.
Avec ce modèle, vous pouvez indiquer au noyau (avec epoll
): "Avertissez-moi quand une nouvelle connexion sur le port 80 ou de nouvelles données seront lues sur l'une de ces 9471 autres connexions que j'ai ouvertes". epoll
bloque jusqu'à ce que l'une de ces choses soit prête, puis vous le faites. Ensuite, vous répétez. Les appels système tels que accept
et read
et write
ne bloquent jamais, en partie parce que, chaque fois que vous les appelez, epoll
vient de vous dire qu'ils sont prêts, il n'y a donc aucune raison de bloquer, mais également lorsque vous ouvrez le socket ou le fichier que vous spécifiez. que vous les vouliez en mode non bloquant, donc ces appels échoueront avec EWOULDBLOCK
au lieu de bloquer.
L'avantage de ce modèle est que vous n'avez besoin que d'un seul processus. Cela signifie que vous n'avez pas besoin d'allouer une pile et des structures de noyau pour chaque requête. Nginx et HAProxy utilisent ce modèle, et c'est l'une des principales raisons pour lesquelles ils peuvent gérer autant de connexions qu'Apache sur un matériel similaire.