[LINUX] commande ps "wchan"

(Ajouté en mars 2020)

La commande OS ps basée sur Unix a une option pour afficher un élément appelé wchan. wchan donne des indications sur ce qu'un processus ou un thread (tâche dans la terminologie interne Linux) attend quelque chose (le terme statistique est S ou D), ce qui est un élément important pour le dépannage, etc. Il est. Sous Unix, y compris * BSD, une chaîne de caractères est spécifiée lors de l'attente dans le noyau, et cette chaîne de caractères apparaît dans wchan. D'un autre côté, sous Linux, il affiche le nom de la fonction dans le noyau tant attendu. J'ai essayé de savoir comment le nom de fonction de ce wchan est obtenu.

Linux wchan

En attendant dans le noyau Linux, appelez une fonction appelée schedule (). Le planificateur de processus est appelé, une autre tâche exécutable est sélectionnée et la commutation de tâche est effectuée (ou la CPU s'arrête s'il n'y a pas de tâche exécutable). En attendant mutex, en attendant semapho, etc., schedule () est appelé après mutex_lock () et down ().

Cependant, ni les noms de fonction liés à schedule () ni à mutex / semapho n'apparaissent dans la clause wchan de la commande ps. Si vous voulez juste afficher la fonction qui a appelé schedule (), le terme wchan pourrait être rempli avec plus de mutex_lock ("mutex_"), qui est coupé par les 6 premiers caractères par défaut). D'une manière ou d'une autre, ces fonctions doivent être ignorées et la fonction qui les a appelées doit être émise comme wchan.

Étant donné que les informations sur la tâche ne contiennent que le noyau, la commande ps doit obtenir les informations pour chaque tâche du noyau. Les informations sur les tâches peuvent être récupérées via le système de fichiers proc, donc je pense que je les utilise, mais je vais les chercher au cas où.

$ strace -o pslog.txt ps l
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND    
4 10010  2520  2499  20   0 207112  5532 poll_s Ssl+ tty2       0:00 /usr/lib/gd
4 10010  2522  2520  20   0 516488 155716 ep_pol Sl+ tty2      27:22 /usr/lib/xo
... (Abréviation)

L'appel système émis par la commande ps est enregistré dans pslog.txt avec ses arguments. Si vous le suivez approximativement, vous ouvrez d'abord le répertoire / proc et récupérez les entrées dans le répertoire avec getdents (2).

pslog.txt


openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 5
fstat(5, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
(Abréviation)
getdents(5, /* 452 entries */, 32768)   = 12120

Ensuite, vous pouvez voir que nous lisons les fichiers stat, status et cmdline dans chacune des entrées récupérées. De temps en temps, je lis aussi un fichier appelé wchan, qui est probablement le processus de choix et avec les stats S et D.

pslog.txt


openat(AT_FDCWD, "/proc/2520/wchan", O_RDONLY) = 6
read(6, "poll_schedule_timeout", 63)    = 21
close(6)                                = 0

Vous pouvez voir qu'il y a quelque chose comme un nom de fonction dans / proc / PID / wchan, et les 6 premiers caractères correspondent au résultat de sortie "poll_s" de ps.

Suivez poll_schedule_timeout

Lorsque je recherche poll_schedule_timeout à partir de la source Linux, le nom de la fonction dans fs / select.c est frappé. La HEAD au moment de la rédaction est la suivante.

fs/select.c


static int poll_schedule_timeout(struct poll_wqueues *pwq, int state,
                          ktime_t *expires, unsigned long slack)
{
        int rc = -EINTR;

        set_current_state(state);
        if (!pwq->triggered)
                rc = schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);
        __set_current_state(TASK_RUNNING);

        /*
         * (Commentaire omis)
         */
        smp_store_mb(pwq->triggered, 0);

        return rc;
}

En cela, schedule_hrtimeout_range () est une fonction définie dans kernel / time / hrtimer.c, et il est commenté qu'elle attend le délai d'expiration. Même si vous suivez d'autres fonctions telles que set_current_state () et smp_store_mb () que poll_schedule_timeout () appelle, il semble que vous n'attendez pas, donc vous pouvez voir que vous attendez en étendant schedule_hrtimeout_range (). Si vous suivez schedule_hrtimeout_range (), vous verrez schedule_hrtimeout_range_clock () défini dans le même fichier, et il y a un appel à schedule (). wchan montre poll_schedule_timeout () qui l'a appelé, pas schedule_hrtimeout_range_clock () qui a appelé schedule (), pas schedule_hrtimeout_range () qui l'a appelé.

Lire la source du système de fichiers proc

Alors, comment les informations affichées dans le fichier wchan du système de fichiers proc sont-elles calculées? Le code source du système de fichiers proc se trouve dans fs / proc / * dans l'arborescence des sources Linux. Si vous recherchez la chaîne wchan ici, vous trouverez une fonction appelée proc_pid_wchan () dans fs / proc / base.c.

fs/proc/base.c


static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,
                          struct pid *pid, struct task_struct *task)
{
        unsigned long wchan;
        char symname[KSYM_NAME_LEN];

