Unix/Linux环境C编程入门教程(27) 内存那些事儿

  1. calloc() free() getpagesize() malloc() mmap() munmap()函数介绍

calloc(配置内存空间)

相关函数

malloc,free,realloc,brk

表头文件

#include <stdlib.h>

定义函数

void *calloc(size_t nmemb,size_t size);

函数说明

calloc()用来配置nmemb个相邻的内存单位,每一单位的大小为size,并返回指向第一个元素的指针。这和使用下列的方式效果相同:malloc(nmemb*size);不过,在利用calloc()配置内存时会将内存内容初始化为0。

返回值

若配置成功则返回一指针,失败则返回NULL。

范例

/* 动态配置10个struct test 空间*/
#include<stdlib.h>
struct test
{
int a[10];
char b[20];
}
main()
{
struct test *ptr=calloc(sizeof(struct test),10);
}

 

free(释放原先配置的内存)

相关函数

malloc,calloc,realloc,brk

表头文件

#include<stdlib.h>

定义函数

void free(void *ptr);

函数说明

参数ptr为指向先前由malloc()、calloc()或realloc()所返回的内存指针。调用free()后ptr所指的内存空间便会被收回。假若参数ptr所指的内存空间已被收回或是未知的内存地址,则调用free()可能会有无法预期的情况发生。若参数ptr为NULL,则free()不会有任何作用。

 

getpagesize(取得内存分页大小)

相关函数

sbrk

表头文件

#include<unistd.h>

定义函数

size_t getpagesize(void);

函数说明

返回一分页的大小,单位为字节(byte)。此为系统的分页大小,不一定会和硬件分页大小相同。

返回值

内存分页大小。附加说明在Intel x86 上其返回值应为4096bytes。

范例

#include <unistd.h>
main()
{
printf("page size = %d\n",getpagesize( ) );
}

 

malloc(配置内存空间)

相关函数

calloc,free,realloc,brk

表头文件

#include<stdlib.h>

定义函数

void * malloc(size_t size);

函数说明

malloc()用来配置内存空间,其大小由指定的size决定。

返回值

若配置成功则返回一指针,失败则返回NULL。

范例

void p = malloc(1024); /*配置1k的内存*/

 

相关函数

munmap,open

表头文件

#include <unistd.h>
#include <sys/mman.h>

定义函数

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);

函数说明

mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。参数start指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。参数length代表将文件中多大的部分对应到内存。

参数

prot代表映射区域的保护方式有下列组合
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取

参数

