热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。

本文背景:

在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。

本文目的:

对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。

本文内容:

本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管理及C++内存分配实例(一)(二)(三)(五)和(六)。

4.      内存管理机制--内存映射文件 (Map)

    和虚拟内存一样,内存映射文件可以用来保留一个进程地址区域;但是,与虚拟内存不同,它提交的不是物理内存或是虚拟页文件,而是硬盘上的文件。

·        使用场合

它有三个主要用途:

系统加载EXE和DLL文件

操作系统就是用它来加载exe和dll文件建立进程,运行exe。这样可以节省页文件和启动时间。

访问大数据文件

如果文件太大,比如超过了进程用户区2G,用fopen是不能对文件进行操作的。这时,可用内存映射文件。对于大数据文件可以不必对文件执行I/O操作,不必对所有文件内容进行缓存。

进程共享机制

内存映射文件是多个进程共享数据的一种较高性能的有效方式,它也是操作系统进程通信机制的底层实现方法。RPC、COM、OLE、DDE、窗口消息、剪贴板、管道、Socket等都是使用内存映射文件实现的。

·        系统加载EXE和DLL文件

ü      EXE文件格式

每个EXE和DLL文件由许多节(Section)组成,每个节都有保护属性:READ,WRITE,EXECUTE和SHARED(可以被多个进程共享,关闭页面的COPY-ON-WRITE属性)。

以下是常见的节和作用:


节名

作用

.text

.exe和.dll文件的代码

.data

已经初始化的数据

.bss

未初始化的数据

.reloc

重定位表(装载进程的进程地址空间)

.rdata

运行期只读数据

.CRT

C运行期只读数据

.debug

调试信息

.xdata

异常处理表

.tls

线程的本地化存储

.idata

输入文件名表

.edata

输出文件名表

.rsrc

资源表

.didata

延迟输入文件名表


 

ü      加载过程

1.      系统根据exe文件名建立进程内核对象、页目和页表,也就是建立了进程的虚拟空间。

2.      读取exe文件的大小,在默认基地址0x0040 0000上保留适当大小的区域。可以在链接程序时用/BASE 选项更改基地址(在VC工程属性/链接器/高级上设置)。提交时,操作系统会管理页目和页表,将硬盘上的文件映射到进程空间中,页表中保存的地址是exe文件的页偏移。

3.      读取exe文件的.idata节,此节列出exe所用到的所有dll文件。然后和

exe文件一样,将dll文件映射到进程空间中。如果无法映射到基地址,系统会重新定位。

4.   映射成功后,系统会把第一页代码加载到内存,然后更新页目和页

表。将第一条指令的地址交给线程指令指针。当系统执行时,发现代码没有在内存中,会将exe文件中的代码加载到内存中。

              

ü      第二次加载时(运行多个进程实例)

1.      建立进程、映射进程空间都跟前面一样,只是当系统发现这个exe已

      经建立了内存映射文件对象时,它就直接映射到进程空间了;只是当

     系统分配物理页面时,根据节的保护属性赋予页面保护属性,对于代码

     节赋予READ属性,全局变量节赋予COPY-ON-WRITE属性。

2.      不同的实例共享代码节和其他的节,当实例需要改变页面内容时,会

      拷贝页面内容到新页面,更新页目和页表。

3.      对于不同进程实例需要共享的变量,exe文件有一

      个默认的节, 给这个节赋予SHARED属性。

4.      你也可以创建自己的SHARED节

#pragma data_seg(“节名”)

Long instCount;

#pragma data_seg()

然后,你需要在链接程序时告诉编译器节的默认属性。

/SECTION: 节名,RWS

或者,在程序里用以下表达式:

#pragma comment(linker,“/SECTION:节名,RWS”)

这样的话编译器会创建.drective节来保存上述命令,然后链接时会用它改变节属性。

注意,共享变量有可能有安全隐患,因为它可以读到其他进程的数据。

C++程序:多个进程共享变量举例

*.cpp开始处:

#pragma data_seg(".share")

long shareCount=0;

#pragma data_seg()

