那时候总会问自己, 这次写的驱动, 用 Windbg 调试过吗? 写 SsdtHook, 手动找过吗? 写 ObjectHook 知道对象结构吗? 用 FS 寄存器获取信息? 为什么能获取那么多的信息?,_kpcr 与他有什么关系?
要从那次学习双进程守护说起, 也算小半年以前的事情了. 不懂互斥体, 不懂事件, 信号量等机制, 你就说用互斥体, 事件写双进程守护?
科普:
当年自学操作系统的时候不懂, 慢慢的也就懂了. 穿孔器, 纸卡带的年代只有程序, 为了解决人与 CPU 的交互效率低下, 单批道处理器当年就出现了. 但是仍然满足不了需求, 这时候多批道处理也就成了时间产物 (从晶体管到小规模集成电路到 3D 晶体管技术). 进程(PCB 进程控制块), 也是为了解决多批道处理下程序不可控, 结果不可复用(资源共享带来的双刃剑) 问题才真正的被运用起来. 然而分时系统, 实时系统精准程度要求, 高并发的需求等, 线程也才真正的立足于系统, 成为最小的调度执行单位.
而原子操作, 临界区, 互斥体这些东西都要基于多线程来说, 线程同步, 异步同步. 也就有了消费者与生产者, 哲学家等同步问题. 简单理解为保证数据不混乱, 有层次有逻辑的去相互配合执行任务. 有紧急任务 (高优先级) 可以抢占处理机, 而且具有公平性, 每个进程都有机会被运行; 有较大的吞吐量, 所有要有合理的调度算法.
原子操作:
简单来说, 保证利用某一资源时候, 当前资源不被其他 CPU 抢占使用.
缺点: 只能解决某一个变量, 比如是一个变量数据做简单运算(有相对的原子操作 API).
临界区:
基于原子操作的缺点, 临界区概念慢慢形成. 临界区可以保护一段代码指令.
由 InitializeCriticalSection(..)初始化一个临界区, 谁初始化的这个临界区就属于谁, 有拥有者的概念. 拥有者无限调用 EnterCriticalSection()则不会被阻塞, 其他的则会被阻塞在外, 直到 DeleteCriricalSection()销毁 , 临界区由 InitializeCriticalSection ------> LeaveCriticalSection 形成了保护.
互斥体:
临界区啥缺点? 他不是内核对象? 什么是内核对象, 我们可以把进程, 线程, 文件 IO, 互斥体, 信号量, 事件, 线程池, 访问令牌, 计时器等都叫做内核对象, 可以参考《Windows 核心编程》一书.
既然是内核对象? 当然可以跨进程, 临界区是无法做到这一点, 互斥体也有类似于临界区拥有则的概念, 重要的是有两种状态: 1, 激发态 2, 非激发态. 来判断当前互斥体是否被使用, 而且如果互斥体内部进程或者线程崩溃, 那么互斥体空间将自动释放且为激发态, 但是他只能被拥者去则释放, 不可以被别的线程释放.
创建一个互斥体 CreateMutex(), 一般互斥体用于写单实例进程, 因为互斥体 (参数 3) 是系统全局唯一, 可以判断当前系统是否已存在该进程, 如果存在则不再打开或则创建.
OpenMutexW()打开一个互斥体
ReleaseMutex 释放存在的互斥体.
利用互斥体实现单一进程检测源码如下:
- #include <stdio.h>
- #include <iostream>
- #include <Windows.h>
- using std::cout;
- using std::endl;
- BOOL IsMutex()
- {
- HANDLE hMutex = NULL;
- hMutex = CreateMutex(NULL, FALSE, L"TEXT");
- if (hMutex)
- {
- if (ERROR_ALREADY_EXISTS == GetLastError())
- {
- ReleaseMutex(hMutex);
- CloseHandle(hMutex);
- return TRUE;
- }
- }
- return FALSE;
- }
- int main(void)
- {
- if(IsMutex())
- cout << "系统已存在 TEXT 互斥体" << endl;
- else
- cout << "第一次创建互斥体成功" << endl;
- system("pause");
- return 0;
- }
信号量:
信号量当前信号数不为 0, 则代表为激发态. 注意调用 WaitForSingleObject()的时候, 就会把信号数 - 1, 也就是说如果信号数不为 0, 那么使用该函数信号数 - 1, 相当于又上了一把锁, 记得调用函数 ReleaseSemaphore()恢复(信号数 + 1). 任何一个线程都可以进行释放(互斥体成对出现), 意味着多个线程可保护同一段代码或者指令.
事件:
这是一个相对民主的内核对象, 进程同步中用的也比较多. 他可以设置等待函数对于此事件对象有没有后遗症. 而且可以手动设置激发态或者非激发态, 自主性非常强, 很灵活.
1,CreateEventW()用来创建一个事件对象
2,OPenEventA()打开一个事件对象
3,SetEvent()设置为激发态
4,ReSetEvent()设置为非激发态
5,PulseEvent()手动设置激发态
6,CloseHandle()内核对象当引用计数为 0, 系统管理销毁.
这些函数具体参数可以 msdn 查看或者百度看详细信息, 介绍那么多下面也要贴上一段双进程守护代码.
双进程守护程序一:
- int main(void)
- {
- // 创建事件对象
- HANDLE hEvent = CreateEvent(NULL, FALSE, TRUE, L"守护 One.exe");
- while (TRUE)
- {
- HANDLE hEventTow = OpenEvent(NULL, FALSE, L"守护 Two.exe");
- // 如果不存在则创建
- if (!hEventTow)
- {
- CreateProcess(L"守护 Two.exe", NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &s_Si, &s_Pi);
- WaitForSingleObject(s_Pi.hProcess, INFINITE);
- CloseHandle(s_Pi.hThread);
- CloseHandle(s_Pi.hProcess);
- }
- else
- CloseHandle(hEventTow);
- }
- system("pause");
- return 0;
- }
双进程守护程序二:
- STARTUPINFO s_Si = {};
- PROCESS_INFORMATION s_Pi = {};
- // 创建事件对象
- HANDLE hEvent = CreateEvent(NULL, FALSE, TRUE, L"守护 Two.exe");
- while (TRUE)
- {
- HANDLE hEventTow = OpenEvent(NULL, FALSE, L"守护 One.exe");
- // 如果不存在则创建
- if (!hEventTow)
- {
- CreateProcess(L"守护 One.exe", NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &s_Si, &s_Pi);
- WaitForSingleObject(s_Pi.hProcess, INFINITE);
- CloseHandle(s_Pi.hThread);
- CloseHandle(s_Pi.hProcess);
- }
- else
- CloseHandle(hEventTow);
- }
- system("pause");
- return 0;
双进程守护缺点很多, 假如我挂起其中一个进程(不被响应), 另一个进程则可关闭.
我们还有 HOOK 来保护自己, 下次用一个简单的 mfc 来聊一聊, fs 寄存器_kpcr, 还有一些 Hook.
来源: http://www.bubuko.com/infodetail-2921060.html