Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Linux "Thread-Information-Block" und Linux Null Thread-ID

3 views
Skip to first unread message

Bonita Montero

unread,
Dec 6, 2020, 4:37:20 AM12/6/20
to
Unter Windows wird im x86-Modus das FS-Register als Basis des Thread
-Information-Blocks verwendet. Da findet sich z.B. die global eindeu-
tige Thread-ID, die Stack-Boundaries u.s.w. ([*]). Ich benutze das
in einem eigenen Mutex, dass einen Schreiber oder mehrere Leser mit
Priorität des Schreibers erlaubt um den Schreiber-Zustand rekursiv
zu machen (wenn Schreiber && Schreiber-Thread-ID == aktuelle Thread
-id ...).
Aktuell habe ich eine kleine Thread-ID-Klasse, und die sieht so aus:

#pragma once
#if defined(_MSC_VER)
#include <Windows.h>
#include <intrin.h>
#elif defined(__unix__)
#include <pthread.h>
#endif

struct thread_id
{
thread_id();
thread_id &operator =( thread_id const &other );
bool operator ==( thread_id const &other );
thread_id &to_self();
static
thread_id self();
private:
#if defined(_MSC_VER)
DWORD m_dwThreadId;
#elif defined(__unix__)
bool m_set;
int m_threadId;
#else
#error "unsupported platform for thread_id"
#endif
};

inline
thread_id::thread_id()
{
#if defined(_MSC_VER)
m_dwThreadId = 0;
#elif defined(__unix__)
m_set = false;
#endif
}

inline
thread_id &thread_id::operator =( thread_id const &other )
{
#if defined(_MSC_VER)
m_dwThreadId = other.m_dwThreadId;
#elif defined(__unix__)
m_set = other.m_set;
m_threadId = other.m_threadId;
#endif
return *this;
}

inline
bool thread_id::operator ==( thread_id const &other )
{
#if defined(_MSC_VER)
return m_dwThreadId == other.m_dwThreadId;
#elif defined(__unix__)
return m_set && other.m_set && m_threadId == other.m_threadId;
#endif
}

inline
thread_id &thread_id::to_self()
{
thread_id tid;
#if defined(_MSC_VER)
#if defined(_M_X64)
m_dwThreadId = __readgsdword( 0x30 );
#elif defined(_M_IX86)
m_dwThreadId = __readfsdword( 0x18 );
#else
#error "not supported Windows-platform"
#endif
#elif defined(__unix__)
m_set = true;
m_threadId = pthread_self();
#endif
return *this;
}

inline
thread_id thread_id::self()
{
thread_id tid;
return tid.to_self();
}

Wie man sieht benutze ich unter Windows spezielle Intrinsics um die
Thread-ID aus dem TIB auszulesen. Das ist ca. fünfmal Schneller als
ein Aufruf von GetThreadId(). Etwas analoges wünsche ich mir auch
unter Linux.

Jetzt habe ich zwei Fragen:
1. gibt es unter Linux auch so eine Thead-ID in etwas analogem zum
Thread-Information-Block ?
2. Wenn nicht, dann muss ich wohl obigen Code ungefähr weiterverwenden.
Kann ich mir ggf. m_set ersparen und für m_Set == false eine reser-
vierte Thread-ID setzen bzw. ist vielleicht eine Thread-ID von Null
reserviert ? Mir würde ggf. sogar eine Aussage mit der Einschränkung
auf Linux reichen, denn auf anderen Unix-Plattformen soll mein Code
eh nicht laufen.

[*] https://en.wikipedia.org/wiki/Win32_Thread_Information_Block

Matthias Andree

unread,
Dec 6, 2020, 6:47:05 AM12/6/20
to
Am 06.12.20 um 10:37 schrieb Bonita Montero:
> Unter Windows wird im x86-Modus das FS-Register als Basis des Thread
> -Information-Blocks verwendet. Da findet sich z.B. die global eindeu-
> tige Thread-ID, die Stack-Boundaries u.s.w. ([*]). Ich benutze das
> in einem eigenen Mutex, dass einen Schreiber oder mehrere Leser mit
> Priorität des Schreibers erlaubt um den Schreiber-Zustand rekursiv
> zu machen (wenn Schreiber && Schreiber-Thread-ID == aktuelle Thread
> -id ...).
> Aktuell habe ich eine kleine Thread-ID-Klasse, und die sieht so aus:

[...]

> Wie man sieht benutze ich unter Windows spezielle Intrinsics um die
> Thread-ID aus dem TIB auszulesen. Das ist ca. fünfmal Schneller als
> ein Aufruf von GetThreadId(). Etwas analoges wünsche ich mir auch
> unter Linux.
>
> Jetzt habe ich zwei Fragen:
> 1. gibt es unter Linux auch so eine Thead-ID in etwas analogem zum
>    Thread-Information-Block ?
> 2. Wenn nicht, dann muss ich wohl obigen Code ungefähr weiterverwenden.
>    Kann ich mir ggf. m_set ersparen und für m_Set == false eine reser-
>    vierte Thread-ID setzen bzw. ist vielleicht eine Thread-ID von Null
>    reserviert ? Mir würde ggf. sogar eine Aussage mit der Einschränkung
>    auf Linux reichen, denn auf anderen Unix-Plattformen soll mein Code
>    eh nicht laufen.
>
> [*] https://en.wikipedia.org/wiki/Win32_Thread_Information_Block

