父进程文件句柄被子进程占用

场景描述:
1.父进程A使用函数fopen打开(创建)一个磁盘文件file.exe.tmp
2.父进程进行长时间的边下载边写入
3.下载写入完成后,使用fclose关闭文件句柄
4.重命名file.exe.tmp为file.exe

以上为理想情况下的代码执行流程。

问题:
在第四步,重命名文件有可能失败,使用 GetLastError 函数发现失败原因为 此文件被另外一个进程占用。

解决:
1.使用微软官方开发的handle.exe来查找是哪个进程占用了这个文件
(handle.exe的下载地址  https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx )
使用方法:在文件下载完成,执行改名函数之前,执行 Handle.exe file.exe.tmp /accepteula 命令(使用管道获取直接结果并打印)
2.在打印结果中发现,文件是被父进程A的子进程 B 占用了。
在file.exe.tmp的下载过程中,父进程A使用 createprocess 函数创建了子进程B,看一下函数原型:
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
);

其中 bInheritHandles 参数的描述为:
If this parameter TRUE, each inheritable handle in the calling process is inherited by the new process. If the parameter is FALSE, the handles are not inherited. Note that inherited handles have the same value and access rights as the original handles.
简单的说就是允许子进程继承父进程的句柄。

3.一种简单的解决方法:bInheritHandles参数设置为flase,那么子进程B就不会占用这个文件了。
但是我这里无法采用该方法,因为这个子进程B是第三方程序,他必须继承某些句柄才能玩,那么我换一种方法解决。

4.看fopen函数,实现代码在CRT中,CRT代码是开源的,路径在:Microsoft Visual Studio 10.0\VC\crt\src
一层层往下跟,发现fopen 实际是 __topenfile函数,__topenfile函数中有详细的 文件打开mode处理流程。
mode处理流程中有一段代码为:

case _T(‘N‘):
    modeflag |= _O_NOINHERIT;
    break;

_O_NOINHERIT的定义为:    
/* Open handle inherit bit */
#define _O_NOINHERIT    0x0080  /* child process doesn‘t inherit file */

这里就是控制该句柄是否允许被子进程占用了。
那么只需要在fopen的Mode设置为  "ab+N" 就可以了。

沿着源码继续往下看,发现在CRT这一层最终的调用函数为 _tsopen_nolock (文件\Microsoft Visual Studio 10.0\VC\crt\src\open.c)
里面对flag进行了大量的处理,最终调用 CreateFile 函数创建、打开文件,并且有不少的CreateFile函数异常处理代码。
CreateFile函数是系统API,不开源,要继续跟踪就得逆向,就不继续往下了。

5.所以得出第3种解决方案:直接使用系统API CreateFile 创建、打开文件,

6.第四种解决方案:参考unlock的方法:先openprocess,然后通过 DuplicateHandle 复制句柄,再结束句柄,从而解决子进程B的占用。这个方法网上可以很容易搜到源码,我就不废话了。

7.总结
以上一共提出了4种方法解决 父进程句柄被子进程占用的问题,以下对比4种方案:
1.CreateProcess设置参数:很方便,但是如果子进程有特殊的继承句柄需求,则不适用
2.fopen设置参数:很方便,可跨平台兼容,修改代码只需增加一个字节即可解决问题
3.使用 CreateFile 创建、打开文件,替代fopen:CreateFile功能强大,但是要在商业软件中玩好这个函数不容易,需要处理大量的异常(可参考_tsopen_nolock函数的实现源码)
4.DuplicateHandle后closehandle:最强大的关闭其他进程中句柄的方式,主要运用于ring3中解除简单木马病毒的文件占用技术(无法适用于文件占坑技术),但是使用风险是有的,真正应用也需要写一大堆异常处理代码。

时间: 2024-10-15 04:12:51

父进程文件句柄被子进程占用的相关文章

qt新进程工作目录的设置(工作目录确实是被子进程继承的,但也可以设置)

经过试验,qt启动一个新的进程时,这个进程的工作目录是继承父进程的,无论是通过start还是startDetached来启动. 其实对于linux系统,qt底层应该也是调用fork.exec之类的函数,对于fork,参看apue中文版第三版,有以下解析: 在f o r k之后处理文件描述符有两种常见的情况:(1) 父进程等待子进程完成.在这种情况下,父进程无需对其描述符做任何处理.当子进程终止后,它曾进行过读.写操作的任一共享描述符的文件位移量已做了相应更新.(2) 父.子进程各自执行不同的程序

