Linuxc - 共享內存

linuxc-共享內存

​ --詳細概念見《unix環境高級編程》15.9小節linux

共享內存能夠說是Linux 下最快速、最有效的進程間通訊方式。兩個不一樣進程A 、B 共享內存的意思是,同一塊物理內存被映射到進程A 、B 各自的進程地址空間,進程A 能夠即時看到進程B 對共享內存中數據的更新;反之,進程B 也能夠即時看到進程A對共享內存中數據的更新。編程

​ 這裏簡單說下映射的概念:bash

​ Linux系統會爲每一個進程分配 4GB 的虛擬地址空間,必定狀況下,須要將虛擬內存轉換成物理內存,這就須要內存映射。爲何咱們須要使用虛擬地址呢?最主要的緣由是不一樣PC的物理內存會不同,若是直接使用物理地址,會形成程序的移植性不好,另外虛擬地址訪問內存有如下優點:服務器

一、程序可使用一系列相鄰的虛擬地址來訪問物理內存中不相鄰的大內存緩衝區。app

二、程序可使用一系列虛擬地址來訪問大於可用物理內存的內存緩衝區。當物理內存的供應量變小時,內存管理器會將物理內存頁(一般大小爲 4 KB)保存到磁盤文件。數據或代碼頁會根據須要在物理內存與磁盤之間移動。函數

三、不一樣進程使用的虛擬地址彼此隔離。一個進程中的代碼沒法更改正在由另外一進程或操做系統使用的物理內存。工具

​ 進程可用的虛擬地址範圍稱爲該進程的「虛擬地址空間」。每一個用戶模式進程都有其各自的專用虛擬地址空間。對於 32 位進程,虛擬地址空間一般爲 4 GB,範圍從 0x00000000 至 0xFFFFFFFF。ui

1. 共享內存的概念

​ 共享內存從字面意義解釋就是多個進程能夠把一段內存映射到本身的進程空間,以此來實現數據的共享及傳輸,這也是全部進程間通訊方式最快的一種,共享內存是存在於內核級別的一種資源。操作系統

​ 在Shell 中可使用ipcs 命令查看當前系統IPC中的狀態,在文件系統中/proc目錄下有對其描述的相應文件.net

root@jonathan-pc:~/test/shm# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x01011d17 0          root       666        2176       1                       
0x02011d17 32769      root       666        2176       1                       
0xffffffff 65538      root       666        1024       0

ipcs -m ,其中 -m 是 memory 的意思 。

​ 在系統內核爲一個進程分配內存地址時,經過分頁機制可讓一個進程的物理地址不連續,同時也可讓一段內存同時分配給不一樣的進程。共享內存機制就是經過該原理實現的,共享內存機制只是提供數據的傳送,如何控制服務器端和客戶端的讀寫操做互斥,這就須要一些其餘的輔助工具,例如信號量。

​ 採用共享內存通訊的一個顯而易見的好處就是效率高,由於進程能夠直接讀寫內存,而不須要任何數據的拷貝。對於像管道和消息隊列等通訊方式,則須要在內核和用戶控件進行四次數據的拷貝,而共享內存只拷貝兩次數據:一次從輸入文件到共享區,另外一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再從新創建共享內存區域。而是保持共享區域,知道通訊完畢爲止,這樣,數據內同一直保存在共享內存中,並無寫回文件。共享內存中的內容每每是在接觸映射時才寫回文件的。所以,採用共享內存的通訊方式效率是最高的。

​ 共享內存最大不足之處在乎,因爲多個進程對同一塊內存區域具備訪問的權限,各個進程之間的同步問題顯得尤其重要。必須控制同一時刻只有一個進程對共享內存區域寫入數據,不然會形成數據的混亂。同步控制問題能夠由信號量來解決;

