day4 windows核心编程总结 第二部分工作机理

第四章 进程

一、进程定义:分为两个部分

1、进程内核对象,一个内存块,一个数据结构,维护该内核对象的某些属性 。地址所在:我猜在内存中

2、进程地址空间,包含可执行文件或者dll模块的代码和数据。还包含动态内存分配。比如线程堆栈和堆的分配。地址所在:我猜在磁盘中

二、进程启动

启动流程:嵌入启动函数->入口点函数->入口点函数返回->嵌入启动函数返回

三、CreateProcess函数

1、略

四、终止进程

1、ExitProcess:该函数执行后,函数后面代码不会执行,c/c++运行库不会正常执行清理工作

2、TerminnateProcess:同ExitProcess的缺点,不同的是该函数是异步,且可以销毁其他进程,需要用到waitforobject函数

3、进程中所有线程停止结束,没有活动线程

明确一点:虽然1和2的调用方式没有正确执行清理工作,导致暂时内存泄露。但是进程结束以后,会清理一切内存,不会内存泄露。

第五章 作业

相当于进程manager,管理进程,进程容器

可以统一管理限制进程,改变进程属性

第六章 线程基础

1、windows是抢占式操作系统

2、cpu20ms查询一次可调用的线程内核对象

3、线程定义

一、线程内核对象,操作系统内核可以操纵这个对象,管理线程;他是一个内存块,数据结构,存放线程信息,上下文context,context包含:1、指针指令寄存器 2、堆栈指针寄存器

二、线程栈,用户储存线程运行时所需要的所有函数参数和局部变量

4、终止线程

1、ExitThread

2、TerminnateThread

基本和进程终止方式类似,都不推荐,调用后不会之后后续代码,不会执行入口函数返回,不会执行清理工作

5、线程内幕

1、初始化信息:引用计数:2 , 退出代码:active ,信号标志:未激活 ,中断计数 :1

2、context存在于线程内核对象中,包含指令指针寄存器:存放RtlUserThreadStart;推展指针寄存器:存放pfnStartAddr

在调用CreateThread后,会先调用RtlUserThreadStart,起始函数

函数模型

ExitProcess后不会返回到RtlUserThreadStart

6、c/c++运行库注意事项

一、_beginthreadex代替CreateThread

二、理由:c/c++运行库初始设计为单线程,很多全局变量和全局函数不是线程安全的,比如_error

所以需要每个线程创建自己独立的数据块_ptidddata,存放属于线程自己的变量和函数,_ptidddata是在c/c++运行库的堆里申请的

在线程调用c/c++运行库函数_error的时候,其实c/c++库会拿到主调线程绑定的_ptidddata,然后调用线程绑定的专属自己_ptidddata中的_error函数,这样就实现了线程安全

三、_beginthreadex实现逻辑:_beginthreadex内部封装了CreateThread,申请_ptidddata内存,绑定主调线程id和_ptiddata,

执行线程函数pfnStartAddr

函数顺序逻辑:_beginthreadex->CreateThread->RtlUserThreadStart->_threadstartex(绑定主调线程id和_ptiddata)->_callthreadex(异常优化:包括运行异常等还有signal异常)->pfnStartAddr->_endthreadex

_endthreadex的工作是释放_ptidddata的内存还有结束退出线程,ExitThread

PS:如果使用CreateThread后果:

1、假如线程使用需要_ptidddata结构的c/c++运行库函数或者变量,那么运行库会自动生成_ptidddata并和线程绑定

那么在线程结束后,这个_ptidddata内存不会释放,造成内存泄漏(大多数c/c++运行库函数都是线程安全的,不用这个_ptidddata结构,那就不会有这个问题,要注意的是少部分需要_ptidddata的函数)

2、创建线程后立刻使用signal信号函数,那么会崩溃

四、不要使用老版本的_beginthread和_endthread

 

7、注意自己的身份

GetCurrrentProcess返回的是-1或者-2,是伪句柄,指的是当前进程,

当前进程使用正常,传给需要句柄的函数的时候,会被翻译成当前进程句柄值

但是其他进程使用这个句柄,其他进程的函数就会默认翻译成其他进程的句柄

但是CloseHandle的时候,只是会返回调用失败

利用DuplicateHandle可以将伪句柄变成实际句柄值

第七章 线程调度优先级关联性

上下文切换概念:系统大每隔20ms检查当前存在的可调度的线程内核对象,调用该内核对象,加载context。进行调度。

一、线程挂起和恢复

函数:resumethread,suspendthread,存在挂起计数概念,挂起计数>1,是挂起,挂起计数为0,恢复

挂起时候为不可调度状态,可以在创建挂起线程,在代码运行之前,改变他的环境(优先级)

