进程分2部分组成:
a.内核对象,操作系统管理内核对象,内核对象保存进程统计信息。
b.地址空间,包括可执行程序或dll的代码和数据。包括线程堆栈。
剖解:
a.进程间共享内核对象,所以可以通过内核对象命名的方法来实现进程通讯
b.不同的进程,内存地址不能共享,那就涉及进程间通讯了;同一进程的不同线程,内存地址共享。
window的入口点函数
但在实际运行中,在入口点函数前,会先执行c/c++ 运行时启动函数,进行初始化c/c++ 运行库,还有全局变量和静态c++对象构造。具体如下:
此外相关的函数有:
- getversionex:
- getcommandline:
- getenvironmentstrings
- getmodulefilename:获取运行程序的名称及其路径
有getmodulefilename 和 getmodulehandle():
getmodulehandle:找出可执行文件或dll被加载到进程地址空间的地址位置,如果传递NULL值,会返回进程的地址空间中可执行文件的基地址。所有即使调用getmodulehandle(NULL)的代码是一个DLL文件,返回值是可执行文件的基地址,非DLL文件的基地址。在dll中,还可以使用LoadLibrary。
getmoduleFileName():正在运行的程序及其完整路径。
GetProcAddress:功能是检索指定的动态链接库(DLL)中的输出库函数地址。
getcommandline:获取进程完整的命令行。
环境变量跟注册表的关系,使用regedit进行查看。
同时,从进程的主线程入口函数返回时,返回到c++运行库启动代码,执行c++资源释放(如c++对象的析构函数),然后c++运行库启动代码再显性调用exitprocess来终止线程。
进程的创建
createprocess函数解析:
1.pszapplicationname= null ,pszcommandline必须是可执行文件,可以在绝对路径或者是相对路径。
2.pszapplicationname不为null,必须有扩展名,当前目录或者指定目录 ,pszcommandline新的命令行参数,不能为直接填写常量字符串。
3.psaprocess 表示子进程内核对象安全属性。
4.psathread表示子进程的主线程的内核对象安全属性。
5.binherithandles=ture,表示子进程继承父进程可进程的评审组。
6.fdwcreate按位或标志组合,可以直接设置为0.
7.pvEnvironment = null,表示子进程继承父进程的环境变量。
8.pszcurdir=null,表示子进程的工作目录与父进程一样。
9.psistartinfo 所有成员初始化为0,并且是cb成员为结构的大小。
10.ppiprocinfo 指向process_information结构
会创建一个进程内核对象和线程内核对象,创建时系统会为每一个对象指定一个初始的使用计数1,CreateProcess返回时由于PROCESS_INFORMATION结构中再次引用了进程和线程内核对象 此时它们的使用计数都变为2。可以理解为进程和线程实例本身也占有一个计数。当它们结束运行时这个使用计数被递减1。
所以如果系统要释放进程对象,1:进程必须终止,此时使用计数递减1。2:父进程必须调用CloseHandle,使用计数再次减去1。线程类似。
进程id:getcurrentprocessid 或getprocessid getprocessidofthread
线程id:getcurrentthreadid 或gettrheadid
进程的退出
疑问:进程内核对象状态变成已触发状态怎理解?
进程初始化时为未触发状态,进程终止时自动变已触发状态。
为了断绝与子进程的联系,需在生成子进程的后,立即调用closehandle关闭新进程及其主线程的句柄。目的是使父进程的内核对象使用次数减1。
进程的终止方式
解释:
VOID WINAPI ExitProcess(_In_ UINT uExitCode);
只能退出自己当前的进程。
问题:直接终止运行,再也不会返回当前函数调用,会导致局部或全局的c++对象无法析构,无法释放内存。同理ExitThread也是一样。所以
TerminateProcess()
可以退出自己和别人的进程。也会导致和ExitProcess一样的结果:内存泄露
TerminateProcess()是异步执行的,在调用返回后并不能确定被终止进程是否已经真的退出,如果调用TerminateProcess()的进程对此细节关心,可以通过WaitForSingleObject()来等待进程的真正结束。
如果我们在编写应用程序时,打算终止当前进程,我们该调用哪个函数?答案是:三者其实都一样! 因为三者都可能导致内存泄露,但我们担心 的过多了,因为进程在结束时,即使有ExitProcess,TerminateProcess,以及exit函数调用而导致的内存泄露,OS也会进行清理工作,能保证 我们泄露的内存最终被还回到OS中去,而不必担心当前进程已经退出而导致内存泄露,致使其它进程无法使用该内存块。
因为:
一个进程无论在什么情 况下终止,都会进行如下工作:
1) 进程指定的所有用户对象和G U I对象均被释放,所有内核对象均被关闭(如果没有其他 进程打开它们的句柄,那么这些内核对象将被撤消。 但是,如果其他进程打开了它们的句柄, 内核对象将不会撤消)。
2) 进程的退出代码将从S T I L L _ A C T I V E改为传递给E x i t P r o c e s s或Te r m i n a t e P r o c e s s的代码。
3) 进程内核对象的状态变成收到通知的状态(关于传送通知的详细说明,参见第9章)。系 统中的其他线程可以挂起,直到进程终止运行。
4) 进程内核对象的使用计数递减1。
进程和线程的区别:
1.根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。
2.从属关系不同:进程中包含了线程,线程属于进程。
3.开销不同:进程的创建、销毁和切换的开销都远大于线程。
4.拥有资源不同:每个进程有自己的内存和资源,一个进程中的线程会共享这些内存和资源。
5.控制和影响能力不同:子进程无法影响父进程,而子线程可以影响父线程,如果主线程发生异常会影响其所在进程和子线程。一个进程崩溃,不会对其他进程产生影响,而一个线程崩溃,会让同一进程内的所有其他线程也死掉
6.CPU利用率不同:进程的CPU利用率较低,因为上下文切换开销较大,而线程的CPU的利用率较高,上下文的切换速度快。
7.操纵者不同:进程的操纵者一般是操作系统,线程的操纵者一般是编程人员。
进程间通讯办法:
进程间共享内核对象有以下三种方式:
- 继承内核对象句柄
- 命名内核对象
- 复制DupilateHandle
命名内核对象延伸文件映射内核对象(实现跨进程共享内存)
1.创建文件内核对象,调用createfile.目的是为了告诉系统文件映射的物理存储器的位置。
2.创建文件映射内核对象 ,调用createfilemapping.目的就是系统需要多少的物理存储器。
欲在其中创建映射的一个文件句柄。0xFFFFFFFF(-1,即INVALID_HANDLE_VALUE)表示在页面文件中创建一个可共享的文件映射,表明创建的文件映射对象的物理存储器不是磁盘的文件,而是系统从页交换文件中调拨物理存储器。即在磁盘c的pagefile.sys文件,因此不需要再调用createfile函数。
3.将文件的数据映射到进程的地址空间,调用mapviewoffile
4.从进程的地址空间撤销文件数据的映射,调用unmapviewoffile,释放进程地址空间。
5.关闭文件映射对象,调用closehandle
6.关闭文件对象,调用closehandle
进程间同步
1.事件量 参考:
2.互斥量
3.信号量