Linux内核共享内存和信号量

本文介绍进程间通信共享内存信号量。之所以将二者一起叙述,是因为二者有着密不可分的关系。共享内存会利用虚拟内存和物理内存的映射关系,让不同进程开辟一块虚拟空间映射到相同的物理内存上,从而实现了两个进程对相同区域的读写,即进程间通信。而信号量则实现了互斥锁,可以为共享内存提供数据一致性的保证,因此二者常结合使用。

基础知识

共享内存的使用包括

  • 调用shmget()创建共享内存
  • 调用shmat()映射共享内存至进程虚拟空间
  • 调用shmdt()接触映射关系

信号量有着类似的操作

  • 调用semget()创建信号量集合。
  • 调用semctl(),信号量往往代表某种资源的数量,如果用信号量做互斥,那往往将信号量设置为 1。
  • 调用semop()修改信号量数目,即加锁和解锁之用

整体通信过程可用如下生产者消费者的模式图来理解。

Linux内核共享内存和信号量

统一封装的接口

消息队列、共享内存和信号量有着统一的封装和管理机制,为此我们提供了对应的名字空间和ipc_ids结构体。根据代码中的定义,第 0 项用于信号量,第 1 项用于消息队列,第 2 项用于共享内存,分别可以通过 sem_ids、msg_ids、shm_ids 来访问。ipc_ids中in_use 表示当前有多少个 ipc,seq 和 next_id 用于一起生成 ipc 唯一的 id,ipcs_idr 是一棵基数树,一旦涉及从一个整数查找一个对象它都是最好的选择。

struct ipc_namespace {
......
    struct ipc_ids  ids[3];
......
} __randomize_layout;

#define IPC_SEM_IDS 0
#define IPC_MSG_IDS 1
#define IPC_SHM_IDS 2

#define sem_ids(ns) ((ns)->ids[IPC_SEM_IDS])
#define msg_ids(ns) ((ns)->ids[IPC_MSG_IDS])
#define shm_ids(ns) ((ns)->ids[IPC_SHM_IDS])

struct ipc_ids {
    int in_use;
    unsigned short seq;
    struct rw_semaphore rwsem;
    struct idr ipcs_idr;
    int max_idx;
    struct rhashtable key_ht;
};

struct idr {
    struct radix_tree_root  idr_rt;
    unsigned int        idr_base;
    unsigned int        idr_next;
};

信号量、消息队列、共享内存的通过基数树来管理各自的对象,三种ipc对应的结构体中第一项均为struct kern_ipc_perm,该结构体对应的id会存储在基数树之中,可以通过ipc_obtain_object_idr()获取。

struct sem_array {
    struct kern_ipc_perm  sem_perm;  /* permissions .. see ipc.h */
......
} __randomize_layout;

struct msg_queue {
    struct kern_ipc_perm q_perm;
......
} __randomize_layout;

struct shmid_kernel /* private to the kernel */
{  
    struct kern_ipc_perm  shm_perm;
......
} __randomize_layout;

struct kern_ipc_perm *ipc_obtain_object_idr(struct ipc_ids *ids, int id)
{
    struct kern_ipc_perm *out;
    int lid = ipcid_to_idx(id);
    out = idr_find(&ids->ipcs_idr, lid);
    return out;
}
  对于这三种不同的通信方式,会对ipc_obtain_object_idr()进行封装

static inline struct sem_array *sem_obtain_object(struct ipc_namespace *ns, int id)
{
    struct kern_ipc_perm *ipcp = ipc_obtain_object_idr(&sem_ids(ns), id);
    return container_of(ipcp, struct sem_array, sem_perm);
}

static inline struct msg_queue *msq_obtain_object(struct ipc_namespace *ns, int id)
{
    struct kern_ipc_perm *ipcp = ipc_obtain_object_idr(&msg_ids(ns), id);
    return container_of(ipcp, struct msg_queue, q_perm);
}

static inline struct shmid_kernel *shm_obtain_object(struct ipc_namespace *ns, int id)
{
    struct kern_ipc_perm *ipcp = ipc_obtain_object_idr(&shm_ids(ns), id);
    return container_of(ipcp, struct shmid_kernel, shm_perm);
}

由此,我们实现了对这三种进程间通信方式统一的封装抽象。首先用名字空间存储三种ipc,然后对应的ipc_ids会描述该通信方式的特点,并包含一个基数树存储id从而找到其实际运行的多个通信的结构体。

Linux内核共享内存和信号量

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!