#pragma comment(linker,"/SECTION:.share,RWS")

ShareCount++;

 

注意,同一个exe文件产生的进程会共享shareCount,必须是处于同一个位置上exe

 

·        访问大数据文件

ü      创建文件内核对象

使用CreateFile(文件名,访问属性,共享模式,…) API可以创建。

其中,访问属性有:

0 不能读写 (用它可以访问文件属性)

GENERIC_READ

GENERIC_WRITE

GENERIC_READ|GENERIC_WRITE;

共享模式:

0 独享文件,其他应用程序无法打开

FILE_SHARE_WRITE

FILE_SHARE_READ|FILE_SHARE_WRITE

这个属性依赖于访问属性,必须和访问属性不冲突。

当创建失败时,返回INVALID_HANDLE_VALUE。

 

C++程序如下:

试图打开一个1G的文件:

MEMORYSTATUS memStatus;

GlobalMemoryStatus(&memStatus);

HANDLE hn=CreateFile(L"D://1G.rmvb",GENERIC_READ|GENERIC_WRITE,

FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

              if(hn==INVALID_HANDLE_VALUE)

                        cout<<"打开文件失败!"<

              FILE *p&#61;fopen("D://1G.rmvb","rb");

              if(p&#61;&#61;NULL)

                        cout<<"用fopen不能打开大文件!"<

              MEMORYSTATUS memStatus2;

              GlobalMemoryStatus(&memStatus2);

              cout<<"打开文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"

<

结果如下&#xff1a;

 

可见&#xff0c;系统需要一些内存来管理内核对象&#xff0c;每一次运行的结果都不一样&#xff0c;但差别不会太大。

用c语言的fopen不能打开这么大的文件。理论上&#xff0c;32位系统能支持232字节&#xff0c;但是&#xff0c;进程空间只有2G&#xff0c;它只能表示那么大的空间。

ü      创建文件映射内核对象

API如下&#xff1a;

HANDLE CreateFileMapping(Handle 文件&#xff0c;PSECURITY_ATTRIBUTES 安全属性&#xff0c;DWORD 保护属性&#xff0c;DWORD 文件大小高32位&#xff0c;DWORD 文件大小低32位&#xff0c;PCTSTR  映射名称)

“文件”是上面创建的句柄&#xff1b;

“安全属性”是内核对象需要的&#xff0c;NULL表示使用系统默认的安全属性&#xff1b;“保护属性”是当将存储器提交给进程空间时&#xff0c;需要的页面属性&#xff1a;PAGE_READONLY, PAGE_READWRITE和PAGE_WRITECOPY。这个属性不能和文件对象的访问属性冲突。除了这三个外&#xff0c;还有两个属性可以和它们连接使用(|)。当更新文件内容时&#xff0c;不提供缓存&#xff0c;直接写入文件&#xff0c;可用SEC_NOCACHE&#xff1b;当文件是可执行文件时&#xff0c;系统会根据节赋予不同的页面属性&#xff0c;可用SEC_IMAGE。另外&#xff0c;SEC_RESERVE和SEC_COMMIT用于稀疏提交的文件映射&#xff0c;详细介绍请参考下文。

“文件大小高32位”和“文件大小低32位”联合起来告诉系统&#xff0c;这个映射所能支持的文件大小&#xff08;操作系统支持264B文件大小&#xff09;&#xff1b;当这个值大于实际的文件大小时&#xff0c;系统会扩大文件到这个值&#xff0c;因为系统需要保证进程空间能完全被映射。值为0默认为文件的大小&#xff0c;这时候如果文件大小为0&#xff0c;创建失败。

“映射名称”是给用户标识此内核对象&#xff0c;供各进程共享&#xff0c;如果为NULL&#xff0c;则不能共享。

对象创建失败时返回NULL。

创建成功后&#xff0c;系统仍未为文件保留进程空间。

 

C&#43;&#43;程序&#xff1a;

                        MEMORYSTATUS memStatus2;

                        GlobalMemoryStatus(&memStatus2);

HANDLE hmap&#61;CreateFileMapping(hn,NULL,PAGE_READWRITE,0,0,L"Yeming-Map");

                        if(hmap&#61;&#61;NULL)

                        cout<<"建立内存映射对象失败!"<

                        MEMORYSTATUS memStatus3;

                        GlobalMemoryStatus(&memStatus3);

                        cout<<"建立内存映射文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

         cout<<"减少可用进程空间&#61;"

<

            结果如下&#xff1a;

      

 

默认内存映射的大小是1G文件。没有损失内存和进程空间。它所做的是建立内核对象&#xff0c;收集一些属性。

 

ü      文件映射内核对象映射到进程空间

API如下&#xff1a;

PVOID MAPViewOfFile(HANDLE 映射对象&#xff0c;DWORD访问属性&#xff0c;DWORD 偏移量高32位&#xff0c;DWORD 偏移量低32位&#xff0c;SIZE_T 字节数)

“映射对象”是前面建立的对象&#xff1b;

“访问属性”可以是下面的值&#xff1a;FILE_MAP_WRITE(读和写)、FILE_MAP_READ、FILE_MAP_ALL_ACCESS(读和写)、FILE_MAP_COPY。当使用FILE_MAP_COPY时&#xff0c;系统分配虚拟页文件&#xff0c;当有写操作时&#xff0c;系统会拷贝数据到这些页面&#xff0c;并赋予PAGE_READWRITE属性。

可以看到&#xff0c;每一步都需要设置这类属性&#xff0c;是为了可以多点控制&#xff0c;试想&#xff0c;如果在这一步想有多种不同的属性操作文件的不同部分&#xff0c;就比较有用。

“偏移高32位”和“偏移低32位”联合起来标识映射的开始字节&#xff08;地址是分配粒度的倍数&#xff09;&#xff1b;

“字节数”指映射的字节数&#xff0c;默认0为到文件尾。

 

当你需要指定映射到哪里时&#xff0c;你可以使用&#xff1a;

PVOID MAPViewOfFile(HANDLE 映射对象&#xff0c;DWORD访问属性&#xff0c;DWORD 偏移量高32位&#xff0c;DWORD 偏移量低32位&#xff0c;SIZE_T 字节数&#xff0c;PVOID 基地址)

“基地址”是映射到进程空间的首地址&#xff0c;必须是分配粒度的倍数。

 

C&#43;&#43;程序&#xff1a;

MEMORYSTATUS memStatus3;

            GlobalMemoryStatus(&memStatus3);

            LPVOID pMAP&#61;MapViewOfFile(hmap,FILE_MAP_WRITE,0,0,0);

            cout<<"映射内存映射文件后的空间&#xff1a;"<

if(pMAP&#61;&#61;NULL)

               cout<<"映射进程空间失败!"<

            else

               printf("首地址&#61;%x/n",pMAP);

            MEMORYSTATUS memStatus4;

            GlobalMemoryStatus(&memStatus4);

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"

<

结果如下&#xff1a;

 

进程空间减少了1G&#xff0c;系统同时会开辟一些内存来做文件缓存。

ü      使用文件

1.      对于大文件&#xff0c;可以用多次映射的方法达到访问的目的。有点像AWE技术。

2.      Windows只保证同一文件映射内核对象的多次映射的数据一致性&#xff0c;比如&#xff0c;当有两次映射同一对象到二个进程空间时&#xff0c;一个进程空间的数据改变后&#xff0c;另一个进程空间的数据也会跟着改变&#xff1b;不保证不同映射内核对象的多次映射的一致性。所以&#xff0c;使用文件映射时&#xff0c;最好在CreateFile时将共享模型设置为0独享&#xff0c;当然&#xff0c;对于只读文件没这个必要。

    C&#43;&#43;程序&#xff1a;使用1G的文件

MEMORYSTATUS memStatus4;

                        GlobalMemoryStatus(&memStatus4);

                        cout<<"读取1G文件前&#xff1a;"<

                        cout<<"可用物理内存&#61;"<

                        cout<<"可用页文件&#61;"<

                        cout<<"可用进程空间&#61;"<

                        int* pInt&#61;(int*)pMAP;

                        cout<<"更改前&#61;"<

                        for(int i&#61;0;i<1000001536/4-1;i&#43;&#43;)

                             pInt[i]&#43;&#43;;

                        pInt[1000001536/4-1]&#61;10;

                        pInt[100]&#61;90;

                        pInt[101]&#61;100;

                        cout<<"读取1G文件后&#xff1a;"<

                        MEMORYSTATUS memStatus5;

                        GlobalMemoryStatus(&memStatus5);

                        cout<<"可用物理内存&#61;"<

                        cout<<"可用页文件&#61;"<

                        cout<<"可用进程空间&#61;"<

           

结果如下&#xff1a;

 

程序将1G文件的各个整型数据加1&#xff0c;从上图看出内存损失了600多兆&#xff0c;但有时候损失不过十几兆&#xff0c;可能跟系统当时的状态有关。

不管怎样&#xff0c;这样你完全看不到I/O操作&#xff0c;就像访问普通数据结构一样方便。

 

ü      保存文件修改

为了提高速度&#xff0c;更改文件时可能只更改到了系统缓存&#xff0c;这时&#xff0c;需要强制保存更改到硬盘&#xff0c;特别是撤销映射前。

BOOL FlushViewOfFile(PVOID 进程空间地址&#xff0c;SIZE_T 字节数)

“进程空间地址”指的是需要更改的第一个字节地址&#xff0c;系统会变成页面的地址&#xff1b;

“字节数”&#xff0c;系统会变成页面大小的倍数。

写入磁盘后&#xff0c;函数返回&#xff0c;对于网络硬盘&#xff0c;如果希望写入网络硬盘后才返回的话&#xff0c;需要将FILE_FLAG_WRITE_THROUGH参数传给CreateFile。

 

当使用FILE_MAP_COPY建立映射时&#xff0c;由于对数据的更改只是对虚拟页文件的修改而不是硬盘文件的修改&#xff0c;当撤销映射时&#xff0c;会丢失所做的修改。如果要保存&#xff0c;怎么办&#xff1f;

你可以用FILE_MAP_WRITE建立另外一个映射&#xff0c;它映射到进程的另外一段空间&#xff1b;扫描第一个映射的PAGE_READWRITE页面(因为属性被更改)&#xff0c;如果页面改变&#xff0c;用MoveMemory或其他拷贝函数将页面内容拷贝到第二次映射的空间里&#xff0c;然后再调用FlushViewOfFile。当然&#xff0c;你要记录哪个页面被更改。

ü      撤销映射

用以下API可以撤销映射&#xff1a;

BOOL  UnmapViewOfFile(PVOID pvBaseAddress)

这个地址必须与MapViewOfFile返回值相同。

 

ü      关闭内核对象

在不需要内核对象时&#xff0c;尽早将其释放&#xff0c;防止内存泄露。由于它们是内核对象&#xff0c;调用CloseHandle(HANDLE)就可以了。

在CreateFileMapping后马上关闭文件句柄&#xff1b;

在MapViewOfFile后马上关闭内存映射句柄&#xff1b;

最后再撤销映射。

·        进程共享机制

ü      基于硬盘文件的内存映射

如果进程需要共享文件&#xff0c;只要按照前面的方式建立内存映射对象&#xff0c;然后按照名字来共享&#xff0c;那么进程就可以映射这个对象到自己的进程空间中。

C&#43;&#43;程序如下&#xff1a;

HANDLE mapYeming&#61;OpenFileMapping(FILE_MAP_WRITE,true,L"Yeming-Map");

                        if(mapYeming&#61;&#61;NULL)

                        cout<<"找不到内存映射对象:Yeming-Map!"<

                        MEMORYSTATUS memStatus3;

                        GlobalMemoryStatus(&memStatus3);

LPVOID pMAP&#61;MapViewOfFile(mapYeming,FILE_MAP_WRITE,0,0,100000000);

                        cout<<"建立内存映射文件后的空间&#xff1a;"<

                        if(pMAP&#61;&#61;NULL)

                        cout<<"映射进程空间失败!"<

                        else

                        printf("首地址&#61;%x/n",pMAP);

           

                        MEMORYSTATUS memStatus4;

                        GlobalMemoryStatus(&memStatus4);

           

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

 

                        int* pInt&#61;(int*)pMAP;

         cout<

        

         结果如下&#xff1a;

 

在2.exe中打开之前1.exe创建的内存映射对象(当然&#xff0c;1.exe得处于运行状态)&#xff0c;然后映射进自己的进程空间&#xff0c;当1.exe改变文件的值时&#xff0c;2.exe的文件对应值也跟着改变&#xff0c;Windows保证同一个内存映射对象映射出来的数据是一致的。可以看见&#xff0c;1.exe将值从90改为91&#xff0c;2.exe也跟着改变&#xff0c;因为它们有共同的缓冲页。

 

ü      基于页文件的内存映射

如果只想共享内存数据时&#xff0c;没有必要创建硬盘文件&#xff0c;再建立映射。可以直

接建立映射对象&#xff1a;

只要传给CreateFileMapping一个文件句柄INVALID_HANDLE_VALUE就行了。所以&#xff0c;CreateFile时&#xff0c;一定要检查返回值&#xff0c;否则会建立一个基于页文件的内存映射对象。接下来就是映射到进程空间了&#xff0c;这时&#xff0c;系统会分配页文件给它。

C&#43;&#43;程序如下&#xff1a;

 

HANDLE hPageMap&#61;CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,

100000000,L"Yeming-Map-Page");

            if(hPageMap&#61;&#61;NULL)

                        cout<<"建立基于页文件的内存映射对象失败!"<

            MEMORYSTATUS memStatus6;

            GlobalMemoryStatus(&memStatus6);

            cout<<"建立基于页文件的内存映射文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

LPVOID pPageMAP&#61;MapViewOfFile(hPageMap,FILE_MAP_WRITE,0,0,0);        

            结果如下&#xff1a;

       

 

可见&#xff0c;和基于数据文件的内存映射不同&#xff0c;现在刚建立内核对象时就分配了所要的100M内存。好处是&#xff0c;别的进程可以通过这个内核对象共享这段内存&#xff0c;只要它也做了映射。

 

ü      稀疏内存映射文件

在虚拟内存一节中&#xff0c;提到了电子表格程序。虚拟内存解决了表示很少单元格有数据但必须分配所有内存的内存浪费问题&#xff1b;但是&#xff0c;如果想在多个进程之间共享这个电子表格结构呢&#xff1f;

如果用基于页文件的内存映射&#xff0c;需要先分配页文件&#xff0c;还是浪费了空间&#xff0c;没有了虚拟内存的优点。

Windows提供了稀疏提交的内存映射机制。

当使用CreateFileMapping时&#xff0c;保护属性用SEC_RESERVE时&#xff0c;其不提交物理存储器&#xff0c;使用SEC_COMMIT时&#xff0c;其马上提交物理存储器。注意&#xff0c;只有文件句柄为INVALID_HANDLE_VALUE时&#xff0c;才能使用这两个参数。

按照通常的方法映射时&#xff0c;系统只保留进程地址空间&#xff0c;不会提交物理存储器。

当需要提交物理内存时才提交&#xff0c;利用通常的VirtualAlloc函数就可以提交。

当释放内存时&#xff0c;不能调用VirtualFree函数&#xff0c;只能调用UnmapViewOfFile来撤销映射&#xff0c;从而释放内存。

 

C&#43;&#43;程序如下&#xff1a;

HANDLE hVirtualMap&#61;CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE|SEC_RESERVE,0,100000000,L"Yeming-Map-Virtual");

if(hPageMap&#61;&#61;NULL)

                        cout<<"建立基于页文件的稀疏内存映射对象失败!"<

            MEMORYSTATUS memStatus8;

            GlobalMemoryStatus(&memStatus8);

            cout<<"建立基于页文件的稀疏内存映射文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

           

LPVOID pVirtualMAP&#61;MapViewOfFile(hVirtualMap,FILE_MAP_WRITE,0,0,0);

            cout<<"内存映射进程后的空间&#xff1a;"<

            if(pVirtualMAP&#61;&#61;NULL)

                        cout<<"映射进程空间失败!"<

            else

                        printf("首地址&#61;%x/n",pVirtualMAP);

           

            MEMORYSTATUS memStatus9;

            GlobalMemoryStatus(&memStatus9);

           

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

        

结果如下&#xff1a;

 

用了SEC_RESERVE后&#xff0c;只是建立了一个内存映射对象&#xff0c;和普通的一样&#xff1b;不同的是&#xff0c;它映射完后&#xff0c;得到了一个虚拟进程空间。现在&#xff0c;这个空间没有分配任何的物理存储器给它&#xff0c;你可以用VirtualAlloc 提交存储器给它&#xff0c;详细请参考上一篇<虚拟内存&#xff08;VM&#xff09;>。

注意&#xff0c;你不可以用VirtualFree来释放了,只能用UnmapViewOfFile来。

C&#43;&#43;程序如下&#xff1a;

LPVOID pP&#61;VirtualAlloc(pVirtualMAP,100*1000*1000,MEM_COMMIT,PAGE_READWRITE); 

            MEMORYSTATUS memStatus10;

            GlobalMemoryStatus(&memStatus10);

           

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

 

            bool result&#61;VirtualFree(pP,100000000,MEM_DECOMMIT);

            if(!result)

                        cout<<"释放失败!"<

             result&#61;VirtualFree(pP,100000000,MEM_RELEASE);

            if(!result)

                        cout<<"释放失败!"<

 

            CloseHandle(hVirtualMap);

            MEMORYSTATUS memStatus11;

            GlobalMemoryStatus(&memStatus11);

cout<<"增加物理内存&#61;"<

cout<<"增加可用页文件&#61;"<

cout<<"增加可用进程空间&#61;"<

 

            result&#61;UnmapViewOfFile(pVirtualMAP);

            if(!result)

                        cout<<"撤销映射失败!"<

 

            MEMORYSTATUS memStatus12;

            GlobalMemoryStatus(&memStatus12);

cout<<"增加物理内存&#61;"<

cout<<"增加可用页文件&#61;"<

cout<<"增加可用进程空间&#61;"

<

结果如下&#xff1a;

 

可以看见&#xff0c;用VirtualFree是不能够释放这个稀疏映射的&#xff1b;最后用UnmapViewOfFile得以释放进程空间和物理内存。

 


推荐阅读
  • Windows7企业版怎样存储安全新功能详解
    本文介绍了电脑公司发布的GHOST WIN7 SP1 X64 通用特别版 V2019.12,软件大小为5.71 GB,支持简体中文,属于国产软件,免费使用。文章还提到了用户评分和软件分类为Win7系统,运行环境为Windows。同时,文章还介绍了平台检测结果,无插件,通过了360、腾讯、金山和瑞星的检测。此外,文章还提到了本地下载文件大小为5.71 GB,需要先下载高速下载器才能进行高速下载。最后,文章详细解释了Windows7企业版的存储安全新功能。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • DSP中cmd文件的命令文件组成及其作用
    本文介绍了DSP中cmd文件的命令文件的组成和作用,包括链接器配置文件的存放链接器配置信息、命令文件的组成、MEMORY和SECTIONS两个伪指令的使用、CMD分配ROM和RAM空间的目的以及MEMORY指定芯片的ROM和RAM大小和划分区间的方法。同时强调了根据不同芯片进行修改的必要性,以适应不同芯片的存储用户程序的需求。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • Linux环境变量$PATH的作用及使用方法
    本文介绍了Linux环境变量$PATH的作用及使用方法。$PATH是一个由多个目录组成的变量,用冒号分隔。当执行一个指令时,系统会按照$PATH定义的目录顺序搜索同名的可执行文件,如果有多个同名指令,则先找到的会被执行。通过设置$PATH变量,可以在任何地方执行指令,无需输入绝对路径。 ... [详细]
author-avatar
雨爱艳6688
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有