Windows内核对象,句柄,信号量和关键代码段


一.内核对象

Windows 中每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核进
行访问,应用程序不能在内存中定位这些数据结构并直接更改其内容。这个内存块是一个数据结
构,其成员维护着与对象相关的信息。少数成员(安全描述符和使用计数)是所有内核对象都有
的,但大多数成员都是不同类型对象特有的。

如:file 文件对象、event 事件对象、process 进程、thread 线程、
iocompletationport 完成端口(windows 服务器)、mailslot 邮槽、mutex 互斥量
和 registry 注册表 等

二.内核对象的使用计数和生命周期

1.内核对象的所有者是操作系统内核,而非进程。换言之也就是说当进程退出,内核对象不一定会销毁。

2.操作系统内核通过内核对象的使用计数,知道当前有多少个进程正在使用一个特定的内核对象。初次创建内核对象,使用计数为 1。当另一个进程获得该内核对象的访问权之后,使用计数加1。如果内核对象的使用计数递减为 0,操作系统内核就会销毁该内核对象。也就是说内核对象在当前进程中创建,但是当前进程退出时,内核对象有可能被另外一个进程访问。这时,进程退出只会减少当前进程对引用的所有内核对象的使用计数,而不会减少其他进程对内核对象的使用计数(即使该内核对象由当前进程创建)。那么内核对象的使用计数未递减为 0,操作系统内核不会销毁该内核对象。
在这里插入图片描述
(1)进程 1 退出,2 不退出时。内核对象 A,B 的引用计数减为 0,被操作系统内核销毁,而进程 1只减少自身对 C,D 的引用计数,不会影响进程 2 对 C,D 的引用计数,此时 C,D 引用计数不为 0,不会被销毁。

(2)进程 2 退出,1 不退出时。进程 2 减少自身对 C,D 的引用计数,不会影响进程 1,故 A,B,C,D都不会被销毁

(3)进程 1,2 均退出时,只要 ABCD 不被别的进程使用,内核对象 A,B,C,D 的引用计数均递减为0,被内核销毁

(4)进程 1 和 2 均为退出时,内核对象 A,B,C,D 的引用计数只要有一个递减为 0,那么递减为 0的内核对象便被内核销毁

三.操作内核对象

Windows 提供了一组函数进行操作内核对象。成功调用一个创建内核对象的函数后,会返回一个句柄,它表示了所创建的内核对象,可由进程中的任何线程使用。在 32 位进程中,句柄是一个32 位值,在 64 位进程中句柄是一个 64 位值。我们可以使用唯一标识内核对象的句柄,调用内核操作函数对内核对象进行操作

四.内核对象与其他类型的对象

Windows 进程中除了内核对象还有其他类型的对象,比如窗口,菜单,字体等,这些属于用户对象和 GDI 对象。要区分内核对象与非内核对象,最简单的方式就是查看创建这个对象的函数,几乎所有创建内核对象的函数都有一个允许我们指定安全属性的参数。

1 一个对象是不是内核对象,通常可以看创建此对象 API 的参数中是否需要:PSECURITY_ATTRIBUTES 类型的参数。

2 内核对象只是一个内存块,这块内存位于操作系统内核的地址空间,内存块中存放一个数据结构(此数据结构的成员有如:安全描述符、使用计数等)。

3 每个进程中有一个句柄表(handle table),这个句柄表仅供内核对象使用.

例如

Thread = CreateThread(… , &threadId);
当调用了 CreateThread CreateFile 等创建内核对象的函数后,
就是相当于操作系统多了一个内存块,这个内存块就是内核对象也是此时内核对象被创建,其数据结构中的引用计数初始为 1(这样理解:只要内核对象被创建,其引用计数被初始化为 1)
这里实则发生两件事:创建了一个内核对象和创建线程的函数打开(访问)了此对象,所以内核对象的引用计数加 1,这时引用计数就为 2 了。

CloseHanlde

它只是解除了handle和内核对象的引用和关系,不能通过句柄去访问内核对象了
当引用计数为0是才会销毁

五.信号量

1.内核对象状态

触发状态(有信号状态),表示有可用资源。

未触发状态(无信号状态),表示没有可用资源

2.信号量的组成

①计数器:该内核对象被使用的次数

②最大资源数量:标识信号量可以控制的最大资源数量(带符号的 32 位)

③当前资源数量:标识当前可用资源的数量(带符号的 32 位)。即表示当前开放资源的个数(注意不是剩下资源的个数),只有开放的资源才能被线程所申请。但这些开放的资源不一定被线程占用完。比如,当前开放 5 个资源,而只有3 个线程申请,则还有 2 个资源可被申请,但如果这时总共是 7 个线程要使用信号量,显然开放的资源 5 个是不够的。这时还可以再开放 2 个,直到达到最大资源数量。

3.信号量的规则

(1)如果当前资源计数大于 0,那么信号量处于触发状态(有信号状态),表示有可用资源。

(2)如果当前资源计数等于 0,那么信号量属于未触发状态(无信号状态),表示没有可用资源。

(3)系统绝对不会让当前资源计数变为负数

(4)当前资源计数绝对不会大于最大资源计数
在这里插入图片描述

4.与互斥量的区别

信号量与互斥量不同的地方是,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源。

5.创建信号量

WINAPI
CreateSemaphoreW(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Null 安全属性
_In_ LONG lInitialCount, //初始化时,共有多少个资源是可以用的。 0:未触发状//态(无信号
状态),表示没有可用资源
_In_ LONG lMaximumCount, //能够处理的最大的资源数量 3
_In_opt_ LPCWSTR lpName //NULL 信号量的名称
);

6.增加信号量

