linux SysV IPC实现

IPC(Interprocess Communication)表示进程间通信机制;System V IPC机制主要有消息队列、共享内存、信号量,linux中实现了SysV IPC。

 

I.SysV IPC创建/获取
消息队列、共享内存、信号量的创建/获取API原型如下:
int msgget(key_t key, int msgflg);
int shmget(key_t key, size_t size, int shmflg);
int semget(key_t key, int nsems, int semflg);

以上API主要用来创建新的IPC或根据key值查找IPC,并返回IPC id;内核实现均调用ipcget:

ipc/util.c:
734 /**
735  * ipcget - Common sys_*get() code
736  * @ns : namsepace
737  * @ids : IPC identifier set
738  * @ops : operations to be called on ipc object creation, permission checks
739  *        and further checks
740  * @params : the parameters needed by the previous operations.
741  *
742  * Common routine called by sys_msgget(), sys_semget() and sys_shmget().
743  */
744 int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
745                         struct ipc_ops *ops, struct ipc_params *params)
746 {
747         if (params->key == IPC_PRIVATE)
748                 return ipcget_new(ns, ids, ops, params);
749         else
750                 return ipcget_public(ns, ids, ops, params);
751 }


i.key
key类型为key_t,即int:

/usr/include/bits/types.h:101:#define	__S32_TYPE		int
/usr/include/bits/typesizes.h:55:#define __KEY_T_TYPE		__S32_TYPE
/usr/include/bits/types.h:155:__STD_TYPE __KEY_T_TYPE __key_t;	/* Type of an IPC key.  */
/usr/include/sys/ipc.h:48:typedef __key_t key_t;
SysV IPC均用key值作为主键,系统级标识出IPC;同时每一个IPC都有一个id与之对应。
key和id都能标识出IPC,区别主要是,key由用户程序提供,以便用户程序标识IPC实现进程间通信;id由系统返回,能快速查找到IPC并使用。
key查找IPC过程:遍历IPC id基数树的叶子结点,找出key对应的IPC。实现为ipc_findkey:
170 /**
171  *      ipc_findkey     -       find a key in an ipc identifier set     
172  *      @ids: Identifier set
173  *      @key: The key to find
174  *      
175  *      Requires ipc_ids.rw_mutex locked.
176  *      Returns the LOCKED pointer to the ipc structure if found or NULL
177  *      if not.
178  *      If key is found ipc points to the owning ipc structure
179  */
180 
181 static struct kern_ipc_perm *ipc_findkey(struct ipc_ids *ids, key_t key)
182 {
183         struct kern_ipc_perm *ipc;
184         int next_id;
185         int total;
186 
187         for (total = 0, next_id = 0; total < ids->in_use; next_id++) {
188                 ipc = idr_find(&ids->ipcs_idr, next_id);
189 
190                 if (ipc == NULL)
191                         continue;
192 
193                 if (ipc->key != key) {
194                         total++;
195                         continue;
196                 }
197 
198                 ipc_lock_by_ptr(ipc);
199                 return ipc;
200         }
201 
202         return NULL;
203 }
KEY_PRIVATE是一个特殊的key值,不能作为系统范围标识IPC的key;主要用于创建进程私有的IPC(其它进程不能根据key值使用),而非系统级IPC(其它进程能够根据key值使用)。
KEY_PRIVATE值如下:
/usr/include/bits/ipc.h:39:#define IPC_PRIVATE	((__key_t) 0)	/* Private key.  */

ii.SysV IPC创建
在以下情况创建IPC:
1.key=IPC_PRIVATE
2.key!=IPC_PRIVATE,key值标识的IPC不存在,且flg中IPC_CREATE置位
当创建IPC时,用flg的低9位作为mode(用户、组、其它的读、写、执行权限)来创建IPC

key=IPC_PRIVATE时,调用ipcget_new创建新的IPC;
否则,使用ipcget_public查找key标识的IPC,如果存在校验权限,如果不存在创建IPC或报错。
实现如下:

