linux中几种共享内存

共享内存用处

使用文件或者管道进行进程间通信会有很多局限性。管道只能在父进程和子进程间使用;通过文件共享,在处理效率上又差一些,而且访问文件描述符不如访问内存地址方便。

Linux系统在编程上提供的共享内存方案有三种:

  • mmap内存共享映射
  • XSI共享内存
  • POSIX共享内存

mmap内存共享映射

mmap本来是存储映射功能。它可以将一个文件映射到内存中,在程序里就可以直接使用内存地址对文件内容进行访问。

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int port, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

Linux通过系统调用fork派生出的子进程和父进程共用内存地址空间,Linux的mmap实现了一种可以在父子进程之间共享内存地址的方式。

  1. 父进程将flags参数设置MAP_SHARED方式通过mmap申请一段内存。内存可以映射某个具体文件(fd),也可以不映射具体文件(fd置为-1,flag设置为MAP_ANONYMOUS).
  2. 父进程调用fork产生子进程,之后在父子进程内都可以访问到mmap所返回的地址,就可以共享内存了。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/mman.h>

#define COUNT 100

int do_child(int *count)
{
        int interval;

        // critical section
        interval = *count;
        interval++;
        usleep(1);
        *count = interval;
        // critical section

        exit(0);
}

int main()
{
    pid_t pid;
    int count;
    int *shm_p;

    shm_p = (int *)mmap(NULL, sizeof(int), PROT_WRITE|PROT_READ, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(MAP_FAILED == shm_p) {
        perror("mmap()");
        exit(1);
    }

    *shm_p = 0;

    for(count = 0; count < COUNT; count++) {
        pid = fork();
        if(pid < 0) {
            perror("fork()");
            exit(1);
        }

        if(pid == 0) {
            do_child(shm_p);
        }
    }

    for(count = 0; count < COUNT; count++) {
        wait(NULL);
    }

    printf("shm_p: %d\n", *shm_p);
    munmap(shm_p, sizeof(int));
    exit(0);
}

这段共享内存的使用是有竞争条件的。进程间通信不仅仅是通信这么简单,还要处理类似的这样的临界区代码。在这里,可以采用文件锁进行处理。但是共享内存使用文件锁显得不太协调。除了不方便和效率低下以外,文件锁还不能进行更高级的进程控制。这里可以使用信号量这种更高级的进程同步控制原语来实现相关功能。

下面这段程序用来帮助理解mmap的内存占用情况。

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
#include<sys/file.h>
#include<sys/wait.h>
#include<sys/mman.h>

#define COUNT 100
#define MEMSIZE 1024*1024*1023*2

int main()
{
    pid_t pid;
    int count;
    void *shm_p;

    shm_p = mmap(NULL, MEMSIZE, PROT_WRITE|PROT_READ, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(MAP_FAILED == shm_p) {
        perror("mmap()");
        exit(1);
    }

    bzero(shm_p, MEMSIZE);

    sleep(3000);

    munmap(shm_p, MEMSIZE);
    exit(0);
}

申请了一段近2G的内存,并置0.观察内存变化

[zorro@zorrozou-pc0 sharemem]$ free -g
              total        used        free      shared  buff/cache   available
Mem:             15           2           2           0          10          11
Swap:            31           0          31
[zorro@zorrozou-pc0 sharemem]$ ./mmap_mem &
[1] 32036
[zorro@zorrozou-pc0 sharemem]$ free -g
              total        used        free      shared  buff/cache   available
Mem:             15           2           0           2          12           9
Swap:            31           0          31

可以看出,这段内存被记录到shared和buff/cache中了。

mmap有一个缺点,那就是共享的内存只能在父进程和fork产生的子进程间使用,除此之外的其它进程无法得到共享内存段的地址。

XSI共享内存

XSI是X/Open组织对UNIX定义的一套接口标准(X/Open System Interface)。XSI共享内存在Linux底层的实现实际上跟mmap没有什么本质不同,只是在使用方法上有所区别。

#include<sys/ipc.h>
#include<sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

#include<sys/types.h>
#include<sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);

shmget的第三个参数,指定创建标志。支持的标志为:IPC_CREAT、IPC_EXCL。从Linux 2.6之后,还引入了支持大页的共享内存,标志为:SHM_HUGETLB、SHM_HUGE_2MB等。shemget除了可以创建一个新的共享内存外,还可以访问一个已经存在的内存,此时可以将shmflg置为0,不加任何标志打开。

shmget返回的int类型的shmid类似于文件描述符,注意只是类似,而并非同样的实现,所以,不能用select、poll、epoll这样的方法去控制一个XSI共享内存。对于一个XSI共享内存,其key是系统全局唯一的,这就方便其它进程使用同样的key,打开同样一段共享内存,以便进行进程间通信。而是用fork产生的子进程,可以直接通过shmid访问到相关共享内存段。这就是key的本质:系统中对XSI共享内存的全局唯一表示符。

#include<sys/types.h>
#include<sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

key是通过ftok函数,使用一个约定好的文件名和proj_id生成的。ftok不会创建文件,所以必须指定一个存在并且进程可以访问的pathname路径。另外,ftok并不是根据文件的路径和文件名生成key的,在具体实现上,它使用的是指定文件的inode编号和文件所在设备的设备编号。所以,不同的文件名也可能得到同一个key(不同的文件名指向同一个inode,硬链接)。同样的文件名也不一定就能得到相同的key,一个文件名有可能被删除重建,这种行为会导致inode变化。

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
#include<sys/file.h>
#include<sys/wait.h>
#include<sys/mman.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>

#define COUNT 100
#define PATHNAME "/etc/passwd"

int do_child(int proj_id)
{
    int interval;
    int *shm_p, shm_id;
    key_t shm_key;

    if((shm_key = ftok(PATHNAME, proj_id)) == -1) {
        perror("ftok()");
        exit(1);
    }

    shm_id = shmget(shm_key, sizeof(int), 0);
    if(shm_id < 0)
    {
        perror("shmget()");
        exit(1);
    }

    //使用shmat将相关共享内存映射到本进程的内存地址
    shm_p = (int *)shmat(shm_id, NULL, 0);
    if((void *)shm_p == (void *)-1)
    {
        perror("shmat()");
        exit(1);
    }

    // critical section
    interval = *shm_p;
    interval++;
    usleep(1);
    *shm_p = interval;
    // critical section

    //使用shmdt解除本进程内存对共享内存的地址映射,本操作不会删除共享内存
    if(shmdt(shm_p) < 0){
        perror("shmdt()");
        exit(1);
    }

    exit(0);
}

int main()
{
    pid_t pid;
    int count;
    int *shm_p;
    int shm_id, proj_id;
    key_t shm_key;

    proj_id = 1234;

    if((shm_key = ftok(PATHNAME, proj_id)) == -1)
    {
        perror("ftok()");
        exit(1);
    }

    //使用shm_key创建一个共享内存,如果系统中已经存在此共享内存,则报错退出。创建出来的共享内存权限为0600
    shm_id = shmget(shm_key, sizeof(int), IPC_CREAT|IPC_EXCL|0600);
    if(shm_id < 0) {
        perror("shmget()");
        exit(1);
    }

    shm_p = (int *)shmat(shm_id, NULL, 0);
    if((void *)shm_p == (void *) -1)
    {
        perror("shmat()");
        exit(1);
    }

    *shm_p = 0;

    for(count = 0; count < COUNT; count++) {
        pid = fork();
        if(pid < 0) {
            perror("fork()");
            exit(1);
        }

        if(pid == 0) {
            do_child(proj_id);
        }
    }

    for(count = 0; count < COUNT; count ++) {
        wait(NULL);
    }

    printf("shm_p: %d\n", *shm_p);

    if(shmdt(shm_p) < 0) {
        perror("shmdt()");
        exit(1);
    }

    if(shmctl(shm_id, IPC_RMID, NULL) < 0) {
        perror("shmctl");
        exit(1);
    }

    exit(0);
}

在某些情况下,也可以不通过一个key来创建共享内存。此时可以在key的参数所在位置填IPC_PRIVATE,这样内核会在保证不冲突的共享内存段id的情况下新建一段共享内存。因为只能是创建,所以flag位一定是IPC_CREAT。可以将shmid传给子进程。

当获取到shmid之后,就可以使用shmat来进行地址映射。shmat之后,通过访问返回的当前进程的虚拟地址就可以访问到共享内存段了。注意使用之后要调用shmdt解除映射,否则对于长期运行的程序,可能会造成虚拟内存地址泄露。shmdt并不能删除共享内存段,只是解除共享内存段和进程虚拟地址的映射关系。只要shmid对应的共享内存段还存在,就可以使用shmat继续映射使用。想要删除一个共享内存段,需要使用shmctl的IPC_RMID指令处理,或者在命令行中使用ipcrm删除指定的共享内存id或key。

shmctl还可以查看、修改共享内存的相关属性,可以在man 2 shmctl中查看。在系统中还可以使用ipcs -m 命令查看系统中所有共享内存的信息。

ipcs - provide information on ipc facilities
ipcs [-asmq] [-tclup]
ipcs [-smq] -i id

-m 共享内存
-q 消息队列
-s 信号量数组
-a all(缺省)

输出选项:

-t time
-p pid
-c creator
-l limits
-u summary

在Linux系统中,使用XSI共享内存调用shmget时,可以通过设置shmflg参数来申请大页内存(huge pages)。

SHM_HUGETLB(since Linux 2.6)
SHM_HUGE_2MB, SHM_HUGE_1GB(since Linux 3.8)

使用大页内存的好处是提高内核对内存管理的处理效率。因为在相同内存大小的情况下,使用大页内存(2M一页)将比使用一般内存页(4K一页)的内存页管理的数量大大减少,从而减少内存页表项的缓存压力和CPU cache缓存内存地址的映射压力。但是需要注意一些地方:

  • 大页内存不能交换(SWAP)
  • 使用不当时可能造成更大的内存泄露
  • 大页内存需要使用root权限
  • 需要修改系统配置
shm_id = shmget(IPC_PRIVATE, MEMSIZE, SHM_HUGETLB|0600)

如果要申请2G以下的大页内存,需要系统预留2G以上的大页内存。

echo 2048 > /proc/sys/vm/nr_hugepages
cat /proc/meminfo | grep -i huge
    AnonHugePages:      841728 KB
    HugePages_Total:    2020
    HugePages_Free:     2020
    HugePages_Rsvd:     0
    HugePages_Surp:     0
    Hugepagesize:       2048 kB

2048是页数,每页2M。

还需要注意共享内存的限制:

echo 2147483648 > /proc/sys/kernel/shmmax
echo 33554432 > /proc/sys/kernel/shmall

/proc/sys/kernel/shmall:限制系统用在共享内存上的内存页总数。一页一般是4k(可以通过getconf PAGE_SIZE查看)

/proc/sys/kernel/shmmax:限制一个共享内存段的最大长度,单位是字节

/proc/sys/kernel/shmmni:限制整个系统可以创建的最大的共享内存段的个数

POSIX共享内存

POSIX共享内存实际上毫无新意,它本质上是mmap对文件的共享方式映射,只不过映射的是tmpfs文件系统上的文件。

tmpfs是将一部分内存空间用作文件系统,一般挂在/dev/shm目录。

Linux提供的POSIX共享内存,实际上就是在/dev/shm下创建一个文件,并将其mmap之后映射其内存地址即可。可以通过man shm_overview查看使用方法。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/mman.h>

#define COUNT 100
#define SHMPATH "shm"

int do_child(char * shmpath)
{
    int interval, shmfd, ret;
    int *shm_p;
    // 使用shm_open访问一个已经创建的POSIX共享内存
    shmfd = shm_open(shmpath, O_RDWR, 0600);
    if (shmfd < 0) {
        perror("shm_open()");
        exit(1);
    }

    // 用mmap将对应的tmpfs文件映射到本进程内存 */
    shm_p = (int *)mmap(NULL, sizeof(int), PROT_WRITE|PROT_READ, MAP_SHARED, shmfd, 0);
    if (MAP_FAILED == shm_p) {
        perror("mmap()");
        exit(1);
    }
    /* critical section */
    interval = *shm_p;
    interval++;
    usleep(1);
    *shm_p = interval;
    /* critical section */

    munmap(shm_p, sizeof(int));
    close(shmfd);

    exit(0);
}

int main()
{
    pid_t pid;
    int count, shmfd, ret;
    int *shm_p;

    /* 创建一个POSIX共享内存 */
    shmfd = shm_open(SHMPATH, O_RDWR|O_CREAT|O_TRUNC, 0600);
    if (shmfd < 0) {
        perror("shm_open()");
        exit(1);
    }
    /* 使用ftruncate设置共享内存段大小 */
    ret = ftruncate(shmfd, sizeof(int));
    if (ret < 0) {
        perror("ftruncate()");
        exit(1);
    }
    /* 使用mmap将对应的tmpfs文件映射到本进程内存 */
    shm_p = (int *)mmap(NULL, sizeof(int), PROT_WRITE|PROT_READ, MAP_SHARED, shmfd, 0);
    if (MAP_FAILED == shm_p) {
        perror("mmap()");
        exit(1);
    }
    *shm_p = 0;

    for (count=0;count<COUNT;count++) {
        pid = fork();
        if (pid < 0) {
            perror("fork()");
            exit(1);
        }

        if (pid == 0) {
            do_child(SHMPATH);
        }
    }

    for (count=0;count<COUNT;count++) {
        wait(NULL);
    }

    printf("shm_p: %d\n", *shm_p);
    munmap(shm_p, sizeof(int));
    close(shmfd);
    shm_unlink(SHMPATH);
    exit(0);
}

编译该段代码的时候需要指定一个库,-lrt,这是linux的real time库。

  • shm_open的SHMPATH参数是一个路径,这个路径默认放在系统的/dev/shm目录下。这是shm_open封装好的,保证文件一定在tmpfs下。
  • 使用ftruncate改变共享内存的大小,实际就是改变文件的长度。
  • shm_unlink实际就是unlink系统调用的封装。如果不做unlink操作,那么文件会一直存在/dev/shm目录下。
  • 关闭共享内存描述符,使用close.

修改共享内存内核配置

  1. SHMMAX

一个进程可以在它的虚拟地址空间分配给一个共享内存端的最大大小(单位是字节)

echo 2147483648 > /proc/sys/kernel/shmmax
或
sysctl -w kernel.shmmax=2147483648
或
echo "kenerl.shmmax=2147483648" >> /etc/sysctl.conf
  1. SHMMNI

系统范围内共享内存段的数量


echo 4096 > /proc/sys/kernel/shmmni
或
sysctl -w kernel.shmmni=4096
或
echo "kernel.shmmni=4096" >> /etc/sysctl.conf
  1. SHMALL

这个参数设置了系统范围内共享内存可以使用的页数。单位是PAGE_SIZE(通常是4096,可以通过getconf PAGE_SIZE获得)。

echo 2097152 > /proc/sys/kernel/shmall
或
sysctl -w kernel.shmall=2097152
或
echo "kernel.shmall=2097152" >> /etc/sysctl.conf
  1. 移除共享内存

执行ipcs -m查看系统所有的共享内存。如果status字段是dest,表明这段共享内存需要被删除。

ipcs -m -i $shmid
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区。在/proc/sys/kernel/目录下,记录着共享内存的一些限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。 一、应用 共享内存的使用,主要有以下几个API:ftok()、shmget()、shmat()、shmdt()及shmctl()。 1)用ftok()函数获得一个ID号. 应用说明: 在IPC,我们经常用用key_t的值来创建或者打开信号量,共享内存和消息队列。 函数原型: key_t ftok(const char *pathname, int proj_id); Keys: 1)pathname一定要在系统存在并且进程能够访问的 3)proj_id是一个1-255之间的一个整数值,典型的值是一个ASCII值。 当成功执行的时候,一个key_t值将会被返回,否则-1被返回。我们可以使用strerror(errno)来确定具体的错误信息。 考虑到应用系统可能在不同的主机上应用,可以直接定义一个key,而不用ftok获得: #define IPCKEY 0x344378 2)shmget()用来开辟/指向一块共享内存的函数 应用说明: shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。 函数原型: int shmget(key_t key, size_t size, int shmflg); key_t key 是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。如果两个进程没有任何关系,所以就用ftok()算出来一个标识符(或者自己定义一个)使用了。 int size 是这块内存的大小. int flag 是这块内存的模式(mode)以及权限标识。 模式可取如下值: IPC_CREAT 新建(如果已创建则返回目前共享内存的id) IPC_EXCL 与IPC_CREAT结合使用,如果已创建则则返回错误 然后将“模式” 和“权限标识”进行“或”运算,做为第三个参数。 如: IPC_CREAT | IPC_EXCL | 0640 例子的0666为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限。 这个函数成功时返回共享内存的ID,失败时返回-1。 关于这个函数,要多说两句。 创建共享内存时,shmflg参数至少需要 IPC_CREAT | 权限标识,如果只有IPC_CREAT 则申请的地址都是k=0xffffffff,不能使用; 获取已创建的共享内存时,shmflg不要用IPC_CREAT(只能用创建共享内存时的权限标识,如0640),否则在某些情况下,比如用ipcrm删除共享内存后,用该函数并用IPC_CREAT参数获取一次共享内存(当然,获取失败),则即使再次创建共享内存也不能成功,此时必须更改key来重建共享内存。 3) shmat()将这个内存区映射到本进程的虚拟地址空间。 函数原型: void *shmat( int shmid , char *shmaddr , int shmflag ); shmat()是用来允许本进程访问一块共享内存的函数。 int shmid是那块共享内存的ID。 char *shmaddr是共享内存的起始地址,如果shmaddr为0,内核会把共享内存映像到调用进程的地址空间选定位置;如果shmaddr不为0,内核会把共享内存映像到shmaddr指定的位置。所以一般把shmaddr设为0。 int shmflag是本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式 成功时,这个函数返回共享内存的起始地址。失败时返回-1。 4) shmdt()函数删除本进程对这块内存的使用,shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数。 函数原型: int shmdt( char *shmaddr ); 参数char *shmaddr是那块共享内存的起始地址。 成功时返回0。失败时返回-1。 5) shmctl() 控制对这块共享内存的使用 函数原型: int shmctl( int shmid , int cmd , struct shmid_ds *buf ); int shmid是共享内存的ID。 int cmd是控制命令,可取值如下: IPC_STAT 得到共享内存的状态 IPC_SET 改变共享内存的状态 IPC_RMID 删除共享内存 struct shmid_ds *buf是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体。如果要改变共享内存的状态,用这个结构体指定。 返回值: 成功:0 失败:-1
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值