Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3036499
  • 博文数量: 167
  • 博客积分: 613
  • 博客等级: 中士
  • 技术积分: 5473
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-13 21:35
个人简介

人, 既无虎狼之爪牙,亦无狮象之力量,却能擒狼缚虎,驯狮猎象,无他,唯智慧耳。

文章分类
文章存档

2015年(19)

2014年(70)

2013年(54)

2012年(14)

2011年(10)

分类: WINDOWS

2012-03-22 15:46:51


线程基础

一、概述

由于编程中遇到的诸多问题,这周终于开始下决心学习进程线程编程部分。首先看的是这部分的基础知识,主要是线程的创建和终止以及线程优先级与调度。下面对现在自己所理解的东西简单做一个笔记,以资日后复习回顾。这篇笔记主要简单谈谈以下几个问题:

1.       线程内幕

l  线程的创建和终止

l  线程内核对象

2.       线程的调度与优先级

l  线程调度

l  查看修改上下文Context

l  线程优先级

 

二、线程内幕

虽说是“线程内幕”,也不是很深入的内容,只是相对于自己之前对线程的理解而言又深入了一步。

Ø  线程的创建和终止

尽管Windows所提供的C/C++运行库中也有_beginthreadex()这样的创建线程的函数,但是其实质也只是在调用Windows API的基础上又做了一些优化工作。在Windows下创建线程的实质还是调用CreateThread()函数来实现的。

HANDLE  CreateThread(

PSECURITY_ATTRIBUTES  psa,     //线程的安全属性结构,NULL为使用默认值

DWORD  cbStackSize,           //线程堆栈的大小,0为默认大小

PTHREAD_START_ROUTINE  pfnStartAddress,   //线程函数的地址

PVOID  pvParam,              //线程函数的参数

DWORD  dwCreateFlags,    //创建线程时标志,0表示立即执行,Create_Suspend表示//挂起

PDWORD  pdwThreadID    //新线程的IDNULL时表示不需要返回该值

);

函数执行成功会返回新线程句柄,这同CreateProcess()不同,它没有PROCESS_INFORMATION结构,不能在参数中返回新进程相关的进程句柄和线程句柄,因而需要作为函数的返回值返回。

终止一个线程一般有以下几种方式:

F  线程函数返回

这是我们最希望发生的情况,因为可以保证所有资源都被正确清理。比如:

1.       线程函数中创建的所有C++对象都通过其析构函数得以执行

2.       操作系统正确释放线程栈使用的内存

3.       操作系统把线程退出代码设为线程函数的返回值

4.       系统递减线程内核对象的使用计数

F  使用ExitThread()结束一个线程

VOID ExitThread(DWORD dwExitCode);

本函数可以终止一个线程,并且导致系统清理该线程使用的所有操作系统资源,如线程栈。但是不会销毁C/C++对象。退出代码可以在参数中进行设置。因此,本函数只能实现上述的2,3,4项。

F  使用TerminateThread()结束线程

可以利用本函数结束另一个线程。参数需要指定线程句柄和退出代码。这个函数要注意两点:

1.       该函数是一个异步函数,即便函数本身已经返回也不代表目标线程已经被成功结束

2.       该函数直接“杀死”了目标线程,目标线程提前没有任何“觉察”,因而所有的线程资源都没有得以释放。即只是实现了上述的34项。线程堆栈依旧可以使用,当然这是Microsoft为了使相关的其他线程可以继续使用该线程栈而故意为之的。

F  进程终止时,所有线程都会终止执行。随着进程清理,所有的线程资源也会被系统干净的彻底清理。

Ø  线程内核对象

系统创建一个进程时,会同时创建一个主线程;系统对进程和线程的管理都是通过相应的内核对象实现的。如同进程有进程内核对象和地址空间两个组成部分一样,创建线程时也会有两个工作:

1. 创建一个线程内核对象,操作系统用它来管理线程。系统还用内核对象来存放线程统计信息

2. 在进程地址空间中分配一个线程栈,用户维护线程执行所需的所有函数参数和局部变量

一个线程内核对象的结构大致如下:

 

线程内核对象主要由两个部分组成。上下文(Context)用来存储线程执行状态的一系列CPU寄存器的值,这里面最重要的是堆栈指针SP和指令指针IP,分别指向了线程执行的数据和指令的地址。统计信息部分中需要特别注意的是使用计数一开始就被初始化为了2,如果要完全释放该线程内核对象,除了线程函数正常退出之外,主调线程还需要CloseHandle()一次。

