Cette note technique explore les structures de données et les flux d'exécution liés aux signaux dans le noyau Linux version 2.6.18, en mettant l'accent sur les appels système et la gestion interne.
Structures de données fondamentales
Les signaux dans Linux sont gérés à travers plusieurs structures clés intégrées dans le descripteur de processus task_struct, défini dans include/linux/sched.h.
Le champ pending de type struct sigpending stocke les signaux temps réel en attente pour le processus. Cette structure, localisée dans include/linux/signal.h, contient une liste chaînée list pointant vers des éléments sigqueue et un bitmap signal pour indiquer les signaux en attente.
struct attente_signaux {
struct liste_chainee liste;
ensemble_signaux signal;
};
Chaque sigqueue encapsule des informations détaillées sur un signal spécifique, dont une structure siginfo_t définie dans include/asm-generic/siginfo.h.
struct file_signaux {
struct liste_chainee noeuds;
int indicateurs;
details_signal informations;
struct utilisateur_struct *utilisateur;
};
Le champ signal dans task_struct est un pointeur vers struct signal_struct, qui représente les signaux partagés par tous les threads d'un groupe de processus. Cette structure inclut un autre sigpending pour les signaux partagés.
struct descripteur_signaux {
atomique_t compteur_ref;
atomique_t actifs;
file_attente_chld attente_sortie;
struct task_struct *cible_courante;
struct attente_signaux signaux_partages;
int code_sortie_groupe;
struct task_struct *tache_sortie_groupe;
int compteur_notification;
int compteur_arret_groupe;
unsigned int drapeaux;
};
Le champ sighand pointe vers struct sighand_struct, qui définit les actions de traitement pour chaque signal.
struct gestion_signaux {
atomique_t compteur_ref;
struct action_signal_k actions[_NSIG];
spinlock_t verrou_signaux;
};
L'action pour chaque signal est encapsulée dans struct k_sigaction, contenant une structure sigaction avec le gestionnaire (sa_handler), les drapeaux (sa_flags) et le masque (sa_mask). Les valeurs spéciales du gestionnaire sont définies dans include/asm-generic/signal.h : SIG_DFL (traitement par défaut), SIG_IGN (ignorer), et SIG_ERR (ereur).
struct action_signal_k {
struct action_signal details;
};
struct action_signal {
__gestionnaire_signaux sa_handler;
unsigned long sa_flags;
ensemble_signaux sa_mask;
};
#define SIG_DFL ((__force __gestionnaire_signaux)0)
#define SIG_IGN ((__force __gestionnaire_signaux)1)
#define SIG_ERR ((__force __gestionnaire_signaux)-1)
Les opérations sur les bitmaps de signaux, telles que ajouter_signaux ou verifier_appartenance, sont essentielles pour manipuler les masques bloqués et enregistrés dans task_struct.
Appels système pour la modification des actions de signaux
L'appel système sigaction permet de définir ou de récupérer l'action associée à un signal. Sa routine de service sys_sigaction se trouve dans arch/体系结构/kernel/signal.c. Elle copie la nouvelle action depuis l'espace utilisateur, invoque executer_sigaction, puis restitue l'ancienne action.
asmlinkage int
sys_sigaction(int signum, const struct ancienne_action_signal __user *nouvelle_action,
struct ancienne_action_signal __user *ancienne_action)
{
struct action_signal_k nouveau_ka, ancien_ka;
int resultat;
if (nouvelle_action) {
ancien_ensemble_signaux masque;
if (!verifier_acces_lecture(nouvelle_action, sizeof(*nouvelle_action)) ||
__obtenir_utilisateur(nouveau_ka.details.sa_handler, &nouvelle_action->sa_handler) ||
__obtenir_utilisateur(nouveau_ka.details.sa_restorer, &nouvelle_action->sa_restorer))
return -EFAULT;
__obtenir_utilisateur(nouveau_ka.details.sa_flags, &nouvelle_action->sa_flags);
__obtenir_utilisateur(masque, &nouvelle_action->sa_mask);
initialiser_ensemble(&nouveau_ka.details.sa_mask, masque);
}
resultat = executer_sigaction(signum, nouvelle_action ? &nouveau_ka : NULL, ancienne_action ? &ancien_ka : NULL);
if (!resultat && ancienne_action) {
if (!verifier_acces_ecriture(ancienne_action, sizeof(*ancienne_action)) ||
__mettre_utilisateur(ancien_ka.details.sa_handler, &ancienne_action->sa_handler) ||
__mettre_utilisateur(ancien_ka.details.sa_restorer, &ancienne_action->sa_restorer))
return -EFAULT;
__mettre_utilisateur(ancien_ka.details.sa_flags, &ancienne_action->sa_flags);
__mettre_utilisateur(ancien_ka.details.sa_mask.sig[0], &ancienne_action->sa_mask);
}
return resultat;
}
La fonction executer_sigaction dans kernel/signal.c valide le numéro de signal, vérifie les signaux en attente, met à jour l'action, et, si l'action est SIG_IGN ou SIG_DFL pour certains signaux, supprime les signaux correspondants des files d'attente partagées et privées.
int executer_sigaction(int signum, struct action_signal_k *nouvelle, struct action_signal_k *ancienne)
{
struct action_signal_k *action_cible;
ensemble_signaux masque;
if (!signal_valide(signum) || signum < 1 || (nouvelle && signal_noyau_seulement(signum)))
return -EINVAL;
action_cible = &actuel->gestion_signaux->actions[signum-1];
verrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
if (signaux_en_attente(actuel)) {
deverrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
return -ERESTARTNOINTR;
}
if (ancienne)
*ancienne = *action_cible;
if (nouvelle) {
supprimer_masque_signaux(&nouvelle->details.sa_mask,
masque_signal(SIGKILL) | masque_signal(SIGSTOP));
*action_cible = *nouvelle;
if (nouvelle->details.sa_handler == SIG_IGN ||
(nouvelle->details.sa_handler == SIG_DFL && signal_noyau_ignorer(signum))) {
struct task_struct *processus = actuel;
vider_ensemble(&masque);
ajouter_signaux(&masque, signum);
retirer_de_file_complete(&masque, &processus->signaux->signaux_partages);
do {
retirer_de_file_complete(&masque, &processus->en_attente);
recalculer_signaux_en_attente_tache(processus);
processus = thread_suivant(processus);
} while (processus != actuel);
}
}
deverrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
return 0;
}
L'appel système signal est une version simplifiée de sigaction, tandis que rt_sigaction gère les ensembles de signaux de taille variable.
Distribution des signaux à des processus ou des groupes
L'appel système kill envoie un signal à un groupe de processus. Sa routine sys_kill appelle envoyer_signal_quelconque, qui interprète le paramètre pid pour cibler un processus spécifique, un groupe, ou tous les processus.
static int envoyer_signal_quelconque(int signum, struct details_signal *info, int pid)
{
if (!pid) {
return envoyer_signal_groupe(signum, info, groupe_processus(actuel));
} else if (pid == -1) {
int resultat = 0, compteur = 0;
struct task_struct *processus;
lire_verrou(&verrou_liste_taches);
pour_chaque_processus(processus) {
if (processus->pid > 1 && processus->tgid != actuel->tgid) {
int erreur = envoyer_signal_groupe_info(signum, info, processus);
++compteur;
if (erreur != -EPERM)
resultat = erreur;
}
}
deverrouiller_lecture(&verrou_liste_taches);
return compteur ? resultat : -ESRCH;
} else if (pid < 0) {
return envoyer_signal_groupe(signum, info, -pid);
} else {
return envoyer_signal_processus(signum, info, pid);
}
}
La fonction envoyer_signal_groupe_info vérifie les permissions, gère les signaux d'arrêt/continuation via gerer_signal_arret, et insère le signal dans la file d'attente partagée. Pour les signaux non temps réel, elle évite les doublons.
int envoyer_signal_groupe_info(int signum, struct details_signal *info, struct task_struct *processus)
{
unsigned long indicateurs;
int resultat;
resultat = verifier_permission_signal(signum, info, processus);
if (!resultat && signum) {
resultat = -ESRCH;
if (verrouiller_tache_signaux(processus, &indicateurs)) {
resultat = envoyer_signal_groupe_interne(signum, info, processus);
deverrouiller_tache_signaux(processus, &indicateurs);
}
}
return resultat;
}
L'envoi de signaux à des threads individuels est géré par tkill et tgkill, qui utilisent envoyer_signal_specifique pour cibler un processus spécifique dans sa file privée.
Traitement et distribution des signaux
Lors du retour d'interruption, si le drapeau TIF_SIGPENDING est défini, traiter_signaux est invoquée. Elle appelle obtenir_signaux_a_delivrer, qui défile les signaux des files d'attente et détermine l'action appropriée.
static void traiter_signaux(struct registres_pt *registres)
{
details_signal info;
int numero_signal;
struct action_signal_k action;
ensemble_signaux *ancien_masque;
if (!mode_utilisateur(registres))
return;
if (tester_drapeau_thread(TIF_RESTORE_SIGMASK))
ancien_masque = &actuel->masque_signaux_sauvegarde;
else
ancien_masque = &actuel->bloques;
numero_signal = obtenir_signaux_a_delivrer(&info, &action, registres, NULL);
if (numero_signal > 0) {
if (traiter_signal_capture(numero_signal, &info, &action, ancien_masque, registres) == 0) {
if (tester_drapeau_thread(TIF_RESTORE_SIGMASK))
effacer_drapeau_thread(TIF_RESTORE_SIGMASK);
}
return;
}
// Gestion de la reprise des appels système
...
}
Pour les signaux capturés, traiter_signal_capture configure la pile utilisateur avec un cadre de signal (cadre_signal ou cadre_signal_rt) et ajuste les registres pour exécuter le gestionnaire. Le masque de signaux bloqués est mis à jour pour inclure le signal en cours et ceux définis dans sa_mask.
static int
traiter_signal_capture(unsigned long signum, details_signal *info, struct action_signal_k *action,
ensemble_signaux *ancien_masque, struct registres_pt *registres)
{
int resultat;
// Gestion de la reprise des appels système
...
if (action->details.sa_flags & SA_SIGINFO)
resultat = configurer_cadre_rt(signum, action, info, ancien_masque, registres);
else
resultat = configurer_cadre(signum, action, ancien_masque, registres);
if (resultat == 0) {
verrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
ou_ensembles(&actuel->bloques, &actuel->bloques, &action->details.sa_mask);
if (!(action->details.sa_flags & SA_NODEFER))
ajouter_signaux(&actuel->bloques, signum);
recalculer_signaux_en_attente();
deverrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
}
return resultat;
}
La fonction configurer_cadre alloue un cadre sur la pile utilisateur, y stocke les registres et le masque, et prépare le saut vers le gestionnaire. Le restaureur de pile est soit __kernel_sigreturn soit un restaureru personnalisé via SA_RESTORER.
Restauration du contexte après traitement
Après l'exécution du gestionnaire, l'appel système sigreturn restaure le contexte initial. Sa routine sys_sigreturn recopie les registres et le masque de signaux depuis le cadre utilisateur.
asmlinkage int sys_sigreturn(unsigned long __inutilise)
{
struct registres_pt *registres = (struct registres_pt *) &__inutilise;
struct cadre_signal __user *cadre = (struct cadre_signal __user *)(registres->esp - 8);
ensemble_signaux masque;
int eax;
if (!verifier_acces_lecture(cadre, sizeof(*cadre)))
goto cadre_invalide;
if (__obtenir_utilisateur(masque.sig[0], &cadre->contexte.ancien_masque)
|| (_NSIG_MOTS > 1
&& __copier_depuis_utilisateur(&masque.sig[1], &cadre->masque_supplementaire,
sizeof(cadre->masque_supplementaire))))
goto cadre_invalide;
supprimer_masque_signaux(&masque, ~_BLOCABLE);
verrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
actuel->bloques = masque;
recalculer_signaux_en_attente();
deverrouiller_signaux_irq(&actuel->gestion_signaux->verrou_signaux);
if (restaurer_contexte_signaux(registres, &cadre->contexte, &eax))
goto cadre_invalide;
return eax;
cadre_invalide:
forcer_signal(SIGSEGV, actuel);
return 0;
}
La fonction forcer_signal est utilisée pour injecter des signaux même si le processus les ignore ou les bloque, en modifiant directement l'action et le masque.