【进程间通信】报文传递

信号所能传送的信息量很小;而命名管道所载送的信息时无格式的,无通信格式信息,无优先级别,管道机制的缓冲区有限,还要占用打开文件号;


(1)Linux内核为系统V IPC扩展了一个统一的系统调用ipc();对于MSG开头的是为报文传递而设置的,SHM是为共享内存区而设置的,C语言函数库中semget(),msgget()等库函数把用户程序对它们的调用ipc(),使用户感到内核好像提供了这么一些不同的系统调用一样;

(2)在sys_ipc()中,根据调用参数操作码的不同,进行不同的操作;每一个进程可以通过库函数msget()建立报文队列,报文队列是通过key加以标识的,而不是文件名;其他进程可以通过msget()用相同的key来取得已建立报文队列的访问途径;发送进程可以通过msgrcv()从指定的队列接收报文;还可通过msgctl调用对指定的报文队列进行一些控制;由于报文队列不属于文件系统的范畴,并不占用打开文件号;

(3)在报文队列的建立和取得sys_msgget中;通过一个key可以创建一个报文队列,也可以根据一个键值找到已建立的报文队列;当msgflg为1表示创建,而msgflg为0时表示寻找;使用msg_ids专门用来管理报文队列;当key为0,也就是IPC_PRIVATE,则每个进程可以使用key值0建立一个专供其私用(自己接收和自己发送)的报文队列,使用newque()来建立,每一个报文队列都有一个队列头,即msg_queue,它唯一地对应着一个报文队列;

(3.1)全局量ipc_ids的msg_ids是所有报文队列的总根,msg_ids中的指针entries指向一个ipc_ids结构数组,数组中每一个元素都是ipc_id数据结构,该结构中有个指针p指向kern_ipc_perm;而kern_ipc_perm是报文队列msg_queue数据结构的第一个成分,因此p是指向了一个报文队列;数组的大小决定了已经或可以建立的报文队列数量;

(3.2)报文队列号是全局的,具有全局唯一性;使用ipc_addid()来分配一个标识号,分配成功,将报文队列的报文队列头和ipc_ids挂上钩,其实也就是填入地址;size字段可以使用grow_ary来完成的;最后在newque()返回的是一个使用ipc_buidid包装的一体化的序列号;

(4)如果键值不是IPC_PRIVATE,那就先寻找已给定键值建立的报文队列是否存在;找不到就创建,找到了是独创性的IPC_EXCL,这是失败;最后找到了,还要使用ipc_perms()检查访问权限(只有与创建者属于同一用户或者超级用户的进程才有资格来使用或共享),最后还要通过ipc_buidid返回一体化的序列号;

(5)在报文发送msgsnd()中,参与通信的双方在通过msgget()创建一个报文队列或取得了该队列的标识号后,就可以向该队列发送和接收报文了;首先是参数检查,将报文从用户空间复制过来,其中msgp是指向一个msgbuf结构的指针当报文本身的大小加上msg_msg的大小任何一个页面时,msg_msg结构就与报文在同一页面中,而本文紧跟在msg_msg数据结构之后;否则就要将它们处于不同页面的报文段,每一个报文段的开头就是msg_msgseg指针,作为报文段的分段连接;使用load来拷贝用户空间的报文;

(6)在msgsnd()中,使用msg_lock()将给定的标识号找到相应的报文队列将其上锁,要使用msg_checkid()检查是否上锁成功;还要使用ipcperms来检查权限;若当前报文的大小加上队列中当前总计的字节数msq->q_qbytes超出了容量,则暂时不能往队列里送;不能超过msq->q_qnum的个数;若系统调用参数IPC_NOWAIT是否为1,是的话就出错返回了,否则就睡眠了;此外,还要使用ss_add将该进程(mss->list)放入到报文队列中q_senders中;msq中还有一条q_receivers链,当有进程在睡眠中等待接收报文时,就挂入该队列中;但是q_senders和q_receivers两个只能有一个;当有进程从报文队列中读取报文而腾出一些空间时,等待发送的进程也就返回了,此时还要检查,此时又得将报文队列锁住,对于有信号,就要优先来处理,并返回出错码EINTR,重新安排一次系统调用,否则就goto retry处重新检查一遍;此时做一些优化,当进程中没有接受进程时,才会将报文入msq的q_massages队列中;有接收进程,是通过pipelined_send来确定的,处理msq的q_receivers队列,逐个检查等待中的进程里想要接收的报文种类与模式是否与到来的报文相符;相符,则进一步检查其缓冲区是否够用,不够用就将该进程唤醒令其出错返回,此过程知道碰到第一个所有条件都相符的接收进程,然后把报文交给他,并将其唤醒;

(7)在报文接收msgrecv()中,先使用convert_mode根据参数msgtyp和msgflg归纳出接收报文时所遵循的准则;首先使用ipcperms()来检查当前进程对队列的访问权限;然后就是检查已经在队列中的报文了;用msg指向对应的list_entry中报文指针,并不是将他们删除,然后使用testmsg()来进行测试,如果此时报文的类型值msg->type小于msgtyp;但是此时找到的未必是最佳选择,此时减小单签的msgtype的大小;最后会只有一个报文符合接收准则和类型就是found_msg;此时不一定就能接收这个报文,还要看用户程序所提供的缓冲区空间是否足够大,不大且不允许截断则只好返回出错代码,反之可以接收该报文,将其移除队列,并调整msq的相关变量,如q_num--,q_rtime;还要ss_wakeup唤醒msq中的等待发送的进程;最后要将实际接收的报文类型通过put_user送回用户空间,然后还要将系统中的报文缓冲区释放;如果此时没有报文接收,若参数是IPC_NOWAIT,那就立即返回了,出错代码为ENOMSG;否则,就要睡眠等待了;睡之前要将队列解锁;唤醒有两种情况,一种是信号打断,也就是-EAGAIN,使用signal_pending查看是否有信号在处理,有的话结束本次系统调用,设-EINTR,提前返回;否则也就是发送中pipelined_send,来唤醒的,又有两种情况,一个是有数据也可处理,那就去处理吧;还有就是有数据但是因为缓冲区太小,则不能处理,返回出错码-E2BIG;

(8)在报文机制的控制与设置msgctl中,命名管道和无名管道缺乏对管道的控制手段,也缺乏获取其状态信息,而MSGCTL就为报文队列提供了这样的手段;在V IPC中,IPC_RMID用来撤销一个标识号,对报文队列而言也就是撤销一个报文队列;IPC_SET是用来改变相应的IPC设施的各种状态和属性;IPC_STAT和IPC_INFO则分别用来获取关于相关设施的状态或统计信息;IPC_SET和IPC_STAT,buf指向一个msgid_ds指针;而IPC_INFO,buf指向一个msginfo;如在sys_msgctl中cmd为IPC_SET时,一是将所有邓艾此队列接收报文的进程全部出错返回,出错码为EAGAIN,而是ss_wakeup()将所有的等待向此队列发送报文的进程全部唤醒,让他们执行重新尝试;有如IPC_RMID,使用freeque()将所有正在等待接收报文的进程从接收队列中移除队列并唤醒,出错返回,出错码为EIDRM,ss_wakeup()将所有的等待向此队列发送报文的进程全部唤醒,也出错返回;然后将报文队列中所有报文移除队列释放空间,最后连报文头也要予以释放;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值