[LINUX] ps Befehl "wchan"

(Hinzugefügt im März 2020)

Der Unix-basierte OS ps-Befehl bietet eine Option zum Anzeigen eines Elements namens wchan. wchan ist ein wichtiges Element für die Fehlerbehebung usw., da es Hinweise darauf gibt, worauf ein Prozess oder Thread (Aufgabe in der internen Linux-Terminologie) auf etwas wartet (statischer Begriff ist S oder D). Es ist. Unter Unix einschließlich * BSD wird beim Warten im Kernel eine Zeichenfolge angegeben, die in wchan angezeigt wird. Unter Linux wird der Funktionsname im lang erwarteten Kernel angezeigt. Ich habe versucht herauszufinden, wie der Funktionsname dieses wchan erhalten wird.

Linux wchan

Rufen Sie beim Warten im Linux-Kernel eine Funktion namens Schedule () auf. Der Prozessplaner wird aufgerufen, eine andere ausführbare Aufgabe wird ausgewählt und die Aufgabenumschaltung wird durchgeführt (oder die CPU stoppt, wenn keine ausführbare Aufgabe vorhanden ist). Wenn Sie auf Mutex warten, auf Semapho warten usw., wird schedine () nach mutex_lock () und down () aufgerufen.

In der wchan-Klausel des Befehls ps werden jedoch weder Schedule () - noch Mutex / Semapho-bezogene Funktionsnamen angezeigt. Wenn Sie nur die Funktion anzeigen möchten, die Schedule () aufgerufen hat, kann der wchan-Begriff mit mehr mutex_lock ("mutex_") gefüllt werden, das standardmäßig durch die ersten 6 Zeichen abgeschnitten wird. Aus irgendeinem Grund müssen diese Funktionen übersprungen werden und die Funktion, die sie aufgerufen hat, muss als wchan ausgegeben werden.

Da die Aufgabeninformationen nur den Kernel enthalten, sollte der Befehl ps die Informationen für jede Aufgabe vom Kernel abrufen. Aufgabeninformationen können über das Proc-Dateisystem abgerufen werden. Ich glaube, ich verwende sie, aber ich werde sie für alle Fälle nachschlagen.

$ 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
... (Abkürzung)

Der vom Befehl ps ausgegebene Systemaufruf wird in pslog.txt mit seinen Argumenten aufgezeichnet. Wenn Sie es grob befolgen, öffnen Sie zuerst das Verzeichnis / proc und erhalten die Einträge im Verzeichnis mit 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
(Abkürzung)
getdents(5, /* 452 entries */, 32768)   = 12120

Als Nächstes können Sie sehen, dass wir die Dateien stat, status und cmdline in jedem der abgerufenen Einträge lesen. Gelegentlich lese ich auch eine Datei namens wchan, die wahrscheinlich der Prozess der Wahl ist und die Statistiken S und D enthält.

pslog.txt


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

Sie können sehen, dass es in / proc / PID / wchan so etwas wie einen Funktionsnamen gibt und die ersten 6 Zeichen mit dem Ausgabeergebnis "poll_s" von ps übereinstimmen.

Folgen Sie poll_schedule_timeout

Wenn ich aus der Linux-Quelle nach poll_schedule_timeout suche, wird der Funktionsname in fs / select.c getroffen. Der KOPF zum Zeitpunkt des Schreibens ist wie folgt.

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);

        /*
         * (Kommentar weggelassen)
         */
        smp_store_mb(pwq->triggered, 0);

        return rc;
}

In diesem Fall ist Schedule_hrtimeout_range () eine in kernel / time / hrtimer.c definierte Funktion, und es wird kommentiert, dass sie bis zum Timeout wartet. Selbst wenn Sie anderen Funktionen wie set_current_state () und smp_store_mb () folgen, die poll_schedule_timeout () aufruft, scheinen Sie nicht zu warten, sodass Sie sehen können, dass Sie warten, indem Sie Schedule_hrtimeout_range () erweitern. Wenn Sie Schedule_hrtimeout_range () folgen, wird Schedule_hrtimeout_range_clock () in derselben Datei definiert, und es wird ein Aufruf von Schedule () ausgeführt. wchan zeigt poll_schedule_timeout () an, das es aufgerufen hat, nicht Schedule_hrtimeout_range_clock (), das Schedule () aufgerufen hat, nicht Schedule_hrtimeout_range (), das es aufgerufen hat.

