数据段共享
数据段共享起源于Windows16位的时代,在Win16操作系统时代下,16位windows用一个全局堆和局部堆来管理内存,每一个应用程序或dll装入内存时,代码段被装入全局堆,而系统又为每个实例从全局堆中分配了一个64kb的数据段作为该实例的局部堆,用来存放应用程序的堆栈和所有全局或静态变量。而LocalAlloc/GlobalAlloc就是分别用于在局部堆或全局堆中分配内存。
由于每个进程的局部堆很小,所以在局部堆中分配内存会受到空间的限制。但这个堆是每个进程私有的,相对而言分配数据较安全,数据访问出错不至于影响到整个系统。 而在全局堆中分配的内存是为各个进程共享的,每个进程只要拥有这个内存块的句柄都可以访问这块内存,但是每个全局内存空间需要额外的内存开销,造成分配浪费。而且一旦发生严重错误,可能会影响到整个系统的稳定。
不过在Win32中,每个进程都只拥有一个缺省的私有堆(默认的私有堆),它只能被当前进程访问。应用程序也不可能直接访问系统内存。所以在Win32中全局堆和局部堆都指向进程的省缺堆。用LocalAlloc/GlobalAlloc分配内存没有任何区别。甚至LocalAlloc分配的内存可以被GlobalFree释放掉。所以在Win32下编程,无需注意Local和Global的区别。
可以说是LocalAlloc分配的堆只能被当前进程下所拥有,GlobalAlloc分配的堆,只要其它进程拥有首地址都均可以访问,GlobalAlloc分配的内存空间区段上拥有的权限一般为:RWS,而LocalAlloc分配的内存区段一般为:RW
S代表特权,任何文件或进程都可以访问这个空间
注意每从堆中分配一次内存,这个内存都会携带一个区段用于表明此内存空间的作用,前40个字节为区段!
综上所述,堆在早期Windows下,可分配内存一律称为堆,没有栈可言,栈的的概念来自于局部变量或全局变量,这些变量的内存空间也是从堆中索取来的,当为这些基本类型比如int char分配的内存一律称为栈,而主动使用malloc或new分配的内存称之为堆,并且栈的大小是有限制的!
这里用dll做个比喻:
dll全局变量数据段是不共享的,我们不能直接对它进行访问和操作,比如有同一个dll,名为sll.dll的16位动态库,里面有一个全局变量
int num;
但是有两个操作函数(这两个函数(代码段)会被放入全局堆):
void setnum(int newnum){
num = newnum;
}
int getnum(void){
return num;
}
当两个16位的进程加载这个动态库时
当A进程调用setnum设置新值以后:
A:setnum(16);
B进程中调用getnum获取:
B:int b = getnum();
那么此时B进程获取到的是A进程刚刚修改的值:16
双方可以很快完成进程间的通讯!
在win16下每个进程只能被加载到内存一次,但dll不一样,dll也只能被加载到内存一次,但是当其它进程想要在加载这个dll时将会引用这个dll内存,因为早期的dll是有自己的内存的,而不是加载到进程空间下,早期的dll是有自己的实列内存空间的!
但是到了win32以后,这种方法被删除了,但是被保留到编译器命令当中了!
#pragma data_seg
预处理指令
#pragma data_seg的使用方法如下:
#pragma data_seg(“mydate”)
Int num = 0;
#pragma data_seg()
注意必须是全局的,并且变量必须显示初始化,如果不初始化,编译器会自动帮你赋值0然后放到.BSS段里去,这样就不会放到共享内存段中去了!
并且也可以使用#pragma comment预处理指令里的linker来显示规定此共享数据段的连接方法:
#pragma comment(linker,”/SECTION:mydate,RWS")
R可读性
W可写
S任意程序/文件都可以使用此共享数据段
注意前面一定要加上/SECTION:这是来表明区段的,不然编译器不认!
而且此方法还可以用来检测当前程序在同一电脑上运行了多少个!
#pragma data_seg("flag_data")int app_count = 0;#pragma data_seg()#pragma comment(linker,”/SECTION:flag_data,RWS")
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL, // 指向自身的句柄
_In_ DWORD fdwReason, // 调用原因
_In_ LPVOID lpvReserved // 隐式加载和显式加载
){
if(app_count>0) // 如果计数大于0,则退出应用程序。{//MessageBox(NULL, "已经启动一个应用程序", "Warning", MB_OK); //printf("no%d application", app_count);return FALSE;}else{//计数+1app_count++;
}
}
有的dll可能不用dllmain函数,所以可以增加两个接口:
#pragma data_seg("flag_data")int app_count = 0;#pragma data_seg()#pragma comment(linker,”/SECTION:flag_data,RWS”)
void add(){
app_count++;
}
int returnapp(){
return app_count;
}
然后在应用程序入口使用:
int main(){
if(returnapp() > 0){
//MessageBox(NULL, "已经启动一个应用程序", "Warning", MB_OK); //printf("no%d application", app_count);return FALSE;
}else{
add();
}
}
或者用来判断当前系统下运行了多个此程序:
int main(){
add();
printf(“当前程序运行数量:%d”,returnapp);
}
只要你显示的调用dll动态库里的函数,那么根据windows内核规则,动态库一定会被加载进来!
并且共享的数据段,不属于任何一个进程,Windows会把它独立放在一个内存段里,并维护它,然后有新的进程加载动态库时,Windows会检查dll里的共享数据段名是否已经存在,如果已经存在,则不在开辟,并且此动态库共享此段!那么问题来了,如果有两个新的动态库,并且段名一样怎么办?
答:操作系统是根据动态库名来区分此段属于那个动态库的,所以此问题无需我们关心,Wdindows会严格帮我们区分开!
使用此方法可以很轻松的实现进程间的共享数据!
最后值得提一下的是上面这些方法在进程中也是有效的!