Comment fonctionnent les appels système?

Je comprends qu’un utilisateur peut posséder un processus et que chaque processus possède un espace adresse (qui contient des emplacements de mémoire valides, ce processus peut faire référence). Je sais qu’un processus peut appeler un appel système et lui transmettre des parameters, comme toute autre fonction de bibliothèque. Cela semble suggérer que tous les appels système se trouvent dans un espace d’adressage de processus en partageant la mémoire, etc. Mais peut-être que ceci n’est qu’une illusion créée par le fait que dans un langage de programmation de haut niveau, les appels système l’appelle.

Mais maintenant, laissez-moi aller plus loin et parsingr plus en détail ce qui se passe sous le capot. Comment le compilateur comstack-t-il un appel système? Il pousse peut-être le nom de l’appel système et les parameters fournis par le processus dans une stack et place ensuite l’instruction d’assemblage “TRAP” ou quelque chose – essentiellement l’instruction d’assemblage pour appeler une interruption logicielle.

Cette instruction d’assemblage TRAP est exécutée par le matériel en basculant tout d’abord le bit de mode d’utilisateur à kernel, puis en définissant le pointeur de code sur le début des routines de service d’interruption. À partir de ce moment, l’ISR s’exécute en mode kernel, qui récupère les parameters de la stack (cela est possible car le kernel a access à n’importe quel emplacement de mémoire, même ceux appartenant aux processus utilisateur) et exécute l’appel système et le end abandonne le CPU, qui bascule à nouveau le bit de mode et le processus utilisateur commence à partir de là où il s’est arrêté.

Est-ce que ma compréhension est correcte?

Ci-joint un diagramme approximatif de ma compréhension: entrer la description de l'image ici

Votre compréhension est assez proche; L’astuce est que la plupart des compilateurs n’écriront jamais d’appels système, car les fonctions getpid(2) par les programmes (par exemple, getpid(2) , chdir(2) , etc.) sont réellement fournies par la bibliothèque C standard. La bibliothèque C standard contient le code de l’appel système, qu’il soit appelé via INT 0x80 ou SYSENTER . Ce serait un programme étrange qui fait des appels système sans bibliothèque. (Même si perl fournit une fonction syscall() qui permet de passer directement des appels système! C’est fou, non?)

Ensuite, la mémoire. Le kernel du système d’exploitation dispose parfois d’un access facile à l’espace d’adressage à la mémoire du processus utilisateur. Bien entendu, les modes de protection sont différents et les données fournies par l’utilisateur doivent être copiées dans l’espace d’adressage protégé du kernel pour empêcher la modification des données fournies par l’utilisateur lorsque l’appel système est en cours :

 static int do_getname(const char __user *filename, char *page) { int retval; unsigned long len = PATH_MAX; if (!segment_eq(get_fs(), KERNEL_DS)) { if ((unsigned long) filename >= TASK_SIZE) return -EFAULT; if (TASK_SIZE - (unsigned long) filename < PATH_MAX) len = TASK_SIZE - (unsigned long) filename; } retval = strncpy_from_user(page, filename, len); if (retval > 0) { if (retval < len) return 0; return -ENAMETOOLONG; } else if (!retval) retval = -ENOENT; return retval; } 

Bien qu'il ne s'agisse pas d'un appel système, cette fonction est appelée par les fonctions d'appel du système qui copient les noms de fichiers dans l'espace d'adressage du kernel. Il vérifie que l'intégralité du nom de fichier réside dans la plage de données de l'utilisateur, appelle une fonction qui copie la chaîne depuis l'espace utilisateur et effectue certaines vérifications avant de retourner.

get_fs() et des fonctions similaires sont des rests des racines x86 de Linux. Les fonctions ont des implémentations de travail pour toutes les architectures, mais les noms restnt archaïques.

Tout le travail supplémentaire avec les segments est dû au fait que le kernel et l'espace utilisateur peuvent partager une partie de l'espace d'adressage disponible. Sur une plate-forme 32 bits (où les chiffres sont faciles à comprendre), le kernel aura généralement un gigaoctet d'espace d'adressage virtuel et les processus utilisateur auront généralement trois gigaoctets d'espace d'adressage virtuel.

Lorsqu'un processus appelle le kernel, le kernel «corrige» les permissions de la table de page pour lui permettre d'accéder à toute la plage et bénéficie des entrées TLB préremplies pour la mémoire fournie par l'utilisateur. Grand succès. Mais lorsque le kernel doit revenir en contexte à l'espace utilisateur, il doit vider le TLB pour supprimer les privilèges mis en cache sur les pages d'espace d'adressage du kernel.

