热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

VC++中进程与多进程管理的方法

VC++中进程与多进程管理的方法--Linux通用技术-Linux编程与内核信息,下面是详情阅读。
进程是当前操作系统下一个被加载到内存的、正在运行的应用程序的实例。每一个进程都是由内核对象和地址空间所组成的,内核对象可以让系统在其内存放有关进程的统计信息并使系统能够以此来管理进程,而地址空间则包括了所有程序模块的代码和数据以及线程堆栈、堆分配空间等动态分配的空间。进程仅仅是一个存在,是不能独自完成任何操作的,必须拥有至少一个在其环境下运行的线程,并由其负责执行在进程地址空间内的代码。在进程启动的同时即同时启动了一个线程,该线程被称作主线程或是执行线程,由此线程可以继续创建子线程。如果主线程退出,那么进程也就没有存在的可能了,系统将自动撤消该进程并完成对其地址空间的释放。

  加载到进程地址空间的每一个可执行文件或动态链接库文件的映象都会被分配一个与之相关联的全局唯一的实例句柄(Hinstance)。该实例句柄实际是一个记录有进程加载位置的基本内存地址。进程的实例句柄在程序入口函数WinMain()中通过第一个参数 HINSTANCE hinstExe传递,其实际值即为进程所使用的基本地址空间的地址。对于VC++链接程序所链接产生的程序,其默认的基本地址空间地址为 0x00400000,如没有必要一般不要修改该值。在程序中,可以通过GetModuleHandle()函数得到指定模块所使用的基本地址空间。
  子进程的创建

  进程的创建通过CreateProcess()函数来实现,CreateProcess()通过创建一个新的进程及在其地址空间内运行的主线程来启动并运行一个新的程序。具体的,在执行CreateProcess()函数时,首先由操作系统负责创建一个进程内核对象,初始化计数为1,并立即为新进程创建一块虚拟地址空间。随后将可执行文件或其他任何必要的动态链接库文件的代码和数据装载到该地址空间中。在创建主线程时,也是首先由系统负责创建一个线程内核对象,并初始化为1。最后启动主线程并执行进程的入口函数WinMain(),完成对进程和执行线程的创建。

  CreateProcess()函数的原型声明如下:

BOOL CreateProcess(
 LPCTSTR lpApplicationName, // 可执行模块名
 LPTSTR lpCommandLine, // 命令行字符串
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性
 BOOL bInheritHandles, // 句柄继承标志
 DWORD dwCreationFlags, // 创建标志
 LPVOID lpEnvironment, // 指向新的环境块的指针
 LPCTSTR lpCurrentDirectory, // 指向当前目录名的指针
 LPSTARTUPINFO lpStartupInfo, // 指向启动信息结构的指针
 LPPROCESS_INFORMATION lpProcessInformation // 指向进程信息结构的指针
);

在程序设计时,某一个具体的功能模块可以通过函数或是线程等不同的形式来实现。对于同一进程而言,这些函数、线程都是存在于同一个地址空间下的,而且在执行时,大多只对与其相关的一些数据进行处理。如果算法存在某种错误,将有可能破坏与其同处一个地址空间的其他一些重要内容,这将造成比较严重的后果。为保护地址空间中的内容可以考虑将那些需要对地址空间中的数据进行访问的操作部分放到另外一个进程的地址空间中运行,并且只允许其访问原进程地址空间中的相关数据。具体的,可在进程中通过CreateProcess()函数去创建一个子进程,子进程在全部处理过程中只对父进程地址空间中的相关数据进行访问,从而可以保护父进程地址空间中与当前子进程执行任务无关的全部数据。对于这种情况,子进程所体现出来的作用同函数和线程比较相似,可以看成是父进程在运行期间的一个过程。为此,需要由父进程来掌握子进程的启动、执行和退出。下面这段代码即展示了此过程:

// 临时变量
CString sCommandLine;
char cWindowsDirectory[MAX_PATH];
char cCommandLine[MAX_PATH];
DWORD dwExitCode;
PROCESS_INFORMATION pi;
STARTUPINFO si = {sizeof(si)};
// 得到Windows目录
GetWindowsDirectory(cWindowsDirectory, MAX_PATH);
// 启动"记事本"程序的命令行
sCommandLine = CString(cWindowsDirectory) + "\\NotePad.exe";
::strcpy(cCommandLine, sCommandLine);
// 启动"记事本"作为子进程
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, π);
if (ret) {
 // 关闭子进程的主线程句柄
 CloseHandle(pi.hThread);
 // 等待子进程的退出
 WaitForSingleObject(pi.hProcess, INFINITE);
 // 获取子进程的退出码
 GetExitCodeProcess(pi.hProcess, &dwExitCode);
 // 关闭子进程句柄
 CloseHandle(pi.hProcess);
}

  此段代码首先通过CreateProcess()创建Windows自带的“记事本”程序为子进程,子进程启动后父进程通过 WaitForSingleObject()函数等待其执行的结束,在子进程没有退出前父进程是一直处于阻塞状态的,这里子进程的作用同单线程中的函数类似。一旦子进程退出,WaitForSingleObject()函数所等待的pi.hProcess对象将得到通知,父进程将得以继续,如有必要可以通过GetExitCodeProcess()来获取子进程的退出代码。

