8 points par GN⁺ 2023-07-24 | 1 commentaires | Partager sur WhatsApp
  • Introduction d’un projet d’apprentissage personnel visant à comprendre jusqu’au bout ce qui se passe réellement quand on exécute un programme sur un ordinateur
  • Les questions centrales portent sur le fait de savoir si un programme s’exécute directement sur le CPU, sur le fonctionnement des appels système, et sur la manière dont plusieurs programmes s’exécutent en même temps
  • En dehors des cours universitaires, il était difficile de trouver des ressources système complètes, et il a fallu trier soi-même des informations contradictoires issues de sources de qualité inégale
  • Après plusieurs semaines de recherches et près de 40 pages de notes, l’auteur a mieux compris le processus qui mène du démarrage de l’ordinateur à l’exécution d’un programme
  • L’auteur estime que même les lecteurs qui pensent déjà connaître le sujet pourraient y apprendre quelque chose, et recommande de commencer par le chapitre 3 si l’on manque de temps

Point de départ pour comprendre l’exécution d’un programme

  • La question de ce qui se passe exactement quand on lance un programme sur un ordinateur est le point de départ de cet article
  • L’auteur avait déjà quelques connaissances bas niveau, mais avait du mal à relier les différents éléments en un seul déroulé cohérent
  • Les questions se divisent en trois axes
    • un programme s’exécute-t-il vraiment directement sur le CPU ?
    • que sont les appels système et comment fonctionnent-ils concrètement ?
    • comment plusieurs programmes peuvent-ils s’exécuter en même temps ?

Démarche de recherche et ordre de lecture

  • Faute de ressources système complètes en nombre suffisant, il a fallu enquêter à partir de plusieurs sources, dont la qualité et le contenu variaient
  • Grâce à plusieurs semaines de recherche et à près de 40 pages de notes, l’auteur a mieux compris le fonctionnement de l’ordinateur, du processus de démarrage jusqu’à l’exécution d’un programme
  • L’objectif est de rédiger soi-même l’explication solide et unifiée qu’on aurait voulu trouver
  • Pour les lecteurs qui pensent déjà connaître le sujet ou qui manquent de temps, il est recommandé de commencer par le chapitre 3
  • La suite mène à chapitre 1 : The “Basics”

