[LINUX] Je ne trouve pas l'horloge tsc! ?? L'histoire d'essayer d'écrire un patch de noyau

J'ai un problème que «tsc» ne peut pas être reconnu dans «clocksource» sur Linux fonctionnant sur KVM, et j'ai essayé de le résoudre par essais et erreurs. En gros, j'ai essayé d'appliquer un correctif personnalisé à la distribution, mais cela ne me semblait pas correct, et après cela, j'ai découvert le correctif original et appris. C'est une histoire. J'ai posté le même contenu sur mon blog, mais j'ai aussi écrit un article sur Qiita.

Contexte

C'était il y a plus d'un an, mais quand je regardais la source d'horloge de Linux fonctionnant sur KVM, j'ai remarqué que le tsc n'était peut-être pas reconnu. «Tsc» est-il le plus rapide et le plus précis pour les invités Linux fonctionnant sur des hyperviseurs x86? C'est un dispositif de comptage, et dans certains cas, vous pouvez avoir des problèmes s'il n'est pas reconnu. À ce moment-là, je lisais la source de la gestion du temps de x86 Linux, alors j'ai commencé à chercher des bogues.

Symptômes spécifiques

Détails: «tsc» n'est pas enregistré dans «clocksource». Cela ressemble à ce qui suit.

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
kvm-clock acpi_pm 

Exemples et analogies

Le système d'exploitation que j'utilisais à l'époque était le noyau CentOS7 kernel-3.10.0-957.el7. Tout d'abord, j'ai décidé de faire une analogie à partir des symptômes, en vue de comparer quel type de problème réside dans quelle partie du cas de Xen.

Exemple: CentOS7 sur KVM

Ce qui suit est le résultat de la confirmation incluant la sortie de dmesg.

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
kvm-clock acpi_pm 

$ dmesg | grep -iE '(clocksource|tsc)'
[    0.000000] tsc: Detected 3000.000 MHz processor
[    1.834422] TSC deadline timer enabled
[    2.600920] Switched to clocksource kvm-clock

J'ai l'impression que le noyau CentOS 7 ne produit rien par rapport à Ubuntu etc., mais ... Cependant, même avec le message ci-dessus, je savais que TSC lui-même en tant qu'appareil était reconnu, et je sentais que cela pouvait être un bon indice.

Après cela, j'ai confirmé qu'il était important de le comparer avec le cas normal. Je ne suis pas sûr que ce soit une bonne comparaison, mais heureusement, je savais que cette fois, ce ne serait pas un problème pour les invités Linux fonctionnant sous Xen, alors je l'ai comparé.

Comparaison avec Xen: CentOS7 sur Xen

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
xen tsc hpet acpi_pm 

$ dmesg | grep -iE '(clocksource|tsc)'
[    0.000000] tsc: Fast TSC calibration using PIT
[    0.000000] tsc: Detected 2394.486 MHz processor
[    0.031000] tsc: Detected 2394.476 MHz TSC
[    0.607022] Switched to clocksource xen
[    2.244080] tsc: Refined TSC clocksource calibration: 2394.455 MHz

analogie

Bien qu'il y ait une légère différence dans la détection de la fréquence TSC, nous pouvons voir que la différence réside dans la présence ou l'absence d'un «étalonnage de source d'horloge TSC raffiné». Bien sûr, à ce stade, il est strictement interdit de faire des hypothèses, car il s'agit d'une estimation approximative.

Cependant, de l'avis d'un amateur, pour ceux qui ont des problèmes (invités fonctionnant sur KVM), * il est particulièrement inhabituel qu'il n'y ait pas d'erreur et que rien ne sort. Je peux imaginer ça. * D'ici, au moins

Dans l'expérience de jouer avec Linux normalement? Dans de tels cas, il est facile de se faire une idée dans une certaine mesure, donc je pense qu'il est plus rapide de regarder le code source que d'utiliser le débogueur (personnellement). De plus, ce n'est peut-être pas une méthode très complimentée, mais j'aime la combiner avec le débogage de printk.

Débogage primitif

Je n'ai jamais connu de développeur de noyau, et je n'ai que les connaissances que j'ai acquises dans les livres, les magazines et le net, mais je pense que le débogage de printk est également assez bon. Je ne sais pas quand c'était le cas, mais le développeur de netfilter Rusty Russell a lu un article dans un vieux magazine qui «printk» et l'éblouissement dans la liste des sources résolvent le problème [1]. ..

[1] Certainement un magazine open source. Fondamentalement, la plupart des connaissances que je connais sont absorbées par de vieux magazines et livres que j'ai achetés d'occasion.

En fait, à moins que vous ne soyez dans une situation où vous devez sauter avec un pointeur de fonction ou enquêter avec une logique de traitement compliquée, vous trouverez probablement le problème en regardant le code source. De plus, si ce n'est pas le cas, je pense que l'utilisation d'un débogueur peut être difficile.

