本文由danny发表于 http://blog.csdn.net/danny_share
本篇写的比较随性,有点洋洋洒洒,望见谅。
既然要聊进程间通信,首先得理一些概念,
1. 进程相关概念
(1)程序:
在Windows下,PE文件前两个字节是十六进制的4D 5A(PE文件详细头信息参考http://blog.csdn.net/qiming_zhang/article/details/7309909)
将exe文件载入WinHex,可看到文件前两个字节就是MZ(另外,比如医疗行业的dicom文件,其实也就是一种具备特殊格式的文件:有128字节的导言跟着四个字节的DICM。。。)
一切文件都可看做是特殊格式的字符串,从而任何文件我们都可以看做是txt文件,包括exe(这点跟各种数据类型比如int,string都可理解成一个个的char有异曲同工之妙),只要我们将其文件头看做是普通的文本。
(2)进程
【a】对于DOS,进程就是一个运行着的程序
【b】对于Windows,进程是资源分配的基本单位,是线程运行的容器
(3)线程
Windows95开始支持多线程技术以便实现类似于“边听歌边写程序”的需求。
刚接触进程和线程时,一直困惑既然有了进程,为何还要设计线程,比如要实现“边听歌边写程序”,则均分CPU给这两个进程不就行了吗。后细想,如何能够在一个exe里同时实现这两个功能呢,除非运行一个能够实现写程序的A.exe,A.exe运行以后默默的在后台打开能够听歌的B.exe。这还好办,假如要A.exe在编译的同时还能搜索函数名,好,A.exe又要打开能实现搜索函数名的C.exe,且A.exe和C.exe要共享所有的函数名。A.exe在编译和搜索函数名的同时还要实现阅读代码,好了,又来D.exe,又要共享数据。。。
这里,我们发现这么一个基础设施,就是最好有一个独立于进程本身的运行概念,以便CPU独立于进程来管理运行单元,且同一个进程的多个运行单元最好能够共享内存,于是聪明的IT先辈们设计出了线程。
进程于是变成了分配内存等非CPU运行资源的线程运行容器,
而线程则成了真正干活的“运行单元”
(3)多核
刚谈到为实现“边听歌边写程序”的需求,CPU可以通过分时实现,但反过来一想,CPU之所以分时,是因为只有一个没法分身啊,要是有两个就好了,2005年Intel的PentiumD处理器正式拉开了多核处理器时代的大幕(详见CPU历史http://jettcai.blog.51cto.com/1447637/845866),当线程数量不超过CPU数量时,计算机理论上可实现真正的同时。
(4)多处理器
一个电脑上装有多个CPU(当然每个CPU又可以有多个核),有种分布式的感觉。
记得上学时做过一个风电项目,计算风力发电机叶片和旋转轮毂的载荷计算。数据量太大,1台普通双核电脑要跑几天才能算完,后改用IBM刀片机,8核32线程,4个小时就算完了。
(5)超线程
本来一颗CPU同时只能运行一个线程,通过超线程技术从逻辑上分成两个或多个,使之可以运行多个线程
(6)超频
提高CPU主频,同样的时间可以跑更多指令
2.进程的生命周期(这话很有Java的味道啊)
2,1出生
2.2.1出生之函数篇
Windows平台下打开外部文件有多个函数
ID | 函数 |
| 特点 |
1 | system | 同步 | 标准C语言库函数,调用时会有DOS窗口 |
2 | ShellExecute | 异步 | 比较适合打开网站、打开默认邮件、打开外部图片等 |
3 | ShellExecuteEx | 异步 | 可以返回新创建进程的句柄 适合打开外部程序,并要等它执行结束的情况 |
4 | WinExec | 异步 | 老函数了,只能打开可执行文件,但很简单明了 |
5 | CreateProcess | 同步 | 适合需要和新建进程交互的情况,用法复杂 |
(1) system函数
system("C:\\Windows\\system32\\NotePad.exe");
(2) WinExec函数
UINT result= WinExec("C:\\Windows\\system32\\NotePad.exe",SW_SHOWMAXIMIZED);
switch(result)
{
case 0:
{
}
break;
case ERROR_BAD_FORMAT:
{
//WinExec只能运行可执行文件,不像ShellExecute和ShellExecuteEx可打开文件
//如果WinExec打开文件,如D:\\InstallInfo.txt,则引发此错误
}
break;
case ERROR_FILE_NOT_FOUND:
{
}
break;
case ERROR_PATH_NOT_FOUND:
{
}
break;
}
(3) ShellExecute函数
<pre name="code" class="cpp"> static const int ShellExecute_SUCCESS_CODE=32;
// TODO: Add your control notification handler code here
//(1)此时记事本始终是最大化显示的
//(2)应用实例:例如,Varian的加速器RapidArc配备的OBI开机时会自启动一个文本来显示安装信息
//(3)最后一个参数表示显示方式,值在0-11之间,这里设置成最大化
//(4)异步执行
HINSTANCE myInstance=ShellExecute( AfxGetMainWnd()->m_hWnd, "open", "C:\\Windows\\system32\\NotePad.exe", "D:\\InstallInfo.txt",NULL,SW_SHOWMAXIMIZED);
int result=(int)myInstance;
if(ShellExecute_SUCCESS_CODE<result)
{
//success
//(1)假如把SW_SHOWMAXIMIZED换成100(正常在0-11之间)
// 虽返回值仍大于32,但此时我们却没有看见notepad
// 是因为打开了notepad.exe进程,但系统无法正确显示它
// 导致任务管理器有notepad.exe进程用户却看不见
//(2)假如外部MFC程序在OnInitDialog()里ShowWindow(SW_MINIMIZE);了,ShellExecute再设置成SW_SHOWMAXIMIZED,则认外部程序自己的设置
}
else
{
switch(result)
{
case 0:
{
//内存不足或者资源不足
}
break;
case ERROR_FILE_NOT_FOUND:
{
//文件不存在,
//比如把NotePad.exe改成note.exe,
//但若只把本地文件"D:\\InstallInfo.txt"删除,不会引发本错误,因为此时"D:\\InstallInfo.txt"只是“NotePad.exe”的参数
}
break;
case ERROR_PATH_NOT_FOUND:
{
//路径不存在,
//但实际上,将参数open改为open1,会返回这个错误
}
break;
case ERROR_BAD_FORMAT:
{
//exe格式不对,比如打开exe后缀文件时,该文件不是标准exe文件
//但实际上我将一个纯文本文件后后缀名改成exe后,再用ShellExecute打开它,返回的是SE_ERR_ACCESSDENIED
}
break;
case SE_ERR_ACCESSDENIED:
{
//无权访问
//这里我把C盘改成本机不存在的M盘时,会引发本错误
}
break;
case SE_ERR_ASSOCINCOMPLETE:
{
//文件名不符合Windows规范
}
break;
case SE_ERR_DDEBUSY:
{
//DDE繁忙
}
break;
case SE_ERR_DDEFAIL:
{
//DDE事务失败
}
break;
case SE_ERR_DDETIMEOUT:
{
//DDE事务超时
}
break;
case SE_ERR_DLLNOTFOUND:
{
//DLL不存在
}
break;
//case SE_ERR_FNF: //注意:此值和ERROR_FILE_NOT_FOUND一样
// {
//没有找到文件 估计FNF是File Not Found的缩写
// }
// break;
case SE_ERR_NOASSOC:
{
//无相关应用程序能够打开该文件
}
break;
case SE_ERR_OOM:
{
//内存不足,无法打开该文件
}
break;
//case SE_ERR_PNF: //注意:此值和ERROR_PATH_NOT_FOUND一样
// {
// }
// break;
case SE_ERR_SHARE:
{
}
break;
}
}
(4) ShellExecuteEx函数
// TODO: Add your control notification handler code here
SHELLEXECUTEINFO myshell;
memset(&myshell, 0, sizeof(myshell));
myshell.cbSize = sizeof(myshell);
myshell.hwnd = NULL;
myshell.lpVerb = _T("open");
//myshell.lpFile = "C:\\Windows\\system32\\NotePad.exe";
// myshell.lpParameters="D:\\InstallInfo.txt" ;
myshell.lpFile = "D:\\InstallInfo.txt"; //可以直接写文件名,不一定是可执行文件,ShellExecute也是一样
myshell.nShow = SW_SHOWNORMAL;
myshell.fMask = SEE_MASK_NOCLOSEPROCESS;
BOOL bResult = ShellExecuteEx(&myshell);
DWORD result=WaitForSingleObject(myshell.hProcess,INFINITE);
if(WAIT_OBJECT_0 ==result)
{
//此时只有新打开的记事本进程关闭的时候,才会结束
}
(5) CreateProcess函数
// TODO: Add your control notification handler code here
SECURITY_ATTRIBUTES saProcess;
saProcess.nLength=sizeof(saProcess);
saProcess.lpSecurityDescriptor=NULL;
saProcess.bInheritHandle=TRUE;
SECURITY_ATTRIBUTES saThread;
saThread.nLength=sizeof(saThread);
saThread.lpSecurityDescriptor=NULL;
saThread.bInheritHandle=FALSE;
PROCESS_INFORMATION piProcess;
STARTUPINFO si={sizeof(si)};
//可调整进程优先级
if(TRUE==CreateProcess(NULL,"C:\\Windows\\system32\\NotePad.exe",&saProcess,&saThread,FALSE,ABOVE_NORMAL_PRIORITY_CLASS,NULL,NULL,&si,&piProcess))
{
//
}
2.2.2出生之过程篇
详见http://www.longene.org/techdoc/0625005001224576737.html
也可参见《深入浅出MFC第二版》中39页
【1】shell调用CreateProcess激活我们的程序
【2】系统产生一个核心对象,计数值为1
【3】对于32位操作系统,系统为此进程分配4GB的地址空间
【4】加载器加载相应资源
【5】系统建立该进程的一个主线程
【6】开始运行
2.2.3出生之内存篇
进程作为给线程运行提供资源的平台,其中一项最重要的资源就是内存,这里仅简述不同平台所支持的内存大小,分别是DOS时代的16位,(之前以为DOS已被淘汰,但前阵子看到Varian的加速器的上位机软件就是DOS平台,瞬感医疗行业稳定压倒一切啊),win32位年代和x64年代(关于内存寻址有篇好玩的文章http://bbs.pediy.com/showthread.php?t=115101)。以后有机会的话整理一下内存方面的内容。
ID | 平台 | 特点 |
1 | DOS16位平台 | (1)20跟地址线,16跟数据线 (2)最大1M寻址能力 |
2 | Win32平台 | (1)4G虚拟地址空间 |
3 | X64平台 | (1)理论支持16TB内存,实际64位Windows最大支持192G |
(1)DOS16位平台
VMware安装了MS-DOS7.1(VmWare安装DOS教程见http://blog.csdn.net/fengfengdiandia/article/details/7457803),然后使用UltraISO制作了TurboC的ISO镜像,再通过命令行从虚拟光驱中拷贝到DOS系统下。然后在调用turboC文件夹里的Install安装,最后启动C:\TC下的TC,即可打开TurboC啦,古老而经典的IDE啊。
运行以后发现变量a的地址是FFF4,即指针是16位的,
同时,实模式的实在体现于所访问的地址就是物理地址,不像保护模式下,经过操作系统的映射,进程访问的地址和物理地址不再一一对应
(2)win32年代
保护模式下,Windows给每个进程分配4G的虚拟地址空间。
地址较下的2G属于用户空间(比如这里的0x0015FD44就是较低地址的),较上的2G用于共享系统使用,当然也可以配置成用户空间为3G。
(3)X64平台
64位操作系统理论上支持2^64约16TB的内存。
2.2成长
拷贝一张来自http://oa.gdut.edu.cn/os/multimedia/oscai/chapter2/pages/ch22.htm的图片表示进程的各种状态
但实际上使用CPU资源的是线程,这故里不再赘述,有空的时候整理线程相关的内容
2.3死亡
进程的退出方式分成两种,一种是退出本进程的,另一种是退出非本进程
http://blog.csdn.net/claien/article/details/5796693
2.3.1退出本进程
ID | 退出方式 | 备注 |
1 | 正常退出 | 即主线程正常结束以后,进程正常退出
|
2 | exit | 性质差不多 |
3 | ExitProcess | |
4 | TerminateProcess | |
5 | 发送WM_CLOSE消息 | 跟点击窗口上的关闭按钮是一个道理
|
6 | PostThreadMessage |
|
(1)正常退出
(2)exit、ExitProcess和TerminateProcess
exit对Console和窗口程序都适用
exit(0)
ExitProcess用于结束本进程
ExitProcess(0);
TerminateProcess主要用于结束其他进程
TerminateProcess(GetCurrentProcess(),0);
(5)发送WM_CLOSE消息
SendMessage(WM_CLOSE,0,0);
(6)PostThreadMessage
通过让线程发送WM_QUIT消息
PostThreadMessage(GetCurrentThreadId(),WM_QUIT,0,0);
2.3.2结束其他进程
ID | 退出方式 | 备注 |
1 | 发送WM_CLOSE消息 |
|
2 | TerminateProcess |
|
(1)发送WM_CLOSE消息
首先获取目标窗口的句柄pCWnd,然后pCWnd发送WM_CLOSE消息
CWnd* pCWnd=FindWindow(NULL,"Test");
if(pCWnd!=NULL)
{
pCWnd->SendMessage(WM_CLOSE,0,0);
}
(2)TerminateProcess
通过获取目标窗口句柄再转换成目标进程句柄,在TerminateProcess即可
HWND hWnd = ::FindWindow(NULL,"Test");
DWORD myProcess=0;
GetWindowThreadProcessId(hWnd,&myProcess); //注意:第二个参数是进程的ID,返回值是线程的ID。
HANDLE hd =OpenProcess(PROCESS_ALL_ACCESS,FALSE, myProcess);
TerminateProcess(hd, 0);
本文相关源代码下载地址
http://download.csdn.net/detail/danny_share/7680987
Danny
2014年7月26号
于天津河西7天酒店