zeerd's blog     Article     Search     About     Tags     Pebble     Feed

闲来生雅趣,无事乐逍遥。对窗相望雪,一盏茶香飘。

在Android中使用共享内存

#Android #SHM


如果去网络上搜索如何在Android系统中使用共享内存,绝大部分文章都会提到下面的做法。

#define __ASHMEMIOC 0x77
#define ASHMEM_NAME_LEN 256
#define ASHMEM_SET_NAME (_IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]))
#define ASHMEM_SET_SIZE (_IOW(__ASHMEMIOC, 3, size_t))

int ashm_open(const char *_name, size_t _size)
{
    int fd = open("/dev/ashmem", O_RDWR);

    if(fd < 0) {
        a::cerr << "could not open /dev/ashmem";
    }
    else {
        ioctl(fd, ASHMEM_SET_NAME, _name);
        ioctl(fd, ASHMEM_SET_SIZE, _size);
    }
    return fd;
}

这个函数基本上可以起到Linux中的shm_open的作用。区别是需要给出共享内存的预期大小。

但是,实际上,事情远没有这么简单。在Android系统中,那个ASHMEM_SET_NAME的作用并不等同于Linux系统中shm_open的第一个参数。

在Android系统中,即便在不同的进程中使用同样的ASHMEM_SET_NAME,打开的也不是相同的共享内存空间。两者根本无法互通。

如果要互通,就需要第一个调用ashm_open的进程将fd传给其他进程。然后其他进程使用这个fd来直接操作。也就是用于直接调用mmap

将fd从一个进程共享给另一个进程的方法可能不止一个。下面这个是我使用的方案。也就是利用Domain Socket。

具体创建Domain Socket的过程就不展开了。主要讲收发:

发送fd的方法如下:

static int send_fd(int _sock, int _fd)
{
    struct iovec iov;
    char buffer[1];

    buffer[0] = 0;
    iov.iov_base = buffer;
    iov.iov_len = 1;

    struct msghdr msg;
    struct cmsghdr* cmsg;
    char cms[CMSG_SPACE(sizeof(int))];

    ::memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = reinterpret_cast<caddr_t>(cms);
    msg.msg_controllen = CMSG_LEN(sizeof(int));
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    ::memcpy(CMSG_DATA(cmsg), &_fd, sizeof(int));

    int ret = sendmsg(_sock, &msg, 0);
    if (ret > 0 && ret != iov.iov_len) {
        errno = EIO;
        ret = -1;
    }

    return ret;
}

接收fd的进程类似如下:

char buffer[1];
struct iovec iov;
iov.iov_base = buffer;
iov.iov_len = 1;

struct msghdr msg;
struct cmsghdr* cmsg;
char cms[CMSG_SPACE(sizeof(int))];
::memset(&msg, 0, sizeof msg);

msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = reinterpret_cast<caddr_t>(cms);
msg.msg_controllen = sizeof(cms);

int ret = recvmsg(sk_fd, &msg, 0);
if (ret < 0)
    continue;
if (ret == 0) {
    errno = EIO;
    continue;
}
cmsg = CMSG_FIRSTHDR(&msg);
::memcpy(&shm_fd, CMSG_DATA(cmsg), sizeof(int));

shm_fd就是获得的fd。