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.
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.
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
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.
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é.
$ 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
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
clocksource
elle-même.source d'horloge
qui est normalement mauvaise, vous devriez recevoir un message indiquant qu'elle est mauvaise.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
.
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
.
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
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é.
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
.
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.
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.
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
.
$ 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
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
.
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.
t1 = get_cycles ();
hpet
ou ʻacpi_pm` par accès IO.t2 = get_cycles ();
Donc, en résumé, ce code est problématique des manières suivantes:
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.
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.
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.Pensons concrètement à ce qui peut être fait avec les solutions de 1 et 2 ci-dessus.
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
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
$ 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
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.
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
$ 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 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.
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;
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;
}
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.
N'hésitez pas à commenter si vous avez des conseils comme celui-ci n'est pas une bonne idée. Étudiera!
Recommended Posts