挂起和恢复函数可以被重复挂起和恢复,但是挂起次数要等于恢复次数,两个函数返回值是函数操作前的挂起计数

注意事项:suspendthread是异步的,在恢复之前,是不能执行用户模式的代码。

挂起线程要注意线程在做什么,比如线程正在分配堆内存,其他线程将该线程挂起,这样,如果其他线程再去申请分配内存

,就会等待,等待挂起的那个线程恢复,死锁问题出现。要明确线程在做什么,避免死锁发生,才能用suspendthread

二、进程的挂起和恢复

进程的挂起和恢复,其实是用的线程的挂起和恢复,通过利用toolhelp拿到进程的所有线程,遍历所有线程,执行挂起或者恢复

resumethread和suspendthread

注意事项:用toolhelp拿到所有线程的时候,如果创建和销毁线程,那么对拿到的线程进行挂起和恢复就会出现问题

三、睡眠

sleep。线程告诉系设定一个时间范围,可以不调度自己

传入infinit说明是需要永远不调用自己,不要给自己留时间片段

传入0说明,告诉系统,主调线程放弃剩余时间片。切换到其他同等或者高于此线程的其他线程。如果没有这样的其他线程,那么会再次调用此线程

四、切换到另一个线程

switchtothread

如果存在其他可调度线程,那么可以切换线程

sleep和switchtothread的区别:switchtothread允许低优先级的线程切换

五、线程的执行时间

执行时间=用户模式执行时间(也就是应用代码部分)

不包含内核模式执行时间(系统内核)

方法一:使用gettickcount64->dowork->gettickcount64

比较两次获得的时间戳,缺点:包含内核模式执行时间,因为切换线程也要时间,内核代码也要时间。不准确、

方法二:getthreadtime

可以分别获得内核时间和用户时间,使用getprocesstimes可以获得所有线程累加的内核和用户时间

缺点:这是vista版本之前的方法,目前cpu时间分配改变,并不好用了

方法三:vista版本后,获得时钟周期数,queryperformancefrequency+queryperformancecounter

分别是获取频率和时钟周期数

用户模式线程执行时间 =  queryperformancecounter/queryperformancefrequency,周期数/频率 = 时间,这里针对短生命代码,排除内核内码时间

缺点:长生命周期内核代码时间怎么办,对不对

方法四:querythreadcycletime/获取cpu频率函数

推荐:获取线程所用的时钟周期数后,再去获取cpu主频,然后相除

cpu主频获得方式:通过方式三,计算睡眠一秒,得出的时钟周期数/频率,更精确,因为这个是实时的

这里的querythreadcycletime应该除去了内核时间,应该为纯线程用户时间

 

六、context上下文

context包含了好几种寄存器

控制寄存器:指令指针,栈指针,标志函数返回等,

整形寄存器,浮点型寄存器,调试扩展和标识寄存器

getthreadcontext获得上下文,只能获取用户模式的上下文,获取之前要suspendthread,不然线程上下文改变,获取过来的信息不是旧的。

setthreadcontext设置上下文,也是要先挂起线程,如果设置不好,会把线程异常

增加这些函数的目的是帮助调试器和其他工具实现  设置下一个语句

七、线程优先级

优先级0-31,高优先级会抢占低优先级线程,只有一个为0优先级的页面清零线程

八、抽象角度看线程优先级

线程基本优先级 = 进程优先级+线程相对优先级

用户应用程序优先级范围:1-16

内核模式驱动程序:17-31

realtime线程优先级的最终优先级不能低于16,非reatime的不能高于15

设计优先级原则:

高优先级的线程应该是不可调度的,占用cpu资源少的,等待调度,但是如果需要的话,要及时响应

低优先级的线程应该是处理大量任务的,占用cpu资源

九、优先级编程

1、setthreadpriority,getthreadpriority

setpriorityclass,getpriortyclass

注意事项:改变优先级的时候,要挂起线程或者进程,然后在恢复线程或者进程

创建子进程的进程会选择子进程运行环境的优先级(也就是父进程优先级)

2、动态提升优先级

如果要动态提升的优先级,可以通过

setthreadpriortyboost,

setprocesspriortyboost

动态提升只提升1-15优先级的线程,而且提升后的线程也不会超过15

动态提升优先级情况有两种:

io事件:磁盘读取,窗口事件,维持两个时间片,每个时间片依次减一

饥饿线程:如果系统检测存饥饿线程3,4秒,会将饥饿线程提升至15的基本优先级,维持两个时间片

3、为前台程序微调调度程序

程序分为前台程序和后台程序

系统可以让用户微调提升前台优先级,因为窗口是展示给用户看,理所应当优先处理

前台进程是normal才能提升