Du schreibst ganz viel zum "wie", aber nicht zum "was" oder vor allem
"warum". Warum braucht man das?

Warum genügen keine standardisierten Schnittstellen (unter Linux) wie z.
B. POSIX threads, OpenMP oder moderne C++-Features? Die möglicherweise
in 5 Jahren oder auf einem verwandten Betriebssystem auch funktionieren?

Ist die Zeit, in Implementierungsdetails herumzustochern, nicht seit
mindestens 20 Jahren vorbei?

Das sieht mir aus wie eine Mikrooptimierung (unter Windows 5x so
schnell, fein, aber wozu?), deren Zweck unklar bleibt aus Deinem
Posting. Und wirft die Frage auf, die Du aber selbst beantworten musst,
ob das Konzept (in der Gesamtschau) das richtige ist.

Florian Weimer

unread,
Dec 6, 2020, 6:52:19 AM12/6/20
to
* Bonita Montero:

> 1. gibt es unter Linux auch so eine Thead-ID in etwas analogem zum
> Thread-Information-Block ?

Uner Linux steht TCB-Adresse bei der x86-64-Architektur an der Stelle
%fs:0. D.h. das ist die Adresse des Wortes %fs:0 im normalen
Adreßraum. Diese Adresse ist eindeutig, solange der Thread läuft und
wird vom System nicht wiederverwendet. (Die Addresse unterscheidet
sich von dem, was pthread_self zurückgibt.)

Das war schon immer so und ist auch grundsätzlich notwendig dafür, daß
die TLS-ABI funktioniert. Wir haben das aber erst jüngst ausdrücklich
dokumentiert:

<https://gitlab.com/x86-psABIs/x86-64-ABI/-/commit/c506311c5e396f4>

Auf neueren GCC-Versionen kann man die Adresse mittels
__builtin_thread_pointer () auslesen. Seit GCC 6 funktioniert:

*(void *__seg_fs *) 0

Für ältere GCC-Versionen braucht es Inline-Assembly.

Bonita Montero

unread,
Dec 6, 2020, 7:25:00 AM12/6/20
to
> Ist die Zeit, in Implementierungsdetails herumzustochern, nicht seit
> mindestens 20 Jahren vorbei?

Ich habe halt eine Routine die sehr fein-granular synchronisiert.
Jetzt habe ich den direkten Zugriff auf die TIB mal ge-unrollt und
dann habe ich unter Windows einen ziemlich exakt 10-fachen Speedup
gemessen. Und der Zugriff hat halt einen wesentlichen Anteil an der
Performance wenn die Standard-Methode gewählt wird. Ich gehe davon
aus, dass das auch unter Linux nicht anders wäre da der GetCurrent
ThreadId()-Aufruf unter Windows so knapp ist wie er eben nur sein
kann, und Linux ist sicher mindestens auch so gut optimiert.

Bonita Montero

unread,
Dec 6, 2020, 7:32:41 AM12/6/20
to
> Uner Linux steht TCB-Adresse bei der x86-64-Architektur an der Stelle
> %fs: ...

Ich weiß jetzt gerade nicht wie sie heißt, aber es gibt eine Instruktion
die die lineare Adresse eines Segmentregisters in ein Register lädt. Ist
sicher schneller aus dem Segmentselektor-Cache geladen als aus dem RAM /
Cache.


Florian Weimer

unread,
Dec 6, 2020, 7:48:37 AM12/6/20
to
* Bonita Montero:
Sie nennt sich READFSBASE. Die CPU muß dafür aber neu genug sein
(sollte kein Problem sein), und der Kernel muß die Instruktion
freischalten. Letzteres macht Linux erst seit diesem Jahr, d.h. man
muß zur Laufzeit testen, ob die Funktion unterstützt wird.

Der Vorteil gegenüber movq %fs:0, %rax dürfte die Komplexität nicht
wert sein (wenn man keinen JIT-Compiler hat).

Bonita Montero

unread,
Dec 6, 2020, 9:19:36 AM12/6/20
to
> Sie nennt sich READFSBASE. Die CPU muß dafür aber neu genug sein
> (sollte kein Problem sein), und der Kernel muß die Instruktion
> freischalten. Letzteres macht Linux erst seit diesem Jahr, d.h.
> man muß zur Laufzeit testen, ob die Funktion unterstützt wird.