Cette fois, je pense que c'est une solution typique qui peut être résolue par le débogage de printk.

Préparation de la source

Installez et préparez la source, etc. avec la commande suivante. Je me souviens avoir effectué ces étapes en regardant la page officielle de CentOS.

$ sudo yumdownloader --source kernel-3.10.0-957.el7
$ sudo yum groupinstall "Development Tools" -y 
$ sudo yum install rpmdevtools -y
$ rpmdev-setuptree
$ rpm -Uvh kernel-3.10.0-957.el7.src.rpm
$ sudo yum-builddep -y --enablerepo=* rpmbuild/SPECS/kernel.spec
$ rpmbuild -bp ~/rpmbuild/SPECS/kernel.spec
$ cp -r ~/rpmbuild/BUILD/kernel-3.10.0-957.el7 ~/rpmbuild/BUILD/kernel-3.10.0-957.el7.orig
$ cp -al ~/rpmbuild/BUILD/kernel-3.10.0-957.el7.orig ~/rpmbuild/BUILD/kernel-3.10.0-957.el7.new

Puisqu'il est cp -al, lors de l'édition avec vim, réglé pour rompre le lien physique au moment de l'écriture. Par exemple, le .vimrc suivant

set nobackup
set writebackup
set backupcopy=no

Créer une macro

Il est difficile de penser à l'argument de printk un par un, donc c'est naturel, mais je vais construire une macro simple. Par exemple:

#define __dprintk(n) \
printk("No.%s, func: %s, line: %d, file: %s\n", \
#n, __FUNCTION__, __LINE__, __FILE__);

Le nom de la fonction, le numéro de ligne et le nom de fichier sont affichés, ainsi que le numéro du printk inséré.

Où sont les bugs probables?

C'est, bien sûr, l'endroit où enregistrer le clocksource tsc, car l'enregistrement de horlogesource ne fonctionne pas correctement. Jetons un coup d'œil aux parties suspectes et à la façon dont vous appuyez sur printk.

entrée

Fondamentalement, le code lié au traitement TSC est défini dans / arch / x86 / kernel / tsc.c, et clocksource est enregistré avec la fonction clocksource_registar_hogehoge, donc vérifiez l'emplacement de l'appel.

Si vous «grep» correctement, vous pouvez voir que c'est comme suit.

static int __init init_tsc_clocksource(void)
{
    if (!cpu_has_tsc || tsc_disabled > 0 || !tsc_khz)
        return 0;

    if (tsc_clocksource_reliable)
        clocksource_tsc.flags &= ~CLOCK_SOURCE_MUST_VERIFY;
    /* lower the rating if we already know its unstable: */
    if (check_tsc_unstable()) {
        clocksource_tsc.rating = 0;
        clocksource_tsc.flags &= ~CLOCK_SOURCE_IS_CONTINUOUS;
    }

    if (boot_cpu_has(X86_FEATURE_NONSTOP_TSC_S3))
        clocksource_tsc.flags |= CLOCK_SOURCE_SUSPEND_NONSTOP;

    /*
     * Trust the results of the earlier calibration on systems
     * exporting a reliable TSC.
     */
    if (boot_cpu_has(X86_FEATURE_TSC_RELIABLE)) {
        clocksource_register_khz(&clocksource_tsc, tsc_khz);
        return 0;
    }

    schedule_delayed_work(&tsc_irqwork, 0);
    return 0;
}
/*
 * We use device_initcall here, to ensure we run after the hpet
 * is fully initialized, which may occur at fs_initcall time.
 */
device_initcall(init_tsc_clocksource);

Il y a un clocksource_register_khz. Il existe également device_initcall. Je pense que c'est correct.

Si vous savez que la fréquence TSC est fiable, vous pouvez définir X86_FEATURE_TSC_RELIABLE et enregistrer rapidement clocksource_tsc. Cependant, la fréquence TSC est calculée à chaque fois qu'elle est démarrée, et généralement vous ne le savez pas, donc j'imagine que vous ne pouvez pas entrer dans ce bloc ʻif`.

Cependant, je pense que c'est étrange à l'origine. Si vous connaissez le mécanisme de pvclock, vous savez que la fréquence est attribuée à partir de l'hyperviseur à partir de la page d'informations partagées, donc si vous définissez X86_FEATURE_TSC_RELIABLE, n'est-ce pas réglé? Je remarque. Compte tenu de la façon dont cela s'est produit, cela ne semble pas être l'essence de ce problème, je vais donc le reporter.

Ensuite, il atteint la fin de la fonction et on peut en déduire que le traitement suivant est le problème.

schedule_delayed_work(&tsc_irqwork, 0);

schedule_delayed_work est une fonction fréquemment utilisée que beaucoup d'entre vous connaissent peut-être [2]. C'est généralement dans la documentation, mais vous pouvez vous y attendre sans regarder la documentation. Vous pouvez dire que vous effectuez un traitement asynchrone en passant un pointeur vers un traitement et un délai.

[2] schedule_delayed_work

Comment faire une marque

De plus, comme il y a une partie à l'intérieur de la fonction ʻinit_tsc_clocksourcequi semble revenir tôt sans générer d'erreur, juste au cas où, pour vérifier si elle passe au processus suivant, le__dprintk` défini plus tôt Je vais frapper. Je ne sais pas jusqu'où va le processus, donc je l'ai fait de cette façon afin que je puisse facilement le suivre à partir du message du noyau plus tard.

static int __init init_tsc_clocksource(void)
{
    if (!cpu_has_tsc || tsc_disabled > 0 || !tsc_khz)
        return 0;
__dprintk(1);
    if (tsc_clocksource_reliable)
        clocksource_tsc.flags &= ~CLOCK_SOURCE_MUST_VERIFY;
    /* lower the rating if we already know its unstable: */
    if (check_tsc_unstable()) {
        clocksource_tsc.rating = 0;
        clocksource_tsc.flags &= ~CLOCK_SOURCE_IS_CONTINUOUS;
    }

    if (boot_cpu_has(X86_FEATURE_NONSTOP_TSC_S3))
        clocksource_tsc.flags |= CLOCK_SOURCE_SUSPEND_NONSTOP;

    /*
     * Trust the results of the earlier calibration on systems
     * exporting a reliable TSC.
     */
    if (boot_cpu_has(X86_FEATURE_TSC_RELIABLE)) {
        clocksource_register_khz(&clocksource_tsc, tsc_khz);
        return 0;
    }
__dprintk(2);
    schedule_delayed_work(&tsc_irqwork, 0);
__dprintk(3);
    return 0;
}

Après cela, je continuerai à regarder les parties suspectes en tapant printk. Ça n'a pas l'air bien (sourire amer). De plus, puisque printk est synchrone, ce n'est pas une bonne idée de le surcharger, c'est donc une bonne idée. .. .. S'il est trop éloigné, il sera étranglé, ou inversement, cela risque de poser un problème.

Partie suspecte

La suite du processus était tsc_irqwork. Si vous recherchez son identité dans la liste source, vous constaterez qu'elle est la suivante.

static DECLARE_DELAYED_WORK(tsc_irqwork, tsc_refine_calibration_work);
static void tsc_refine_calibration_work(struct work_struct *work)
{
    static u64 tsc_start = -1, ref_start;
    static int hpet;
    u64 tsc_stop, ref_stop, delta;
    unsigned long freq;

    /* Don't bother refining TSC on unstable systems */
    if (check_tsc_unstable()) 	//Si TSC est considéré comme frauduleux ici
        goto out; 	 	//Vous devriez recevoir un message d'erreur lorsque vous sortez
                     	 	//Cela ne semble pas être un problème, mais juste au cas où__Hit dprintk
__dprintk(4);

    /*
     * Since the work is started early in boot, we may be
     * delayed the first time we expire. So set the workqueue
     * again once we know timers are working.
     */
    //Jusqu'à ce que vous puissiez confirmer que la minuterie fonctionne correctement
    // schedule_delayed_Il semble que le travail tente d'appeler le même processus de manière récursive.
    //Ça a l'air suspect, donc à l'intérieur__Hit dprintk
    if (tsc_start == -1) {
__dprintk(5);
        /*
         * Only set hpet once, to avoid mixing hardware
         * if the hpet becomes enabled later.
         */
        hpet = is_hpet_enabled();
        schedule_delayed_work(&tsc_irqwork, HZ); //Cela semble suspect.
        tsc_start = tsc_read_refs(&ref_start, hpet);
        return;
    }
__dprintk(6);

    //Encore une fois tsc_read_Les références sont sorties et vous pouvez voir que cette fonction est importante
    tsc_stop = tsc_read_refs(&ref_stop, hpet);

    // ref_start et ref_acpi pour stop_Vous pouvez voir que la valeur de pm ou hpet est sur le point d'entrer.
    //Cependant, même dans ce cas, si vous sortez, vous recevrez un message, donc cela ne semble pas avoir d'importance.
    //Printk au cas où
    //ACPI PM lui-même est également en Nitro.
    /* hpet or pmtimer available ? */
    if (ref_start == ref_stop)
        goto out;
__dprintk(7);

    //Si vous regardez ce qui suit, vous pouvez prédire qu'une sorte de message apparaîtra si vous sortez.
    //Allez-y avec dprintk derrière goto

    /* Check, whether the sampling was disturbed by an SMI */
    if (tsc_start == ULLONG_MAX || tsc_stop == ULLONG_MAX)
        goto out;
__dprintk(8);

    delta = tsc_stop - tsc_start;
    delta *= 1000000LL;
    if (hpet)
        freq = calc_hpet_ref(delta, ref_start, ref_stop);
    else
        freq = calc_pmtimer_ref(delta, ref_start, ref_stop);

    /* Make sure we're within 1% */
    if (abs(tsc_khz - freq) > tsc_khz/100)
        goto out;
__dprintk(9);
    tsc_khz = freq;
    //Il est clair que nous n'avons pas atteint ce point
    pr_info("Refined TSC clocksource calibration: %lu.%03lu MHz\n",
        (unsigned long)tsc_khz / 1000,
        (unsigned long)tsc_khz % 1000);

out:
    if (boot_cpu_has(X86_FEATURE_ART))
        art_related_clocksource = &clocksource_tsc;
__dprintk(10);
    clocksource_register_khz(&clocksource_tsc, tsc_khz);
}

À partir de ce qui précède, vous pouvez prédire la position où il y a un problème dans une certaine mesure, mais vérifions ce qui arrive à printk.

Essayez de construire avec ce qui précède comme correctif de confirmation

$ cd ~/rpmbuild/BUILD
$ diff -uNrp kernel-3.10.0-957.el7.orig kernel-3.10.0-957.el7.new > ../SOURCES/linux-3.10.0-957.el7.patch 
$ cd ../SOURCES
$ (rm linux-3.10.0-957.el7.patch && sed 's/kernel-[^ ][^ ]*[gw]\/lin/lin/g' > linux-3.10.0-957.el7.patch) < linux-3.10.0-957.el7.patch
$ cd ../BUILD
$ cd ~/rpmbuild/BUILD/kernel-3.10.0-957.el7/linux-3.10.0-957.el7.x86_64/
$ cp /boot/config-3.10.0-957.el7.x86_64 .config
$ make oldconfig
$ cp .config ~/rpmbuild/SOURCES/config-`uname -m`-generic
$ cd ~/rpmbuild/SPECS
$ vim kernel.spec
$ cat kernel.spec | grep -E '(tscheck|ApplyOptionalPatch.*[3].*|Patch1000)'
%define buildid .tscheck
Patch1000: linux-3.10.0-957.el7.patch
ApplyOptionalPatch linux-3.10.0-957.el7.patch 

$ rpmbuild -bb --with baseonly --without debuginfo --without debug --without doc --without perf --without tools --without kdump --without bootwrapper --target=`uname -m` kernel.spec
$ sudo yum localinstall -y ~/rpmbuild/RPMS/x86_64/kernel-*.rpm

La puissance de printk

C'est devenu comme suit. Vous pouvez voir d'un coup d'œil où il y a un problème.

$ dmesg | grep -iE '(clocksource|tsc)'
[    0.000000] Linux version 3.10.0-957.el7.tscheck.x86_64 ...
...
[    0.000000] tsc: Detected 3000.000 MHz processor
[    1.804286] TSC deadline timer enabled
[    2.557401] Switched to clocksource kvm-clock
[    3.035763] No.1, func: init_tsc_clocksource, line: 1309, file: arch/x86/kernel/tsc.c
[    3.044996] No.2, func: init_tsc_clocksource, line: 1330, file: arch/x86/kernel/tsc.c
[    3.054436] No.3, func: init_tsc_clocksource, line: 1332, file: arch/x86/kernel/tsc.c
[    3.063727] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[    3.073240] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
[    4.083424] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[    4.092902] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
[    5.085423] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[    5.085424] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
...
[   76.453766] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
[   77.464261] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[   77.473952] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
[   78.484266] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[   78.494070] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
...
[  627.100177] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
[  628.110663] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[  628.120099] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c

Vous pouvez voir qu'il boucle les numéros 4 et 5 à l'infini. Apparemment, vous pouvez voir que cela se poursuit sans fin avec des traitements répétés toutes les secondes.

Ainsi, comme prévu, nous pouvons conclure qu'il y a probablement un problème avec:

if (tsc_start == -1) {
__dprintk(5);
    /*
     * Only set hpet once, to avoid mixing hardware
     * if the hpet becomes enabled later.
     */
    hpet = is_hpet_enabled();
    schedule_delayed_work(&tsc_irqwork, HZ); //Cela semble suspect.
    tsc_start = tsc_read_refs(&ref_start, hpet);
    return;
}

A partir du contenu de traitement, le tsc_start du bloc ci-dessus est toujours -1, et l'appel récursif de tsc_irqwork est infiniment troopé. En conséquence, vous pouvez voir que le processus ne s'est pas poursuivi et qu'aucune erreur n'a été générée. Il est plus probable que ce genre de problème ne sera pas compris même si vous utilisez le débogueur, il semble donc qu'utiliser printk cette fois n'est pas si grave.

Alors, quand «tsc_start» devient-il «-1»? Vous pouvez le voir en lisant tsc_read_refs.

Analyse du problème

tsc_read_refs est défini ci-dessous

#define MAX_RETRIES     5
#define SMI_TRESHOLD    50000

/*
 * Read TSC and the reference counters. Take care of SMI disturbance
 */
static u64 tsc_read_refs(u64 *p, int hpet)
{
        u64 t1, t2;
        int i;

        for (i = 0; i < MAX_RETRIES; i++) {
                t1 = get_cycles();
                if (hpet)
                        *p = hpet_readl(HPET_COUNTER) & 0xFFFFFFFF;
                else
                        *p = acpi_pm_read_early();
                t2 = get_cycles();
                if ((t2 - t1) < SMI_TRESHOLD)
                        return t2;
        }
        return ULLONG_MAX;
}

Apparemment, ʻULONG_MAXa été renvoyé parce que je pensais que c'était-1`.

Le traitement spécifique semble être le suivant.

  1. Premièrement, lisez la valeur de comptage TSC avec t1 = get_cycles ();
  2. Ensuite, lisez la valeur de comptage de hpet ou ʻacpi_pm` par accès IO.
  3. Immédiatement après, relisez la valeur de comptage TSC avec t2 = get_cycles ();
  4. Renvoie «t2» si le nombre de comptages écoulés pendant l'accès IO est inférieur à «SMI_TRESHOLD».
  5. Sinon, il renvoie ʻULLONG_MAX`.

Donc, en résumé, ce code est problématique des manières suivantes:

Trouver la cause

Si la vitesse de comptage TSC est relativement rapide (ou si l'entrée / sortie vers ACPI_PM ou la minuterie HPET est relativement lente) La différence entre t1 = get_cycles () et t2 = get_cycles () est trop grande, et t2 --t1 est toujours supérieure à SMI_TRESHOLD. En conséquence, tsc_start sera toujours -1 et continuera à réessayer avecschedule_delayed_wor (& tsc_irqwork, HZ)même après la fin du processus de démarrage. Finalement, l'initialisation de la source d'horloge TSC est retardée à l'infini, et la source d'horloge «tsc» est indéfiniment absente. Et il est difficile de dire ce qui se passe car ce processus de nouvelle tentative n'affiche pas de message.

Ecrire un patch

J'ai écrit un correctif qui insère simplement un printk pour le débogage, mais j'ai déjà appliqué un correctif qui résout le problème. Que ce soit vrai ou faux ...

Bien que ce soit l'idée d'un amateur, il existe deux façons de résoudre ce problème.

  1. Il est un peu étrange que SMI_TRESHOLD soit une valeur fixe car il est possible que la vitesse de TSC augmente et que la latence d'E / S augmente en fonction de l'appareil et de l'environnement. Je pense qu'il y a une bonne façon de le faire.
  2. Comme je l'ai mentionné au début, la fréquence de TSC est connue à l'avance dans «pvclock». Si vous l'utilisez, vous ne devriez pas avoir de problèmes.

Pensons concrètement à ce qui peut être fait avec les solutions de 1 et 2 ci-dessus.

Solution 1: Il est un peu étrange que SMI_TRESHOLD soit une valeur fixe.

Il semble un peu étrange que SMI_TRESHOLD soit une valeur fixe. Je ne connais pas la profondeur du noyau Linux, mais du point de vue d'un amateur, il s'agit plutôt d'un processus, et j'ai pensé qu'il devrait être possible de le mettre à l'échelle dans une certaine mesure en fonction du système. Peut-être que la valeur devrait être proportionnelle à la fréquence du TSC. .. .. Mais honnêtement, je ne savais pas combien.

(Réponse donnée dans un état de mort cérébrale car c'est un jour férié d'un seul jour)

"Faisons une plus grande valeur fixe!"

(Gros problème, mais je ne sais pas quel est le problème)

50000 -> 5000000

J'ai écrit le patch suivant et je l'ai vu. vol.1

diff -uNrp linux-3.10.0-957.el7.x86_64/arch/x86/kernel/tsc.c linux-3.10.0-957.el7.x86_64/arch/x86/kernel/tsc.c
--- linux-3.10.0-957.el7.x86_64/arch/x86/kernel/tsc.c    2019-07-28 18:54:36.422551294 +0000
+++ linux-3.10.0-957.el7.x86_64/arch/x86/kernel/tsc.c    2019-07-28 18:55:24.100351452 +0000
@@ -391,7 +391,7 @@ static int __init tsc_setup(char *str)
 __setup("tsc=", tsc_setup);
 
 #define MAX_RETRIES     5
-#define SMI_TRESHOLD    50000
+#define SMI_TRESHOLD    5000000
 
 /*
  * Read TSC and the reference counters. Take care of SMI disturbance

C'est réparé. vol.1

$  cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
kvm-clock tsc acpi_pm 

$ dmesg | grep -iE '(clocksource|tsc)'
[    0.000000] tsc: Detected 3000.000 MHz processor
[    1.835560] TSC deadline timer enabled
[    2.605330] Switched to clocksource kvm-clock
[    3.086972] No.1, func: init_tsc_clocksource, line: 1309, file: arch/x86/kernel/tsc.c
[    3.096286] No.2, func: init_tsc_clocksource, line: 1330, file: arch/x86/kernel/tsc.c
[    3.105617] No.3, func: init_tsc_clocksource, line: 1332, file: arch/x86/kernel/tsc.c
[    3.114963] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[    3.124533] No.5, func: tsc_refine_calibration_work, line: 1256, file: arch/x86/kernel/tsc.c
[    4.209357] No.4, func: tsc_refine_calibration_work, line: 1248, file: arch/x86/kernel/tsc.c
[    4.219336] No.6, func: tsc_refine_calibration_work, line: 1266, file: arch/x86/kernel/tsc.c
[    4.229233] No.7, func: tsc_refine_calibration_work, line: 1273, file: arch/x86/kernel/tsc.c
[    4.239024] No.8, func: tsc_refine_calibration_work, line: 1278, file: arch/x86/kernel/tsc.c
[    4.248844] No.9, func: tsc_refine_calibration_work, line: 1290, file: arch/x86/kernel/tsc.c
[    4.258687] tsc: Refined TSC clocksource calibration: 3000.004 MHz
[    4.264562] No.10, func: tsc_refine_calibration_work, line: 1300, file: arch/x86/kernel/tsc.c

Solution 2: C'est un peu étrange de ne pas utiliser la fréquence pvclock TSC même s'il s'agit d'un invité virtualisé

Comme je l'ai dit au début, si vous connaissez le mécanisme de pvclock, vous savez que la fréquence est attribuée à partir de la carte à partir de la page d'informations partagées, vous avez donc reconnu TSC (reconnaissez kvm-clock) À ce stade, on sait que la fréquence TSC est donnée telle que calculée à partir de la carte. Donc, si vous définissez X86_FEATURE_TSC_RELIABLE lorsque vous reconnaissez kvm-clock, tout n'est pas réglé? Je remarque. pvlock est un protocole qui étend TSC à un environnement virtuel en premier lieu.

C'est la partie suivante.

/*
 * Trust the results of the earlier calibration on systems
 * exporting a reliable TSC.
 */
if (boot_cpu_has(X86_FEATURE_TSC_RELIABLE)) {
    clocksource_register_khz(&clocksource_tsc, tsc_khz);
    return 0;
}

Il devrait y avoir un processus pour détecter la fréquence TSC lorsque l'horloge kvm est reconnue, donc Modifions le code de kvm-clock pour qu'il puisse être placé à l'intérieur de ʻif` dans la partie ci-dessus.

La source de la cible «kvm-clock» est la suivante. Vous pouvez voir que kvm_get_tsc_khz est appelé pendant le processus d'initialisation de l'invité exécuté sur KVM. D'après les commentaires dans le code source, nous pouvons en quelque sorte comprendre que la fréquence TSC est calculée tôt ici.

static unsigned long kvm_get_tsc_khz(void)
{
        struct pvclock_vcpu_time_info *src;
        int cpu;
        unsigned long tsc_khz;
   ...
        src = &hv_clock[cpu].pvti;
        tsc_khz = pvclock_tsc_khz(src);
        preempt_enable();
        return tsc_khz;
}
...
void __init kvmclock_init(void)
{
    ...
        x86_platform.calibrate_tsc = kvm_get_tsc_khz;
        x86_platform.calibrate_cpu = kvm_get_tsc_khz;
...

Si vous êtes un utilisateur Linux, vous savez probablement que les indicateurs de processeur sont gérés par les macros ʻarch / x86 / kernel / cpu / mkcapflags.pl et ʻarch / x86 / include / asm / cpufeature.h. Quand j'ai cherché quelque chose qui a forcé le drapeau, j'ai trouvé ce qui suit: ʻarch / x86 / include / asm / cpufeature.h`.

#define set_cpu_cap(c, bit)    set_bit(bit, (unsigned long *)((c)->x86_capability))

extern void setup_clear_cpu_cap(unsigned int bit);
extern void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int bit);

#define setup_force_cpu_cap(bit) do { \
    set_cpu_cap(&boot_cpu_data, bit);    \
    set_bit(bit, (unsigned long *)cpu_caps_set);    \
} while (0)

Utilisez ceci.

J'ai écrit le patch suivant et je l'ai vu. vol.2

diff -uNrp linux-3.10.0-957.el7.x86_64/arch/x86/kernel/kvmclock.c linux-3.10.0-957.el7.x86_64/arch/x86/kernel/kvmclock.c
--- linux-3.10.0-957.el7.x86_64/arch/x86/kernel/kvmclock.c    2019-07-29 02:35:27.318987845 +0000
+++ linux-3.10.0-957.el7.x86_64/arch/x86/kernel/kvmclock.c    2019-07-29 03:04:11.015862936 +0000
@@ -338,6 +338,7 @@ void __init kvmclock_init(void)
 
     x86_platform.calibrate_tsc = kvm_get_tsc_khz;
     x86_platform.calibrate_cpu = kvm_get_tsc_khz;
+    setup_force_cpu_cap(X86_FEATURE_TSC_RELIABLE);
     x86_platform.get_wallclock = kvm_get_wallclock;
     x86_platform.set_wallclock = kvm_set_wallclock;
 #ifdef CONFIG_X86_LOCAL_APIC

C'est réparé. vol.2

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
kvm-clock tsc acpi_pm 

$  dmesg | grep -iE '(clocksource|tsc)'
[    0.000000] Linux version 3.10.0-957.21.3.el7.tsc_fixed.x86_64...
...
[    0.000000] tsc: Detected 3000.000 MHz processor
[    1.832686] TSC deadline timer enabled
[    1.929653] Skipped synchronization checks as TSC is reliable.
[    2.598602] Switched to clocksource kvm-clock
[    3.078334] No.1, func: init_tsc_clocksource, line: 1309, file: arch/x86/kernel/tsc.c

Le patch ne peut pas être écrit!

Le noyau utilisé dans CentOS / RHEL est un peu vieux. Donc, pour être honnête, je pense que la plupart des bogues que vous trouvez dans votre distribution ont déjà été corrigés en amont. Donc, quand je l'ai cherché, je l'ai trouvé facilement orz.

Meilleur correctif que la solution 1

Comme prévu, il a été modifié à l'échelle proportionnellement à la fréquence TSC. La méthode de comparaison de ʻULONG_MAX` a également été corrigée.

J'ai compris qu'il devrait être décalé quand il est proportionnel à une certaine valeur (pas. Je ne suis pas sûr des détails, mais je pensais que c'était un patch comme l'essence de CS. J'ai beaucoup appris sur ce genre de sentiment, donc je veux m'en servir la prochaine fois.

x86/tsc: Make calibration refinement more robust

Extrait uniquement pour les pièces caractéristiques

diff --git a/arch/x86/kernel/tsc.c b/arch/x86/kernel/tsc.c
index e9f777b..3fae238 100644
--- a/arch/x86/kernel/tsc.c
+++ b/arch/x86/kernel/tsc.c
@@ -297,15 +297,16 @@ static int __init tsc_setup(char *str)
 
 __setup("tsc=", tsc_setup);
 
-#define MAX_RETRIES     5
-#define SMI_TRESHOLD    50000
+#define MAX_RETRIES		5
+#define TSC_DEFAULT_THRESHOLD	0x20000
 
 /*
- * Read TSC and the reference counters. Take care of SMI disturbance
+ * Read TSC and the reference counters. Take care of any disturbances
  */
 static u64 tsc_read_refs(u64 *p, int hpet)
 {
 	u64 t1, t2;
+	u64 thresh = tsc_khz ? tsc_khz >> 5 : TSC_DEFAULT_THRESHOLD;
 	int i;
 
 	for (i = 0; i < MAX_RETRIES; i++) {
@@ -315,7 +316,7 @@ static u64 tsc_read_refs(u64 *p, int hpet)
 		else
 			*p = acpi_pm_read_early();
 		t2 = get_cycles();
-		if ((t2 - t1) < SMI_TRESHOLD)
+		if ((t2 - t1) < thresh)
 			return t2;
 	}
 	return ULLONG_MAX;

Patch similaire à la solution 2

Le code RHEL est un peu ancien, il y a donc une légère différence dans les indicateurs de CPU que vous définissez Pour être honnête, je n'ai pas très bien compris la signification de l'insertion de setup_force_cpu_cap dans ce bloc. Ce code devra réinitialiser l'indicateur CPU plusieurs fois. Je me demande ce que cela signifie, mais cela peut avoir quelque chose à faire.

kvmclock: fix TSC calibration for nested guests

diff --git a/arch/x86/kernel/kvmclock.c b/arch/x86/kernel/kvmclock.c
index d79a18b..4c53d12 100644
--- a/arch/x86/kernel/kvmclock.c
+++ b/arch/x86/kernel/kvmclock.c
@@ -138,6 +138,7 @@ static unsigned long kvm_get_tsc_khz(void)
 	src = &hv_clock[cpu].pvti;
 	tsc_khz = pvclock_tsc_khz(src);
 	put_cpu();
+	setup_force_cpu_cap(X86_FEATURE_TSC_KNOWN_FREQ);
 	return tsc_khz;
 }

Épilogue

Linux est difficile mais amusant. Au fait, j'adore Rust et j'écris même un chargeur de démarrage pour Linux. Il s'agit également d'un article d'introduction, veuillez donc le lire si vous le souhaitez.

Une histoire sur la création d'un chargeur de démarrage x86 qui peut démarrer vmlinux avec Rust --Qiita

N'hésitez pas à commenter si vous avez des conseils comme celui-ci n'est pas une bonne idée. Étudiera!

Recommended Posts

Je ne trouve pas l'horloge tsc! ?? L'histoire d'essayer d'écrire un patch de noyau
L'histoire d'essayer de reconnecter le client
L'histoire de l'adresse IPv6 que je souhaite conserver au minimum
L'histoire d'un technicien de haut niveau essayant de prédire la survie du Titanic
Une histoire sur la tentative d'introduire Linter au milieu d'un projet Python (Flask)
J'ai essayé de trouver la moyenne de plusieurs colonnes avec TensorFlow
Ecrire un programme python pour trouver la distance d'édition [python] [distance Levenshtein]
J'ai fait une fonction pour vérifier le modèle de DCGAN
Comment trouver le coefficient de mise à l'échelle d'une ondelette bipolaire
Je veux trouver l'intersection d'une courbe de Bézier et d'une ligne droite (méthode de découpage de Bézier)
[Introduction à StyleGAN] J'ai joué avec "The Life of a Man" ♬
J'ai écrit le code pour écrire le code Brainf * ck en python
Comment trouver l'adresse mémoire de la valeur de la trame de données Pandas
Une histoire dans laquelle l'algorithme est arrivé à une conclusion ridicule en essayant de résoudre correctement le problème du voyageur de commerce
L'histoire de l'exportation d'un programme
[Python] Une fonction simple pour trouver les coordonnées du centre d'un cercle
Une histoire d'essayer d'améliorer le processus de test d'un système vieux de 20 ans écrit en C
J'ai essayé de vérifier la meilleure façon de trouver un bon partenaire de mariage
zoom J'ai essayé de quantifier le degré d'excitation de l'histoire lors de la conférence
Une histoire à laquelle j'étais accro à essayer d'installer LightFM sur Amazon Linux
Une histoire à laquelle j'étais accro à essayer d'obtenir une URL de vidéo avec tweepy
Un mémorandum sur la façon d'écrire des pandas que j'ai tendance à oublier personnellement
Je souhaite trier une liste dans l'ordre des autres listes
L'histoire d'un débutant en apprentissage profond essayant de classer les guitares avec CNN
J'ai essayé de trouver l'itinéraire optimal du pays des rêves par recuit (quantique)
J'ai essayé d'extraire et d'illustrer l'étape de l'histoire à l'aide de COTOHA
J'ai fait un programme pour vérifier la taille d'un fichier avec Python
J'ai essayé d'afficher la valeur d'altitude du DTM dans un graphique
J'ai essayé l'histoire courante de l'utilisation du Deep Learning pour prédire la moyenne Nikkei
J'ai essayé de vérifier le résultat du test A / B avec le test du chi carré
L'histoire de la tentative de pousser SSH_AUTH_SOCK obsolète avec LD_PRELOAD à l'écran
Python: je souhaite mesurer proprement le temps de traitement d'une fonction
J'ai créé une fonction pour voir le mouvement d'un tableau à deux dimensions (Python)
Faisons un noyau jupyter
L'histoire de la mise en place de MeCab dans Ubuntu 16.04
L'histoire d'essayer deep3d et de perdre
L'histoire du traitement A du blackjack (python)
L'histoire du changement de pep8 en pycodestyle
J'ai fait un outil pour estimer le temps d'exécution de cron (+ débuts de PyPI)
N'hésitez pas à rédiger un test avec nez (dans le cas de + gevent)
[Circuit x Python] Comment trouver la fonction de transfert d'un circuit en utilisant Lcapy
Un débutant en programmation a essayé de vérifier le temps d'exécution du tri, etc.
Notez la solution car django n'a pas pu s'installer avec pip
J'ai créé une commande appdo pour exécuter des commandes dans le contexte de l'application
Je souhaite définir un cycle de vie dans la définition de tâche d'ECS
Je veux ajouter du silence pendant 1 seconde au début d'un fichier wav
Je souhaite voir une liste de fichiers WebDAV dans le module Requêtes
J'ai créé un outil pour sauvegarder automatiquement les métadonnées de l'organisation Salesforce
[GAN] J'ai vu les ténèbres essayer de faire évoluer davantage l'évolution finale de Pokemon
J'ai fait un script pour enregistrer la fenêtre active en utilisant win32gui de Python
L'histoire de la fabrication de soracom_exporter (j'ai essayé de surveiller SORACOM Air avec Prometheus)
Je n'ai pas eu besoin d'écrire décorateur en classe Merci contextmanager
J'ai essayé de créer un modèle avec l'exemple d'Amazon SageMaker Autopilot
Comment calculer la volatilité d'une marque
Comment trouver la zone du diagramme de Boronoi
Trouver la main de "Millijan" par l'optimisation des combinaisons
J'ai essayé de trouver le rapport de circonférence par 100 millions de chiffres