notes / Programmation système
Création d’un shell Unix minimal en C
Notes sur un petit shell de type Unix en C pour comprendre la création de processus, l’analyse des commandes, la recherche PATH, l’environnement et Linux de bas niveau.
Pourquoi cette note existe
Cette note documente le processus de construction d'un shell minimal Unix en C.
Le projet est précieux parce qu'un shell se trouve près du système d'exploitation. Même une petite version vous oblige à comprendre comment Linux démarre les programmes, passe les arguments, gère les variables d'environnement, attend les processus enfant et signale les échecs.
Le but n'était pas de remplacer Bash ou Zsh.
Le but était de comprendre les mécanismes de niveau inférieur derrière une commande comme:
ls -la
Un utilisateur normal voit une commande. Un shell doit :
read input
split arguments
find the executable
create a child process
run the command
wait for it
return to the prompt
Cela en fait une note de programmation de systèmes utile.
Contexte du projet
Le shell a été construit en C comme un projet fondamental axé sur le comportement du processus Linux.
Il fait partie d'un portefeuille comme note technique de niveau inférieur, pas comme un projet d'entreprise/client.
La valeur est de montrer la compréhension de:
- C programmation
- Création de processus Unix
- analyse de commande
- recherche exécutable
- variables environnementales
- Gestion des erreurs
- répartition de la mémoire
- cas bord
- simples programmes interactifs
Ce projet est différent des notes d'infrastructure comme WireGuard, OpenWrt, ou l'hébergement de serveur de jeux. Il montre le côté système d'exploitation de la pile.
Ce que ce projet veut prouver
- un shell est un gestionnaire de processus, et pas seulement une invite de texte
- L'exécution de la commande Linux dépend de
fork,execetwait - arguments doivent être analysés avant l'exécution
- commandes sans
/besoin de résolution PATH - les commandes intégrées doivent être gérées par le shell lui-même
- la mémoire doit être attribuée et libérée avec soin
- les messages d'erreur devraient être prévisibles
- les petits programmes C exigent une discipline parce qu'il n'y a pas de filet de sécurité
Pioche et outils utilisés
Langue et temps d'exécution
- C
- Linux
- Appels système de type POSIX
- GCC
- bibliothèque standard C
Appels et fonctions système
forkexecvewait/waitpidgetlinemallocfreestrtokou tokenisation manuelleaccessstat- gestion variable de l'environnement
Concepts Shell
- boucle rapide
- analyse de commande
- vecteur d'argument
- Recherche PATH
- exécution de processus pour enfants
- commandes intégrées
- État de sortie
- Gestion des erreurs
Construction prévue
La construction prévue est un shell minimal qui peut:
- afficher une invite
- lire la saisie utilisateur
- analyse une commande en arguments
- exécuter des binaires
- Recherche par PATH
- gérer les erreurs de commande
- soutien intégré de base
- sortie proprement
- éviter les fuites de mémoire évidentes
- travail en mode interactif et non interactif
Un shell minimal n'a pas besoin de fonctionnalités avancées au début.
Elle n'a pas besoin de soutenir:
- tuyaux
- redirection
- contrôle de l'emploi
- historique des commandes
- fin de l'onglet
- syntaxe de script
- alias
- citation avancée
Ce sont des couches plus tard.
Loop Shell de base
La boucle du noyau est simple dans le concept:
while shell is running:
display prompt
read line
parse line into arguments
check if command is built-in
if built-in, run inside shell
else fork child process
child executes command
parent waits
Cette boucle est au cœur du programme.
Le défi n'est pas d'écrire la boucle une fois. Le défi est de gérer tous les cas bizarres autour de lui.
Entrée de lecture
Un shell doit lire des lignes complètes à partir de l'entrée standard.
L'utilisation de getline est pratique car elle peut gérer l'entrée de longueur variable.
La coque doit gérer:
- Entrée normale
- lignes vides
- fin de fichier
- entrée non interactive
- défaillance de l'allocation mémoire
Exemple de comportement :
$ ls
$
$ exit
Si l'utilisateur appuie sur Ctrl+D, le shell devrait sortir proprement plutôt que s'écraser.
Parsing Commands
Entrée comme & #160;:
ls -la /tmp
doit devenir un vecteur d'arguments:
argv[0] = "ls"
argv[1] = "-la"
argv[2] = "/tmp"
argv[3] = NULL
Le NULL final compte parce que execve attend un tableau d'arguments null-terminé.
Un tokenizer simple peut se diviser sur les espaces et les onglets.
L'analyse plus avancée supporte les citations et l'évasion, mais un shell minimal peut commencer par des règles plus simples.
Exécution des commandes
Si la commande contient un chemin :
/bin/ls
le shell peut essayer de l'exécuter directement.
Si la commande ne contient pas de slash :
ls
le shell doit chercher dans PATH.
Cela signifie :
read PATH environment variable
split PATH by :
join each directory with command name
check if executable exists
run the first match
Exemple :
/usr/local/bin
/usr/bin
/bin
La coque vérifie :
/usr/local/bin/ls
/usr/bin/ls
/bin/ls
jusqu'à ce qu'il trouve un exécutable valide.
Fourche, Exec, attendez
Le modèle de processus de base Unix est:
fork creates a child process
exec replaces the child process image with the target program
wait lets the parent wait for the child to finish
Le shell parent ne devrait pas devenir la commande.
Au lieu de :
parent shell
↓ fork
child process
↓ execve("/bin/ls", argv, envp)
parent shell
↓ wait
prompt returns
C'est pourquoi exec est habituellement appelé dans le processus de l'enfant, pas le parent.
Si le parent appelle directement exec, le shell disparaît et devient la commande.
Commandes intégrées
Certaines commandes ne peuvent pas être gérées en exécutant simplement un autre exécutable.
Exemples:
cd
exit
env
cd doit changer le répertoire de travail du processus shell lui-même.
Si cd fonctionne uniquement à l'intérieur d'un processus enfant, l'enfant change de répertoire et sort, mais le shell parent reste dans l'ancien répertoire.
C'est pourquoi les éléments intégrés sont manipulés avant fork.
Un débit minimal intégré:
if command is "exit":
clean up and quit
if command is "cd":
call chdir in parent shell
if command is "env":
print environment variables
Résolution PATH
La résolution PATH est l'un des premiers bugs apparaissent.
La coque doit être manipulée:
- PATH manquant
- Entrées PATH vides
- commande avec chemin absolu
- commande avec chemin relatif
- exécutable existe mais la permission est refusée
- commande non trouvée
- répertoires confondus avec les commandes
Un shell fiable ne devrait pas supposer que chaque commande vit dans /bin.
Il devrait utiliser l'environnement.
Gestion de l'environnement
Un shell transmet les variables d'environnement aux processus enfant.
Par exemple, les commandes dépendent souvent de :
PATH
HOME
USER
PWD
SHELL
Un shell minimal peut utiliser l'environnement hérité et le transmettre à execve.
Si le shell prend en charge la modification des variables d'environnement plus tard, cela devient une fonctionnalité plus grande.
Au minimum, le shell doit comprendre que l'environnement fait partie de l'exécution de commande.
Gestion des erreurs
Les erreurs doivent être claires et cohérentes.
Erreurs courantes & #160;:
command not found
permission denied
no such file or directory
fork failed
malloc failed
execve failed
Un mauvais obus s'écrase dessus.
Un meilleur shell signale l'erreur et retourne à l'invite si possible.
Exemple de comportement :
$ unknowncmd
unknowncmd: not found
$
L'obus devrait rester en vie après un échec à moins que l'échec ne soit fatal.
État de sortie
Les vrais obus suivent l'état de sortie.
Une version minimale peut commencer en attendant le processus de l'enfant et en lisant son statut.
Cas utiles:
- la commande sort normalement
- sorties de commande avec statut non-zéro
- le commandement est tué par un signal
execveéchoue
Même si le shell n'expose pas $?, comprendre l'état de sortie est important.
Gestion de la mémoire
C fait de la gestion de la mémoire une partie du projet.
Le shell peut attribuer la mémoire pour:
- ligne d'entrée
- tableau token
- chaînes copiées
- Répertoires PATH
- chemin de commande complet
- tampons temporaires
Chaque allocation a besoin d'un chemin de nettoyage.
Un bug shell commun fuit la mémoire à chaque boucle.
Une règle plus sûre :
allocate for one command
execute command
free command resources
return to prompt
Les shells à long terme rendent les fuites visibles parce que le processus ne sort pas après chaque commande.
Mode interactif et mode non interactif
Un shell peut fonctionner de manière interactive :
./shell
$ ls
$ exit
ou non interactifs:
echo "ls" | ./shell
Le mode interactif affiche habituellement une invite.
Le mode non-interactif ne devrait souvent pas être rapide, selon les besoins.
Cette distinction est importante pour les tests.
Liste de vérification
Entrée de base
- ligne vide
- espaces réservés
- une commande
- commande avec arguments
- Ctrl+D
exit
Exécution des commandes
/bin/lslspwd- commande invalide
- commande avec chemin relatif
- commande sans autorisation d'exécution
Gestion du PATH
- PATH normal
- PATH manquant
- PATH vide
- commande dans
/bin - commande dans
/usr/bin - répertoire invalide dans PATH
Éléments intégrés
exitenvcdcdsans argument si supporté- cible
cdnon valide
Gestion des erreurs
- direction de défaillance du malloc
- direction de rupture de fourche
- défaillance exec
- autorisation refusée
- fichier non trouvé
- répertoire passé comme commande
Mémoire
- commandes répétées
- longue session
- Valgrind vérifier si disponible
- nettoyage après erreurs
- nettoyage avant sortie
Bogues courantes
Shell disparaît après avoir exécuté la commande
Cause:
exec was called in parent instead of child
Correction :
fork first, exec only in child
cd ne fonctionne pas
Cause:
cd was executed in a child process
Correction :
handle cd as a built-in in the parent shell
Commande fonctionne avec /bin/ls mais pas ls
Cause:
PATH lookup not implemented
Correction :
search directories listed in PATH
Défaut de segmentation sur entrée vide
Cause:
parser assumes argv[0] exists
Correction :
check for empty command before execution
La mémoire grandit pour toujours
Cause:
allocated command data not freed after each loop
Correction :
free line/token/path buffers consistently
Message d'erreur incorrect
Cause:
all failures are treated as command not found
Correction :
distinguish not found, permission denied, and execution failure
Décisions pratiques
Commencez par un petit ensemble de fonctionnalités
Un shell minimal devrait exécuter les commandes de manière fiable avant d'ajouter des pipes, des redirections ou de l'historique.
Poignée intégrée séparément
Des commandes comme cd et exit appartiennent au processus shell lui-même.
Traiter la recherche PATH comme une véritable fonctionnalité
La plupart des commandes types utilisateurs ne sont pas des chemins absolus.
Continuez à analyser en premier
L'analyse basique de l'espace blanc suffit pour une première version.
Vérifier chaque allocation
En C, le défaut d'attribution doit être pris en considération.
Mémoire gratuite par commande
Une coquille est de longue durée, de petites fuites se répètent pour toujours.
Préférez des erreurs claires
Une petite coquille devrait encore expliquer ce qui a échoué.
Ce qu'une version terminée devrait montrer
Un shell minimal fortement fini devrait montrer:
- prompt interactif
- commande boucle de lecture
- analyse des arguments
- exécution directe du chemin
- Recherche PATH
fork/execve/waitexitintégréenvintégrécdintégré si implémenté- Gestion propre des erreurs
- nettoyage de la mémoire
- support d'entrée non interactif
- README avec des exemples
- essais ou cas d'essais documentés
Preuves à retenir
Voici quelques éléments de preuve utiles à cette note :
- shell prompt screenshot
- exécutant
/bin/ls - exécuter
lsà travers la recherche PATH - sortie de commande invalide
- comportement
cd - Essai non interactif
- arbre source
- extrait de code de boucle principal
- Extrait de code de recherche PATH
- Sortie Valgrind si disponible
- Section d'utilisation du README
Hypothèses techniques
Cette note suppose un environnement semblable à Linux/POSIX.
Il suppose que le shell est écrit en C et se concentre sur les fondamentaux, pas la pleine compatibilité Bash.
Il suppose également que le projet vise à démontrer la gestion des processus et la discipline de programmation de bas niveau.
Principaux risques
- appelant
execdans le processus parent - ne pas gérer l'entrée vide
argvn'a pas de valeur nulle- fuites de mémoire dans la boucle de commande
- Mauvaise manipulation du PATH
- confusion entre les intégrations et les commandes externes
- mauvais environnement passé à l'enfant processus
- ignorant les erreurs de fork/exec
- s'écraser sur Ctrl+D
- essayant d'implémenter des fonctionnalités shell avancées trop tôt
État actuel
Cette note représente un projet de programmation de systèmes axé sur la compréhension du fonctionnement interne des shells Unix.
Il n'est pas aussi orienté vers l'entreprise que l'ERP, le VPN ou l'outil de déploiement, mais il ajoute une couche inférieure utile au portefeuille.
Il montre que la base technique n'est pas seulement les applications web et la configuration du serveur, mais aussi le comportement Linux au niveau du processus.
Ce que la présente note ne prétend pas
La présente note ne prétend pas remplacer Bash, Zsh, Fish ou d'autres coquilles réelles.
Il ne prétend pas supporter la syntaxe complète du shell.
Il ne prétend pas inclure toutes les fonctionnalités avancées comme les pipes, la redirection, les tâches, les signaux, les alias ou les scripts.
Il documente un shell minimal C utilisé pour comprendre la mécanique de processus de base Unix.
À emporter pratique
La leçon utile est:
Un shell est une boucle qui transforme le texte en processus.
Pour construire même un petit, vous devez comprendre:
- lecture des entrées
- analyse des jetons
- vecteurs d'arguments
- Recherche PATH
- processus pour enfants
- remplacement exécutable
- Attendre
- intégrés
- erreurs
- nettoyage de la mémoire
Cela fait du projet un élément fondamental important lorsqu'il est conçu comme une programmation de systèmes, et non comme un exercice de formation générique.