Mais le truc, un gigaoctet d'espace d'adressage virtuel n'est pas suffisant pour toutes les structures de données du kernel sur des machines énormes. La gestion des métadonnées des systèmes de fichiers en cache et des pilotes de périphériques en mode bloc, des stacks réseau et des mappages de mémoire pour tous les processus du système peut nécessiter une grande quantité de données.

Il existe donc différents «splits»: deux concerts pour l'utilisateur, deux concerts pour le kernel, un concert pour l'utilisateur, trois concerts pour le kernel, etc. Au fur et à mesure que l'espace du kernel augmente, l'espace pour les processus utilisateur est réduit. Il y a donc un partage de mémoire 4:4 qui donne quatre gigaoctets au processus utilisateur, quatre gigaoctets au kernel, et le kernel doit gérer les descripteurs de segment pour pouvoir accéder à la mémoire utilisateur. Le TLB est purgé en entrant et en sortant des appels système, ce qui constitue une pénalité de vitesse assez importante. Mais cela permet au kernel de conserver des structures de données beaucoup plus importantes.

Les tables de pages et les plages d'adresses beaucoup plus grandes des plates-formes 64 bits rendent probablement tout l'aspect précédent plus pittoresque. J'espère bien, en tout cas.

Oui, vous avez tout à fait raison. Un détail cependant, lorsque le compilateur comstack un appel système, utilisera le numéro de l’appel système plutôt que le nom . Par exemple, voici une liste des appels système Linux (pour une ancienne version, mais le concept est toujours le même).

Vous appelez en fait la bibliothèque d’exécution C. Ce n’est pas le compilateur qui insère TRAP, c’est la bibliothèque C qui encapsule TRAP dans un appel de bibliothèque. Le rest de votre compréhension est correct.

Si vous souhaitez effectuer un appel système directement depuis votre programme, vous pouvez le faire facilement. Cela dépend de la plate-forme, mais supposons que vous vouliez lire un fichier. Chaque appel système a un numéro. Dans ce cas, vous placez le numéro de l’ read_from_file système read_from_file dans le registre EAX. Les arguments de l’appel système sont placés dans des registres différents ou dans la stack (en fonction de l’appel système). Une fois que les registres sont remplis avec les données correctes et que vous êtes prêt à exécuter l’appel système, vous exécutez l’instruction INT 0x80 (dépend de l’architecture). Cette instruction est une interruption qui entraîne le contrôle du système d’exploitation. Le système d’exploitation identifie ensuite le numéro d’appel du système dans le registre EAX, agit en conséquence et redonne le contrôle au processus effectuant l’appel système.

La façon dont les appels système sont utilisés est susceptible de changer et dépend de la plate-forme donnée. En utilisant des bibliothèques qui fournissent des interfaces simples à ces appels système, vous rendrez vos programmes plus indépendants de la plate-forme et votre code sera beaucoup plus lisible et plus rapide à écrire. Envisagez d’implémenter des appels système directement dans un langage de haut niveau. Vous aurez besoin de quelque chose comme un assemblage en ligne pour vous assurer que les données sont placées dans les bons registres.

Les programmes normaux ne comstacknt généralement pas les appels système. Pour chaque syscall, vous avez généralement une fonction de bibliothèque correspondante dans l’espace utilisateur (généralement implémentée dans libc sur les systèmes de type Unix). Par exemple, la fonction mkdir() transfère ses arguments à l’appel système mkdir .

Sur les systèmes GNU (je suppose que c’est la même chose pour les autres), une fonction syscall() est utilisée à partir de la fonction ‘mkdir ()’. Les fonctions / macros syscall sont généralement implémentées dans C. Par exemple, regardez INTERNAL_SYSCALL dans sysdeps/unix/sysv/linux/i386/sysdep.h ou syscall dans sysdeps/unix/sysv/linux/i386/sysdep.S (glibc ).

Maintenant, si vous regardez sysdeps/unix/sysv/linux/i386/sysdep.h , vous pouvez voir que l’appel au kernel est effectué par ENTER_KERNEL qui ENTER_KERNEL historiquement l’interruption 0x80 dans les processeurs i386. Maintenant, il appelle une fonction (je suppose qu’il est implémenté dans linux-gate.so qui est un fichier SO virtuel mappé par le kernel, il contient le moyen le plus efficace de faire un appel système pour votre type un CPU).

Oui, votre compréhension absolument correcte, un programme C peut appeler un appel système direct, lorsque cet appel système se produit, il peut s’agir d’une série d’appels jusqu’à l’assemblage. Je pense immensément que votre compréhension peut aider un débutant. Consultez ce code dans lequel j’appelle l’appel système “système”.

 #include < stdio.h > #include < stdlib.h > int main() { printf("Running ps with "system" system call "); system("ps ax"); printf("Done.\n"); exit(0); }