前一阵子,我在编写文件变化监控程序的时候遇到了文件被占用的问题。很早之前写过一篇关于 CreateFile
函数的 dwDesiredAccess
和 dwShareMode
参数的笔记。我发现之前的理解不够全面、准确。为了更好的理解这两个参数的作用,我搜索了大量资料,编写了测试程序及测试脚本,参考了 xp
源码,终于搞清楚这两个参数的作用。简而言之,需要遵循以下两个规则:
规则 1:后续的访问权限与先前的共享模式不能冲突。
规则 2:后续的共享模式与先前的访问权限不能冲突。
如果你对下面的几个问题有明确的答案并且清楚的知道原因,那么可以跳过本文了。
在总结之前,先看一下关键的权限检查代码。
说明: DesiredAccess
表示 访问权限,DesiredShareAccess
表示 共享模式。
代码中的注释已经写的很清楚了,再整体梳理一下:
更新逻辑(else if 分支):
每次权限检查成功后,如果指定了 Update
参数,SharedAccess->OpenCount
计数会加一。
当 DesiredAccess
包含读 / 写 / 删除标志的时候,SharedAccess->Readers / Writers / Deleters
计数会加一。
当 DesiredShareAccess
包含读 / 写 / 删除标志的时候,ShareAccess->SharedRead / SharedWrite / SharedDelete
计数会加一。
检查逻辑(if 分支):
如果本次调用时 DesiredAccess
包含了读 / 写 / 删除标志(FileObject->ReadAccess / WriteAccess / DeleteAccess
为真)并且在之前的调用中 DesiredShareAccess
缺少对应的读 / 写 / 删除标志(ShareAccess->SharedRead / SharedWrite / SharedDelete < ocount
),违反规则 1,权限检查会失败。
如果在之前的调用中 DesiredAccess
包含了读 / 写 / 删除标志(ShareAccess->Readers / Writers / Deleters != 0
),并且本次调用时 DesiredShareAccess
缺少对应读 / 写 / 删除标志(FileObject->SharedRead / SharedWrite / SharedDelete
为假),违反规则 2,权限检查会失败。
我把各种情况下的打开结果整理成了表格,供大家参考。
各项的意义解释如下:
访问权限
代表 dwDesiredAccess
参数,共享模式
代表 dwShareAccess
参数。1
表示第一次调用,2
表示第二次调用。
R
Read
,表示读。W
Write
,表示写。RW
ReadWrite
,表示读写。N
None
, 表示独占。
/
表示或者。为了减少组合数量。比如第一行中的 访问权限 2 可以是读 / 写 / 读写中的任意一种。
---
表示对应位置是什么都可以,不影响结果。比如,第一行的 访问权限 1 可以是读 / 写 / 读写中的任意一种,不论是哪种都会打开失败。
结果列只统计了第二次的结果,因为第一次总是成功的。
以上结论我在 win10
系统上亲自验证过,整体验证思路是用不同的参数调用 CreateFile
打开同一个文件。关键验证代码如下:
生成的程序名是 CreateFile.exe
,该程序可以接收命令行参数,通过 -f
指定文件名,通过 -a
指定访问权限,通过 -s
指定共享模式, 通过 -h
显示帮助。
为了更方便的验证,我又写了批处理脚本,关键脚本如下:
脚本 CreateFileBatchCaller.bat
接收一个参数,内部会根据 -
分割参数,前四项有固定意义,分别表示第一次调用 CreateFile.exe
的访问权限和共享模式、第二次调用 CreateFile.exe
的访问权限和共享模式。
read-readwrite-write-none-failed.bat
是众多调用脚本中的一个,内部会把当前脚本的文件名(不包括扩展名)当作参数调用 CreateFileBatchCaller.bat
。 该脚本可以验证第一次以读访问权限、读写共享模式打开文件,第二次以写访问权限、独占共享模式打开文件的情况。
所有脚本及源码我已经上传到我的个人仓库了。如果你也想亲自动手验证一下,可以从如下位置获取测试代码,编译好的程序及测试脚本。
github:
https://github.com/BianChengNan/MyBlogStuff/tree/master/review-CreateFile-DesireAccess-ShareMode
gitee:
https://gitee.com/bianchengnan/my-blog-stuff/tree/master/review-CreateFile-DesireAccess-ShareMode
百度云盘:
https://pan.baidu.com/s/10BMMhPGiiBYjlMFrbQH-3g?pwd=tibm
至此,文章开头的几个问题的答案应该已经很明显了。一起来看一下。
第一次尝试以读访问权限,写共享模式打开文件,会成功吗?
答:会成功。
第一次打开时总会成功。
如果第一次打开成功了,第二次尝试以写访问权限,读共享模式打开。会成功吗?
答:会成功。
第一次的共享模式是写,第二次的访问权限是写,第二次的访问权限与第一次的共享模式不冲突。
第二次的共享模式是读,第一次的访问权限是读,第二次的共享模式与第一次的访问权限不冲突。
如果第二次打开成功了,第三次尝试以读/写/读写访问权限,读写共享模式打开,会成功吗?
答:不会成功。
第三次的访问权限是读的话,与第一次的共享模式(写)冲突。
第三次的访问权限是写的话,与第二次的共享模式(读)冲突。
第三次的访问权限是读写的话,既与第一次的共享模式(写)冲突,又与第二次的共享模式(读)冲突。
这里只贴了第三次的访问权限是写的情况,其它两种情况也会失败。
第一次尝试以读访问权限,写共享模式打开文件,第二次尝试以写访问权限,读写共享模式打开。第三次尝试以写访问权限,读写共享模式打开,会成功吗?
答:会成功。
第三次的访问权限(写),既不与第一次的共享模式(写)冲突,又不与第二次的共享模式(读写)冲突。
第三次的共享模式(读写),既不与第一次的访问权限(读)冲突,又不与第二次的访问权限(写)冲突。
最后,贴一下之前整理的笔记,基本正确,但是不够全面,不够深刻。
一直对 CreateFile
的参数 dwDesiredAccess
和 dwShareMode
的具体作用不是很清楚,今天重读《windows 核心编程》的时候有了一些新感悟。 简要总结如下:
对 dwDesiredAccess
各种值及含义抄录如下(摘自 《Windows核心编程》第 5
版 第10
章 p279
):
对 dwShareMode
的各种值及含义抄录如下(摘自 《Windows核心编程》第 5
版 第10
章 p279
):
友情提示: 上表中的 如果设备已经以 xxx 方式打开
指的是先前调用的 dwShareMode
参数。
NTSTATUS IoCheckShareAccess(
IN ACCESS_MASK DesiredAccess,
IN
ULONG
DesiredShareAccess,
IN OUT PFILE_OBJECT FileObject,
IN OUT PSHARE_ACCESS ShareAccess,
IN
BOOLEAN
Update
)
{
PAGED_CODE();
FileObject->ReadAccess = (
BOOLEAN
) ((DesiredAccess & (FILE_EXECUTE | FILE_READ_DATA)) != 0);
FileObject->WriteAccess = (
BOOLEAN
) ((DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
FileObject->DeleteAccess = (
BOOLEAN
) ((DesiredAccess & DELETE) != 0);
if
(FileObject->ReadAccess || FileObject->WriteAccess || FileObject->DeleteAccess)
{
FileObject->SharedRead = (
BOOLEAN
) ((DesiredShareAccess & FILE_SHARE_READ) != 0);
FileObject->SharedWrite = (
BOOLEAN
) ((DesiredShareAccess & FILE_SHARE_WRITE) != 0);
FileObject->SharedDelete = (
BOOLEAN
) ((DesiredShareAccess & FILE_SHARE_DELETE) != 0);
if
(FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
{
PIOP_FILE_OBJECT_EXTENSION fileObjectExtension =(PIOP_FILE_OBJECT_EXTENSION)(FileObject + 1);
if
(fileObjectExtension->FileObjectExtensionFlags & FO_EXTENSION_IGNORE_SHARE_ACCESS_CHECK)
return
STATUS_SUCCESS;
}
ULONG
ocount = ShareAccess->OpenCount;
if
(
(FileObject->ReadAccess && (ShareAccess->SharedRead < ocount))
|| (FileObject->WriteAccess && (ShareAccess->SharedWrite < ocount))
|| (FileObject->DeleteAccess && (ShareAccess->SharedDelete < ocount))
|| ((ShareAccess->Readers != 0) && !FileObject->SharedRead)
|| ((ShareAccess->Writers != 0) && !FileObject->SharedWrite)
|| ((ShareAccess->Deleters != 0) && !FileObject->SharedDelete)
)
{
return
STATUS_SHARING_VIOLATION;
}
else
if
(Update)
{
ShareAccess->OpenCount++;
ShareAccess->Readers += FileObject->ReadAccess;
ShareAccess->Writers += FileObject->WriteAccess;
ShareAccess->Deleters += FileObject->DeleteAccess;
ShareAccess->SharedRead += FileObject->SharedRead;
ShareAccess->SharedWrite += FileObject->SharedWrite;
ShareAccess->SharedDelete += FileObject->SharedDelete;
}
}
return
STATUS_SUCCESS;
}
NTSTATUS IoCheckShareAccess(
IN ACCESS_MASK DesiredAccess,
IN
ULONG
DesiredShareAccess,
IN OUT PFILE_OBJECT FileObject,
IN OUT PSHARE_ACCESS ShareAccess,
IN
BOOLEAN
Update
)
{
PAGED_CODE();
FileObject->ReadAccess = (
BOOLEAN
) ((DesiredAccess & (FILE_EXECUTE | FILE_READ_DATA)) != 0);
FileObject->WriteAccess = (
BOOLEAN
) ((DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
FileObject->DeleteAccess = (
BOOLEAN
) ((DesiredAccess & DELETE) != 0);
if
(FileObject->ReadAccess || FileObject->WriteAccess || FileObject->DeleteAccess)
{
FileObject->SharedRead = (
BOOLEAN
) ((DesiredShareAccess & FILE_SHARE_READ) != 0);
FileObject->SharedWrite = (
BOOLEAN
) ((DesiredShareAccess & FILE_SHARE_WRITE) != 0);
FileObject->SharedDelete = (
BOOLEAN
) ((DesiredShareAccess & FILE_SHARE_DELETE) != 0);
if
(FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
{
PIOP_FILE_OBJECT_EXTENSION fileObjectExtension =(PIOP_FILE_OBJECT_EXTENSION)(FileObject + 1);
if
(fileObjectExtension->FileObjectExtensionFlags & FO_EXTENSION_IGNORE_SHARE_ACCESS_CHECK)
return
STATUS_SUCCESS;
}
ULONG
ocount = ShareAccess->OpenCount;
if
(
(FileObject->ReadAccess && (ShareAccess->SharedRead < ocount))
|| (FileObject->WriteAccess && (ShareAccess->SharedWrite < ocount))
|| (FileObject->DeleteAccess && (ShareAccess->SharedDelete < ocount))
|| ((ShareAccess->Readers != 0) && !FileObject->SharedRead)
|| ((ShareAccess->Writers != 0) && !FileObject->SharedWrite)
|| ((ShareAccess->Deleters != 0) && !FileObject->SharedDelete)
)
{
return
STATUS_SHARING_VIOLATION;
}
else
if
(Update)
{
ShareAccess->OpenCount++;
ShareAccess->Readers += FileObject->ReadAccess;
ShareAccess->Writers += FileObject->WriteAccess;
ShareAccess->Deleters += FileObject->DeleteAccess;
ShareAccess->SharedRead += FileObject->SharedRead;
ShareAccess->SharedWrite += FileObject->SharedWrite;
ShareAccess->SharedDelete += FileObject->SharedDelete;
}
}
return
STATUS_SUCCESS;
}
访问权限 1 |
共享模式 1 |
访问权限 2 |
共享模式 2 |
结果 |
说明 |
--- |
N |
R / W / RW |
--- |
失败 |
违反了 规则1
|
--- |
R |
W / RW |
--- |
失败 |
违反了 规则1
|
--- |
W |
R / RW |
--- |
失败 |
违反了 规则1
|
R / W / RW |
--- |
--- |
N |
失败 |
违反了 规则2
|
W / RW |
--- |
--- |
R |
失败 |
违反了 规则2
|
R / RW |
--- |
--- |
W |
失败 |
违反了 规则2
|
R |
R |
R |
R / RW |
成功 |
第二次的访问权限与第一次的共享模式不冲突。 <br/>第二次的共享模式与第一次的访问权限不冲突。 |
R |
W |
W |
R / RW |
成功 |
同上 |
R |
RW |
R / W / RW |
R / RW |
成功 |
同上 |
W |
W |
W |
W / RW |
成功 |
同上 |
W |
R |
R |
W / RW |
成功 |
同上 |
W |
RW |
R / W / RW |
W / RW |
成功 |
同上 |
RW |
R |
R |
RW |
成功 |
同上 |
RW |
W |
W |
RW |
成功 |
同上 |
RW |
RW |
R / W / RW |
RW |
成功 |
同上 |
using
System;
using
System.Collections.Generic;
using
System.CommandLine;
using
System.IO;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
namespace
CreateFile
{
class
Program
{
// ref https://learn.microsoft.com/en-us/dotnet/standard/commandline/define-commands
static
Command SetupCommandHandler()
{
var
filePathOption =
new
Option<
string
>(name:
"--path"
, getDefaultValue: () =>
"test.txt"
, description:
"file path"
);
filePathOption.AddAlias(
"-f"
);
filePathOption.AddAlias(
"-p"
);
var
fileModeOption =
new
Option<
string
>(name:
"--mode"
, getDefaultValue: () =>
"Open"
, description:
"file mode"
)
.FromAmong(
"CreateNew"
,
"Create"
,
"Open"
,
"OpenOrCreate"
,
"Truncate"
,
"Append"
);
fileModeOption.AddAlias(
"-m"
);
var
fileShareOption =
new
Option<
string
>(
"--share"
,
"file share"
) { IsRequired =
true
}
.FromAmong(
"None"
,
"Read"
,
"Write"
,
"ReadWrite"
,
"Delete"
,
"Inheritable"
);
fileShareOption.AddAlias(
"-s"
);
var
fileAccessOption =
new
Option<
string
>(
"--access"
,
"file access"
) { IsRequired =
true
}
.FromAmong(
"Read"
,
"Write"
,
"ReadWrite"
);
fileAccessOption.AddAlias(
"-a"
);
var
autoQuitOption =
new
Option<
bool
>(name:
"--autoquit"
, getDefaultValue: () =>
false
, description:
"auto quit"
);
autoQuitOption.AddAlias(
"-q"
);
var
command =
new
RootCommand();
command.Add(filePathOption);
command.Add(fileModeOption);
command.Add(fileShareOption);
command.Add(fileAccessOption);
command.Add(autoQuitOption);
command.SetHandler((filePath, fileMode, fileShare, fileAccess, autoQuit) =>
{
OpenFileAndWait(filePath, fileMode, fileShare, fileAccess, autoQuit);
}, filePathOption, fileModeOption, fileShareOption, fileAccessOption, autoQuitOption);
return
command;
}
static
void
Main(
string
[] args)
{
var
command = SetupCommandHandler();
command.Invoke(args);
}
static
void
OpenFileAndWait(
string
strFilePath,
string
strFileMode,
string
strFileShare,
string
strFileAccess,
bool
autoQuit)
{
FileStream stream =
null
;
try
{
var
fileMode = (FileMode)System.Enum.Parse(
typeof
(FileMode), strFileMode);
var
fileShare = (FileShare)System.Enum.Parse(
typeof
(FileShare), strFileShare);
var
fileAccess = (FileAccess)System.Enum.Parse(
typeof
(FileAccess), strFileAccess);
System.Console.WriteLine(
string
.Format(
"[{0}] file:{1}, mode: {2}, share: {3}, access: {4}!"
, System.DateTime.Now.ToString(
"yyyy-MM-dd HH:mm:ss.fff"
), strFilePath, strFileMode, strFileShare, strFileAccess));
stream = File.Open(strFilePath, fileMode, fileAccess, fileShare);
}
catch
(Exception ex)
{
System.Console.WriteLine(
string
.Format(
"opening file [{0}] failed with {1}!"
, strFilePath, ex));
}
if
(!autoQuit)
{
System.Console.WriteLine(
"press any key to continue..."
);
System.Console.ReadKey();
}
if
(stream !=
null
)
{
stream.Dispose();
}
}
}
}
using
System;
using
System.Collections.Generic;
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课