288 /**
289  *      ipcget_new      -       create a new ipc object
290  *      @ns: namespace
291  *      @ids: IPC identifer set
292  *      @ops: the actual creation routine to call
293  *      @params: its parameters
294  *
295  *      This routine is called by sys_msgget, sys_semget() and sys_shmget()
296  *      when the key is IPC_PRIVATE.
297  */
298 static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids,
299                 struct ipc_ops *ops, struct ipc_params *params)
300 {
301         int err;
302 retry:
303         err = idr_pre_get(&ids->ipcs_idr, GFP_KERNEL);
304 
305         if (!err)
306                 return -ENOMEM;
307 
308         down_write(&ids->rw_mutex);
309         err = ops->getnew(ns, params);
310         up_write(&ids->rw_mutex);
311 
312         if (err == -EAGAIN)
313                 goto retry;
314 
315         return err;
316 }

1.idr_pre_get分配idr缓存,供分配IPC id使用
2.具体的IPC函数,分配相应的IPC。newque、newseg、newary分别创建消息队列、共享内存、信号量

348 /**
349  *      ipcget_public   -       get an ipc object or create a new one
350  *      @ns: namespace
351  *      @ids: IPC identifer set
352  *      @ops: the actual creation routine to call
353  *      @params: its parameters
354  *
355  *      This routine is called by sys_msgget, sys_semget() and sys_shmget()
356  *      when the key is not IPC_PRIVATE.
357  *      It adds a new entry if the key is not found and does some permission
358  *      / security checkings if the key is found.
359  *
360  *      On success, the ipc id is returned.
361  */
362 static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
363                 struct ipc_ops *ops, struct ipc_params *params)
364 {
365         struct kern_ipc_perm *ipcp;
366         int flg = params->flg;
367         int err;
368 retry:
369         err = idr_pre_get(&ids->ipcs_idr, GFP_KERNEL);
370 
371         /*
372          * Take the lock as a writer since we are potentially going to add
373          * a new entry + read locks are not "upgradable"
374          */
375         down_write(&ids->rw_mutex);
376         ipcp = ipc_findkey(ids, params->key);
377         if (ipcp == NULL) {
378                 /* key not used */
379                 if (!(flg & IPC_CREAT))
380                         err = -ENOENT;
381                 else if (!err)
382                         err = -ENOMEM;
383                 else
384                         err = ops->getnew(ns, params);
385         } else {
386                 /* ipc object has been locked by ipc_findkey() */
387 
388                 if (flg & IPC_CREAT && flg & IPC_EXCL)
389                         err = -EEXIST;
390                 else {
391                         err = 0;
392                         if (ops->more_checks)
393                                 err = ops->more_checks(ipcp, params);
394                         if (!err)
395                                 /*
396                                  * ipc_check_perms returns the IPC id on
397                                  * success
398                                  */
399                                 err = ipc_check_perms(ipcp, ops, params);
400                 }
401                 ipc_unlock(ipcp);
402         }
403         up_write(&ids->rw_mutex);
404 
405         if (err == -EAGAIN)
406                 goto retry;
407 
408         return err;
409 }

1.idr_pre_get分配idr缓存,供分配IPC id使用
2.如果key对应的IPC不存在,分配IPC
3.如果key对应的IPC存在,做权限等校验

 

 

II.SysV IPC共性
SysV IPC有许多共同的属性,如key、id、mode等;IPC共性用kern_ipc_perm表示:

include/linux/ipc.h:
 85 /* used by in-kernel data structures */
 86 struct kern_ipc_perm
 87 {
 88         spinlock_t      lock;
 89         int             deleted;
 90         int             id;
 91         key_t           key;
 92         uid_t           uid;
 93         gid_t           gid;
 94         uid_t           cuid;
 95         gid_t           cgid;
 96         mode_t          mode;
 97         unsigned long   seq;
 98         void            *security;
 99 };

lock:IPC锁
deleted:删除标识
id:IPC id
key:IPC key
uid:IPC所属用户
gid:IPC所属用户组
cuid:IPC创建用户
cgid:IPC创建用户组
mode:用户、用户组、其它的读、写、执行权限
seq:序列号

 

i.seq
seq用于表示IPC id分配序列号;每次分配IPC id,seq会自增1,到seq_max时会从0重新开始。
1.seq初始化

ipc/util.h:
 16 #define SEQ_MULTIPLIER  (IPCMNI)
