Как процессор распределяет регистры между потоками?

Если любые вычисления процессор вынужден проводить через регистры, даже перемещение одного фрагмента памяти в другой: он должен переместить память в регистр, а потом из регистра обратно в память. Процессор ещё и выполняет несколько операций параллельно. Но как он может это делать, если регистров общего назначения не так уж и много. Плюс, с каким регистром работать в конкретной команде, должно быть зашито в программе. Это можно понять, если рассматривать многоядерные процессоры, но процессор выполняет намного больше операций за раз, чем ядер в нём. Не стоит забывать и о одноядерных процессорах, ведь они быстро переключаются между задачами. Каким же образом информация в регистрах не смешивается от выполнения разных программ?


Ответы (1 шт):

Автор решения: PRODIGY

Каким же образом информация в регистрах не смешивается от выполнения разных программ?

Для поддержки многозадачности процессоры используют "сегмент состояния задачи" 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
→ Ссылка