使用 Windows 函数 CreateFileMapping 或 CreateFileMappingNuma 创建 section 对象,指定要映射到的文件句柄(或者,对于页面文件备份 sections ,指定 INVALID_HANDLE_VALUE 参数),以及一个可选的名称,一个安全描述符。如果有节名称,其它进程可以通过 OpenFileMapping 函数将其打开。或者,你可以使用句柄继承(通过在打开或创建句柄时,指定要被继承的句柄)和句柄复制(通过使用 DuplicateHandle 函数)授予对该节对象的访问。
设备驱动程序也可以使用 ZwOpenSection,ZwMapViewOfSection,以及 ZwUnmapViewOfSection 等函数操纵 section 对象
。
(译注:CreateFileMapping 函数原型如下:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);[/COLOR][/SIZE][/FONT]
第一个参数就是要映射到的文件句柄,其值为 INVALID_HANDLE_VALUE,即无效的句柄值时,实际上是创建了实现共享内存的内核对象(section 对象),因为没有任何文件句柄与一个打开的磁盘文件关联
;第二个参数是一个 SECURITY_ATTRIBUTES 结构类型的变量;该结构定义如下:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor; //只有该成员与安全性有关
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;[/COLOR][/SIZE][/FONT]
其中第二个成员就是原文提及的“安全描述符”;如果我们想对创建的内核对象施加访问控制,就必须创建一个 lpSecutrtyDescripter,分配并初始化 SECURITY_ATTRIBUTES 结构:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecutrtyDescripter = pSD;
sa.bInheritHandle = FALSE;[/COLOR][/SIZE][/FONT]
调用创建内核对象的函数时,将 SECURITY_ATTRIBUTES 结构变量的地址作为第二个实参传入:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, 1024, TEXT("MyFileMapping"));[/COLOR][/SIZE][/FONT]
最后一个参数就是原文提及的可选的 section 名称,使用 TEXT 宏的目的在于根据编译器的UNICODE 设置情况,自动转换名称为 ASCII/ANSI 或 unicode 字符串。
原文提到,如果有section 名称,其它进程可以通过 OpenFileMapping 函数将其打开
,也就是如下调用:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE, TEXT("MyFileMapping"));[/COLOR][/SIZE][/FONT]
将 FILE_MAP_READ 作为第一个参数传给 OpenFileMapping,表明在获得对这个内核对象的访问权后,要从中读取数据。OpenFileMapping 函数在返回一个有效的句柄值前,会先执行一次安全检查。如果当前登录的用户或以特定用户帐户身份执行的进程被允许访问该文件映射内核对象(section 对象),OpenFileMapping 返回一个有效的句柄值;如果访问被拒绝,则返回 NULL;此时调用 GetLastError,返回值为 5(ERROR_ACCESS_DENIED)。同样的,如果利用返回的有效句柄调用其它 Windows API,但被调函数需要的权限不是 FILE_MAP_READ,也会发生拒绝访问错误。
下面的例子中,父进程在创建一个互斥量内核对象时,使用了句柄继承:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecutrtyDescripter = NULL;
sa.bInheritHandle = TRUE;
HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);[/COLOR][/SIZE][/FONT]
根据 MSDN 上相关内容的解释,在调用需要 SECURITY_ATTRIBUTES 结构实例作为参数的函数时(多数创建内核对象的函数都要求此参数),如果将其设置为 NULL,或者将其 lpSecutrtyDescripter 成员设置为 NULL(如上述代码所示),那么创建的对象具有默认安全性——来自于调用或创建者的安全令牌(例如 ACLs ,访问控制列表)。
其次,如果将 SECURITY_ATTRIBUTES 结构的 bInheritHandle 成员设置为 TRUE,然后向 CreateMutex() 传入这个结构实例的地址,那么该函数返回的互斥量句柄值是可继承的。如此一来,在父进程的内核对象句柄表中将添加一项互斥量句柄值,其“可继承的标志位” = 1。
接着,父进程调用 CreateProcess() 创建子进程时,将前面的句柄值作为 CreateProcess() 的第2个参数 pszCommandLine 传递,并且将第5个参数 bInheritHandles 的值设为 TURE(表明“主动”请求继承;反之如果 bInheritHandles = FALSE ,则无论 SECURITY_ATTRIBUTES 结构的 bInheritHandle 成员值为何,都不会继承 ),这样子进程就会继承父进程句柄表中,可继承的标志位 = 1 的句柄。然后父子进程可以使用相同的句柄值访问同一个互斥量对象。CreateProcess() 函数原型如下:
[FONT="微软雅黑"][SIZE="4"][COLOR="Black"]BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
[/COLOR][/SIZE][/FONT]
CreateProcess() 是一个复杂的函数,它拥有 10 个参数,每个参数都足够复杂,因此想要用好 CreateProcess() 并不容易。详细用法可以参考 MSDN 网站上的原文;日后有机会再发一帖该原文的原创翻译。
句柄继承导致增加内核对象的使用计数,因为父子进程引用相同的对象;仅当父子进程都调用 CloseHandle() 关闭引用该对象的句柄,或者父子进程都退出,让使用计数 = 0,该对象才会实际在内核空间中被销毁。强调一下,如果此后父进程又创建新的内核对象并将其设置为可继承的,现存子进程并不会自动继承新的对象,句柄继承仅发生在 CreateProcess() 调用并传入了相关参数时。
)