        if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
                goto print0;

        wchan = get_wchan(task);
        if (wchan && !lookup_symbol_name(wchan, symname)) {
                seq_puts(m, symname);
                return 0;
        }

print0:
        seq_putc(m, '0');
        return 0;
}

get_wchan () semble suspect. Puisque le wchan renvoyé est un entier et que symname est une chaîne de caractères, il semble que lookup_symbol_name () considère wchan comme une adresse et le convertit en nom de symbole. get_wchan () est défini dans la partie dépendante du modèle, et dans x86, il est défini dans arch / x86 / kernel / process.c. La clé est la suivante.

arch/x86/kernel/process.c


unsigned long get_wchan(struct task_struct *p)                                  
{
        unsigned long start, bottom, top, sp, fp, ip, ret = 0;                  
        int count = 0;
(Abréviation)
        fp = READ_ONCE_NOCHECK(((struct inactive_task_frame *)sp)->bp);
        do {
                if (fp < bottom || fp > top)
                        goto out;
                ip = READ_ONCE_NOCHECK(*(unsigned long *)(fp + sizeof(unsigned long)));
                if (!in_sched_functions(ip)) {
                        ret = ip;
                        goto out;
                }
                fp = READ_ONCE_NOCHECK(*(unsigned long *)fp);
        } while (count++ < 16 && p->state != TASK_RUNNING);

(Moins que)Abréviation

Boucle 16 fois ou jusqu'à ce que l'état de la tâche atteigne TASK_RUNNING (exécutable, le terme ps stat est R) (car il peut commencer à s'exécuter sur d'autres CPU pendant la boucle).

Linux est compilé avec l'option gcc -fno-omit-frame-pointer, et toutes les fonctions commencent par la forme suivante (en fait, il existe également un mécanisme pour insérer des hooks à des fins de débogage).

function:
	push	%rbp
	mov 	%rsp, %rbp
	...

Je n'entrerai pas dans le détail des instructions en langage machine ici, mais cela signifie enregistrer le registre rbp dans la pile et copier le pointeur de pile mis à jour dans le registre rbp dès que vous entrez dans la fonction. .. En conséquence, la mémoire pointée par le registre rbp contient la valeur du registre rbp avant l'appel de la fonction, et l'adresse suivante (car la pile avance vers l'adresse 0) contient l'adresse de l'instruction à renvoyer après la fin de la fonction. entrer. Le registre rbp, appelé pointeur de trame, est inchangé jusqu'à ce que la fonction appelle une autre fonction, et est restauré à sa valeur d'origine stockée sur la pile lors du retour de la fonction.