Window通过cmd查看端口占用、相应进程、杀死进程等的命令

如何查看程序占用的端口 一. 查看所有进程占用的端口 在开始-运行-cmd,输入:netstat –ano可以查看所有进程 二.查看占用指定端口的程序 当你在用tomcat发布程序时,经常会遇到端口被占用的情况,我们想知道是哪个程序或进程占用了端口,可以用该命令 netstat –ano|findstr "指定端口号" 二.查看占用指定端口的程序 当你在用tomcat发布程序时,经常会遇到端口被占用的情况,我们想知道是哪个程序或进程占用了端口,可以用该命令 netstat –ano|f

Window 通过cmd查看端口占用、相应进程、杀死进程等的命令【转】

一. 查看所有进程占用的端口  在开始-运行-cmd,输入:netstat –ano可以查看所有进程 二.查看占用指定端口的程序  当你在用tomcat发布程序时,经常会遇到端口被占用的情况,我们想知道是哪个程序或进程占用了端口,可以用该命令 netstat –ano|findstr “指定端口号” 二.查看占用指定端口的程序 当你在用tomcat发布程序时,经常会遇到端口被占用的情况,我们想知道是哪个程序或进程占用了端口,可以用该命令 netstat –ano|findstr “指定端口号”

win/linux 下使用 psutil 获取进程 CPU / memory / IO 占用信息

psutil - A cross-platform process and system utilities module for Python 1. 安装 pip 安装即可. windows 下需要安装 vs2008,否则报错: Unable to find vcvarsall.bat 如果已经安装 vs2010 / vs2012 则需要设置环境变量,VS90COMNTOOLS 指向已有的 vs 变量. vs2010 设置如下: VS90COMNTOOLS = %VS100COMNTOOLS%

进程的端口被占用的解决方案

今天,不小心把IIS的默认站点给删除了,再次添加默认站点,路径C:\inetpub\wwwroot,发现启用时,提示--另一个程序正在使用此文件,进程无法访问! 解决方案:在DOS下输入: netstat -ano 查看80端口信息中的PID号,然后打开任务管理器查看对应PID的程序名称. 打开任务管理器,点击“查看”/选择列,勾选“PID(进程标识符)”,然后单击“进程”标签,找到80端口对应的pid,就可以看到是那个程序占用的了,最后发现是迅雷占用了80端口.更改这个程序的port,再重启这

进程的基本属性:进程ID、父进程ID、进程组ID、会话和控制终端

摘要:本文主要介绍进程的基本属性,基本属性包括:进程ID.父进程ID.进程组ID.会话和控制终端. 进程基本属性 1.进程ID(PID) 函数定义: #include <sys/types.h> #include <unistd.h> pid_t getpid(void); 函数说明: 每个进程都有一个非负整型表示的唯一进程ID(PID).好比如我们的身份证一样,每个人的身份证号是唯一的.因为进程ID标示符总是唯一的,常将其用来做其他标示符的一部分以保证其唯一性,进程ID(PID)

通过进程id找到进程对应的容器并统计每个进程的内存占用写到excel里

1 # coding=utf-8 2 import re 3 import os 4 import commands 5 import json 6 import psutil 7 from pyExcelerator import * 8 9 10 def execute(cmd): 11 status, output = commands.getstatusoutput(cmd) 12 if status != 0: 13 raise Exception('status is %s, out

windows查看端口占用的进程和杀死进程

1通过端口查进程 idnetstat -ano | findstr "LISTENING"| findstr ":10000" 2.通过进程查应用(图为百度网盘的子程序) tasklist |findstr "2708" 3.通过进程id杀死进程 taskkill /f /t /im “2708” 原文地址:https://www.cnblogs.com/jkwll/p/10813719.html

Linux中的僵尸进程和孤儿进程

在UNIX里,除了进程0(即PID=0的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程.        操作系统内核以进程标识符(Process Identifier,即PID)来识别进程.进程0是系统引导时创建的一个特殊进程,在其调用fork创建出一个子进程(即PID=1的进程1,又称init)