Linux内核IPC机制之信号量
一.概述
Linux的IPC包括三种进程间的通信方式,信号量,消息队列,共享内存。它们都是通过内核起作用的。当某个进程使用到信号量的时候,它会进行信号量的创建,创建时传入一个键值,同时返回信号量的描述符,该创建进程的命名空间中保存了与该信号量键值对应的id,该id对应sem_array结构,id和该结构的对应是通过idr来实现的,以上两者关系在创建后是在整个内核里的对应关系。当其他进程也需要访问该信号量时,需要有相同信号量有的键值,通过键值找到id,再找到对应的sem_array。该对应关系如下图中所示。
具体进程描述符中的结构如下所示:
struct task_struct
{
…………
struct nsproxy *nsproxy;
………….
}
struct nsproxy
{
………….
struct ipc_namespace*ipc_ns;
………….
};
struct ipc_namespace
{
struct kref kref;
struct ipc_ids ids[3];
int sem_ctls[4];
int used_sems;
…………
}
struct ipc_ids {
int in_use;
unsigned short seq;
unsigned short seq_max;
struct rw_semaphorerw_mutex;
struct idr ipcs_idr;
};
二.信号量的创建
asmlinkage long sys_semget(key_t key, intnsems, int semflg)
{
structipc_namespace *ns;
structipc_ops sem_ops;
structipc_params sem_params;
ns= current->nsproxy->ipc_ns; //当前进程的命名空间,current是当前进程的task_struct
if(nsems < 0 || nsems > ns->sc_semmsl)
return-EINVAL;
sem_ops.getnew= newary;
sem_ops.associate= sem_security;
sem_ops.more_checks= sem_more_checks;
sem_params.key= key;
sem_params.flg= semflg;
sem_params.u.nsems= nsems;
returnipcget(ns, &sem_ids(ns), &sem_ops, &sem_params);
}
在ipcget函数中的参数如下:
#define sem_ids(ns) ((ns)->ids[IPC_SEM_IDS]
Task_struct -> nsproxy -> ipc_ns-> ids[3], 3即分别为
#define IPC_SEM_IDS 0
#define IPC_MSG_IDS 1
#define IPC_SHM_IDS 2
具体进入到函数内部如下:
int ipcget(struct ipc_namespace *ns, structipc_ids *ids,
structipc_ops *ops, struct ipc_params *params)
{
if(params->key == IPC_PRIVATE)
returnipcget_new(ns, ids, ops, params);
else
returnipcget_public(ns, ids, ops, params);
}
此处分析ipcget_public()
static int ipcget_public(structipc_namespace *ns, struct ipc_ids *ids,
structipc_ops *ops, struct ipc_params *params)
//该函数返回SEM_ID,即为信号量的标志符
{
structkern_ipc_perm *ipcp;
intflg = params->flg;
interr;
retry:
err= idr_pre_get(&ids->ipcs_idr, GFP_KERNEL);
down_write(&ids->rw_mutex);
ipcp= ipc_findkey(ids, params->key);//是否已经存在键值相同的IPCP
if(ipcp == NULL) {//不存在
/*key not used */
if(!(flg & IPC_CREAT))
err= -ENOENT;
elseif (!err)
err= -ENOMEM;
else
err= ops->getnew(ns, params);
//调用函数中传入的回调函数sem_ops.getnew = newary;该函数创建结构体sem_array,并且建立起sem_array和ipc_ids之间的映射关系
}
else
{//该分支中存在相同的IPCP
/*ipc object has been locked by ipc_findkey() */
if(flg & IPC_CREAT && flg & IPC_EXCL)
err= -EEXIST;
else{
err= 0;
if(ops->more_checks)
err= ops->more_checks(ipcp, params);
//即调用传入的回调函数sem_ops.more_checks =sem_more_checks;该函数判断信号量的个数是否正确
if(!err)
err= ipc_check_perms(ipcp, ops, params);
//返回成功的IPCP
}
ipc_unlock(ipcp);
}
up_write(&ids->rw_mutex);
if(err == -EAGAIN)
gotoretry;
returnerr;
}
//接下来分析newary()
static int newary(struct ipc_namespace *ns,struct ipc_params *params)
{
intid;
intretval;
structsem_array *sma;
intsize;
key_tkey = params->key; //信号量的键值
intnsems = params->u.nsems;//信号量的数目
intsemflg = params->flg; //信号量的常规访问标志
if(!nsems)
return-EINVAL;
if(ns->used_sems + nsems > ns->sc_semmns)
return-ENOSPC;
size= sizeof (*sma) + nsems * sizeof (struct sem);
sma= ipc_rcu_alloc(size);
if(!sma) {
return-ENOMEM;
}
memset(sma, 0, size);
sma->sem_perm.mode= (semflg & S_IRWXUGO);
sma->sem_perm.key= key;
sma->sem_perm.security= NULL;
retval= security_sem_alloc(sma);
if(retval) {
ipc_rcu_putref(sma);
returnretval;
}
id= ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni);//建立映射
if(id < 0) {
security_sem_free(sma);
ipc_rcu_putref(sma);
returnid;
}
ns->used_sems+= nsems;
sma->sem_base= (struct sem *) &sma[1];
/*sma->sem_pending = NULL; */
sma->sem_pending_last= &sma->sem_pending;
/*sma->undo = NULL; */
sma->sem_nsems= nsems;
sma->sem_ctime= get_seconds();
sem_unlock(sma);
returnsma->sem_perm.id;
}
函数newary()执行完后基本建立如下的关系,如下图所示:
信号量各数据结构之间的相互关系
本图片出自《深入linux内核架构》第五章
三.信号量的操作
sys_semctl (int semid, int semnum, int cmd,union semun arg)
semid是信号量描述符
semnum是信号量集中的某个信号灯,
cmd是对指定资源的操作。
asmlinkage long sys_semctl (int semid, intsemnum, int cmd, union semun arg)
{
interr = -EINVAL;
intversion;
structipc_namespace *ns;
if(semid < 0)
return-EINVAL;
version= ipc_parse_version(&cmd);
ns= current->nsproxy->ipc_ns;
switch(cmd){
caseIPC_INFO:
caseSEM_INFO:
caseIPC_STAT:
caseSEM_STAT:
err= semctl_nolock(ns, semid, cmd, version, arg);
returnerr;
caseGETALL:
caseGETVAL:
caseGETPID:
caseGETNCNT:
caseGETZCNT:
caseSETVAL:
caseSETALL:
err= semctl_main(ns,semid,semnum,cmd,version,arg);
returnerr;
caseIPC_RMID:
caseIPC_SET:
err= semctl_down(ns, semid, cmd, version, arg);
returnerr;
default:
return-EINVAL;
}
}