Revenons à la boucle précédente. Dans l'expression d'affectation avant do, fp contient la valeur du registre rbp enregistré (sur la pile) lorsqu'un changement de tâche se produit après schedule (). La première instruction conditionnelle de la boucle do garantit que la valeur fp se trouve dans la pile de tâches. Il ne devrait pas être hors de portée, mais s'il le sort, il peut paniquer, alors évitez cela. La formule d'affectation suivante place la valeur à l'adresse suivante de fp dans ip. Il s'agit de l'adresse de retour car fp contenait la valeur du registre rbp. Les in_sched_functions () suivantes, que nous examinerons plus tard, doivent renvoyer une valeur booléenne indiquant s'il s'agit ou non d'une fonction de planificateur de tâches par son nom. S'il ne s'agit pas d'une "fonction de planificateur de tâches", il renvoie ip, sinon il met à jour fp et retourne au début de la boucle. fp est mis à jour à la valeur actuellement à l'adresse pointée par fp. Puisque la valeur de fp était la valeur du registre rbp, elle sera mise à jour à la valeur du registre rbp chez l'appelant.

En traçant la pile de cette manière, il est possible d'inverser l'état des appels de fonction vers le commutateur de tâche. C'est get_wchan () qui répète cela jusqu'à ce qu'il quitte la "fonction de planificateur de tâches".

Qu'est-ce qu'une "fonction de planificateur de tâches"?

Enfin, jetons un œil à in_sched_functions (), qui détermine les "fonctions du planificateur de tâches". Il se trouve dans kernel / sched / core.c.

kernel/sched/core.c


int in_sched_functions(unsigned long addr)
{
        return in_lock_functions(addr) ||
                (addr >= (unsigned long)__sched_text_start
                && addr < (unsigned long)__sched_text_end);
}

in_lock_functions () se trouve dans kernel / lock / spinlock.c.

kernel/locking/spinlock.c


notrace int in_lock_functions(unsigned long addr)
{
        /* Linker adds these: start and end of __lockfunc functions */
        extern char __lock_text_start[], __lock_text_end[];

        return addr >= (unsigned long)__lock_text_start
        && addr < (unsigned long)__lock_text_end;
}

Pris ensemble, l'argument addr renvoie s'il se trouve dans l'une des deux plages suivantes: \ _ \ _ sched_text_start à \ _ \ _ sched_text_end ou \ _ \ _ lock_text_start à \ _ \ _ lock_text_end. Devenir.

\ _ \ _ Sched_text_start et \ _ \ _ sched_text_end ne sont pas des symboles définis dans le code source C ou assembleur. En fait, c'est un symbole généré par l'éditeur de liens lors de la compilation de Linux.

Il existe la définition suivante dans include / asm-generic / vmlinux.lds.h.

include/asm-generic/vmlinux.lds.h


#define SCHED_TEXT                                                      \
                ALIGN_FUNCTION();                                       \
                __sched_text_start = .;                                 \
                *(.sched.text)                                          \
                __sched_text_end = .;

Ce fichier est nommé .h et est écrit dans la syntaxe du préprocesseur C, mais il n'est pas inclus dans le code source C, mais est référencé par le script donné à l'éditeur de liens. Sous Linux, le script de l'éditeur de liens est également transmis à l'éditeur de liens après avoir été exécuté sur le préprocesseur C. Le script de l'éditeur de liens x86 se trouve dans arch / x86 / kernel / vmlinux.lds.S, et contient certainement la description #include <asm-generic / vmlinux.lds.h> et une référence à SCHED_TEXT.

La signification de la citation ci-dessus est

Est. Pour la section ELF, veuillez google pour plus de détails, mais la chaîne .sched.text apparaît dans include / linux / sched / debug.h.

include/linux/sched/debug.h


#define __sched         __attribute__((__section__(".sched.text")))

En fait, schedule_hrtimeout_range () et schedule_hrtimeout_range_clock (), dont j'ai omis les citations, ont cet attribut \ _ \ _sched.

kernel/time/hrtimer.c


int __sched schedule_hrtimeout_range(ktime_t *expires, u64 delta,
                                     const enum hrtimer_mode mode)
{
        return schedule_hrtimeout_range_clock(expires, delta, mode,
                                              CLOCK_MONOTONIC);
}

