目录
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
5.3 System V进程间通信
即 AT&T System V机制。
System V Release 4:即SVR4,最成功的版本。
进程间通信:即IPC。
3种System V的IPC机制:
信号量,消息队列,共享内存。
5.3.1 System V 机制
3种IPC实例在内核都有一个IPC对象。
内核中用magic唯一标识IPC对象,并且可用于获得IPC对象。
一个IPC对象拥有的属性:
1. 用户ID,组ID。
2. 访问权限(读写)。
5.3.2 信号量
应用层使用。
编程实例:
#define SEM_MAGIC 1234L //magic,IPC标识符
#define PERMS 0666 //访问权限
struct sembuf op_down[1] = {0, -1, 0};
struct sembuf op_up[1] = {0, 1, 0};
int semid = semget(SEM_MAGIC, 0, IPC_CREAT | PERMS)
可测试信号量是否已创建。semid在内核标识了信号量。
semctl(semid, 0, SETVAL, 1); //初始化值为1
semop(semid, &op_down[0], 1); // down操作
semop(semid, &op_up[0], 1); // up操作
内核实现
进程所属IPC命名空间:
task_struct -> nsproxy -> ipc_ns
默认的IPC命名空间:
struct ipc_namespace init_ipc_ns;
struct ipc_namespace { //包括:IPC统计信息,IPC资源限制信息。
...
struct ipc_ids ids[3];
//ids[0]:信号量 ids[1]:消息队列 ids[2]:共享内存
...
}
IPC资源限制,如:
用于共享内存的页最大数目。
共享内存段的最大长度。
消息队列的最大数目。
struct ipc_ids { //表示信号量,或消息队列,或共享内存
int in_use; //该类型的IPC对象数目。
unsigned short seq;
//每当创建一个新IPC时,seq递增,标识唯一IPC对象。
struct rw_semaphore rwsem;
//内核信号量,用于实现用户空间信号量。
struct idr ipcs_idr;
// ID基数树,
int next_id;
//存储下一个待分配的IPC ID,简化了ID生成
};
struct idr ipcs_idr;
作用:一个基数树,管理IPC ID与其对应的kern_ipc_perm。
idr:ID Radix Tree,基数树。高效分配、查找、删除和遍历整数 ID。
struct kern_ipc_perm *perm = idr_find(&ids->ipcs_idr, next_id);
以IPC ID为索引,从基数树中查找kern_ipc_perm对象。
每个IPC ID对象有一个kern_ipc_perm实例。
struct kern_ipc_perm { //表示该IPC对象的权限。
spinlock_t lock;
bool deleted;
int id; //内核中IPC ID。
key_t key; //应用层标识IPC的magic。
kuid_t uid; //所有者用户ID。
kgid_t gid; //组ID。
kuid_t cuid; //产生IPC的进程用户ID。
kgid_t cgid; //产生的IPC的进程组ID 。
umode_t mode; //位掩码,表示访问权限。
unsigned long seq; //序号,分配IPC时使用。
void *security;
}
task_struct中包含了IPC信号量信息。
struct task_struct {
...
struct sysv_sem sysvsem; //信号量
}
struct sysv_sem {
struct sem_undo_list *undo_list;
}
undo_list:用于撤销信号量。
场景:修改信号量的进程崩溃,等待信号量的其他进程无法被唤醒,防止死锁。
不详细展开。
struct sem_array { //管理一组信号量struct sem的属性。
struct kern_ipc_perm sem_perm; //权限。
time_t sem_otime; //最后一次操作信号量的时间。
time_t sem_ctime; //最后一次修改信号量的时间。
struct sem *sem_base; //指向信号量集合中第一个信号。
struct list_head sem_pending; //保存待决的操作的struct sem_queue。
struct list_head list_id; //该集合中的撤销请求。
int sem_nsems; //该集合中信号量数目。
int complex_count;
};
struct sem { //内核中表示一个信号量。
int semval; //当前值
int sempid //上一次操作该信号量的进程PID
struct list_head sem_pending;
//信号量的待决队列,每个进程通过struct sem_queue -> list 添加到该队列。
}
struct sem_queue:
每个进程有一个该实例,将该实例添加到信号量的待决队列中。
作用:关联了信号量与睡眠进程。
struct sem_queue {
struct list_head list; //用于将进程加入到信号量的待决队列。
struct task_struct *sleeper; //等待信号量的进程。
struct sem_undo *undo; //用于撤销。
int pid; //请求信号量的进程。
int status; //操作的完成状态。
struct sembuf *sops; //对信号量的操作。
int nsops; //操作数目。
int alter; //操作是否改变了数组。
};
struct sembuf { //对应应用层对信号量的操作。
unsigned short sem_num; //操作的信号量集合中索引。
short sem_op; //操作。如减3,则sem_op=-3。
short sem_flg; //操作标识。
};
实现系统调用
sys_semctl:操作信号量值
sys_semget:读取信号量ID
权限检查
int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flag)
检查是否有权限操作IPC对象。
5.3.3 消息队列
一个进程向队列发送消息,而其他进程从队列中接收消息。
每个消息格式:消息正文+编号。
编号的作用:标识一个消息。
一个进程读取消息后,内核将从队列中将消息删除。
所以每个消息只被一个进程读取。
同一编号消息:按先进先出处理。
ipc_ids[1] ---> ID号 --->(基数树)--> kern_ipc_perm
struct msg_queue{
time_t q_stime; //上一次调用msgsnd的时间。
time_t q_rtime; //上一次调用msgrcv的时间。
time_t q_ctime; //上一次修改队列属性的时间。
pid_t q_lspid; //上一次调用msgsnd的pid
pid_t q_lrpid; //上一次调用msgrcv的pid
unsigned long q_cbytes; //队列中当前字节数。
unsigned long q_qnum; //队列中消息数目。
unsigned long q_qbytes; //队列中最大字节数。
struct list_head q_messages; //连接所有消息。
struct list_head q_receivers; //连接消息的接收进程。
struct list_head q_senders; //连接消息的发送进程。
}
q_messages:
作用:连接所有消息,其中每个消息用struct msg_msg表示。
struct msg_msg { //一个消息
struct list_head m_list; //连接其他消息。
long m_type; //消息类型
int m_ts; //消息正文长度。
struct msg_msgseg *next; //若消息超过一页,用next连接
}
消息读取后,从队列中删除, 所以每个消息只能由一个进程读取。
若消息队列已满,发消息的进程将睡眠。
若消息队列为空,接收消息的进程将睡眠。
struct msg_sender {
struct list_head list; //将进程连接到msg_queue的q_senders链表中
struct task_struct *tsk; //发送消息的进程。
}
struct msg_receiver {
struct list_head r_list; //将进程连接到msg_queue的q_receivers链表中
struct task_struct *r_tsk; //接收消息的进程
int r_mode;
long r_msgtype;
long r_maxsize;
struct msg_msg *volatile r_msg;
}
5.3.4 共享内存
struct shmid_kernel {
...
struct kern_ipc_perm shm_perm;
struct file *shm_file; //每个共享内存都创建一个伪文件
}
通过shm_file->f_mapping 访问 struct address_space。即可创建、读写对应匿名映射的VMA。