首页 > 系统 > Linux >

Linux共享内存系列——System V共享内存(三)

2016-09-06

Linux共享内存系列——System V共享内存(三)

实战共享内存

先来一个最简单的共享内存使用演示,下面代码分为客户端和服务端,服务端启动后打开共享内存并打印出共享内存标识符,向共享内存写入数据后退出。客户端接收共享内存标识符,读取并打印内容后删除共享内存:

//server.cpp
#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    printf("server start...\n");
    int shmId = shmget(IPC_PRIVATE, 1024, S_IRUSR | S_IWUSR);//创建一个长度是1024的共享内存
    if (-1 == shmId)
    {
        perror("shmget");
        return -1;
    }
    char* shmAddr = (char*)shmat(shmId, NULL, 0);//附加到本进程虚拟内存上
    if (NULL == shmAddr)
    {
        perror("shmat");
        return -1;
    }
    sprintf(shmAddr, "Hello,client!");//写入内容
    printf("server writed... %d\n", shmId);//打印出共享内存标识符
    int iRet = shmdt(shmAddr);//分离共享内存,客户端负责关闭
    if (-1 == iRet)
    {
        perror("shmdt");
    }
    return 0;
}
//client.cpp
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    if (argc < 2)//没有传入标识符的话就退出,简单起见这里不做太多错误处理
    {
        printf("please input the shmid...\n");
        return -1;
    }
    int shmId = atoi(argv[1]);
    char* shmAddr = (char*)shmat(shmId, NULL, 0);
    if (NULL == shmAddr)
    {
        perror("shmat");
        return -1;
    }
    printf("client read:%s...\n", shmAddr);//将共享内存内容打印出来
    int iRet = shmdt(shmAddr);//分离共享内存
    if (-1 == iRet)
    {
        perror("shmdt");
    }
    iRet = shmctl(shmId, IPC_RMID, NULL);//删除
    if (-1 == iRet)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}

编译的时候用命令g++ server.cpp -o server和g++ client.cpp -o client,分别编译两个可执行程序,先启动server,获取到shmid后启动并将shmid传给client:./client 123456。如果我们在server运行完毕后不执行client,使用ipcs -m命令来查看共享内存使用情况,可以看到刚刚创建的共享内存标识符和权限、大小以及附加的进程数。在client执行完毕后再次查看,共享内存已经被删除。

下面的例子使用fork()创建子进程,在fork()之前先shmget()创建共享内存,这样子进程就能够获取到共享内存标识符,父子进程之间就不需要通过参数来保证打开的是同一个共享内存了:

//main.cpp
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    printf("server start...\n");
    int shmId = shmget(IPC_PRIVATE, 1024, S_IRUSR | S_IWUSR);//创建一个长度是1024的共享内存
    if (-1 == shmId)
    {
        perror("shmget");
        return -1;
    }
    pid_t pid = fork();
    if (-1 == pid)
    {
        perror("fork");
        return -1;
    }
    char* shmAddr = (char*)shmat(shmId, NULL, 0);//附加到本进程虚拟内存上
    if (NULL == shmAddr)
    {
        perror("shmat");
        return -1;
    }
    if (0 == pid)
    {
        sprintf(shmAddr, "Hello,client!");//写入内容
        printf("server writed... %d\n", shmId);//打印出共享内存标识符
        int iRet = shmdt(shmAddr);//分离共享内存,客户端负责关闭
        if (-1 == iRet)
        {
            perror("shmdt");
        }
    }
    else
    {
        sleep(1);//实际项目应该采用更安全的同步机制,例如信号量等
        printf("client read:%s...\n", shmAddr);//将共享内存内容打印出来
        int iRet = shmdt(shmAddr);//分离共享内存
        if (-1 == iRet)
        {
            perror("shmdt");
        }
        iRet = shmctl(shmId, IPC_RMID, NULL);//删除
        if (-1 == iRet)
        {
            perror("shmctl");
            return -1;
        }
    }

    return 0;
}

下面的例子演示信号量和共享内存的配合,这个例子稍微有点工程代码的感觉了,但是为了简单起见,依然没有对错误进行处理:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

union semun{
    int val;
    struct semid_ds* buf;
    unsigned short* array;
    struct seminfo* __buf;
};//使用信号量必须要定义这个结构,信号量相关章节会细讲

int main(int argc, char* argv[])
{
    printf("server start...\n");
    int shmId = shmget(IPC_PRIVATE, 1024, S_IRUSR | S_IWUSR);//创建一个长度是1024的共享内存
    if (-1 == shmId)
    {
        perror("shmget");
        return -1;
    }

    int semId = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR);//使用信号量做进程同步,避免共享内存的竞态
    if(-1 == semId)
    {
        perror("semget");
        return -1;
    }
    union semun semarg;
    semarg.val = 0;
    if(-1 == semctl(semId, 0, SETVAL, semarg))//初始化信号量,如果确认代码只在Linux下允许,可以不初始化——semget能够自动初始化
    {
        perror("semctl");
        return -1;
    }

    pid_t pid = fork();
    if (-1 == pid)
    {
        perror("fork");
        return -1;
    }
    char* shmAddr = (char*)shmat(shmId, NULL, 0);//附加到本进程虚拟内存上
    if (NULL == shmAddr)
    {
        perror("shmat");
        return -1;
    }
    if (0 == pid)
    {
        sleep(1);
        sprintf(shmAddr, "Hello,client!");//写入内容
        printf("server writed... %d\n", shmId);//打印出共享内存标识符
        int iRet = shmdt(shmAddr);//分离共享内存,客户端负责关闭
        if (-1 == iRet)
        {
            perror("shmdt");
        }
        struct sembuf semVal;
        semVal.sem_num = 0;//对第一个信号量操作
        semVal.sem_op = 1;//操作内容是+1
        semVal.sem_flg = 0;
        if(-1 == semop(semId, &semVal, 1))//信号量+1,父进程可以从阻塞中恢复
        {
            perror("semop");//简单起见,不再错误处理
            return -1;
        }
    }
    else
    {
        printf("client wait...\n");
        struct sembuf semVal;
        semVal.sem_num = 0;//对第一个信号量操作
        semVal.sem_op = -1;//操作内容是-1
        semVal.sem_flg = 0;
        int iRet = semop(semId, &semVal, 1);//阻塞在这里等待信号量从0变为1,返回时信号量恢复成0
        if(-1 == iRet)
        {
            perror("semop");
            return -1;
        }

        printf("client read:%s...\n", shmAddr);//将共享内存内容打印出来
        iRet = shmdt(shmAddr);//分离共享内存
        if (-1 == iRet)
        {
            perror("shmdt");
        }
        iRet = shmctl(shmId, IPC_RMID, NULL);//删除
        if (-1 == iRet)
        {
            perror("shmctl");
            return -1;
        }
        iRet = semctl(semId, IPC_RMID, 0);
        if (-1 == iRet)
        {
            perror("semctl");
            return -1;
        }
    }

    return 0;
}

以上,其实从代码的角度来讲共享内存的使用非常简单,关键是如何定义进程间数据的协议。上面的例子中都是只读写一条数据,但是真正的工程中是不可能只在共享内存中保存一条记录的,因此读写双方约定以何种方式解释共享内存中的数据、如何保证多条数据存在的情况下过期数据不被读取等业务代码才是更需要仔细构思的。

相关文章
最新文章
热点推荐