Suspend count计数初始化为1,创建线程时会检查dwCreateFlags参数,只有当参数是CREATE_SUSPEND时才会真正挂起此线程。

 

三、线程的调度与优先级

了解了线程内核对象的大致结构,我们现在来趁热打铁来了解下线程的调度问题。

Ø  线程的调度

Windows系统是一个抢占式多线程操作系统,什么意思呢?我的理解有以下几点:

1. 系统可以在任何时刻停止一个线程而另行调度另一个线程;

2. 调度的依据是各线程的优先级,0~31,依次从高到低优先调用。

我们知道,真正的执行流程是由线程实现的,因而,实质上不存在调度进程的问题,调度的实质对象都是各个线程。一般来说,如果各条件相同,CPU先运行一个线程大约20ms,之后将该线程的Context存入内核对象,从剩余的其他线程中选择一个新线程,载入其Context,再运行大约20ms,之后再次重复轮询线程。这里注意的是各个线程上下文的来回切换。

当然,系统每次只会调度可以调度的线程,有些线程本身是不可调度的。比如被挂起的线程,比如等待某种事件发生的线程。很简单的例子,打开一个记事本程序,不输入任何数据,也不拖动窗口,该线程就会陷入睡眠,等待点击触发。

一般来说,挂起一个线程有两种方法,一种是在CreateThread()时指定CREATE_SUSPEND参数,新线程一旦创建即被挂起;另一种是利用函数

DWORD SuspendThread(HANDLE hThread);  //挂起线程

DWORD ResumeThread(HANDLE hThread);   //恢复线程

两个函数都会返回调用之前线程的挂起计数,只有当计数为0,线程才会执行。多次挂起的线程需要多次恢复。

Windows没有直接提供挂起进程的函数,只是提供了调试器使用的方法来挂起进程。

Ø  查看修改上下文(Context)

Conetext结构在线程的调度切换中起着至关重要的作用,其实质是当时CPU一系列寄存器的值。Windows定义了CONTEXT结构体,主要包含:

CONTEXT_CONTROLCPU控制寄存器

CONTEXT_INTEGERCPU整数寄存器

CONTEXT_SEGMENTSCPU段寄存器

CONTEXT_DEBUG_REGISTERSCPU调式寄存器

各个部分分别由变量来存放寄存器中相应的数值。Windows同样提供了查看和修改CONETXT的函数

BOOL GetThreadContext(

     HANDLE hThread,

     PCONTEXT  pContext );

BOOL SetThreadContext(

     HANDLE hThread,

     CONST CONTEXT *pContext );

当然,使用这些函数之前,我们需要先定义一个CONTEXT结构,具体使用例子如下:

 

 

//定义CONTEXT结构体

CONTEXT Context;

SuspendThread(hThread);   //需要先挂起目标线程

//获取上下文中控制寄存器的值

Context.ContextFlags = CONTEXT_CONTROL;  //指定CONETXT中包含的寄存器类型

GetThreadContext(hThread, &Context);

//修改寄存器的值

Context.Eip = 0x00010000;

SetThreadContext(hThread, &Context);

//继续线程

ResumeThread(hThread);

Ø  线程优先级

线程的优先级由进程优先级类和线程相对于进程的优先级决定,具体的对应关系在不同的版本中会有所变化。一般来说normal类的进程中的normal线程优先级值为8。系统调度时当然从可调度的线程中从高到低来选择线程。这里需要具体说明几个问题:

1. 优先级的值范围为031,但是应用程序最大值是16,至于17212730是只有内核模式下的设备驱动程序才可以获得的优先级。后面我们会看到360木马防火墙中就存在着优先级为16的最高级应用程序线程。

2. 虽然系统调度依据优先级来选择线程,但是如果优先级较低的线程长期不能得到执行,比如(24秒),系统就会认为该线程处于“饥饿”状态,就会临时提高该线程的优先级使得其可以执行一到两次。

3. 实际应用中,我们应当确保优先级高的程序能够快速执行,然后恢复到睡眠或者挂起状态,以保证最大的实时性;而另优先级低的程序在大多时候可以调度执行。

最后,看下360中的线程优先级,说不定会为如何免杀提供些思路

0322.JPG

阅读(3459) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~