Sowohl in der Intel- als auch AMD-Doku steht pauschal, dass die
Instruktion im 64-Bit-Modus verfügbar ist. Dass das erst seit
kurzem über das CR4-Register freigeschaltet werden muss hat ja
nichts zu heißen.

> Der Vorteil gegenüber movq %fs:0, %rax dürfte die Komplexität
> nicht wert sein (wenn man keinen JIT-Compiler hat).

Ich sehe da keine Komplexität bei so einer simplen Instruktion.

Ich habe einen Algorithmus wo mehrere hundert Millionen mal pro
Sekunde von verschiedenen Threads ein shared-lock gelockt wird.
Dazu habe ich ein shared Mutex mit Schreiber-Priorität. Das ist
aus einer atomaren 64-Bit-Variable mit drei 21 Bit Zählern (ex-
klusiv gelockt ein Bit und je 21 Bit für Anzahl der Threads die
auf ein exkusives Lock oder ein shared Lock warten und 21 Bit
für die die es aktuell gesharet gelockt haben) und zwei Sema-
phore mit denen entweder eine einer oder mehrere Sharer oder
ein exklusiver benachrichtigt wird. Dabei kann es in seltenen
Fällen sein, dass das Lock bereits vom selben Thread exklusiv
gelockt ist während es versucht, das im shared Modus zu locken.
In dem Fall soll das Lock dann rekursiv nochmal exklusiv gelockt
werden. Dazu frag ich das Lock-Bit ab und vergleiche ggf. dann
noch, die Thread-ID gleich der aktuellen Thread-ID ist. Und
daran hat die Abfrage der Thread-ID im konventionellen Stil
einen wesentlichen Anteil.

Florian Weimer

unread,
Dec 6, 2020, 9:50:49 AM12/6/20
to
* Bonita Montero:

>> Sie nennt sich READFSBASE. Die CPU muß dafür aber neu genug sein
>> (sollte kein Problem sein), und der Kernel muß die Instruktion
>> freischalten. Letzteres macht Linux erst seit diesem Jahr, d.h.
>> man muß zur Laufzeit testen, ob die Funktion unterstützt wird.
>
> Sowohl in der Intel- als auch AMD-Doku steht pauschal, dass die
> Instruktion im 64-Bit-Modus verfügbar ist. Dass das erst seit
> kurzem über das CR4-Register freigeschaltet werden muss hat ja
> nichts zu heißen.

Wenn der Kernel die Instruktion nicht freischaltet, erzeugt der
Prozessor eine Ausnahme bei der Ausführung. Deswegen braucht es den
Laufzeit-Test.

Bonita Montero

unread,
Dec 6, 2020, 10:06:56 AM12/6/20
to
>> Sowohl in der Intel- als auch AMD-Doku steht pauschal, dass die
>> Instruktion im 64-Bit-Modus verfügbar ist. Dass das erst seit
>> kurzem über das CR4-Register freigeschaltet werden muss hat ja
>> nichts zu heißen.

> Wenn der Kernel die Instruktion nicht freischaltet, erzeugt der
> Prozessor eine Ausnahme bei der Ausführung. Deswegen braucht es den
> Laufzeit-Test.

Das ist mir schon klar.
Aber dann ist die Software halt auf neuere Kernel beschränkt.

Matthias Andree

unread,
Dec 6, 2020, 4:57:37 PM12/6/20
to
Am 06.12.20 um 13:24 schrieb Bonita Montero:
>> Ist die Zeit, in Implementierungsdetails herumzustochern, nicht seit
>> mindestens 20 Jahren vorbei?
>
> Ich habe halt eine Routine die sehr fein-granular synchronisiert.

Liest sich wie ein Entwurfsfehler beim Aufgabenzuschnitt auf die Threads.

Bonita Montero

unread,
Dec 7, 2020, 11:08:50 AM12/7/20
to
>> Ich habe halt eine Routine die sehr fein-granular synchronisiert.

> Liest sich wie ein Entwurfsfehler beim Aufgabenzuschnitt auf die Threads.

Nein, das liegt bei mir in der Natur der Sache. Die feine Granulariät
ist nötig damit zwischenzeitliche Schreibzugriffe nicht zu sehr ausge-
bremst werden. Wenn nämlich ein Schreiber ran will, dann werden alle
weiteren die lesen wollen aufgestaut, dann müssen alle bisherigen
Leser ihren Lese-Zugriff aufgeben und dann ist der Schreiber dann.
Damit das nicht zu lange dauert müssen die Leser den Lese-Zugriff
möglichst kurz halten. Ggf. können sich die Schreiber die Klinke in
die Hand geben, denn die haben in letzterer Situation die Priorität
und könten theoretisch Party machen bis der Arzt kommt. Bei meinem
Szenario wollen Schreiber aber eine Größenordnung seltener ans Ruder
(dass die aber kurzfristig rankommen ist wichtig, und daher die feine
Granularität der Leser) da das Anwendungszenario eben so ist.
0 new messages