Linux进程间通信

Linux进程间通信,第1张

linux下进程间通信的几种主要手段简介:

一般文件的I/O函数都可以用于管道,如close、read、write等等。

实例1:用于shell

管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。比如,当在某个shell程序(Bourne shell或C shell等)键入who│wc -l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。

实例二:用于具有亲缘关系的进程间通信

管道的主要局限性正体现在它的特点上:

有名管道的创建

小结:

管道常用于两个方面:(1)在shell中时常会用到管道(作为输入输入的重定向),在这种应用方式下,管道的创建对于用户来说是透明的;(2)用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写 *** 作。

FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。

管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息

要灵活应用管道及FIFO,理解它们的读写规则是关键。

信号生命周期

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。

(1) 可靠信号与不可靠信号

不可靠信号 :Linux下的不可靠信号问题主要指的是信号可能丢失。

可靠信号 :信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。

对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

(2) 实时信号与非实时信号

前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

调用成功返回 0;否则,返回 -1。

sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。

sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号。sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号。

inux主要有两个函数实现信号的安装: signal() sigaction() 。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。

消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的

消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可;

消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。

信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号灯有以下两种类型:

int semop(int semid, struct sembuf *sops, unsigned nsops) semid是信号灯集ID,sops指向数组的每一个sembuf结构都刻画一个在特定信号灯上的 *** 作。

int semctl(int semid,int semnum,int cmd,union semun arg)

该系统调用实现对信号灯的各种控制 *** 作,参数semid指定信号灯集,参数cmd指定具体的 *** 作类型;参数semnum指定对哪个信号灯 *** 作,只对几个特殊的cmd *** 作有意义;arg用于设置或返回信号灯信息。

进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构注同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。

shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。shmat()把共享内存区域映射到调用进程的地址空间中去,这样,进程就可以方便地对共享区域进行访问 *** 作。shmdt()调用用来解除进程对共享内存区域的映射。shmctl实现对共享内存区域的控制 *** 作。这里我们不对这些系统调用作具体的介绍,读者可参考相应的手册页面,后面的范例中将给出它们的调用方法。

注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。

第一种:管道通信

两个进程利用管道进行通信时,发送信息的进程称为写进程接收信息的进程称为读进程。管道通信方式的中间介质就是文件,通常称这种文件为管道文件,它就像管道一样将一个写进程和一个读进程连接在一起,实现两个进程之间的通信。写进程通过写入端往管道文件中写入信息读进程通过读出端从管道文件中读取信息。两个进程协调不断地进行写和读,便会构成双方通过管道传递信息的流水线。

第二种:消息缓冲通信

多个独立的进程之间可以通过消息缓冲机制来相互通信。这种通信的实现是以消息缓冲区为中间介质,通信双方的发送和接收 *** 作均以消息为单位。在存储器中,消息缓冲区被组织成队列,通常称之为消息队列。消息队列一旦创建后即可由多进程共享,发送消息的进程可以在任意时刻发送任意个消息到指定的消息队列上,并检查是否有接收进程在等待它所发送的消息。若有则唤醒它,而接收消息的进程可以在需要消息的时候到指定的消息队列上获取消息,如果消息还没有到来,则转入睡眠等待状态。

第三种:共享内存通信

针对消息缓冲需要占用CPU进行消息复制的缺点,OS提供了一种进程间直接进行数据交换的通信方式。共享内存,顾名思义这种通信方式允许多个进程在外部通信协议或同步,互斥机制的支持下使用同一个内存段进行通信,它是一种最有效的数据通信方式,其特点是没有中间环节,直接将共享的内存页面通过附接映射到相互通信的进程各自的虚拟地址空间中,从而使多个进程可以直接访问同一个物理内存页面。

为了能够有效的控制多个进程之间的沟通过程,保证沟通过程的有序和和谐,OS必须提供一定的同步机制保证进程之间不会自说自话而是有效的协同工作。比如在 共享内存的通信方式中,两个或者多个进程都要对共享的内存进行数据写入,那么怎么才能保证一个进程在写入的过程中不被其它的进程打断,保证数据的完整性 呢?又怎么保证读取进程在读取数据的过程中数据不会变动,保证读取出的数据是完整有效的呢?

常用的同步方式有: 互斥锁、条件变量、读写锁、记录锁(文件锁)和信号灯.

互斥锁:

顾名思义,锁是用来锁住某种东西的,锁住之后只有有钥匙的人才能对锁住的东西拥有控制权(把锁砸了,把东西偷走的小偷不在我们的讨论范围了)。所谓互斥, 从字面上理解就是互相排斥。因此互斥锁从字面上理解就是一点进程拥有了这个锁,它将排斥其它所有的进程访问被锁住的东西,其它的进程如果需要锁就只能等待,等待拥有锁的进程把锁打开后才能继续运行。 在实现中,锁并不是与某个具体的变量进行关联,它本身是一个独立的对象。进(线)程在有需要的时候获得此对象,用完不需要时就释放掉。

互斥锁的主要特点是互斥锁的释放必须由上锁的进(线)程释放,如果拥有锁的进(线)程不释放,那么其它的进(线)程永远也没有机会获得所需要的互斥锁。

互斥锁主要用于线程之间的同步。

条件变量:

上文中提到,对于互斥锁而言,如果拥有锁的进(线)程不释放锁,其它进(线)程永远没机会获得锁,也就永远没有机会继续执行后续的逻辑。在实际环境下,一 个线程A需要改变一个共享变量X的值,为了保证在修改的过程中X不会被其它的线程修改,线程A必须首先获得对X的锁。现在假如A已经获得锁了,由于业务逻 辑的需要,只有当X的值小于0时,线程A才能执行后续的逻辑,于是线程A必须把互斥锁释放掉,然后继续“忙等”。如下面的伪代码所示:

1.// get x lock

2.while(x


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/tougao/6075576.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-13
下一篇 2023-03-13

发表评论

登录后才能评论

评论列表(0条)

保存