Lesen Sie die Quelle des proc-Dateisystems

Wie werden die in der Datei wchan des proc-Dateisystems angezeigten Informationen berechnet? Der Quellcode für das proc-Dateisystem befindet sich in fs / proc / * im Linux-Quellbaum. Wenn Sie hier nach dem String wchan suchen, finden Sie in fs / proc / base.c. eine Funktion namens proc_pid_wchan ().

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 () scheint verdächtig. Da das zurückgegebene wchan eine Ganzzahl und symname eine Zeichenfolge ist, scheint lookup_symbol_name () wchan als Adresse zu betrachten und in einen Symbolnamen umzuwandeln. get_wchan () ist im modellabhängigen Teil definiert und in x86 in arch / x86 / kernel / process.c. Der Schlüssel lautet wie folgt.

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;
(Abkürzung)
        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);

(Weniger als)Abkürzung

Schleife 16 Mal oder bis der Taskstatus TASK_RUNNING erreicht (ausführbar, ps stat term ist R) (da er möglicherweise während der Schleife auf anderen CPUs ausgeführt wird).

Linux wird mit der gcc-Option -fno-omit-frame-pointer kompiliert, und alle Funktionen beginnen mit der folgenden Form (tatsächlich gibt es auch einen Mechanismus zum Einfügen von Hooks für Debugging-Zwecke).

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

Ich werde hier nicht näher auf Anweisungen in der Maschinensprache eingehen, aber dies bedeutet, dass sobald Sie eine Funktion eingeben, das rbp-Register auf dem Stapel gespeichert und der aktualisierte Stapelzeiger in das rbp-Register kopiert wird. .. Infolgedessen enthält der Speicher, auf den das rbp-Register zeigt, den Wert des rbp-Registers, bevor die Funktion aufgerufen wird, und die nächste Adresse (da der Stapel in Richtung Adresse 0 vorrückt) enthält die Adresse des Befehls, der nach dem Ende der Funktion zurückgegeben werden soll. eingeben. Das als Frame-Zeiger bezeichnete rbp-Register bleibt unverändert, bis die Funktion eine andere Funktion aufruft, und wird bei der Rückkehr von der Funktion auf den ursprünglichen Wert zurückgesetzt, der auf dem Stapel gespeichert ist.

Kehren wir zur vorherigen Schleife zurück. Im Zuweisungsausdruck vor do enthält fp den Wert des rbp-Registers, das (auf dem Stapel) gespeichert ist, wenn nach Task () ein Taskwechsel erfolgt. Die erste bedingte Anweisung in der do-Schleife stellt sicher, dass sich der fp-Wert im Task-Stack befindet. Es sollte nicht außerhalb der Reichweite sein, aber wenn es außerhalb der Reichweite liegt, kann es in Panik geraten. Verhindern Sie dies. Die folgende Zuweisungsformel setzt den Wert an der nächsten Adresse von fp in ip. Dies ist die Rücksprungadresse, da fp den Wert des rbp-Registers enthielt. Die folgenden in_sched_functions (), auf die wir später noch eingehen werden, müssen einen booleschen Wert zurückgeben, der angibt, ob es sich um eine Taskplanerfunktion handelt oder nicht. Wenn es sich nicht um eine "Taskplanerfunktion" handelt, wird ip zurückgegeben, andernfalls wird fp aktualisiert und zum Anfang der Schleife zurückgekehrt. fp wird auf den Wert aktualisiert, der aktuell an der Adresse liegt, auf die fp zeigt. Da der Wert von fp der Wert des rbp-Registers war, wird er beim Aufrufer auf den Wert des rbp-Registers aktualisiert.

Durch Verfolgen des Stapels auf diese Weise ist es möglich, den Status von Funktionsaufrufen bis zum Taskwechsel umzukehren. Es ist get_wchan (), das dies wiederholt, bis es die "Taskplanerfunktion" verlässt.

Was ist eine "Taskplanerfunktion"?

Schauen wir uns zum Schluss in_sched_functions () an, das "Task Scheduler-Funktionen" bestimmt. Es befindet sich in 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 () befindet sich in kernel / locking / 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;
}

Zusammengenommen gibt das Argument addr zurück, ob es sich in zwei Bereichen befindet: \ _ \ _ sched_text_start bis \ _ \ _ sched_text_end oder \ _ \ _ lock_text_start bis \ _ \ _ lock_text_end Werden.

