【摘要】本文详细讲述了Linux内核中与进程间通信概念相关的内核数据结构及其内在联系。
九、进程间通信(IPC)相关数据结构
9.1 ipc_namespace
- 从内核版本2.6.19开始,IPC机制已经能够意识到命名空间的存在,但管理IPC命名空间比较简单,因为它们之间没有层次关系,给定的进程属于task_struct->nsproxy->ipc_ns指向的命名空间,初始的默认命名空间通过ipc_namespace的静态实例init_ipc_ns实现,每个命名空间都包括如下结构:
- source/include/linux/ipc_namespace.h
struct ipc_namespace
{
atomic_t count;
/*
每个数组元素对应一种IPC机制
1) ids[0]: 信号量
2) ids[1]: 消息队列
3) ids[2]: 共享内存
*/
struct ipc_ids ids[3];
int sem_ctls[4];
int used_sems;
int msg_ctlmax;
int msg_ctlmnb;
int msg_ctlmni;
atomic_t msg_bytes;
atomic_t msg_hdrs;
int auto_msgmni;
size_t shm_ctlmax;
size_t shm_ctlall;
int shm_ctlmni;
int shm_tot;
struct notifier_block ipcns_nb;
/* The kern_mount of the mqueuefs sb. We take a ref on it */
struct vfsmount *mq_mnt;
/* # queues in this ns, protected by mq_lock */
unsigned int mq_queues_count;
/* next fields are set through sysctl */
unsigned int mq_queues_max; /* initialized to DFLT_QUEUESMAX */
unsigned int mq_msg_max; /* initialized to DFLT_MSGMAX */
unsigned int mq_msgsize_max; /* initialized to DFLT_MSGSIZEMAX */
};
- 拓展链接
- http://blog.csdn.net/bullbat/article/details/7781027
- http://book.51cto.com/art/201005/200882.htm
9.2 ipc_ids
- 这个结构保存了有关IPC对象状态的一般信息,每个struct ipc_ids结构实例对应于一种IPC机制: 共享内存、信号量、消息队列。为了防止对每个类别都需要查找对应的正确数组索引,内核提供了辅助函数msg_ids、shm_ids、sem_ids
- source/include/linux/ipc_namespace.h
struct ipc_ids
{
//1. 当前使用中IPC对象的数目
int in_use;
/*
2. 用户连续产生用户空间IPC ID,需要注意的是,ID不等同于序号,内核通过ID来标识IPC对象,ID按资源类型管理,即一个ID用于消息队列,一个用于信号量、一个用于共享内存对象
每次创建新的IPC对象时,序号加1(自动进行回绕,即到达最大值自动变为0)
用户层可见的ID = s * SEQ_MULTIPLER + i,其中s是当前序号,i是内核内部的ID,SEQ_MULTIPLER设置为IPC对象的上限
如果重用了内部ID,仍然会产生不同的用户空间ID,因为序号不会重用,在用户层传递了一个旧的ID时,这种做法最小化了使用错误资源的风险
*/
unsigned short seq;
unsigned short seq_max;
//3. 内核信号量,它用于实现信号量操作,避免用户空间中的竞态条件,该互斥量有效地保护了包含信号量值的数据结构
struct rw_semaphore rw_mutex;
//4. 每个IPC对象都由kern_ipc_perm的一个实例表示,ipcs_idr用于将ID关联到指向对应的kern_ipc_perm实例的指针
struct idr ipcs_idr;
};
- 每个IPC对象都由kern_ipc_perm的一个实例表示,每个对象都有一个内核内部ID,ipcs_idr用于将ID关联到指向对应的kern_ipc_perm实例的指针
9.3 kern_ipc_perm
- 这个结构保存了当前IPC对象的"所有者"、和访问权限等相关信息.
- /source/include/linux/ipc.h
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
//1. 保存了用户程序用来标识信号量的魔数
__kernel_key_t key;
//2. 当前IPC对象所有者的UID
__kernel_uid_t uid;
//3. 当前IPC对象所有者的组ID
__kernel_gid_t gid;
//4. 产生信号量的进程的用户ID
__kernel_uid_t cuid;
//5. 产生信号量的进程的用户组ID
__kernel_gid_t cgid;
//6. 位掩码。指定了所有者、组、其他用户的访问权限
__kernel_mode_t mode;
//7. 一个序号,在分配IPC时使用
unsigned short seq;
};
- 这个结果不足以保存信号量所需的所有信息。在进程的task_struct实例中有一个与IPC相关的成员
struct task_struct
{
...
#ifdef CONFIG_SYSVIPC
struct sysv_sem sysvsem;
#endif
...
}
//只有设置了配置选项CONFIG_SYSVIPC时,Sysv相关代码才会被编译到内核中
9.4 sysv_sem
-
struct sysv_sem数据结构封装了另一个成员:
struct sysv_sem { //用于撤销信号量 struct sem_undo_list *undo_list; };
-
如果崩溃金曾修改了信号量状态之后,可能会导致有等待该信号量的进程无法唤醒(伪死锁),则该机制在这种情况下很有用。通过使用撤销列表中的信息在适当的时候撤销这些操作,信号量可以恢复到一致状态,防止死锁。
9.5 sem_queue
- struct sem_queue数据结构用于将信号量与睡眠进程关联起来,该进程想要执行信号量操作,但目前因为资源争夺关系不允许。简单来说,信号量的"待决操作列表"中,每一项都是该数据结构的实例。
/* One queue for each sleeping process in the system. */
struct sem_queue
{
/*
queue of pending operations: 等待队列,使用next、prev串联起来的双向链表
*/
struct list_head list;
/*
this process: 睡眠的结构
*/
struct task_struct *sleeper;
/*
undo structure: 用于撤销的结构
*/
struct sem_undo *undo;
/*
process id of requesting process: 请求信号量操作的进程ID
*/
int pid;
/*
completion status of operation: 操作的完成状态
*/
int status;
/*
array of pending operations: 待决操作数组
*/
struct sembuf *sops;
/*
number of operations: 操作数目
*/
int nsops;
/*
does the operation alter the array?: 操作是否改变了数组?
*/
int alter;
};
-
对每个信号量,都有一个队列管理与信号量相关的所有睡眠进程(待决进程),该队列并未使用内核的标准设施实现,而是通过next、prev指针手工实现。
-
信号量各数据结构之间的相互关系:
9.6 msg_queue
- 和消息队列相关的数据结构,struct msg_queue作为消息队列的链表头,描述了当前消息队列的相关信息以及队列的访问权限.
- /source/include/linux/msg.h
/* one msq_queue structure for each present queue on the system */
struct msg_queue
{
struct kern_ipc_perm q_perm;
/*
last msgsnd time: 上一次调用msgsnd发送消息的时间
*/
time_t q_stime;
/*
last msgrcv time: 上一次调用msgrcv接收消息的时间
*/
time_t q_rtime;
/*
last change time: 上一次修改的时间
*/
time_t q_ctime;
/*
current number of bytes on queue: 队列上当前字节数目
*/
unsigned long q_cbytes;
/*
number of messages in queue: 队列中的消息数目
*/
unsigned long q_qnum;
/*
max number of bytes on queue: 队列上最大字节数目
*/
unsigned long q_qbytes;
/*
pid of last msgsnd: 上一次调用msgsnd的pid
*/
pid_t q_lspid;
/*
last receive pid: 上一次接收消息的pid
*/
pid_t q_lrpid;
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
- 我们重点关注一下结构体的最后3个成员,它们是3个标准的内核链表:
- struct list_head q_messages : 消息本身
- struct list_head q_receivers : 睡眠的接收者
- struct list_head q_senders : 睡眠的发送者
- q_messages(消息本身)中的各个消息都封装在一个msg_msg实例中。
9.7 msg_msg
- \linux-2.6.32.63\include\linux\msg.h
/* one msg_msg structure for each message */
struct msg_msg
{
//用作连接各个消息的链表元素
struct list_head m_list;
//消息类型
long m_type;
/*
message text size: 消息长度
*/
int m_ts;
/*
如果保存了超过一个内存页的长消息,则需要next
每个消息都(至少)分配了一个内存页,msg_msg实例保存在该页的起始处,剩余的空间可以用于存储消息的正文
*/
struct msg_msgseg* next;
void *security;
/* the actual message follows immediately */
};
-
消息队列通信时,发送进程和接收进程都可以进入睡眠:
- 如果消息队列已经达到最大容量,则发送者在试图写入时会进入睡眠
- 如果接受者在试图获取消息时会进入睡眠。
-
在实际的编程中,为了缓解因为消息队列上限满导致消息发送者(senders 向消息队列中写入数据的进程)被强制睡眠阻塞,我们可以采取几个措施:
1. vim /etc/sysctl.conf 2. 使用非阻塞消息发送方式调用msgsnd() API /* int ret = msgsnd(msgq_id, msg, msg_size, IPC_NOWAIT); IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回 */
-
拓展链接:http://blog.csdn.net/guoping16/article/details/6584024
9.8 msg_sender
- 对于消息队列来说,睡眠的发送者放置在msg_queue的q_senders链表中,链表元素使用下列数据结构
/* one msg_sender for each sleeping sender */
struct msg_sender
{
//链表元素
struct list_head list;
//指向对应进程的task_struct的指针
struct task_struct *tsk;
};
- 这里不需要额外的信息,因为发送进程是sys_msgsnd系统调用期间进入睡眠,也可能是通过sys_ipc系统调用期间进入睡眠(sys_ipc会在唤醒后自动重试发送操作)
9.9 msg_receiver
/*
* one msg_receiver structure for each sleeping receiver:
*/
struct msg_receiver
{
struct list_head r_list;
struct task_struct *r_tsk;
int r_mode;
//对预期消息的描述
long r_msgtype;
long r_maxsize;
//指向msg_msg实例的指针,在消息可用的情况下,该指针指定了复制数据的目标地址
struct msg_msg *volatile r_msg;
};
- 每个消息队列都有一个msqid_ds结构与其关联。
9.10 struct msqid_ds
- \linux-2.6.32.63\include\linux\msg.h
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
- 下图说明了消息队列所涉及各数据结构的相互关系