Я прочитал ряд статей и ответы S / O, в которых говорилось, что (в linux x86_64) FS (или GS в некоторых вариантах) ссылается на запись таблицы страниц, зависящую от потока, которая затем дает массив указателей на фактические данные, которые доступны для совместного использования. данные. Когда потоки меняются местами, все регистры переключаются, и поэтому изменяется базовая страница с потоками. Доступ к многопоточным переменным осуществляется по имени с помощью всего 1 дополнительного перехода указателя, а значения, на которые имеются ссылки, могут использоваться другими потоками. Все хорошо и правдоподобно.
В самом деле, если вы посмотрите на код для __errno_location(void)
, функции, стоящей за errno
, вы найдете что-то вроде (это от musl, но gnu не сильно отличается):
static inline struct pthread *__pthread_self()
{
struct pthread *self;
__asm__ __volatile__ ("mov %%fs:0,%0" : "=r" (self) );
return self;
}
И из glibc:
=> 0x7ffff6efb4c0 <__errno_location>: endbr64
0x7ffff6efb4c4 <__errno_location+4>: mov 0x6add(%rip),%rax # 0x7ffff6f01fa8
0x7ffff6efb4cb <__errno_location+11>: add %fs:0x0,%rax
0x7ffff6efb4d4 <__errno_location+20>: retq
Поэтому я ожидаю, что фактическое значение FS изменится для каждого потока. Например. в отладчике, gdb: info reg
или p $fs
, я бы увидел, что значение FS будет различным в разных потоках, но нет: ds, es, fs, gs все время равны нулю.
В моем собственном коде я пишу что-то вроде ниже и получаю то же самое - FS неизменен, но TLV работает:
struct Segregs
{
unsigned short int cs, ss, ds, es, fs, gs;
friend std::ostream& operator << (std::ostream& str, const Segregs& sr)
{
str << "[cs:" << sr.cs << ",ss:" << sr.ss << ",ds:" << sr.ds
<< ",es:" << sr.es << ",fs:" << sr.fs << ",gs:" << sr.gs << "]";
return str;
}
};
Segregs GetSegRegs()
{
unsigned short int r_cs, r_ss, r_ds, r_es, r_fs, r_gs;
__asm__ __volatile__ ("mov %%cs,%0" : "=r" (r_cs) );
__asm__ __volatile__ ("mov %%ss,%0" : "=r" (r_ss) );
__asm__ __volatile__ ("mov %%ds,%0" : "=r" (r_ds) );
__asm__ __volatile__ ("mov %%es,%0" : "=r" (r_es) );
__asm__ __volatile__ ("mov %%fs,%0" : "=r" (r_fs) );
__asm__ __volatile__ ("mov %%gs,%0" : "=r" (r_gs) );
return {r_cs, r_ss, r_ds, r_es, r_fs, r_gs};
}
Но на выходе?
Main: Seg regs : [cs:51,ss:43,ds:0,es:0,fs:0,gs:0]
Main: tls @0x7ffff699307c=0
Main: static @0x96996c=0
Modified to 1234
Main: tls @0x7ffff699307c=1234
Main: static @0x96996c=1234
Async thread
[New Thread 0x7ffff695e700 (LWP 3335119)]
Thread: Seg regs : [cs:51,ss:43,ds:0,es:0,fs:0,gs:0]
Thread: tls @0x7ffff695e6fc=0
Thread: static @0x96996c=1234
Так что-то еще происходит на самом деле? Какие дополнительные уловки творится и зачем усложнять?
Для контекста я пытаюсь сделать что-нибудь забавное с вилками, поэтому я хотел бы знать кровавые детали.