1 commentaires

 
GN⁺ 2023-07-24
Avis de Hacker News
  • Je suis la personne qui a créé ça. Merci de l’avoir lu et, comme il se doit sur Hacker News, de m’avoir signalé plusieurs corrections. Putting the "You" in CPU est encore en plein chantier ; à l’origine, je pensais le peaufiner davantage, ajouter du contenu, puis le publier sur HN vers la semaine prochaine.
    J’ai 17 ans et, il y a un an, j’ai quitté le lycée pour commencer à travailler à plein temps chez Hack Club (https://hackclub.com/). Je programme depuis aussi loin que je me souvienne, et il y a environ 6 ans j’ai commencé l’école à la maison pour me concentrer davantage sur la programmation et mes autres centres d’intérêt.
    J’ai tout appris en autodidacte, donc je n’ai jamais suivi de cours universitaire sur les systèmes, et je n’étais pas satisfait de ma réponse à la question « que se passe-t-il quand on exécute quelque chose ? ». J’ai donc passé énormément de temps à apprendre aussi en profondeur que possible et, au passage, j’ai découvert que les systèmes d’exploitation et le matériel sont vraiment amusants à étudier, mais que les ressources en ligne sur le sujet sont souvent médiocres.
    En faisant des recherches, on tombe souvent sur des PDF de slides de cours de 2014, ou sur des réponses StackOverflow qui sont en réalité fausses ou trop simplifiées. J’ai donc écrit Putting the "You" in CPU dans l’espoir d’en faire une meilleure ressource pour les personnes qui veulent apprendre ce domaine par elles-mêmes. Ce n’est pas encore parfait, car je dois encore écrire quelques paragraphes sur le SMP, mais je pense que c’est largement meilleur que la plupart des ressources que j’ai vues. C’était aussi la première fois que je faisais des illustrations et des schémas ; ils s’améliorent au fil des chapitres, et je suis assez fier de quelques dessins de la fin.
    Le tout est disponible en open source sur GitHub : https://github.com/hackclub/putting-the-you-in-cpu

    • Ces derniers temps, je me fais souvent la réflexion que « ce genre de ressources en ligne est vraiment trop mauvais ». C’est particulièrement le cas quand je cherche des schémas-blocs détaillés du noyau Linux, le fonctionnement des tables de pages, des comparaisons d’ABI d’appel de fonctions selon les plateformes, des détails de protocoles réseau bas niveau, ou encore les numéros de PID Linux réservés.
      Il m’arrive de trouver des réponses dans des fils Reddit, mais il y a généralement du bruit autour, et je doute aussi que Reddit soit un endroit fiable pour stocker du savoir. StackOverflow est particulièrement peu utile, parce qu’il est surtout centré sur des questions visant à résoudre une tâche précise, ce qui rend inévitablement les réponses courtes.
      Pour les connaissances générales, je contourne désormais la recherche cassée de Google et je vais directement sur Wikipedia, mais Wikipedia a aussi ses limites quant à la profondeur avec laquelle il peut traiter les sujets techniques. Je me demande s’il ne faudrait pas un wiki qui rassemble de manière exhaustive les détails de l’architecture interne des ordinateurs. Il existe beaucoup de ressources dispersées, mais ce serait vraiment bien de les avoir au même endroit.
    • À peu près au même âge, je me posais des questions similaires, et j’ai trouvé beaucoup de réponses intéressantes dans le code source de Linux. Bien sûr, à l’époque c’était beaucoup plus simple. La communauté osdev et la documentation d’architecture système d’Intel ont été des déclencheurs importants, en particulier les parties 3a ou 3b, si ma mémoire est bonne, qui contenaient beaucoup de pépites.
      Cette documentation en dit beaucoup plus sur ce que le monde pourrait être que sur ce qu’il est aujourd’hui. En faisant de l’OSDEV, on apprend aussi plein de choses amusantes, comme la manière dont les processeurs 32 bits faisaient de l’adressage 36 bits avec PAE, ou comment les processeurs 64 bits faisaient de l’adressage 52 bits pour soutirer plus d’argent.
      On comprend aussi pourquoi un ordinateur doit faire des acrobaties en assembleur au niveau système pour passer du mode 16 bits jusqu’au long mode, ou pourquoi le mode 8 bits existe encore. Si apprendre le chargement de binaires t’a plu, il reste encore beaucoup de choses surprenantes. Par exemple, même en mode 64 bits, la segmentation mémoire existe encore, mais en long mode elle est forcée en mappage plat.
    • Excellent travail. C’est une bonne ressource pour débuter dans le développement de systèmes d’exploitation ; il y a beaucoup de détails, mais ça reste facile à assimiler. La fin de la note #2 du chapitre 2 mériterait une petite correction. Elle devrait refléter le fait que le paradigme asynchrone de certains langages de programmation relève du multitâche coopératif. À l’intérieur d’un processus, utiliser le multitâche coopératif est généralement considéré comme sûr, mais la question de savoir si c’est souhaitable est différente. À ma connaissance, Erlang conserve un modèle préemptif pour des raisons de robustesse.
  • J’aime ce genre de ressource. Je mets en ce moment un pied dans le monde RISC-V, et il paraît clair que les gens qui veulent faire avancer le logiciel libre et open source auront besoin de ce type de connaissances pour que les logiciels fonctionnent correctement sur les nombreux nouveaux ordinateurs monocarte qui arrivent.

    • À titre de remarque, l’encodage des instructions RISC-V fait partie des choses assez étranges à explorer, à l’exception peut-être de Thumb-2. Il y avait de bonnes raisons de le concevoir ainsi, mais si tu comptes lire des dumps hexadécimaux, RISC-V ne te fera pas de cadeau. Même s’il reste simple de la plupart des autres points de vue.
    • Si on a fait des études jusqu’à la licence ou au-delà, j’ai l’impression qu’un programmeur connaît généralement déjà ce genre de choses. Du moins si on n’a pas esquivé les cours d’architecture des ordinateurs et de systèmes d’exploitation.
      Dans mon cas, au fil de l’apprentissage du fonctionnement d’un ordinateur, il était courant de définir une architecture de jeu d’instructions, d’implémenter un simulateur et un assembleur, et d’écrire directement de l’assembleur. Les plus motivés allaient jusqu’à l’implémenter sur FPGA ou à écrire un backend LLVM.
      Côté systèmes d’exploitation, c’est pareil. Apprendre en implémentant soi-même un noyau, ou au minimum en modifiant un noyau existant, est un parcours assez courant.
  • Si vous voulez mieux comprendre comment fonctionne un ordinateur à un niveau très fondamental, je recommande vraiment le livre Code de Charles Petzold. Il part des premiers principes et monte jusqu’à un niveau assez avancé.

    • Si vous préférez apprendre en construisant par vous-même, Nandgame https://nandgame.com est aussi très bien.
  • L’une des choses que cette ressource fait bien est de mettre en évidence les attentes et les résultats réels de l’apprentissage au fil de la collecte d’informations. Du genre : « je m’attendais à ce que ce soit x à cause de y, mais en réalité c’était a à cause de b ! »
    Dissiper les malentendus est souvent une étape essentielle dans la formation de nouvelles connaissances, et il est utile que l’auteur reconnaisse que la personne qui lit doit dépasser ses propres idées fausses en traitant l’information. C’est beaucoup plus accueillant pour le lecteur qu’une attitude du type « voici la bonne réponse, et si tu pensais autrement, tu es idiot ».

  • Le chapitre 6 explique mal les valeurs de retour de fork(). Contrairement à ce qui est écrit, le parent doit recevoir le PID de l’enfant, et l’enfant doit recevoir 0. J’ai ouvert une pull request à ce sujet.

    • Merci beaucoup, c’était une erreur de ma part. J’ai corrigé l’exemple de code et je l’ai mergé.
  • Dans l’explication selon laquelle add eax, 512 est traduit en 05 00 02 00 00, le premier octet 05 est présenté comme l’opcode qui ajoute un nombre sur 16 bits au registre EAX, et les octets restants comme 512 (0x200) en little endian.
    Mais cette explication ne couvre que les 3 premiers octets de l’instruction de 5 octets. Je me demande ce que sont les 00 restants. Il voulait peut-être écrire que l’opcode ajoute un nombre sur 32 bits à EAX ?

    • Ce n’est pas « he » mais « she », et oui. Je pense qu’il fallait écrire 32 bits à cet endroit. Bien vu
    • Si on parle de x86, oui. Le registre eax a une largeur de 32 bits, et les 16 bits de poids faible du même registre s’appellent ax
  • Bon article. En tant que développeur web autodidacte, je trouve ce genre de ressource très précieux. Ça aide à sortir des différentes abstractions et à comprendre plus concrètement comment les choses fonctionnent réellement

  • Ça a l’air d’une bonne ressource, ça entre assez vite dans le vif du sujet, et c’est même un peu amusant. C’est moins du style « tutoriel sur les monades » que ce à quoi je m’attendais. Je compte continuer à lire

    • Ravi que ça t’ait plu. Si tu as des retours plus tard, n’hésite pas à me les faire parvenir
  • Bonne ressource. Il me reste toutefois une question à propos du noyau. Dans [0], il est expliqué que le shebang est traité par le noyau et lu depuis buf sans charger tout le fichier, ce qui fait qu’il est toujours tronqué à la longueur de buf.
    Il y a 4 ans, quelqu’un s’est agacé du fait que le noyau tronquait les chemins dépassant 128 caractères, et a doublé la taille du tampon, ce qui a doublé le point de troncature. Donc aujourd’hui, sous Linux, si une ligne shebang dépasse 256 caractères, tout ce qui suit les 256 caractères disparaît complètement.
    Je ne comprends pas pourquoi on tronquerait silencieusement le chemin de fichier de quelqu’un. Et le noyau, plus que tout autre composant, ne devrait pas faire ce genre de chose. Un chemin de fichier de 256 octets peut être absurde, mais une chaîne chemin+arguments de 256 octets finira presque certainement par exister un jour. Casser silencieusement des scripts, c’est une erreur.
    [0] : https://cpu.land/how-to-run-a-program

    • En tant qu’autrice, je suis tout à fait d’accord. On ne devrait pas avoir à comprendre la logique d’exécution du noyau pour comprendre les limites du shebang. C’est une conception stupide, et j’aurais probablement dû insister davantage sur ce point.
      Il y a quelques années, quelqu’un a tenté de patcher le noyau pour qu’un shebang trop long échoue au moins clairement, au lieu d’échouer silencieusement. Mais cela a posé problème sur NixOS. Fidèle à Nix, il utilisait déjà des shebangs excessivement longs avec beaucoup de longs chemins nix-store. Avant, ils étaient tronqués silencieusement mais fonctionnaient quand même ; soudain, tous ces scripts se sont mis à échouer, ce qui cassait la rétrocompatibilité. Le patch a donc dû être reverté, et aucun changement similaire n’a pu être introduit depuis.
      Pour plus de détails, voir https://lwn.net/Articles/779997/
    • Il ne faut pas mettre d’arguments complexes dans un shebang. Le shebang sert à indiquer l’emplacement de l’interpréteur du fichier courant.
      Le shebang ne gère même pas correctement plus d’un argument tout seul. Si vous écrivez #!/bin/program -args somescript, ce sera découpé en ['/bin/program','-args somescript'], ce qui n’est presque jamais le résultat souhaité. C’est spécifique à Linux, et les shebangs ne sont pas non plus universellement portables sur tous les Unix.
      Si vous voulez faire quelque chose de complexe, mieux vaut mettre #!/bin/sh, puis faire un exec depuis le fichier courant avec les arguments nécessaires.
      Un point manquant dans le lien est qu’on peut utiliser env pour trouver l’interpréteur dans le chemin courant. Par exemple, #!/usr/bin/env python3 permet de trouver le python3 utilisé par l’utilisateur sans connaître son emplacement.
      Cela peut être utile pour les scripts exécutés dans des environnements virtuels, qui fonctionnent en remplaçant le PATH vers l’interpréteur Python. En revanche, il ne faut pas faire ça pour les programmes installés. Un programme installé ne doit pas laisser l’utilisateur contrôler quel interpréteur il utilisera ; il faut donc utiliser un chemin absolu.
      env dispose d’une option -S qui permet d’analyser une ligne de commande à partir d’une chaîne unique pour contourner cette limite. Cela dit, après des années à éviter les bricolages dans les lignes shebang, ça me paraît au mieux une pratique douteuse
    • Ce qui me surprend davantage, c’est que le tampon n’ait pas été défini à la même taille que la taille de bloc du disque. C’est généralement 512 octets, mais pas toujours