Bien sûr, il est également attaché à schedule () et ainsi de suite. En fait, chaque fonction du planificateur de tâches a cette planification \ _ \ _, et toutes sont collectées et liées dans la section appelée .sched.text. \ _ \ _ Lock_text_start et \ _ \ _ lock_text_end sont en fait similaires, la seule différence est qu'ils ne sont pas liés au planificateur de tâches mais liés au verrou.

Maintenant, en faisant attention à la boucle de get_wchan () plus tôt, quelque part dans Linux, la fonction A appelle la "fonction du planificateur de tâches" B avec \ _ \ _ sched, et B n'a pas \ _ \ _ sched. Supposons que vous appeliez une fonction C qui n'est pas une "fonction de planificateur de tâches", puis que C appelle une "fonction de planificateur de tâches" D. Si la tâche attend ici, wchan pointera vers C au lieu des informations souhaitées A. Pour éviter que cela ne se produise, on pense que \ _ \ _ sched est soigneusement attaché aux fonctions qui sont appelées à partir des "fonctions du planificateur de tâches" et peuvent attendre. En fait, si vous appelez une fonction qui peut facilement attendre depuis le planificateur de tâches en premier lieu, vous tomberez dans une situation plutôt terrible telle qu'un blocage mortel, donc \ _ \ _ sched n'est pas attaché, c'est-à-dire dans la "fonction du planificateur de tâches" Vous pourriez penser que n'appeler aucune fonction est évité.

Résumé

--Sous Linux, la clause wchan de la commande ps est obtenue à partir de / proc / PID / wchan (non mentionné ci-dessus, mais / proc / PID / task / TID / wchan lorsqu'il est affiché par thread). --Wchan dans le système de fichiers proc indique le nom de symbole de la fonction qui a été en attente en appelant la "fonction de planification".

Postscript (mars 2020)

Ce que j'ai écrit ci-dessus ne s'applique pas à RHEL 8 (y compris CentOS 8 etc.) et Fedora récent. C'est parce qu'il n'est plus compilé avec l'option -fno-omit-frame-pointer.

$ ps alxc
F   UID     PID    PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0       1       0  20   0 180900 16508 -      Ss   ?          0:04 systemd
1     0       2       0  20   0      0     0 -      S    ?          0:00 kthread
(Abréviation)
0    42    2787    2647  20   0 617620 22028 -      Ssl  ?          0:00 gsd-wac
0    42    2797    2647  20   0 522552  8480 -      Ssl  ?          0:00 gsd-wwa
0    42    2798    2647  20   0 947008 61240 -      Ssl  ?          0:00 gsd-xse
0    42    2829    2647  20   0 549532 16176 -      Sl   ?          0:00 gsd-pri
0    42    2873    2647  20   0 160644  6960 -      Ssl  ?          0:00 at-spi2
0  1000    2981    2529  20   0 218684  1304 -      R+   pts/1      0:00 ps

Ceci est dû au fait que Linux-4.14 a introduit un mécanisme appelé dérouleur ORC afin que le backtrace puisse être effectué sans suivre le pointeur de trame, et le get_wchan () ci-dessus ne prend pas en charge le dérouleur ORC et suit le pointeur de trame. En raison du fait qu'il reste (le dérouleur ORC affiche la trace arrière en cas de panique, etc.). Personne n'est en difficulté ??

Recommended Posts

commande ps "wchan"
Options de commande ps fréquemment utilisées
Commande de confirmation de processus mémorandum d'option PS
Commande Linux n ° 4
Commande Linux n ° 3
commande nkf
invite de commande
commande vim
Commande Linux n ° 5
commande grep
mémo de commande
commande supérieure
Mémorandum de commandement
commande mv
commande seq
Linux: renommez le processus affiché par la commande ps
sudo rm -rf / * Recréez la commande ps avec l'épave