3.如何保存CPU现场信息
来源:互联网 发布:ipadmini1实用软件 编辑:程序博客网 时间:2024/05/14 18:35
3.讲完了进程和线程之后,我们的核心问题块来临了,操作系统到底是采用什么策略进行进程或线程调度的,也就是决定让哪个线程或者进程进入到CPU里面去执行。在讲这个问题之前我们还有一个很重要的上课讲的内容,进程或者线程切换的时候,我们如何保存其运行信息?
3.1线程的切换
我花费好几个小时去找这个的内容,因为我借的两本书上都没讲代码,而是直接给出了原理和答案,好在我还是找到了线程切换的一部分代码,但是我看了之后真的是发现好痛苦,不同的处理器体系结构代码是不同的,下面我以简单i386体系结构为例,来简单的描述一下操作系统是如何进程线程切换的,这里涉及到了很多的概念和内容,是需要对前面的内容进程拓展才能理解清楚的,相关方面的内容只能是自己去看书了。在WRK-v1.2\base\ntos\ke\amd64\Ctxswap.asm里面我们终于看到了代码:
includeksamd64.inc
......
;BOOLEAN
;KiSwapContext (
; IN PKTHREAD OldThread,
; IN PKTHREAD NewThread
; )
NESTED_ENTRY KiSwapContext, _TEXT$00
GENERATE_EXCEPTION_FRAME ; 生成异常页
mov rbx, gs:[PcCurrentPrcb] ; 获得当前PRCB地址
mov rdi, rcx ; 旧线程地址换到rdi中
mov rsi, rdx ; 新线程地址换到rsi总
movzx ecx, byte ptr ThWaitIrql[rdi] ; 禁止APC机制的中断影响
call SwapContext ; //调用SwapContext
RESTORE_EXCEPTION_STATE ;
ret ; 返回
NESTED_END KiSwapContext, _TEXT$00
subttl "Dispatch Interrupt"
/*这段程序是作为一个发生在 DISPATCH_LEVEL上的软中断的结果的输入,它的功能是处理DPC链表,当一个新的线程已经准备好到一个处理器上运行时,这个程序将进行新旧线程的上下文切换*/
DiFrame struct
P1Home dq ? ; PRCB地址段
P2Home dq ? ;
P3Home dq ? ;
P4Home dq ? ;
SavedRbx dq ? ; 保存RBX
DiFrame ends
NESTED_ENTRYKiDispatchInterrupt, _TEXT$00
alloc_stack (sizeofDiFrame) ; 进行堆的分配
save_reg rbx, DiFrame.SavedRbx ; 保存非易失性寄存器的内容
END_PROLOGUE
lea rcx, (-128)[rbp] ; 设置trap地址
call KiCheckForSListAddress ; 检查SLIST地址
mov rbx, gs:[PcCurrentPrcb] ; 获得当前 PRCB地址
and byte ptr PbDpcInterruptRequested[rbx], 0;//清除请求
KiDI10: cli //上下文切换不能被中断,此处即禁止中断 ;
mov eax, PbDpcQueueDepth[rbx] ; 获取DPC 队列深度 or rax, PbTimerRequest[rbx] ; 合并计时器的值
ifndef NT_UP
or rax, PbDeferredReadyListHead[rbx] ; 合并延期准备表
endif
jz short KiDI20 ; if z, no DPCs to process
mov PbSavedRsp[rbx], rsp ; save current stack pointer
mov rsp, PbDpcStack[rbx] ; set DPC stack pointer
mov rcx, rbx ; set PRCB address parameter
call KiRetireDpcList ; process the DPC list
mov rsp, PbSavedRsp[rbx] ; restore current stack pointer
KiDI20: sti ; enable interrupts
cmp byte ptr PbQuantumEnd[rbx], 0 ; check ifquantum end request
je short KiDI40 ; if e, quantum end not requested
and byte ptr PbQuantumEnd[rbx], 0 ; clearquantum end indicator
call KiQuantumEnd ; process quantum end
KiDI30: mov rbx,DiFrame.SavedRbx[rsp] ; restore nonvolatile register
add rsp, (sizeof DiFrame) ; deallocate stack frame
ret ; return
mov rbx, DiFrame.SavedRbx[rsp] ; restorenonvolatile register
add rsp, (sizeof DiFrame) ; deallocate stack frame
jmp short KxDispatchInterrupt ;
NESTED_ENDKiDispatchInterrupt, _TEXT$00
NESTED_ENTRYKxDispatchInterrupt, _TEXT$00
GENERATE_EXCEPTION_FRAME ; generate exception frame
mov rbx, gs:[PcCurrentPrcb] ; get current PRCBaddress
mov rdi, PbCurrentThread[rbx] ; get old threadaddress
ifndef NT_UP
mov ecx, SYNCH_LEVEL ; set IRQL to SYNCH level
SetIrql ;
mov byte ptr ThSwapbusy[rdi], 1 ; set contextswap busy
AcquireSpinLockPbPrcbLock[rbx] ; acquire current PRCB lock
endif
mov rsi, PbNextThread[rbx] ; get next thread address
and qword ptr PbNextThread[rbx], 0 ; clear next thread address
mov PbCurrentThread[rbx], rsi ; set currentthread address
mov byte ptr ThState[rsi], Running ; set newthread state
mov byte ptr ThWaitReason[rdi], WrDispatchInt; set wait reason
mov rcx, rdi ; set address of old thread
mov rdx, rbx ; set address of current PRCB
call KiQueueReadyThread ; queue ready thread for execution
mov ecx,APC_LEVEL ; set APC interruptbypass disable
call SwapContext ; call context swap routine
ldmxcsr ExMxCsr[rsp] ; restore current MXCSR value
RESTORE_EXCEPTION_STATE ; restore exception state/deallocate
ret ; return
NESTED_ENDKxDispatchInterrupt, _TEXT$00
subttl "Swap Context"
NESTED_ENTRY SwapContext,_TEXT$00
alloc_stack(KSWITCH_FRAME_LENGTH - (1 * 8)) ; allocate stack frame
save_reg rbp, SwRbp ; save nonvolatile register
END_PROLOGUE
mov SwApcBypass[rsp], cl ; save APC bypass disable
ifndef NT_UP
KiSC00: cmp byte ptrThSwapBusy[rsi], 0 ; check if swap busy for new thread
Yield ; yield processorexecution
jne short KiSC00 ; if ne, context busy for newthread
endif
mov rax, (PcPerfGlobalGroupMask - PcPrcb)[rbx]; get global mask address
test rax, rax ; test if logging enabled
je short KiSC05 ; if e, logging not enabled
test dword ptr PERF_CONTEXTSWAP_OFFSET[rax],PERF_CONTEXTSWAP_FLAG ; check flag
jz short KiSC05 ; if z, context swap events notenabled
mov rcx, rdi ; set address of old thread
mov rdx, rsi ; set address of new thread
call WmiTraceContextSwap ; call trace routine
KiSC05: cmp byte ptrThNpxState[rdi], LEGACY_STATE_SWITCH ; check if switched
jne short KiSC10 ; if ne, legacy state not switched
mov rbp, ThInitialStack[rdi] ; get previousthread initial stack
fxsave [rbp] ; save legacy floatingpoint state
KiSC10: mov ThKernelStack[rdi],rsp ; save old kernel stack pointer
mov rsp, ThKernelStack[rsi] ; get new kernelstack pointer
mov r14, ThApcState + AsProcess[rsi] ; get newprocess address
cmp r14, ThApcState + AsProcess[rdi] ; checkif process match
je short KiSC20 ; if e, process addresses match
ifndef NT_UP
mov rdx, ThApcState + AsProcess[rdi] ; get oldprocess address
mov rcx, PbSetMember[rbx] ; get processor set member
lock xor PrActiveProcessors[rdx], rcx ; clear bitin previous set
if DBG
test PrActiveProcessors[rdx], rcx ; test if bitclear in previous set
jz short @f ; if z, bit clear in previous set
int 3 ; debug break -incorrect active mask
@@: ; reference label
Endif
endif
ifndef NT_UP
lock xor PrActiveProcessors[r14], rcx ; set bit innew set
if DBG
test PrActiveProcessors[r14], rcx ; test if bitset in new set
jnz short @f ; if nz, bit set in new set
int 3 ; debug break -incorrect active mask
@@: ; reference label
endif
endif
mov rdx, PrDirectoryTableBase[r14] ; get newdirectory base
mov cr3, rdx ; flush TLB and set newdirectory base
KiSC20: ;
ifndef NT_UP
mov byte ptr ThSwapBusy[rdi], 0 ; set context swap idle
endif
mov r15, (PcTss - PcPrcb)[rbx] ; get processorTSS address
mov rbp, ThInitialStack[rsi] ; get new stackbase address
mov TssRsp0[r15], rbp ; set stack base address in TSS
mov PbRspBase[rbx], rbp ; set stack base address in PRCB
cmp byte ptr ThNpxState[rsi],LEGACY_STATE_UNUSED ; check if kernel thread
je short KiSC30 ; if e, legacy state unused
fxrstor [rbp] ; restore legacy floatingpoint state
mov eax, ThTeb[rsi] ; compute compatibility mode TEBaddress
add eax, CmThreadEnvironmentBlockOffset ;
mov rcx, (PcGdt - PcPrcb)[rbx] ; get GDT baseaddress
mov KgdtBaseLow + KGDT64_R3_CMTEB[rcx], ax ;set CMTEB base address
shr eax, 16 ;
mov KgdtBaseMiddle + KGDT64_R3_CMTEB[rcx], al;
mov KgdtBaseHigh + KGDT64_R3_CMTEB[rcx],ah ;
mov eax, ds ; compute sum of segmentselectors
mov ecx, es ;
and eax, ecx ;
mov ecx, gs ;
and eax, ecx ;
cmp ax, (KGDT64_R3_DATA or RPL_MASK) ; checkif sum matches
je short KiSC25 ; if e, sum matches expected value
mov ecx, KGDT64_R3_DATA or RPL_MASK ; reloaduser segment selectors
mov ds, ecx ;
mov es, ecx ;
mov eax, (PcSelf - PcPrcb)[rbx] ; get currentPCR address
mov edx, (PcSelf - PcPrcb + 4)[rbx] ;
cli ; disableinterrupts
mov gs, ecx ; reload GS segment selector
mov ecx, MSR_GS_BASE ; get GS base MSR number
wrmsr ; write system PCRbase address
sti ; enableinterrupts
KiSC25: mov eax, KGDT64_R3_CMTEBor RPL_MASK ; reload FS segment selector
mov fs, eax ;
mov eax, ThTeb[rsi] ; get low part of user TEB address
mov edx, ThTeb + 4[rsi] ; get high part of user TEB address
mov (PcTeb - PcPrcb)[rbx], eax ; set user TEBaddress in PCR
mov (PcTeb - PcPrcb + 4)[rbx], edx ;
mov ecx, MSR_GS_SWAP ; get GS base swap MSR number
wrmsr ; write user TEB base address
KiSC30: cmp byte ptrPbDpcRoutineActive[rbx], 0 ; check if DPC active
jne short KiSC50 ; if ne, DPC is active
inc dword ptr ThContextSwitches[rsi] ; threadcount
cmp byte ptr ThApcState +AsKernelApcPending[rsi], TRUE ; check if APC pending
jne short KiSC40 ; if ne, kernel APC not pending
movzx ax, byte ptr SwApcBypass[rsp] ; get disableIRQL level
or ax, ThSpecialApcDisable[rsi] ; mergespecial APC disable
jz short KiSC40 ; if z, kernel APC enabled
mov ecx, APC_LEVEL ; request APC interrupt
call __imp_HalRequestSoftwareInterrupt ;
or rcx, rsp ; clear ZF flag
KiSC40: setz al ; set return value
mov rbp, SwRbp[rsp] ; restore nonvolatile register
add rsp, KSWITCH_FRAME_LENGTH - (1 * 8) ;deallocate stack frame
ret ; return
KiSC50: xor r9, r9 ; clear register
mov SwP5Home[rsp], r9 ; set parameter 5
mov r8, rsi ; set new thread address
mov rdx, rdi ; set old thread address
mov ecx, ATTEMPTED_SWITCH_FROM_DPC ; setbugcheck code
call KeBugCheckEx ; bugcheck system - no return
ret ; return
NESTED_END SwapContext,_TEXT$00
end
光是这段代码就能让我疯掉,而事实上我只解析了一部分就放弃了,因为这段代码有英文注释,我也就没全部翻译过来,但是大体上还是明白了到底如何保存上下文信息。
从代码里面我们发现虽然系统调用的是KiSwapContext,但是其实系统调用 的SwapContext函数,KiSwapContext函数有两个参数,一个是OldThread,一个是NewThread,代码里,系统通过寄存器获取旧指令的指令指针,内核栈的指针,运行地址空间的指针,然后将这些信息保存到当前线程的内核模式栈中,更新栈指针,然后将栈指针保存在当前线程的KTHREAD块中,然后,内核栈指针被设置成新线程的内核栈,此时,新线程就变成了当前进程,但是此时还没有切换页面映射表,如果新旧线程属于同一个进程,就不需要切换,但是如果不属于同一进程,那么系统会将新进程的页表目录加载到一个专门的处理器寄存器中,从而使用新进程的地址空间,接着就是TEB的切换了,这需要去修改GDT中的TEB项,从之前的内容我们找到各个线程的的TEB段起点地址保存在Teb字段中,在代码里面我发现程序是直接禁掉了APC,但是根据潘爱民先生和毛德操先生对这里的解释不是很清楚,而根据我自己的对代码的理解,在进行上下文切换的时候是禁掉了APC,之后才使能的,而如果上下文切换期间发生了APC请求,就会产生一个IRQL的中断,上下文切换之后就会去检查是否有这个中断,如果有的话是要去执行的,到这里为止,算是比较简单的了解了一下线程的上下文切换了,关于此部分内容真是是很难弄懂,我也是磕磕碰碰的了解了一点。
3.2进程和线程的优先级问题。
Windows下线程是从两个角度来分配的,Windows API和Winsows内核,Windows API首先根据进程创建时所分配的攸县级类别来组织线程(数字代表能被内核识别的内部PROCESS_PRIORITY_CLASS_索引):实时:(4),高(3),普通(2),普通之上(6),普通之下(5),空闲(1),未知(0)。
#define PROCESS_PRIORITY_CLASS_UNKNOWN 0
#definePROCESS_PRIORITY_CLASS_IDLE 1
#definePROCESS_PRIORITY_CLASS_NORMAL 2
#definePROCESS_PRIORITY_CLASS_HIGH 3
#definePROCESS_PRIORITY_CLASS_REALTIME 4
#definePROCESS_PRIORITY_CLASS_BELOW_NORMAL 5
#definePROCESS_PRIORITY_CLASS_ABOVE_NORMAL 6
然后分配这些进程内部单个线程的相对优先级。这里数字代表了应用于进程基本优先级的差值:时间关键(15),最高(2),普通之上(1),普通(0),普通之下(-1),最低(-2),空闲(-15)。
#define THREAD_PRIORITY_TIME_CRITICAL 15
#define THREAD_PRIORITY_HIGHEST 2
#define THREAD_PRIORITY_ABOVE_NORMAL (1)
#define THREAD_PRIORITY_NORMAL (0)
#define THREAD_PRIORITY_BELOW_NORMA (-1)
#define THREAD_PRIORITY_LOWEST (-2)
#define THREAD_PRIORITY_IDLE (-15)
在内核中,进程优先级类别通过PspPriortyTable表和前面讲的PROCESS_PRIORITY_CLASS索引值,转换成一个基本优先级,这些优先级分别被设为4,8,13,14,6和10,然后,基本的线程优先级被作为一个差值,加到基本的进程优先级上。
const KPRIORITYPspPriorityTable[PROCESS_PRIORITY_CLASS_ABOVE_NORMAL+1] = {8,4,8,13,24,6,10};
#define LOW_PRIORITY 0 // Lowest thread priority level
#define LOW_REALTIME_PRIORITY 16 // Lowest realtime priority level
#define HIGH_PRIORITY 31 // Highest thread priority level
#define MAXIMUM_PRIORITY 32 // Number of thread priority levels
此问题算是比较圆满的解答了,我们也知道了进程和线程的优先级是怎么回事了,当让我们可以往下追踪一些很有意思的内容,这里就不在赘述了。
3.3线程状态
关于这个就不详细解释了,很简单的。而且这个不同的操作系统版本给出的解释同,也就无所谓了,源代码如下:
typedef enum _KTHREAD_STATE {
Initialized,
Ready,
Running,
Standby,
Terminated,
Waiting,
Transition,
DeferredReady,
GateWait
} KTHREAD_STATE;
3.3分发器数据库
讲完了上面的内容,我们还是不知道操作系统去哪儿找就绪队列,接着去执行。接下来我们就去解决这个问题。如果让你来设计操作系统,你会如何做呢,是让一个寄存器保存就绪队列的启示地址,然后给出一个偏移?如果这样的话,是不是得需要好几个寄存器分别取记录,挂起队列,等待队列等等,如果只有三态的话也无所谓了,但是事实上的线程状态远远多于三态,所以,我们应该从全局的高度来做这件事,给一个专门的一组结构去记录这些线程状态的信息,操作系统只需要去访问这些结构,其他的信息就由这些结构去负责统计数据,这样就各司其职,模块化设计了,不多说了,来一张图:
每个处理器不会去管有多少个除就绪外的其他队列,它只关注就绪队列,其余的都不是它的第一选择,所以windows多处理器系统每个处理器都有一个分发器就绪队列数据器,每个CPU只需要去检查它自己的就绪队列来找到下一个要运行的线程,而不必对整个系统范围内进行搜索。
3.4运行时间
关于这个问题在网上有很多人对此展开了一些讨论,主要问题是windows是基于抢先式多任务系统,但是系统为每个程序分配一定的CPU时间,当程序的运行超过规定时间后,系统就会中断该程序并把CPU控制权转交给别的程序,这不是轮转时间片多任务系统?其实看了代码你就会发现这根本不冲突的,首先多任务系统我们最好的办法就是时间片轮转法了,但是这样的话优先级就没意义了,所以我们才有了抢占式多任务系统的说法。那么每个线程到底运行多久时间?这个问题还真的是难道我了,依据网上和我自己从书上看到的信息来说的话能理解清楚,但是在代码里我还真没找到过这些数据,后来才知道,这些都不是直接固定的,是和硬件有关的,下面我摘引一下《深入理解Windows操作系统》一书中的简要介绍:“时钟间隔的长度随着硬件平台的不同而有所不同,始终中断的平率取决于HAL,而不是内核,例如,大多数x86单处理器系统的时钟间隔是10毫秒左右,而大多数x86和x64多处理器系统则是15毫秒左右,这一时间间隔值保存在内核变量KeMaximuumIncrement中,它的单位是百纳秒。
系统使用处理器周期数为线程运行时间记账,因此,虽然线程运行时间仍是时钟间隔的整数倍,系统却并不使用时钟间隔的数量作为主要依据来衡量一个线程运行了多少时间,或它是否用完它的时限。而是,系统在启动时计算好一个时钟间隔所等价的处理器时钟周期数。计算过程是吧处理器速度的赫兹书(每秒的CPU周期数)乘以单个时钟间隔所对应的秒数(基于前面讲过的KeMaximumIncrement)。”
上述便是潘爱民老师给出的解释了,然后我仔细的在WRK-v1.2里面找相关的代码,很可惜,除了找到一个KeMaximumIncrement外,其余的代码就没看到了,不过之后我还是会去看看的,算是一个遗憾吧。那运行时间到底如何算呢?目标时限值=时限重置值*每时限单元的时钟周期数,其中时限重置值是在KTHREAD结构里面的一个参数,而时限单元值被保存为时钟嘀嗒的1/3,这意味着客户版本的Windows系统上,默认情况下,线程的时限重置值为6(2*3),而在服务器上,时限值为36(12*3)。这些都是理解来的,但是相关代码我看了一下,在很多的地方都是零零散散的存在,这里就不详细的看了,总结一下就是,没有更高优先级来抢占的情况下,一个正在运行的线程在运行完自己的时限的时候是有保障的,但是一旦出现更高优先级的线程来抢占下,就立马让出自己的“位置”给更高优先级的线程。
3.5优先级的改变
优先级是不是一直不变呢?想想都知道肯定是不行的,主要是因为如果一个低优先级的线程一直被抢占,那很有可能一直轮不到它执行,但是如果这个低优先级的线程的结果对某个高优先级的线程很重要呢,想想这种情况,所以在设计的时候,就有一套机制来保证一个低优先级的线程不会处于永远等待的情况。优先级提升的情况有如下几种:
1.由于调度器/分发器时间而提升
2.由于I/O操作完成而提升
3.由于UI输入而提升
4.由于线程等待某个执行体资源的时间太长而提升
5.一个准备运行的线程在相当一段时间内没有被运行过而提升
这里就不详细介绍了,我也还没完全弄懂,设计的地方不是一个地方,我开篇就讲过,很多东西没有达到那个高度的时候是没办法理解的。算是给自己一个偷懒的理由。
- 3.如何保存CPU现场信息
- UiAutomator——如何保存测试异常现场@Before @After
- android开发 - 现场保存
- git stash 保存现场 恢复现场
- Linux 下如何查看cpu信息
- winCE下如何永久保存注册表信息
- 如何保存分页信息及查询条件!
- 如何保存完整的异常信息
- CPU信息
- cpu信息
- cpu信息
- CPU 信息
- 如何进行现场演示
- 如何让新版的top显示多个cpu信息?
- AIX和Linux下如何查看CPU和内存信息
- AIX和Linux下如何查看CPU和内存信息
- Ubuntu Linux下如何查看机器型号和CPU信息
- AIX和Linux下如何查看CPU和内存信息
- 2.进程和线程的结构是什么?
- 关于活动的一些小技巧
- HTML-超链接标签
- 还不知道当listview数据为空时如何处理吗---------为项目建立一个统一的EmptyView
- python 语法的基础-数据类型
- 3.如何保存CPU现场信息
- arp ip mac
- C++中类之间的关系
- HTML-表格标签
- Java语句
- 4.线程到底是如何调度和切换的?
- 对于虚函数概念的理解
- 今日学习札记——C++Primer补充1(11.7)
- 关于web性能的思考与分享[04]——页面优化方案