Как процессор распределяет регистры между потоками?
Если любые вычисления процессор вынужден проводить через регистры, даже перемещение одного фрагмента памяти в другой: он должен переместить память в регистр, а потом из регистра обратно в память. Процессор ещё и выполняет несколько операций параллельно. Но как он может это делать, если регистров общего назначения не так уж и много. Плюс, с каким регистром работать в конкретной команде, должно быть зашито в программе. Это можно понять, если рассматривать многоядерные процессоры, но процессор выполняет намного больше операций за раз, чем ядер в нём. Не стоит забывать и о одноядерных процессорах, ведь они быстро переключаются между задачами. Каким же образом информация в регистрах не смешивается от выполнения разных программ?
Ответы (1 шт):
Каким же образом информация в регистрах не смешивается от выполнения разных программ?
Для поддержки многозадачности процессоры используют "сегмент состояния задачи" TSS (Task State Segment). У каждой задачи он свой, куда и сохраняются при переключении задач планировщиком текущие значения её регистров РОН.
Но cpu сохраняет регистры в TSS только при "аппаратном переключении задач" в устаревшем режиме х32. Если-же ЦП находится в LongMode х64 (IA32e), то планировщик использует уже "программное переключение задач" через шлюзы. Здесь состояние регистров сохраняются не в TSS, а напрямую в структуре процесса "CONTEXT64".
В отличии от структуры TSS, в контекст дампятся буквально все регистры ЦП, включая DR/FPU/MMX/SSE/AVX
, а в TSS были только CS:EIP
и РОН. В некоторых случаях, на х32 это приводило к ошибкам, если задача использовала доп.регистры, типа векторных или с плавающей точкой. Если запросить эту структуру у отладчика WinDbg, можно наблюдать в ней порядок следования полей:
0: kd> dt _context -v
struct _CONTEXT, <------------------ 64 elements, 0x4d0 bytes = 1.232
+0x000 P1Home : Uint8B
.......
+0x028 P6Home : Uint8B
+0x030 ContextFlags : Uint4B
+0x034 MxCsr : Uint4B
+0x038 SegCs : Uint2B
+0x03a SegDs : Uint2B
+0x03c SegEs : Uint2B
+0x03e SegFs : Uint2B
+0x040 SegGs : Uint2B
+0x042 SegSs : Uint2B
+0x044 EFlags : Uint4B
+0x048 Dr0 : Uint8B
.......
+0x070 Dr7 : Uint8B
+0x078 Rax : Uint8B
.......
+0x0f0 R15 : Uint8B
+0x0f8 Rip : Uint8B
+0x100 FltSave : struct _XSAVE_FORMAT, 16 elements, 0x200 bytes
+0x100 Header : [2] struct _M128A, 2 elements, 0x10 bytes
+0x120 Legacy : [8] struct _M128A, 2 elements, 0x10 bytes
+0x1a0 Xmm0 : struct _M128A, 2 elements, 0x10 bytes
.......
+0x290 Xmm15 : struct _M128A, 2 elements, 0x10 bytes
+0x300 VectorRegister : [26] struct _M128A, 2 elements, 0x10 bytes
+0x4a0 VectorControl : Uint8B
+0x4a8 DebugControl : Uint8B
+0x4b0 LastBranchToRip : Uint8B
+0x4b8 LastBranchFromRip : Uint8B
+0x4c0 LastExceptionToRip : Uint8B
+0x4c8 LastExceptionFromRip : Uint8B
0: kd>
Не смотря на то, что в х64 переключение задач программное, сегмент TSS по прежнему в нём присутствует, но назначение его уже чуть иное (см.мануал Интела том3). Теперь в TSS оставили только три поля под указатели на стек RSP
Ring[0:1:2], и под 8 указателей на таблицы стека-прерываний IST (Int Stack Table). Интересно, что при обычном запросе отладчик не видит сегмент TSS64, но можно подобраться к содержимому этой структуры через родительские блоки, запросив их содержимое явно:
// Находим адрес "блока управления процессора" PCR (Control Region)
//-----------------------------------------------------------------
0: kd> !pcr
KPCR for Processor(0) at fffff800`02e0cd00: //<---- у каждого CPU он свой
Prcb: fffff800`02e0ce80
Irql: 00000000`00000000
IRR: 00000000`00000000
IDR: 00000000`00000000
IntMode: 00000000`00000000
IDT: 00000000`00000000
GDT: 00000000`00000000
TSS: 00000000`00000000 //<---- Нет адреса TSS
// Запрашиваем явно структуру PCR,
// где должен быть прописан указатель на сегмент TSS
//--------------------------------------------------
0: kd> dt _kpcr fffff80002e0cd00
ntdll!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : 0xfffff800`00b91000 _KGDTENTRY64
+0x008 TssBase : 0xfffff800`00b92080 _KTSS64 //<---- Нашли адрес!
+0x010 UserRsp : 0x2eec818
+0x018 Self : 0xfffff800`02e0cd00 _KPCR
+0x020 CurrentPrcb : 0xfffff800`02e0ce80 _KPRCB
+0x028 LockArray : 0xfffff800`02e0d4f0 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : 0x000007ff`fffd7000 Void
+0x038 IdtBase : 0xfffff800`00b91080 _KIDTENTRY64
.........
// Просматриваем структуру TSS х64,
// где видим не используемые в х64 поля с флагом "Reserved"
//---------------------------------------------------------
0: kd> dt _ktss64 0xfffff800`00b92080 -v
struct _KTSS64, //<----------------- 8 elements, 0x68 bytes
+0x000 Reserved0 : 0
+0x004 Rsp0 : 0xfffff880`05c37d70
+0x00c Rsp1 : 0
+0x014 Rsp2 : 0
+0x01c Ist : [8] 0
+0x05c Reserved1 : 0
+0x064 Reserved2 : 0
+0x066 IoMapBase : 0x68
// Адрес структуры TSS можно прочитать и напрямую из GDT,
// для чего нужно запросить селектор регистра TR (Task Reg).
// Только здесь он в виде смещения RVA от базы ядра,
// но всё-же совпадает с адресом выше = 0х00b92080.
//---------------------------------------------------------
0: kd> dg @tr
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0040 00000000`00b92080 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b