Où le système d’exploitation stocke-t-il argv et argc lorsqu’un processus enfant est exécuté?

J’ai quelques difficultés à comprendre comment le système d’exploitation transmet les données de l’espace d’adressage d’un processus parent à l’espace d’adressage d’un processus enfant. À savoir, dans un programme C, où argc et argv sont-ils stockés lorsqu’ils sont passés à main?

Je comprends comment argv est essentiellement un double pointeur. Ce que je ne comprends pas, c’est ce que le système d’exploitation fait avec ces valeurs après les avoir chargées dans le kernel. Après avoir créé un espace d’adressage pour le processus enfant, envoie-t-il ces valeurs dans la stack du nouvel espace? Nous ne souhaitons évidemment pas transmettre de pointeurs vers un autre espace d’adressage.

Pour mémoire, je travaille avec l’architecture MIPS32.

    Sous Linux, du moins sur les architectures avec lesquelles j’ai joué, le processus commence par %esp pointe vers quelque chose comme:

     argc | argv[0] | argv[1] | ... argv[argc - 1] | argv[argc] == NULL | envp[0] | envp[1] ... envp[?] == NULL 

    La première fonction appelée s’appelle traditionnellement _start et son travail consiste à calculer (argc = %esp, argv = ((char *)%esp) + 1, envp = ((char *)%esp) + argc + 2) , puis appelez main avec la convention d’appel appropriée.

    Sur x86, les arguments sont passés sur la stack.

    Sur amd64, ils sont passés dans les registres %rdi , %rsi et %rdx .

    Sur mips, Google me dit que différentes conventions d’appel sont en cours d’utilisation, notamment O32, N32 et N64, mais qu’elles utilisent d’abord $a0 , $a1 , $a2 .

    Le processus est différent pour différents systèmes d’exploitation et varie en fonction de la manière dont un nouveau processus est créé. Étant donné que je suis plus familier avec la façon dont les systèmes d’exploitation Microsoft modernes gèrent cela, je vais commencer par là et faire référence à nix à la fin.

    Lorsque le système d’exploitation [Microsoft] crée un processus, il alloue un bloc d’environnement de processus pour stocker les données spécifiques à ce processus. Cela inclut, entre autres choses, les arguments de ligne de commande avec lesquels le programme a été appelé. Ce bloc d’environnement de processus est alloué en dehors de l’espace d’adressage du processus cible et un pointeur s’y rapportant est fourni au point d’entrée du processus. Le bloc d’environnement de processus pour un processus enfant est généralement initialisé en copiant le bloc d’environnement du processus parent dans l’espace d’adressage du nouveau processus – il n’y a pas de partage direct de la mémoire impliqué.

    Dans le cas d’un programme basé sur C, le point d’entrée n’est pas la fonction main() fournie par le programmeur. Il s’agit plutôt d’une routine fournie par la bibliothèque d’exécution C qui est chargée d’initialiser l’environnement d’exécution avant de passer le contrôle à main() .

    Il y a beaucoup de choses à initialiser, mais l’un des aspects est la configuration des valeurs argc et argv . Pour ce faire, le moteur d’exécution consultera le bloc d’environnement de processus pour trouver le nom du programme et les parameters avec lesquels il a été appelé. Il va ensuite (généralement) allouer des valeurs pour argv en dehors du tas de processus (en utilisant quelque chose comme malloc() ), et affecter argc au nombre de parameters trouvés (plus un, pour le nom du programme).

    Les valeurs réelles de argc et argv sont placées dans la stack comme tous les autres parameters sont passés en C, car l’appel de main() par le runtime C n’est qu’un appel de fonction normal.

    Ainsi, lorsque le code que vous écrivez dans main() dans le processus enfant accède à argv, il lira les valeurs à partir de son propre tas de processus. La source de ces valeurs est le bloc d’environnement de processus (stocké par le système d’exploitation dans l’espace d’adressage local), qui a été initialisé à l’origine en copiant le bloc d’environnement de processus à partir du processus parent.

    Sur les plates-formes * nix, les choses sont assez différentes. La principale différence pour la présente discussion est que nix stockera les arguments de ligne de commande du nouveau processus directement dans l’espace de stack du thread initial du processus. (Il stocke également les variables d’environnement ici.) Ainsi, sur * nix, main est appelée avec le paramètre argv pointant vers des valeurs stockées dans la stack elle-même.

    Vous pouvez en trouver une partie dans la page de manuel pour execve , alors que l’ interface de programmation Linux de Michael Kerrisk a une bonne description, que vous trouverez peut-être en ligne dans l’extrait 6.4.