WINAPI
ReleaseSemaphore(
_In_ HANDLE hSemaphore, //信号量的句柄
_In_ LONG lReleaseCount, //将lReleaseCount值加到信号量的当前资源计数上面 0-> 1
_Out_opt_ LPLONG lpPreviousCount //当前资源计数的原始值
);


7.关闭句柄

CloseHandle(
_In_ _Post_ptr_invalid_ HANDLE hObject
);t_ LPCWSTR lpName //NULL 信号量的名称
);

8.代码演示

#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned
    WINAPI
    Read(void *arg);
unsigned
    WINAPI
    Accu(void *arg);
static HANDLE semOne;
static HANDLE semTwo;
static int num;
int main(int argc, char *argv[])
{
    HANDLE hThread1, hThread2;
    semOne =
        CreateSemaphore(
            NULL, 0, 1,
            NULL);
    // semOne 没有可用资源 只能表示0或者1的二进制信号量 无信号
    semTwo =
        CreateSemaphore(
            NULL, 1, 1,
            NULL);
    // semTwo 有可用资源,有信号状态 有信号
    hThread1 = (HANDLE)
        _beginthreadex(
            NULL, 0, Read,
            NULL, 0,
            NULL);
    hThread2 = (HANDLE)
        _beginthreadex(
            NULL, 0, Accu,
            NULL, 0,
            NULL);
    WaitForSingleObject(hThread1,
                        INFINITE);
    WaitForSingleObject(hThread2,
                        INFINITE);
    CloseHandle(semOne);
    CloseHandle(semTwo);
    system("pause");
    return 0;
}
unsigned
    WINAPI
    Read(void *arg)
{
    int i;
    for (i = 0; i < 5; i++)
    {
        fputs("Input num: ",
              stdout);          // 1 5 11
        printf("begin read\n"); // 3 6 12
        // 等待内核对象semTwo的信号,如果有信号,继续执行;如果没有信号,等待
        WaitForSingleObject(semTwo,
                            INFINITE);
        printf("beginning read\n"); // 4 10 16
        scanf("%d", &num);
        ReleaseSemaphore(semOne, 1,
                         NULL);
    }
    return 0;
}
unsigned
    WINAPI
    Accu(void *arg)
{
    int sum = 0, i;
    for (i = 0; i < 5; i++)
    {
        printf("begin Accu\n"); // 2 9 15
        // 等待内核对象semOne的信号,如果有信号,继续执行;如果没有信号,等待
        WaitForSingleObject(semOne,
                            INFINITE);
        printf("beginning Accu\n"); // 7 13
        sum += num;
        printf("sum = %d \n", sum); // 8 14
        ReleaseSemaphore(semTwo, 1,
                         NULL);
    }
    printf("Result: %d \n", sum);
    return 0;
}

六.关键代码段

关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。

1.初始化关键代码段

调用 InitializeCriticalSection 函数初始化一个关键代码段。

InitializeCriticalSection(
_Out_ LPCRITICAL_SECTION lpCriticalSection
);

该函数只有一个指向 CRITICAL_SECTION 结构体的指针。在调用 InitializeCriticalSection 函数之前,首先需要构造一个 CRITICAL_SCTION 结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection 函数。

2 进入关键代码段

VOID
WINAPI
EnterCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);

调用 EnterCriticalSection 函数,以获得指定的临界区对象的所有权,该函数等待指定的临界区对象的所有权,如果该所有权赋予了调用线程,则该函数就返回;否则该函数会一直等待,从而导致线程等待。

3.退出关键代码段

WINAPI
LeaveCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection);

线程使用完临界区所保护的资源之后,需要调用 LeaveCriticalSection 函数,释放指定的临界区对象的所有权。之后,其他想要获得该临界区对象所有权的线程就可以获得该所有权,从而进入关键代码段,访问保护的资源。

4. 删除临界区WINBASEAPI

VOID
WINAPI
DeleteCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);

代码示例

当离开临界区后
可能进入A也可能进入B;

include<stdio.h>
#include <windows.h>
#include <process.h>
    int iTickets = 5000;
CRITICAL_SECTION g_cs;
// A窗口 B窗口DWORD WINAPI SellTicketA(void* lpParam)
{
    while (1)
    {
        EnterCriticalSection(&g_cs); // 进入临界区
        if (iTickets > 0)
        {
            Sleep(1);
            iTickets--;
            printf("A remain %d\n", iTickets);
            LeaveCriticalSection(&g_cs); // 离开临界区
        }
        else
        {
            LeaveCriticalSection(&g_cs); // 离开临界区
            break;
        }
    }
    return 0;
}
DWORD WINAPI SellTicketB(void *lpParam)
{
    while (1)
    {
        EnterCriticalSection(&g_cs); // 进入临界区
        if (iTickets > 0)
        {
            Sleep(1);
            iTickets--;
            printf("B remain %d\n", iTickets);
            LeaveCriticalSection(&g_cs); // 离开临界区
        }
        else
        {
            LeaveCriticalSection(&g_cs); // 离开临界区
            break;
        }
    }
    return 0;
}
int main()
{
    HANDLE hThreadA, hThreadB;
    hThreadA =
        CreateThread(
            NULL, 0, SellTicketA,
            NULL, 0,
            NULL); // 2
    hThreadB =
        CreateThread(
            NULL, 0, SellTicketB,
            NULL, 0,
            NULL);                    // 2
    CloseHandle(hThreadA);            // 1
    CloseHandle(hThreadB);            // 1
    InitializeCriticalSection(&g_cs); // 初始化关键代码段
    Sleep(40000);
    DeleteCriticalSection(&g_cs); // 删除临界区
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值