2.相關函數

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
/*     建立或打開共享內存
    Key:IPC_PRIVATE 或 ftok 的返回值
    size:共享內存區大小
    shmflag :同open函數的權限位,也可用8進製表示法
    返回值:成功:共享內存段標識符        出錯:-1
*/
int shmget(key_t, int size ,int shmflg );
/*    當一個共享內存建立或打開後,某個進程若是要使用該共享內存則必須將此內存區附加到它的地址空間
    shmid :要映射的共享內存區標示符
    shmaddr:將共享內存映射到指定地址(若爲NULL,則表示由系統自動完成映射)
    shmflg:默認0:共享內存只讀
    返回值:成功:映射後的地址        出錯:-1
*/
void *shmat (int shmid, const void *shaddr, int shmflg);
/*當進程對共享內存段的操做完成後,應調用 shmdt 函數,做用是將指定的共享內存段從當前進程空間中脫離出去
    shmaddr:共享內存映射後的地址
    返回值:0        出錯:-1
*/
int shmdt(const void *shmaddr);
/*    共享內存的控制
    shmid:要操做的共享內存標示符
    cmd:     IPC_STAT (獲取對象屬性)
             IPC_SET(設置對象屬性)
             IPC_RMID(刪除對象)
    buf:指定IPC_STAT/ IPC_SET 時用以保存/設置屬性
    返回值:0        出錯:-1
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

示例

無關進程之間使用共享內存

shm.hclient.cserver.c

//shm.h
#ifndef __SHM_H__
#define __SHM_H__

struct people {
    char name[10];
    int age;
};

union semun {
    int val; // value for SETVAL
    struct semid_ds *semid_dsbuf; // buffer for IPC_STAT, IPC_SET
    unsigned short *array; // array foror GETALL, SETALL
    struct seminfo *__buf; // buffer for IPC_INFO
};

#define SHM_KEY_FILE "./client.c"
#define SEM_KEY_FILE "./server.c"

#endif
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <signal.h>

#include "shm.h"

int semid, shmid;

int p(int semid, int semnum)
{
    struct sembuf sops = {semnum, -1, SEM_UNDO};
    return semop(semid, &sops, 1);
}

int v(int semid, int semnum)
{
    struct sembuf sops = {semnum, +1, SEM_UNDO};
    return semop(semid, &sops, 1);
}

void * signal_set(int signo, void (*func)(int))
{
    int ret;
    struct sigaction sig;
    struct sigaction osig;

    sig.sa_handler = func;
    sigemptyset(&sig.sa_mask);
    sig.sa_flags = 0;
#ifdef SA_RESTART
    sig.sa_flags |= SA_RESTART;
#endif
    ret = sigaction(signo, &sig, &osig);

    if (ret < 0) 
        return SIG_ERR;
    else 
        return osig.sa_handler;
}

void sigint(int sig)
{
    union semun arg;
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        fprintf(stderr, "shmctl delete error");
    }
    if (semctl(semid, 0, IPC_RMID, 0) == -1) {
        fprintf(stderr, "semctl delete error");
    }
    exit(sig);
}

int main(int argc, const char *argv[])
{
    key_t semkey;
    key_t shmkey;
    struct people *addr = NULL;
    union semun arg;
    
    signal_set(SIGINT, sigint);
    signal_set(SIGTERM, sigint);
    
    semkey = ftok(SEM_KEY_FILE, 0);
    if (semkey < 0) {
        fprintf(stderr, "ftok semkey error");
        return -1;
    }
    shmkey = ftok(SHM_KEY_FILE, 0);
    if (shmkey < 0) {
        fprintf(stderr, "ftok shmkey error");
        return -1;
    }
    
    semid = semget(semkey, 1, 0666 | IPC_CREAT);
    if (semid < 0) {
        fprintf(stderr, "semget error");
        return -1;
    }
    shmid = shmget(shmkey, sizeof(struct people), 0666 | IPC_CREAT);
    if (shmid < 0) {
        fprintf(stderr, "shmget error");
        goto err1;
    }
    
    arg.val = 0;
    if (semctl(semid, 0, SETVAL, arg) < 0) {
        fprintf(stderr, "ctl sem error");
        goto err2;
    }
    addr = (struct people *)shmat(shmid, NULL, 0);
    if (addr == (struct people *)-1) {
        fprintf(stderr, "shmat error");
        goto err2;
    }
    
    p(semid, 0);
    strncpy(addr->name, "xiaoming", 10);
    addr->age = 10;
    v(semid, 0);
    
    if (shmdt(addr) == -1) {
        fprintf(stderr, "shmdt is error");
        goto err2;
    }

    p(semid, 0);

err2:
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        fprintf(stderr, "shmctl delete error");
    }
err1:
    if (semctl(semid, 0, IPC_RMID, 0) == -1) {
        fprintf(stderr, "semctl delete error");
    }

    return 0;
}
//client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>

#include "shm.h"

int p(int semid, int semnum)
{
    struct sembuf sops = {semnum, -1, SEM_UNDO};
    return semop(semid, &sops, 1);
}

int v(int semid, int semnum)
{
    struct sembuf sops = {semnum, +1, SEM_UNDO};
    return semop(semid, &sops, 1);
}


int main(int argc, const char *argv[])
{
    int semid, shmid;
    key_t semkey;
    key_t shmkey;
    struct people *addr = NULL;
    //獲取ipc key
    semkey = ftok(SEM_KEY_FILE, 0);
    if (semkey < 0) {
        fprintf(stderr, "ftok semkey error");
        return -1;
    }
    shmkey = ftok(SHM_KEY_FILE, 0);
    if (shmkey < 0) {
        fprintf(stderr, "ftok shmkey error");
        return -1;
    }
    //獲取semid和shmid
    semid = semget(semkey, 0, 0666);
    if (semid < 0) {
        fprintf(stderr, "semget error");
        return -1;
    }
    shmid = shmget(shmkey, 0, 0666);
    if (shmid < 0) {
        fprintf(stderr, "shmget error");
        return -1;
    }
    //將共享內存連接到進程內存
    addr = (struct people*)shmat(shmid, 0, 0);
    if (addr == (struct people *)-1) {
        fprintf(stderr, "shmat error");
        return -1;
    }
    
    v(semid, 0);//取消server的阻塞
    sleep(1);
    
    p(semid, 0);//獲取共享內存內容
    printf("name is : %s\n", addr->name);
    printf("age is : %d\n", addr->age);
    v(semid, 0);
    
    if (shmdt(addr) == -1) {//斷開共享內存
        fprintf(stderr, "shmdt is error");
        return -1;
    }


    return 0;
}

運行結果

先運行server,再運行client

root@jonathan-pc:~/test/shm# ls
client.c  server.c  shm.h
root@jonathan-pc:~/test/shm# gcc client.c -o c
root@jonathan-pc:~/test/shm# gcc server.c -o s
root@jonathan-pc:~/test/shm# ./s
root@jonathan-pc:~/test/shm# ./c
name is : xiaoming
age is : 10

相關進程的共享內存

TELL_WAITWAIT_PARENTerr_sys等相關函數見《unix環境高級編程》的代碼

/dev/zero的存儲映射

相關聯進程之間的共享內存,能夠對/dev/zero使用mmap,建立未命名的存儲區

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>

#define    NLOOPS        1000
#define    SIZE        sizeof(long)    /* size of shared memory area */