相比而言,更多的情况是父进程在启动完子进程后就再不与其进行任何数据交换和通讯,由其创建的子进程的执行成功与否均与父进程无关。许多大型软件在设计时也多采用了这类思想,将某些功能完全通过独立的应用程序来完成,当需要执行某操作时只要通过主程序启动相应的子进程即可,具体的处理工作均由子进程去完成。这类子进程的创建过程更为简单,例如对于上面那段代码只需去除对子进程句柄pi.hProcess的等待即可:

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, π);
if (ret) {
 // 关闭子进程的主线程句柄
 CloseHandle(pi.hThread);
 // 关闭子进程句柄
 CloseHandle(pi.hProcess);
}

  可以通过dwCreationFlags参数在创建进程时设置子进程的优先级。前面的示例代码在创建子进程时使用的均是默认的优先级,如果要将优先级设置为高,可以修改如下:

BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, HIGH_PRIORITY_CLASS, NULL, NULL, &si, π);

  如果在进程创建时没有特别设置优先级,可以通过SetPriorityClass()函数来动态设定,该函数需要待操作进程的句柄和优先级标识符作为入口参数,函数原型为:

BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);

  对于前面没有设定优先级的例子代码,可以在子进程启动后由父进程来动态改变其优先级设置:

SetPriorityClass(pi.hProcess, HIGH_PRIORITY_CLASS);

  或是由子进程在其启动后自行改变优先级设置,需要注意的是这时进程句柄应设置为子进程自身的句柄,可通过GetCurrentProcess()函数来获取:

HANDLE hProcess = GetCurrentProcess();
SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS);

  进程的互斥运行

  正常情况下,一个进程的运行一般是不会影响到其他正在运行的进程的。但是对于某些有特殊要求的如以独占方式使用串行口等硬件设备的程序就要求在其进程运行期间不允许其他试图使用此端口设备的程序运行的,而且此类程序通常也不允许运行同一个程序的多个实例。这就引出了进程互斥的问题。

实现进程互斥的核心思想比较简单:进程在启动时首先检查当前系统是否已经存在有此进程的实例,如果没有,进程将成功创建并设置标识实例已经存在的标记。此后再创建进程时将会通过该标记而知晓其实例已经存在,从而保证进程在系统中只能存在一个实例。具体可以采取内存映射文件、有名事件量、有名互斥量以及全局共享变量等多种方法来实现。下面就分别对其中具有代表性的有名互斥量和全局共享变量这两种方法进行介绍:

// 创建互斥量
HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");
// 检查错误代码
if (GetLastError() == ERROR_ALREADY_EXISTS) {
 // 如果已有互斥量存在则释放句柄并复位互斥量
 CloseHandle(m_hMutex);
 m_hMutex = NULL;
 // 程序退出
 return FALSE;
}

  上面这段代码演示了有名互斥量在进程互斥中的用法。代码的核心是CreateMutex()对有名互斥量的创建。CreateMutex()函数可用来创建一个有名或无名的互斥量对象,其函数原型为:

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
 BOOL bInitialOwner, // 初始化互斥对象的所有者
 LPCTSTR lpName // 指向互斥对象名的指针
);

  如果函数成功执行,将返回一个互斥量对象的句柄。如果在CreateMutex()执行前已经存在有相同名字的互斥量,函数将返回这个已经存在互斥量的句柄,并且可以通过GetLastError()得到错误代码ERROR_ALREADY_EXIST。可见,通过对错误代码 ERROR_ALREADY_EXIST的检测可以实现CreateMutex()对进程的互斥。

  使用全局共享变量的方法则主要是在 MFC框架程序中通过编译器来实现的。通过#pragma data_seg预编译指令创建一个新节,在此节中可用volatile关键字定义一个变量,而且必须对其进行初始化。Volatile关键字指定了变量可以为外部进程访问。最后,为了使该变量能够在进程互斥过程中发挥作用,还要将其设置为共享变量,同时允许具有读、写访问权限。这可以通过#pragma comment预编译指令来通知编译器。下面给出使用了全局变量的进程互斥代码清单:

#pragma data_seg("Shared")
int volatile g_lAppInstance =0;
#pragma data_seg()
#pragma comment(linker,"/section:Shared,RWS")
……
if(++g_lAppInstance>1)
return FALSE;

此段代码的作用是在进程启动时对全局共享变量g_nAppInstancd 加1 ,如果发现其值大于1,那么就返回FALSE以通知进程结束。这里需要特别指出的是,为了使以上两段代码能够真正起到对进程互斥的作用,必须将其放置在应用程序的入口代码处,即应用程序类的初始化实例函数InitInstance()的开始处。

  结束进程

  进程只是提供了一段地址空间和内核对象,其运行是通过在其地址空间内的主线程来体现的。当主线程的进入点函数返回时,进程也就随之结束。这种进程的终止方式是进程的正常退出,进程中的所有线程资源都能够得到正确的清除。除了这种进程的正常推出方式外,有时还需要在程序中通过代码来强制结束本进程或其他进程的运行。 ExitProcess()函数即可在进程中的某个线程中使用,并将立即终止本进程的运行。ExitProcess()函数原型为:

VOID ExitProcess(UINT uExitCode);

  其参数uExitCode为进程设置了退出代码。该函数具有强制性,在执行完毕后进程即已经被结束,因此位于其后的任何代码将不能被执行。虽然 ExitProcess()函数可以在结束进程的同时通知与其相关联的动态链接库,但是由于它的这种执行的强制性,使得ExitProcess()函数在使用上将存在有安全隐患。例如,如果在程序调用ExitProcess()函数之前曾用new操作符申请过一段内存,那么将会由于ExitProcess ()函数的强制性而无法通过delete操作符将其释放,从而造成内存泄漏。有鉴于ExitProcess()函数的强制性和不安全性,在使用时一定要引起注意。

  ExitProcess()只能强制执行本进程的退出,如果要在一个进程中强制结束其他的进程就要用 TerminateProcess()来实现。与ExitProcess()不同,TerminateProcess()函数执行后,被终止的进程是不会得到任何关于程序退出的通知的。也就是说,被终止的进程是无法在结束运行前进行退出前的收尾工作的。所以,通常只有在其他任何方法都无法迫使进程退出时才会考虑使用TerminateProcess()去强制结束进程的。下面给出TerminateProcess()的函数原型:

BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);


  参数hProcess和uExitCode分别为进程句柄和退出代码。如果被结束的是本进程,可以通过GetCurrentProcess()获取到句柄。TerminateProcess()是异步执行的,在调用返回后并不能确定被终止进程是否已经真的退出,如果调用TerminateProcess ()的进程对此细节关心,可以通过WaitForSingleObject()来等待进程的真正结束。

  小结

  多进程是多任务管理中的重要内容,文中上述部分对其基本概念和主要的技术如子进程的创建与结束、进程间的互斥运行等做了较详细的介绍。通过本文读者应能对多进程管理有一个初步的认识。
推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • 无线认证设置故障排除方法及注意事项
    本文介绍了解决无线认证设置故障的方法和注意事项,包括检查无线路由器工作状态、关闭手机休眠状态下的网络设置、重启路由器、更改认证类型、恢复出厂设置和手机网络设置等。通过这些方法,可以解决无线认证设置可能出现的问题,确保无线网络正常连接和上网。同时,还提供了一些注意事项,以便用户在进行无线认证设置时能够正确操作。 ... [详细]
  • 本文详细介绍了相机防抖的设置方法和使用技巧,包括索尼防抖设置、VR和Stabilizer档位的选择、机身菜单设置等。同时解释了相机防抖的原理,包括电子防抖和光学防抖的区别,以及它们对画质细节的影响。此外,还提到了一些运动相机的防抖方法,如大疆的Osmo Action的Rock Steady技术。通过本文,你将更好地理解相机防抖的重要性和使用技巧,提高拍摄体验。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • windows便签快捷键_用了windows十几年,没想到竟然这么好用!隐藏的功能你知道吗?
    本文介绍了使用windows操作系统时的一些隐藏功能,包括便签快捷键、截图功能等。同时探讨了windows和macOS操作系统之间的优劣比较,以及人们对于这两个系统的不同看法。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文是一位90后程序员分享的职业发展经验,从年薪3w到30w的薪资增长过程。文章回顾了自己的青春时光,包括与朋友一起玩DOTA的回忆,并附上了一段纪念DOTA青春的视频链接。作者还提到了一些与程序员相关的名词和团队,如Pis、蛛丝马迹、B神、LGD、EHOME等。通过分享自己的经验,作者希望能够给其他程序员提供一些职业发展的思路和启示。 ... [详细]
author-avatar
吃羊的肉
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有