flags会影响映射区域的各种特性
MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的"写入时复制"(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。参数fd为open()返回的文件描述词,代表欲映射到内存的文件。参数offset为文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值

若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码

EBADF 参数fd 不是有效的文件描述词
EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
EINVAL 参数start、length 或offset有一个不合法。
EAGAIN 文件被锁住,或是有太多内存被锁住。
ENOMEM 内存不足。

 

munmap(解除内存映射)

相关函数

mmap

表头文件

#include<unistd.h>
#include<sys/mman.h>

定义函数

int munmap(void *start,size_t length);

函数说明

munmap()用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。

返回值

如果解除映射成功则返回0,否则返回-1,错误原因存于errno中错误代码EINVAL

参数

start或length 不合法。

范例

参考mmap()

  1. Linux文件与文件描述符的概念
  • 文件的概念

    大多数资源,Linux都是以文件的方式来访问。

Linux中主要的文件类型分为4种:普通文件、目录文件、链接文件和设备文件

Linux中的文件属性:

-rwx rwx rwx

首先,Linux中文件的拥有者可以把文件的访问属性设成3种不同的访问权限:可读(r)、可写(w)和可执行(x)。

文件又有3种不同的用户级别:文件拥有者(u)、所属的用户组(g)和系统里的其他用户(o)

第一个字符显示文件的类型:

"-"表示普通文件

"d"表示目录文件

"l"表示链接文件

"c"表示字符设备

"b"表示块设备

"p"表示命名管道比如FIFO文件

"f"表示堆栈文件比如LILO文件

第一个字符之后的3个三位字符组:

第一个三位字符组表示文件拥有者(u)对该文件的权限

第二个三位字符组表示文件用户组(g)对该文件的权限

第三个三位字符组表示系统其他用户(o)对该文件的权限

若该用户组对此没有权限,一般显示"-"字符

如图所示:

Unix/Linux环境C编程入门教程(27)  内存那些事儿

  • 文件描述符。

文件描述符是个很小的正整数,它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。

  1. 内存映射机制与系统调用

    实际上,内存映射机制并不是完全为了共享内存的目的而设计的,它本身提供了不同于一般普通文件的访问方式,进程可以像访问内存一样对普通文件进程操作.而POSIX或System V共享内存IPC则纯粹是用于共享内存的目的.当然内存映射实现共享内存,也是内存映射的应用之一

    内存映射机制的用途: A、以访问内存的方式读写文件; B、实现共享内存;

    mmap()系统调用:
       mmap()系统调用使得进程之间通过映射同一个普通文件而实现共享内存的目的.普通文件被映射到进程的地址空间之后,进程就可以像访问普通内存一样对文件进行访问,不必再调用read()、write()等系统调用操作.

    mmap()系统调用介绍:
       void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);

    该函数在进程的地址空间与文件对象或共享内存对象之间建立一种映射关系;

    addr  :该参数指定文件应该被映射到进程地址空间的起始地址,一般被指定为一个空指针,此时,程序把选择起始地址的任务留给内核来完成了.这个地址是进程地址空间中需要映射到文件中的内存区域的首地址;也就是说,在进程地址空间中用于文件映射的内存区域的首地址;

    len   :文件被映射到调用进程的地址空间中的字节数,它从被映射文件开头offset个字节处开始算起,取len个字节,把文件中的这len个字节的文件空间映射到进程的地址空间中;

    port  :指定文件被映射到内存中之后的访问权限.可取的值有:PORT_READ(可读)、PORT_WRITE(可写)、PORT_EXEC(可执行)、PORT_NONE(不可访问);
       flags :映射标记;取值如下:MAP_SHARED、MAP_PRIVATE、MAP_FIXED,其中,MAP_SHARED和MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用;

    fd    :即将被映射到进程地址空间中的文件的描述符.一般由系统调用open()返回;同时,fd可以指定为-1,此时,必须指定flags参数中的MAP_ANON,表明进程的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然,只能用于具有亲属关系的进程之间的通信).

    offset:从文件开头计算offset个字节处开始映射;也就是,文件中需要被映射的文件内容的起始地址,这个起始地址的计算是以文件开头为参照的;这个参数一般取值为0,表示从文件开头处开始映射;
       返回值:文件最终映射到进程地址空间中的起始地址;进程可直接以该地址为有效的起始地址进行操作;也就是文件中开始映射的起始字节点到进程中对应映射内存区的起始地址点处的一个映射;换句话就是说,在进程地址空间中用于文件映射的内存区域的首地址;

    系统调用mmap()用于共享内存的两种方式:
       A、使用普通文件提供的内存映射/共享内存:适用于任何进程之间;此时,需要使用系统调用open()事先打开或创建一个文件,然后再调用mmap():
          fd = open(filename, flag, mode);
          ......
          ptr = mmap(NULL, len, PORT_READ|PORT_WRITE, MAP_SHARED, fd, 0);
         五、解除内存映射关系:
       当进程间通信结束时,需要解除文件页面空间到进程地址空间之间的映射关系;也就说,进程通信结束时,需要把挂载到进程地址空间上的文件卸载下来;这个任务由系统调用munmap();
       int munmap(void* addr, size_t len);
       该系统调用用于在进程地址空间中结束映射关系;
       addr:是调用mmap()返回的进程地址空间中用于文件映射的内存区域的首地址;
       len :进程地址空间中映射区域的大小,单位:字节;
       当映射关系解除之后,对原来映射地址的访问将导致段错误发生;
       返回值: -1:失败; 0:成功;
    内存映射的同步:
       一般来说,进程在映射空间中对共享内容的修改并不会直接写回到磁盘文件中,可以通过调用msync()来实现磁盘上文件内容与共享内存区中的内容与一致

  2. 小试牛刀

    整体流程就是:

    1. 首先打开一个文件,读取出文件信息
    2. 根据文件长度分配相应长度的堆内存
    3. Mmap函数做内存映射
    4. 从内存映射中开始拷贝内容
    5. 输出函数返回的地址开始的内容
    6. 输出完毕之后关闭内存映射文件
    7. 输出拷贝的文件
    8. 释放内存
    9. 关闭文件描述符

    源代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h> #include <string.h>
    int main()
    {
    int fd;//文件描述符
    char *start = NULL,*flag = NULL;//指针接收文件影射的开始地址
    struct stat st;
    printf("page size = %d\n",getpagesize( ) );
    fd=open("read.log",O_RDONLY); /*打开/etc/passwd*/
    if(fd < -1) //打开失败
    return -1;
    printf("打开成功\n");
    fstat(fd,&st); /*取得文件大小*/
    start =(char *) malloc(st.st_size+1);
    if(start == NULL)
    return -1;
    printf("内存分配成功\n");
    flag=mmap(start,st.st_size,PROT_READ,MAP_PRIVATE,fd,0); /*私有不共享*/
    if(flag == MAP_FAILED && start != flag) /*判断是否映射成功*/
    return -1;
    printf("映射成功\n"); printf("%s",flag);
    memcpy(start,flag,st.st_size);
    printf("输出完成\n");
    munmap(start,st.st_size); /*解除映射*/
    printf("解除映射完毕\n");
    return 0;
    }
  3. 各个平台的运行情况

    首先创建testmem.c

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    再创建read.log

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    在RHEL7上的运行情况

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    在RHEL6上

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    在MAC上

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    在Solaris上

    先进入桌面

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    将两个文件都拷贝过来

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

    Unix/Linux环境C编程入门教程(27)  内存那些事儿

上一篇:Oracle课程档案,第十天


下一篇:练习JavaScript实现过滤特殊字符