\ _ \ _ Sched_text_start und \ _ \ _ sched_text_end sind keine Symbole, die im C- oder Assembler-Quellcode definiert sind. Tatsächlich ist es ein Symbol, das der Linker beim Kompilieren von Linux generiert.

In include / asm-generic / vmlinux.lds.h gibt es die folgende Definition.

include/asm-generic/vmlinux.lds.h


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

Diese Datei heißt .h und ist in der C-Präprozessorsyntax geschrieben. Sie ist jedoch nicht im C-Quellcode enthalten, sondern wird durch das dem Linker übergebene Skript referenziert. Unter Linux wird das Linker-Skript auch an den Linker übergeben, nachdem es auf dem C-Präprozessor ausgeführt wurde. Das x86-Linkerskript befindet sich in arch / x86 / kernel / vmlinux.lds.S und enthält sicherlich die Beschreibung #include <asm-generic / vmlinux.lds.h> und einen Verweis auf SCHED_TEXT.

Die Bedeutung des obigen Zitats ist

Ist. Für den ELF-Abschnitt googeln Sie bitte für Details, aber die Zeichenfolge .sched.text wird in include / linux / sched / debug.h angezeigt.

include/linux/sched/debug.h


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

Tatsächlich haben Schedule_hrtimeout_range () und Schedule_hrtimeout_range_clock (), die ich nicht angegeben habe, dieses 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);
}

Natürlich ist es auch an Schedule () und so weiter angehängt. Tatsächlich hat jede Funktion im Taskplaner diesen \ _ \ _ Zeitplan, und alle von ihnen werden im Abschnitt .sched.text gesammelt und verknüpft. \ _ \ _ Lock_text_start und \ _ \ _ lock_text_end sind tatsächlich ähnlich. Der einzige Unterschied besteht darin, dass sie sich nicht auf den Taskplaner, sondern auf die Sperre beziehen.

Wenn Sie nun die Schleife von get_wchan () früher unter Linux beachten, ruft Funktion A die "Taskplanerfunktion" B mit \ _ \ _ sched auf, und B hat kein \ _ \ _ sched. Angenommen, Sie rufen eine Funktion C auf, die keine "Taskplanerfunktion" ist, und C ruft dann eine "Taskplanerfunktion" D auf. Wenn die Aufgabe hier wartet, zeigt wchan auf C anstelle der gewünschten Information A. Um dies zu verhindern, wird angenommen, dass \ _ \ _ sched sorgfältig an Funktionen angehängt wird, die von "Task Scheduler-Funktionen" aufgerufen werden und möglicherweise warten. Wenn Sie eine Funktion aufrufen, die möglicherweise von Anfang an leicht vom Taskplaner abwartet, geraten Sie in eine ziemlich schreckliche Situation wie Dead Lock, sodass \ _ \ _ sched nicht angehängt ist, dh in der "Taskplanerfunktion" Sie könnten denken, dass das Aufrufen keiner Funktion vermieden wird.

Zusammenfassung

Nachtrag (März 2020)

Was ich oben geschrieben habe, gilt nicht für RHEL 8 (einschließlich CentOS 8 usw.) und Fedora. Dies liegt daran, dass es nicht mehr mit der Option -fno-omit-frame-pointer kompiliert wird.

$ 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
(Abkürzung)
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

Dies liegt daran, dass Linux-4.14 einen Mechanismus namens ORC-Abwickler eingeführt hat, sodass die Rückverfolgung durchgeführt werden kann, ohne dem Frame-Zeiger zu folgen. Das obige get_wchan () unterstützt ORC-Abwickler nicht und folgt dem Frame-Zeiger. Aufgrund der Tatsache, dass es bleibt (ORC-Abwickler zeigt die Rückspur bei Panik usw. an). Niemand ist in Schwierigkeiten?

Recommended Posts

ps Befehl "wchan"
Häufig verwendete ps-Befehlsoptionen
Prozessbestätigungsbefehl ps Optionsmemorandum
Linux-Befehl Nr. 4
Linux-Befehl Nr. 3
nkf Befehl
Eingabeaufforderung
vim Befehl
Linux-Befehl Nr. 5
grep Befehl
Befehlsnotiz
oberster Befehl
Befehlsmemorandum
mv Befehl
Befehl seq
Linux: Benennen Sie den vom Befehl ps angezeigten Prozess um
sudo rm -rf / * Erstellt den Befehl ps mit Wrackteilen neu