ipc/util.c:
111 /**
112  *      ipc_init_ids            -       initialise IPC identifiers
113  *      @ids: Identifier set
114  *
115  *      Set up the sequence range to use for the ipc identifier range (limited
116  *      below IPCMNI) then initialise the ids idr.
117  */
118 
119 void ipc_init_ids(struct ipc_ids *ids)
120 {
121         init_rwsem(&ids->rw_mutex);
122 
123         ids->in_use = 0;
124         ids->seq = 0;
125         {
126                 int seq_limit = INT_MAX/SEQ_MULTIPLIER;
127                 if (seq_limit > USHORT_MAX)
128                         ids->seq_max = USHORT_MAX;
129                  else
130                         ids->seq_max = seq_limit;
131         }
132 
133         idr_init(&ids->ipcs_idr);
134 }
2.seq递增
236 /**
237  *      ipc_addid       -       add an IPC identifier
238  *      @ids: IPC identifier set
239  *      @new: new IPC permission set
240  *      @size: limit for the number of used ids
241  *
242  *      Add an entry 'new' to the IPC ids idr. The permissions object is
243  *      initialised and the first free entry is set up and the id assigned
244  *      is returned. The 'new' entry is returned in a locked state on success.
245  *      On failure the entry is not locked and a negative err-code is returned.
246  *
247  *      Called with ipc_ids.rw_mutex held as a writer.
248  */
249 
250 int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size)
251 {
279 
280         new->seq = ids->seq++;
281         if(ids->seq > ids->seq_max)
282                 ids->seq = 0;
283 
284         new->id = ipc_buildid(id, new->seq);
285         return id;
286 }

ii.id
IPC id是由基数树中空闲id与seq计算得到,实现如下:
ipc/util.h:
 16 #define SEQ_MULTIPLIER  (IPCMNI)
145 static inline int ipc_buildid(int id, int seq)
146 {
147         return SEQ_MULTIPLIER * seq + id;
148 }
include/linux/ipc.h:
 83 #define IPCMNI 32768  /* <= MAX_INT limit for ipc arrays (including sysctl changes) */

如果没有IPC释放的情况时,从基数树中分配的id值会比上次分配id增加1,则IPC id会增大IPCMNI+1=32768+1=32769;情况如下:

[redhat@localhost linux-2.6.32.60]$ ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 98304      redhat     600        393216     2          dest         
0x00000000 131073     redhat     600        393216     2          dest         
0x00000000 163842     redhat     600        393216     2          dest         
0x00000000 196611     redhat     600        393216     2          dest         
0x00000000 229380     redhat     600        393216     2          dest         
0x00000000 262149     redhat     600        393216     2          dest         
0x00000000 622598     redhat     600        393216     2          dest         
0x00000000 327687     redhat     600        393216     2          dest         
0x00000000 360456     redhat     600        393216     2          dest         
0x00000000 393225     redhat     600        393216     2          dest         
0x00000000 425994     redhat     600        393216     2          dest         
0x00000000 458763     redhat     600        393216     2          dest         
0x00000000 491532     redhat     600        393216     2          dest  


iii.mode
mode是在创建IPC时,取自flg的低9位,表示用户、用户组、其它的读、写、执行权限;当IPC已经存在,获取IPC时会做权限校验,由ipcperms实现:

606 /**
607  *      ipcperms        -       check IPC permissions
608  *      @ipcp: IPC permission set
609  *      @flag: desired permission set.
610  *
611  *      Check user, group, other permissions for access
612  *      to ipc resources. return 0 if allowed
613  */
614 
615 int ipcperms (struct kern_ipc_perm *ipcp, short flag)
616 {       /* flag will most probably be 0 or S_...UGO from <linux/stat.h> */
617         uid_t euid = current_euid();
618         int requested_mode, granted_mode;
619 
620         audit_ipc_obj(ipcp);
621         requested_mode = (flag >> 6) | (flag >> 3) | flag;
622         granted_mode = ipcp->mode;
623         if (euid == ipcp->cuid ||
624             euid == ipcp->uid)
625                 granted_mode >>= 6;
626         else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid))
627                 granted_mode >>= 3;
628         /* is there some bit set in requested_mode but not in granted_mode? */
629         if ((requested_mode & ~granted_mode & 0007) &&
630             !capable(CAP_IPC_OWNER))
631                 return -1;
632 
633         return security_ipc_permission(ipcp, flag);
634 }

1.根据进程的身分是IPC的所有者/创建者、组内其它成员、或其它,来确定进程的授权granted_mode
2.计算请求访问IPC的权限requested_mode(读、写、执行);请求权限放在用户、用户组、或其它域中均可,所以做位或运算来计算requested_mode
3.检查请求的权限都有授权


你可能感兴趣的:(linux SysV IPC实现)