4、调度io优先级

这里有个后台优先级设置,通过setthreadpriority,设置后台优先级,这样即可以降低io处理优先级,优先后台程序处理

注意事项:文件io后台处理接口也可以设置,setfileinfomationbuhandle,这会覆盖setthreadpriority

另一个注意事项是小心死锁:比如两个线程a,b,a线程在等待b线程释放锁,由于b线程设置了后台优先级,可能导致不io,b线程可能不释放锁,导致a线程死锁

十、关联性

1、通过函数设置关联性

提高性能,因为程序运行在不同组cpu,会导致性能下降

可以指定进程或者线程在哪些cpu运行

setprocessaffinymask设置进程的线程在哪些cpu里

getsysteminfo获得cpu信息

getprocessaffinymask获得线程在哪些cpu

setthreadaffinymask设置线程在哪些cpu里

setthreadidealprocessor设置理想cpu作用:可以在理想目标cpu被占用或者无法使用的时候,使用其他cpu,这里的参数就是设置理想cpu的id。当通过setprocessaffinymask或者setthreadaffinymask设置目标cpu后,当这些cpu被高优先级线程占用或者其他原因无法使用的时候,我们这个理想cpu就会登场

2、程序未开始时候,通过程序头部设置处理器关联性利用imagehlp.h的一些函数

3、任务管理器设置关联性。如果程序在x86的环境,可以设置bcd限制使用cpu数量

第八章:用户模式下的线程同步

用户模式下的线程同步有interlocked,关键段,读写段,条件变量

其中条件变量可以和关键段或者读写段搭配使用

这几种都是原子方式工作:可以保证一个线程访问修改资源的时候,其他线程不会进入,书上说是通过总线什么的 拉起

性能比较:interlocked系列函数>读写段>关键段

关键段和读写段内部都是interlocked系列函数编写的

如果要编写高性能,优先考虑用户模式的线程同步

interlocked系列:

优点:最快

缺点:interlocked系列函数多,不好记,只能修改单个资源

关键段:

优点:可以对相同逻辑的一些资源进行保护,可以重复锁定资源,和释放资源

缺点:当我们用关键段,出现死锁,互相等待的时候,那么可以设置挨饿时间=最大等待时间。

关键段内部有interlock系列函数和一个事件内核,如果有其他线程访问,会用这个事件内核让线程等待

关键段+旋转锁更配性能更高:因为用户模式切换到内核模式需要很多时间,可能在切完后,资源就可以访问了,所以配上旋转锁。

在旋转锁等待一段时间后,如果资源还是不能访问,再进入内核模式等待。

一些函数整理:

关键段:initializecriticalsection,deletecriticalsection,entercriticalsection,leavecriticalsection,tryentercriticalsection

旋转锁:initializecriticalsectionandspincout,setcriticalsectionandspincout,初始化关键段和旋转锁

书上说保护进程堆的旋转锁次数为4000为宜,其他的需要自己试验

关键段异常出错:

1、initializecriticalsection和initializecriticalsectionandspincout分配内存的时候失败。解决办法:使用异常捕获或者返回返回值发现,等到内存正常或者排查出问题后,再去初始化关键段。

2、entercriticalsection的时候,创建事件内核对象失败,因为内存不够。解决办法:xp系统后,会自动使用有键事件内核代替和处理

这个内核可以多个线程通用,看样子省内存。

读写段srwlock:

优点:读写逻辑分离,支持读的时候不进行原子方式隔离同步,所有线程可以同一时间访问资源;写的时候再原子方式。

缺点:和关键段相比,没有try函数,不能递归重复锁定保护资源

疑问:读写锁是不是跟关键段一样,有内核等待事件?看书上好像没有诶,没有看到读写锁搭配旋转锁的使用方法

 

条件变量:

优点:搭配关键段或者读写段来用的,用来做触发条件

缺点:没啥缺点

其他:volatile作用:确保每次读取是从内存中读取

如果不加的话,如果编译器设置优化的话,变量会被优化成从cpu寄存器或者其他缓冲区读取

高速缓冲区:

内存->高速缓冲区->cpu

优点:提高性能,不需要每次读取数据要去内存那拿

缺点:多处理器中,当cpu修改数据的时候,每个cpu相关联的高速缓冲区要作废,重新从内存读数据写数据到缓冲区,这个时候影响性能

提高高速缓冲区性能办法:

1、读和写,分开写,要修改的数据放在一块,不修改的数据放在一块

2、应用程序的数据要和缓冲区进行字节对其,getlogiccalprocessorinformation获得高速缓冲区的大小

字节对其后,就可以有高速缓冲区功能了

对其,也就是设置高速缓冲区函数:_declspec(algin)

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值