static int
update(long *ptr)
{
    return((*ptr)++);    /* return value before increment */
}

int
main(void)
{
    int        fd, i, counter;
    pid_t    pid;
    void    *area;

    if ((fd = open("/dev/zero", O_RDWR)) < 0)
        err_sys("open error");
    if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
      fd, 0)) == MAP_FAILED)
        err_sys("mmap error");
    close(fd);        /* can close /dev/zero now that it's mapped */

    TELL_WAIT();

    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid > 0) {            /* parent */
        for (i = 0; i < NLOOPS; i += 2) {
            if ((counter = update((long *)area)) != i)
                err_quit("parent: expected %d, got %d", i, counter);

            TELL_CHILD(pid);
            WAIT_CHILD();
        }
    } else {                        /* child */
        for (i = 1; i < NLOOPS + 1; i += 2) {
            WAIT_PARENT();

            if ((counter = update((long *)area)) != i)
                err_quit("child: expected %d, got %d", i, counter);

            TELL_PARENT(getppid());
        }
    }

    exit(0);
}
匿名存儲映射

相關聯進程之間的共享內存,能夠對文件描述符-1使用mmap,建立未命名的存儲區

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>

#define    NLOOPS        1000
#define    SIZE        sizeof(long)    /* size of shared memory area */

static int
update(long *ptr)
{
    return((*ptr)++);    /* return value before increment */
}

int
main(void)
{
    int        i, counter;
    pid_t    pid;
    void    *area;

    if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
      -1, 0)) == MAP_FAILED)
        err_sys("mmap error");

    TELL_WAIT();

    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid > 0) {            /* parent */
        for (i = 0; i < NLOOPS; i += 2) {
            if ((counter = update((long *)area)) != i)
                err_quit("parent: expected %d, got %d", i, counter);

            TELL_CHILD(pid);
            WAIT_CHILD();
        }
    } else {                        /* child */
        for (i = 1; i < NLOOPS + 1; i += 2) {
            WAIT_PARENT();

            if ((counter = update((long *)area)) != i)
                err_quit("child: expected %d, got %d", i, counter);

            TELL_PARENT(getppid());
        }
    }

    exit(0);
}
相關文章
相關標籤/搜索