Linux内核IPC机制之信号量

时间:2022-07-12 15:16:47


Linux内核IPC机制之信号量


一.概述


LinuxIPC包括三种进程间的通信方式,信号量,消息队列,共享内存。它们都是通过内核起作用的。当某个进程使用到信号量的时候,它会进行信号量的创建,创建时传入一个键值,同时返回信号量的描述符,该创建进程的命名空间中保存了与该信号量键值对应的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_arrayipc_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内核IPC机制之信号量

                信号量各数据结构之间的相互关系


本图片出自《深入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;

      }

}