当前位置:首页 » 操作系统 » linux通信方式

linux通信方式

发布时间: 2023-07-14 15:09:55

linux下如何用tcp,c/s模式实现两台电脑之间通信

1.
建议lz使用socket套接字。这个方式可以很好的实现client/server模式,tcp和udp协议都可以选择。使用socket来实现两台电脑的进程间通信,要先理解一些函数,如socket,binder,listen,connect,recv,send等等。。。
2.
lz可以上网搜索关键字“linux
socket编程”,或追问我。

Ⅱ Linux 进程间通信方式有哪些

进程间通信(IPC,Interprocess
communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。
1、无名管道通信
无名管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是指父子进程关系。
2、高级管道通信
高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们称为高级管道方式。
3、有名管道通信
有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4、消息队列通信
消息队列(message
queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识,消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5、信号量通信
信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6、信号
信号(sinal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
7、共享内存通信
共享内存(shared
memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
8、套接字通信
套接字(socket):套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

Ⅲ LINUX设备驱动程序如何与硬件通信

LINUX设备驱动程序是怎么样和硬件通信的?下面将由我带大家来解答这个疑问吧,希望对大家有所收获!

LINUX设备驱动程序与硬件设备之间的通信

设备驱动程序是软件概念和硬件电路之间的一个抽象层,因此两方面都要讨论。到目前为止,我们已经讨论详细讨论了软件概念上的一些细节,现在讨论另一方面,介绍驱动程序在Linux上如何在保持可移植性的前提下访问I/O端口和I/O内存。

我们在需要示例的场合会使用简单的数字I/O端口来讲解I/O指令,并使用普通的帧缓冲区显存来讲解内存映射I/O。

I/O端口和I/O内存

计算机对每种外设都是通过读写它的寄存器进行控制的。大部分外设都有几个寄存器,不管是在内存地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的。

I/O端口就是I/O端口,设备会把寄存器映射到I/O端口,不管处理器是否具有独立的I/O端口地址空间。即使没有在访问外设时也要模拟成读写I/O端口。

I/O内存是设备把寄存器映射到某个内存地址区段(如PCI设备)。这种I/O内存通常是首先方案,它不需要特殊的处理器指令,而且CPU核心访问内存更有效率。

I/O寄存器和常规内存

尽管硬件寄存器和内存非常相似,但程序员在访问I/O寄存器的时候必须注意避免由于CPU或编译器不恰当的优化而改变预期的I/O动作。

I/O寄存器和RAM最主要的区别就是I/O操作具有边际效应,而内存操作则没有:由于内存没有边际效应,所以可以用多种 方法 进行优化,如使用高速缓存保存数值、重新排序读/写指令等。

编译器能够将数值缓存在CPU寄存器中而不写入内存,即使储存数据,读写操作也都能在高速缓存中进行而不用访问物理RAM。无论是在编译器一级或是硬件一级,指令的重新排序都有可能发生:一个指令序列如果以不同于程序文本中的次序运行常常能执行得更快。

在对常规内存进行这些优化的时候,优化过程是透明的,而且效果良好,但是对I/O操作来说这些优化很可能造成致命的错误,这是因为受到边际效应的干扰,而这却是驱动程序访问I/O寄存器的主要目的。处理器无法预料某些 其它 进程(在另一个处理器上运行,或在在某个I/O控制器中发生的操作)是否会依赖于内存访问的顺序。编译器或CPU可能会自作聪明地重新排序所要求的操作,结果会发生奇怪的错误,并且很难调度。因此,驱动程序必须确保不使用高速缓冲,并且在访问寄存器时不发生读或写指令的重新排序。

由硬件自身引起的问题很解决:只要把底层硬件配置成(可以是自动的或是由Linux初始化代码完成)在访问I/O区域(不管是内存还是端口)时禁止硬件缓存即可。

由编译器优化和硬件重新排序引起的问题的解决办法是:对硬件(或其他处理器)必须以特定顺序的操作之间设置内存屏障(memory barrier)。Linux提供了4个宏来解决所有可能的排序问题:

#include <linux/kernel.h>

void barrier(void)

这个函数通知编译器插入一个内存屏障,但对硬件没有影响。编译后的代码会把当前CPU寄存器中的所有修改过的数值保存到内存中,需要这些数据的时候再重新读出来。对barrier的调用可避免在屏障前后的编译器优化,但硬件完成自己的重新排序。

#include <asm/system.h>

void rmb(void);

void read_barrier_depends(void);

void wmb(void);

void mb(void);

这些函数在已编译的指令流中插入硬件内存屏障;具体实现方法是平台相关的。rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作之前完成。wmb保证写操作不会乱序,mb指令保证了两者都不会。这些函数都是barrier的超集。

void smp_rmb(void);

void smp_read_barrier_depends(void);

void smp_wmb(void);

void smp_mb(void);

上述屏障宏版本也插入硬件屏障,但仅仅在内核针对SMP系统编译时有效;在单处理器系统上,它们均会被扩展为上面那些简单的屏障调用。

设备驱动程序中使用内存屏障的典型形式如下:

writel(dev->registers.addr, io_destination_address);

writel(dev->registers.size, io_size);

writel(dev->registers.operation, DEV_READ);

wmb();

writel(dev->registers.control, DEV_GO);

在这个例子中,最重要的是要确保控制某种特定操作的所有设备寄存器一定要在操作开始之前已被正确设置。其中的内存屏障会强制写操作以要求的顺序完成。

因为内存屏障会影响系统性能,所以应该只用于真正需要的地方。不同类型的内存屏障对性能的影响也不尽相同,所以最好尽可能使用最符合需要的特定类型。

值得注意的是,大多数处理同步的内核原语,如自旋锁和atomic_t操作,也能作为内存屏障使用。同时还需要注意,某些外设总线(比如PCI总线)存在自身的高速缓存问题,我们将在后面的章节中讨论相关问题。

在某些体系架构上,允许把赋值语句和内存屏障进行合并以提高效率。内核提供了几个执行这种合并的宏,在默认情况下,这些宏的定义如下:

#define set_mb(var, value) do {var = value; mb();} while 0

#define set_wmb(var, value) do {var = value; wmb();} while 0

#define set_rmb(var, value) do {var = value; rmb();} while 0

在适当的地方,<asm/system.h>中定义的这些宏可以利用体系架构特有的指令更快的完成任务。注意只有小部分体系架构定义了set_rmb宏。

使用I/O端口

I/O端口是驱动程序与许多设备之间的通信方式——至少在部分时间是这样。本节讲解了使用I/O端口的不同函数,另外也涉及到一些可移植性问题。

I/O端口分配

下面我们提供了一个注册的接口,它允允许驱动程序声明自己需要操作的端口:

#include <linux/ioport.h>

struct resource *request_region(unsigned long first, unsigned long n, const char *name);

它告诉内核,我们要使用起始于first的n个端口。name是设备的名称。如果分配成功返回非NULL,如果失败返回NULL。

所有分配的端口可从/proc/ioports中找到。如果我们无法分配到我们要的端口集合,则可以查看这个文件哪个驱动程序已经分配了这些端口。

如果不再使用这些端口,则用下面函数返回这些端口给系统:

void release_region(unsigned long start, unsigned long n);

下面函数允许驱动程序检查给定的I/O端口是否可用:

int check_region(unsigned long first, unsigned long n);//不可用返回负的错误代码

我们不赞成用这个函数,因为它返回成功并不能确保分配能够成功,因为检查和其后的分配并不是原子操作。我们应该始终使用request_region,因为这个函数执行了必要的锁定,以确保分配过程以安全原子的方式完成。

操作I/O端口

当驱动程序请求了需要使用的I/O端口范围后,必须读取和/或写入这些端口。为此,大多数硬件都会把8位、16位、32位区分开来。它们不能像访问系统内存那样混淆使用。

因此,C语言程序必须调用不同的函数访问大小不同的端口。那些只支持映射的I/O寄存器的计算机体系架构通过把I/O端口地址重新映射到内存地址来伪装端口I/O,并且为了易于移植,内核对驱动程序隐藏了这些细节。Linux内核头文件中(在与体系架构相关的头文件<asm/io.h>中)定义了如下一些访问I/O端口的内联函数:

unsigned inb(unsigned port);

void outb(unsigned char byte, unsigned port);

字节读写端口。

unsigned inw(unsigned port);

void outw(unsigned short word, unsigned port);

访问16位端口

unsigned inl(unsigned port);

void outl(unsigned longword, unsigned port);

访问32位端口

在用户空间访问I/O端口

上面这些函数主要是提供给设备驱动程序使用的,但它们也可以用户空间使用,至少在PC类计算机上可以使用。GNU的C库在<sys/io.h>中定义了这些函数。如果要要用户空间使用inb及相关函数,则必须满足正下面这些条件:

编译程序时必须带有-O选项来强制内联函数的展开。

必须用ioperm(获取单个端口的权限)或iopl(获取整个I/O空间)系统调用来获取对端口进行I/O操作的权限。这两个函数都是x86平台特有的。

必须以root身份运行该程序才能调用ioperm或iopl。或者进程的祖先进程之一已经以root身份获取对端口的访问。

如果宿主平台没有以上两个系统调用,则用户空间程序仍然可以使用/dev/port设备文件访问I/O端口。不过要注意,该设备文件的含义与平台密切相关,并且除PC平台以处,它几乎没有什么用处。

串操作

以上的I/O操作都是一次传输一个数据,作为补充,有些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字、双字。这些指令称为串操作指令,它们执行这些任务时比一个C语言编写的循环语句快得多。下面列出的宏实现了串I/O:

void insb(unsigned port, void *addr, unsigned long count);

void outsb(unsigned port, void *addr, unsigned long count);从内存addr开始连续读/写count数目的字节。只对单一端口port读取或写入数据

void insw(unsigned port, void *addr, unsigned long count);

void outsw(unsigned port, void *addr, unsigned long count);对一个16位端口读写16位数据

void insl(unsigned port, void *addr, unsigned long count);

void outsl(unsigned port, void *addr, unsigned long count);对一个32位端口读写32位数据

在使用串I/O操作函数时,需要铭记的是:它们直接将字节流从端口中读取或写入。因此,当端口和主机系统具有不同的字节序时,将导致不可预期的结果。使用inw读取端口将在必要时交换字节,以便确保读入的值匹配于主机的字节序。然而,串函数不会完成这种交换。

暂停式I/O

在处理器试图从总线上快速传输数据时,某些平台(特别是i386)就会出现问题。当处理器时钟比外设时钟(如ISA)快时就会出现问题,并且在设备板上特别慢时表现出来。为了防止出现丢失数据的情况,可以使用暂停式的I/O函数来取代通常的I/O函数,这些暂停式的I/O函数很像前面介绍的那些I/O函数,不同之处是它们的名字用_p结尾,如inb_p、outb_p等等。在linux支持的大多数平台上都定义了这些函数,不过它们常常扩展为非暂停式I/O同样的代码,因为如果不使用过时的外设总线就不需要额外的暂停。

平台相关性

I/O指令是与处理器密切相关的。因为它们的工作涉及到处理器移入移出数据的细节,所以隐藏平台间的差异非常困难。因此,大部分与I/O端口相关的源代码都与平台相关。

回顾前面函数列表可以看到有一处不兼容的地方,即数据类型。函数的参数根据各平台体系架构上的不同要相应地使用不同的数据类型。例如,port参数在x86平台上(处理器只支持64KB的I/O空间)上定义为unsigned short,但在其他平台上定义为unsigned long,在这些平台上,端口是与内存在同一地址空间内的一些特定区域。

感兴趣的读者可以从io.h文件获得更多信息,除了本章介绍的函数,一些与体系架构相关的函数有时也由该文件定义。

值得注意的是,x86家族之外的处理器都不为端口提供独立的地址空间。

I/O操作在各个平台上执行的细节在对应平台的编程手册中有详细的叙述;也可以从web上下载这些手册的PDF文件。

I/O端口示例

演示设备驱动程序的端口I/O的示例代码运行于通用的数字I/O端口上,这种端口在大多数计算机平台上都能找到。

数字I/O端口最常见的一种形式是一个字节宽度的I/O区域,它或者映射到内存,或者映射到端口。当把数字写入到输出区域时,输出引脚上的电平信号随着写入的各位而发生相应变化。从输入区域读取到的数据则是输入引脚各位当前的逻辑电平值。

这类I/O端口的具体实现和软件接口是因系统而异的。大多数情况下,I/O引脚由两个I/O区域控制的:一个区域中可以选择用于输入和输出的引脚,另一个区域中可以读写实际的逻辑电平。不过有时情况简单些,每个位不是输入就是输出(不过这种情况下就不能称为“通用I/O"了);在所有个人计算机上都能找到的并口就是这样的非通用的I/O端口。

并口简介

并口的最小配置由3个8位端口组成。第一个端口是一个双向的数据寄存器,它直接连接到物理连接器的2~9号引脚上。第二个端口是一个只读的状态寄存器;当并口连接打印机时,该寄存器 报告 打印机状态,如是否是线、缺纸、正忙等等。第三个端口是一个只用于输出的控制寄存器,它的作用之一是控制是否启用中断。

如下所示:并口的引脚

示例驱动程序

while(count--) {

outb(*(ptr++), port);

wmb();

}

使用I/O内存

除了x86上普遍使的I/O端口之外,和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存,这两种都称为I/O内存,因为寄存器和内存的差别对软件是透明的。

I/O内存仅仅是类似RAM的一个区域,在那里处理器可以通过总线访问设备。这种内存有很多用途,比如存放视频数据或以太网数据包,也可以用来实现类似I/O端口的设备寄存器(也就是说,对它们的读写也存在边际效应)。

根据计算机平台和所使用总线的不同,i/o内存可能是,也可能不是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读取它们。

不管访问I/O内存是否需要调用ioremap,都不鼓励直接使用指向I/O内存的指针。相反使用包装函数访问I/O内存,这一方面在所有平台上都是安全的,另一方面,在可以直接对指针指向的内存区域执行操作的时候,这些函数是经过优化的。并且直接使用指针会影响程序的可移植性。

I/O内存分配和映射

在使用之前,必须首先分配I/O区域。分配内存区域的接口如下(在<linux/ioport.h>中定义):

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

该函数从start开始分配len字节长的内存区域。如果成功返回非NULL,否则返回NULL值。所有的I/O内存分配情况可从/proc/iomem得到。

不再使用已分配的内存区域时,使用如下接口释放:

void release_mem_region(unsigned long start, unsigned long len);

下面函数用来检查给定的I/O内存区域是否可用的老函数:

int check_mem_region(unsigned long start, unsigned long len);//这个函数和check_region一样不安全,应避免使用

分配内存之后我们还必须确保该I/O内存对内存而言是可访问的。获取I/O内存并不意味着可引用对应的指针;在许多系统上,I/O内存根本不能通过这种方式直接访问。因此,我们必须由ioremap函数建立映射,ioremap专用于为I/O内存区域分配虚拟地址。

我们根据以下定义来调用ioremap函数:

#include <asm/io.h>

void *ioremap(unsigned long phys_addr, unsigned long size);

void *ioremap_nocache(unsigned long phys_addr, unsigned long size);在大多数计算机平台上,该函数和ioremap相同:当所有I/O内存已属于非缓存地址时,就没有必要实现ioremap的独立的,非缓冲版本。

void iounmap(void *addr);

记住,由ioremap返回的地址不应该直接引用,而应该使用内核提供的accessor函数。

访问I/O内存

在某些平台上我们可以将ioremap的返回值直接当作指针使用。但是,这种使用不具有可移植性,访问I/O内存的正确方法是通过一组专用于些目的的函数(在<asm/io.h>中定义)。

从I/O内存中读取,可使用以下函数之一:

unsigned int ioread8(void *addr);

unsigned int ioread16(void *addr);

unsigned int ioread32(void *addr);

其中,addr是从ioremap获得的地址(可能包含一个整数偏移量);返回值是从给定I/O内存读取到的值。

写入I/O内存的函数如下:

void iowrite8(u8 value, void *addr);

void iowrite16(u16 value, void *addr);

void iowrite32(u32 value, void *addr);

如果必须在给定的I/O内存地址处读/写一系列值,则可使用上述函数的重复版本:

void ioread8_rep(void *addr, void *buf, unsigned long count);

void ioread16_rep(void *addr, void *buf, unsigned long count);

void ioread32_rep(void *addr, void *buf, unsigned long count);

void iowrite8_rep(void *addr, const void *buf, unsigned long count);

void iowrite16_rep(void *addr, const void *buf, unsigned long count);

void iowrite32_rep(void *addr, const void *buf, unsigned long count);

上述函数从给定的buf向给定的addr读取或写入count个值。count以被写入数据的大小为单位。

上面函数均在给定的addr处执行所有的I/O操作,如果我们要在一块I/O内存上执行操作,则可以使用下面的函数:

void memset_io(void *addr, u8 value, unsigned int count);

void memcpy_fromio(void *dest, void *source, unsigned int count);

void memcpy_toio(void *dest, void *source, unsigned int count);

上述函数和C函数库的对应函数功能一致。

像I/O内存一样使用I/O端口

某些硬件具有一种有趣的特性:某些版本使用I/O端口,而其他版本则使用I/O内存。导出给处理器的寄存器在两种情况下都是一样的,但访问方法却不同。为了让处理这类硬件的驱动程序更加易于编写,也为了最小化I/O端口和I/O内存访问这间的表面区别,2.6内核引入了ioport_map函数:

void *ioport_map(unsigned long port, unsigned int count);

该函数重新映射count个I/O端口,使其看起来像I/O内存。此后,驱动程序可在该函数返回的地址上使用ioread8及其相关函数,这样就不必理会I/O端口和I/O内存之间的区别了。

当不需要这种映射时使用下面函数一撤消:

void ioport_unmap(void *addr);

这些函数使得I/O端口看起来像内存。但需要注意的是,在重新映射之前,我们必须通过request_region来分配这些I/O端口。

为I/O内存重用short

前面介绍的short示例模块访问的是I/O端口,它也可以访问I/O内存。为此必须在加载时通知它使用I/O内存,另外还要修改base地址以使其指向I/O区域。

下例是在MIPS开发板上点亮调试用的LED:

mips.root# ./short_load use_mem=1 base = 0xb7ffffc0

mips.root# echo -n 7 > /dev/short0

下面代码是short写入内存区域时使用的循环:

while(count--) {

iowrite8(*ptr++, address);

wmb();

}

1MB地址空间之下的ISA内存

最广为人知的I/O内存区之一就是个人计算机上的ISA内存段。它的内存范围在64KB(0xA0000)到1MB(0x100000)之间,因此它正好出现在常规系统RAM的中间。这种地址看上去有点奇怪,因为这个设计决策是20世纪80年代早期作出的,在当时看来没有人会用到640KB以上的内存。

Ⅳ Linux进程间通信

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在把共享内存区域映射到进程空间时,并不真正改变进程的页表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。

Ⅳ Linux进程间通信(互斥锁、条件变量、读写锁、文件锁、信号灯)

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

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

互斥锁:

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

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

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

条件变量:

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

1.// get x lock

2.while(x

Ⅵ linux进程间通讯的几种方式

进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。
1、无名管道通信
无名管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是指父子进程关系。
2、高级管道通信
高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们称为高级管道方式。
3、有名管道通信
有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4、消息队列通信
消息队列(message
queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识,消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5、信号量通信
信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6、信号
信号(sinal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
7、共享内存通信
共享内存(shared
memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
8、套接字通信
套接字(socket):套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

Ⅶ 通信的方式有多种,假设需要在Linux系

进程间的通信方式:
1.管道(pipe)及有名管道(named pipe):
管道可用于具有亲缘关系进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
2.信号(signal):
信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致得。

3.消息队列(message queue):
消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
消息缓冲通信技术是由Hansen首先提出的,其基本思想是:根据”生产者-消费者”原理,利用内存中公用消息缓冲区实现进程之间的信息交换.

内存中开辟了若干消息缓冲区,用以存放消息.每当一个进程向另一个进程发送消息时,便申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知接收进程.接收进程收到发送里程发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需的信息,然后把消息缓冲区不定期给系统.系统负责管理公用消息缓冲区以及消息的传递.

一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息.显然,进程中关于消息队列的操作是临界区.当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中到出消息:反之也一样.

消息缓冲区通信机制包含以下列内容:

(1) 消息缓冲区,这是一个由以下几项组成的数据结构:
1、 消息长度
2、 消息正文
3、 发送者
4、 消息队列指针

(2)消息队列首指针m-q,一般保存在PCB中。
(1) 互斥信号量m-mutex,初值为1,用于互斥访问消息队列,在PCB中设置。
(2) 同步信号量m-syn,初值为0,用于消息计数,在PCB中设置。
(3) 发送消息原语send
(4) 接收消息原语receive(a)

4.共享内存(shared memory):
可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
这种通信模式需要解决两个问题:第一个问题是怎样提供共享内存;第二个是公共内存的互斥关系则是程序开发人员的责任。
5.信号量(semaphore):
主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

6.套接字(socket);
这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

http://blog.csdn.net/eroswang/archive/2007/09/04/1772350.aspx
linux下的进程间通信-详解
详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐着 名作者Richard Stevens的着名作品:《Advanced Programming in the UNIX Environment》,它的中文译本《UNIX环境高级编程》已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓 厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最 最简单的一些知识和概念。
首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。

2.3.1 管道
管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。
无名管道由pipe()函数创建:
#include <unistd.h>
int pipe(int filedis[2]);
参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。

#define INPUT 0
#define OUTPUT 1

void main() {
int file_descriptors[2];
/*定义子进程号 */
pid_t pid;
char buf[256];
int returned_count;
/*创建无名管道*/
pipe(file_descriptors);
/*创建子进程*/
if((pid = fork()) == -1) {
printf("Error in fork\n");
exit(1);
}
/*执行子进程*/
if(pid == 0) {
printf("in the spawned (child) process...\n");
/*子进程向父进程写数据,关闭管道的读端*/
close(file_descriptors[INPUT]);
write(file_descriptors[OUTPUT], "test data", strlen("test data"));
exit(0);
} else {
/*执行父进程*/
printf("in the spawning (parent) process...\n");
/*父进程从管道读取子进程写的数据,关闭管道的写端*/
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
printf("%d bytes of data received from spawned process: %s\n",
returned_count, buf);
}
}
在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:
方式一:mkfifo("myfifo","rw");
方式二:mknod myfifo p
生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建了一个名为myfifo的有名管道。
/* 进程一:读有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * in_file;
int count = 1;
char buf[80];
in_file = fopen("mypipe", "r");
if (in_file == NULL) {
printf("Error in fdopen.\n");
exit(1);
}
while ((count = fread(buf, 1, 80, in_file)) > 0)
printf("received from pipe: %s\n", buf);
fclose(in_file);
}
/* 进程二:写有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * out_file;
int count = 1;
char buf[80];
out_file = fopen("mypipe", "w");
if (out_file == NULL) {
printf("Error opening pipe.");
exit(1);
}
sprintf(buf,"this is test data for the named pipe example\n");
fwrite(buf, 1, 80, out_file);
fclose(out_file);
}

2.3.2 消息队列
消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。
事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。

2.3.3 共享内存
共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是 实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利 用共享内存进行存储的。
首先要用的函数是shmget,它获得一个共享存储标识符。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, int size, int flag);
这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数 的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的 key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。

当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。
void *shmat(int shmid, void *addr, int flag);
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。
使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。

2.3.4 信号量
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
(1) 测试控制该资源的信号量。
(2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。
(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获 得一个信号量ID。

struct sem {
short sempid;/* pid of last operaton */
ushort semval;/* current value */
ushort semncnt;/* num procs awaiting increase in semval */
ushort semzcnt;/* num procs awaiting semval = 0 */
}

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);

key是前面讲过的IPC结构的关键字,flag将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新 集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。

semctl函数用来对信号量进行操作。
int semctl(int semid, int semnum, int cmd, union semun arg);
不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。

semop函数自动执行信号量集合上的操作数组。
int semop(int semid, struct sembuf semoparray[], size_t nops);
semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。

下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后我们清除信号量。在下面的代码中,函数ftok生成我们上文所说的唯一的IPC关键字。

#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
void main() {
key_t unique_key; /* 定义一个IPC关键字*/
int id;
struct sembuf lock_it;
union semun options;
int i;

unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
/* 创建一个新的信号量集合*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
printf("semaphore id=%d\n", id);
options.val = 1; /*设置变量值*/
semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/

/*打印出信号量的值*/
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d\n", i);

/*下面重新设置信号量*/
lock_it.sem_num = 0; /*设置哪个信号量*/
lock_it.sem_op = -1; /*定义操作*/
lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
if (semop(id, &lock_it, 1) == -1) {
printf("can not lock semaphore.\n");
exit(1);
}

i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d\n", i);

/*清除信号量*/
semctl(id, 0, IPC_RMID, 0);
}

semget()

可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:
系统调用:semget();
原型:intsemget(key_t key,int nsems,int semflg);
返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
EEXIST(信号量集已经存在,无法创建)
EIDRM(信号量集已经删除)
ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
ENOMEM(没有足够的内存创建新的信号量集)
ENOSPC(超出限制)
系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:
#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
下面是一个打开和创建信号量集的程序:
intopen_semaphore_set(key_t keyval,int numsems)
{
intsid;
if(!numsems)
return(-1);
if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(sid);
}
};
==============================================================
semop()

系统调用:semop();
调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)
EACCESS(权限不够)
EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)
EFAULT(sops指向的地址无效)
EIDRM(信号量集已经删除)
EINTR(当睡眠时接收到其他信号)
EINVAL(信号量集不存在,或者semid无效)
ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
ERANGE(信号量值超出范围)
第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的:
/*semop systemcall takes an array of these*/
structsembuf{
ushortsem_num;/*semaphore index in array*/
shortsem_op;/*semaphore operation*/
shortsem_flg;/*operation flags*/
sem_num将要处理的信号量的个数。
sem_op要执行的操作。
sem_flg操作标志。
如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。如果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。
===============================================================
semctl()

系统调用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:如果成功,则为一个正数。
如果失败,则为-1:errno=EACCESS(权限不够)
EFAULT(arg指向的地址无效)
EIDRM(信号量集已经删除)
EINVAL(信号量集不存在,或者semid无效)
EPERM(EUID没有cmd的权利)
ERANGE(信号量值超出范围)
系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命令,这样就要求有一个更为复杂的数据结构。
系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。
参数cmd中可以使用的命令如下:
·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf

Ⅷ linux系统的进程间通信有哪几种方式

  • 数据传输
    一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间

  • 共享数据
    多个进程想要操作共享数据,一个进程对共享数据

  • 通知事
    一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 资源共享
    多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

  • 进程控制
    有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

  • Linux 进程间通信(IPC)的发展

    linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。

    前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;

    后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。

    Linux则把两者继承了下来

  • 早期UNIX进程间通信

  • 基于System V进程间通信

  • 基于Socket进程间通信

  • POSIX进程间通信。

  • UNIX进程间通信方式包括:管道、FIFO、信号。

    System V进程间通信方式包括:System V消息队列、System V信号灯、System V共享内存

    POSIX进程间通信包括:posix消息队列、posix信号灯、posix共享内存。

    由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准;

    BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信)。事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等。

    linux使用的进程间通信方式

  • 管道(pipe),流管道(s_pipe)和有名管道(FIFO)

  • 信号(signal)

  • 消息队列

  • 共享内存

  • 信号量

  • 套接字(socket)

  • 管道( pipe )

    管道这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

    流管道s_pipe: 去除了第一种限制,可以双向传输.

    管道可用于具有亲缘关系进程间的通信,命名管道:name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

    信号量( semophore )

    信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

    信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

    消息队列( message queue )

    消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

    消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

    信号 ( singal )

    信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

    主要作为进程间以及同一进程不同线程之间的同步手段。

    共享内存( shared memory )

    共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

    使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

    套接字( socket )

    套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

    更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

    进程间通信各种方式效率比较

    类型

    无连接

    可靠

    流控制

    记录消息类型

    优先级

    普通PIPE N Y Y N

    流PIPE N Y Y N

    命名PIPE(FIFO) N Y Y N

    消息队列 N Y Y Y

    信号量 N Y Y Y

    共享存储 N Y Y Y

    UNIX流SOCKET N Y Y N

    UNIX数据包SOCKET Y Y N N

    注:无连接: 指无需调用某种形式的OPEN,就有发送消息的能力流控制:

    如果系统资源短缺或者不能接收更多消息,则发送进程能进行流量控制

    各种通信方式的比较和优缺点

  • 管道:速度慢,容量有限,只有父子进程能通讯

  • FIFO:任何进程间都能通讯,但速度慢

  • 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

  • 信号量:不能传递复杂消息,只能用来同步

  • 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

  • 如果用户传递的信息较少或是需要通过信号来触发某些行为.前文提到的软中断信号机制不失为一种简捷有效的进程间通信方式.

    但若是进程间要求传递的信息量比较大或者进程间存在交换数据的要求,那就需要考虑别的通信方式了。

    无名管道简单方便.但局限于单向通信的工作方式.并且只能在创建它的进程及其子孙进程之间实现管道的共享:

    有名管道虽然可以提供给任意关系的进程使用.但是由于其长期存在于系统之中,使用不当容易出错.所以普通用户一般不建议使用。

    消息缓冲可以不再局限于父子进程,而允许任意进程通过共享消息队列来实现进程间通信,并由系统调用函数来实现消息发送和接收之间的同步,从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题,使用方便,但是信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合。

    共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点。

    但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,因此,这些进程之间的读写操作的同步问题操作系统无法实现。必须由各进程利用其他同步工具解决。另外,由于内存实体存在于计算机系统中,所以只能由处于同一个计算机系统中的诸进程共享。不方便网络通信。

    共享内存块提供了在任意数量的进程之间进行高效双向通信的机制。每个使用者都可以读取写入数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆写内存空间等竞争状态的出现。

    不幸的是,Linux无法严格保证提供对共享内存块的独占访问,甚至是在您通过使用IPC_PRIVATE创建新的共享内存块的时候也不能保证访问的独占性。 同时,多个使用共享内存块的进程之间必须协调使用同一个键值。

Ⅸ Linux进程间通信的方式有哪些

第一种:管道通信
两个进程利用管道进行通信时,发送信息的进程称为写进程;接收信息的进程称为读进程。管道通信方式的中间介质就是文件,通常称这种文件为管道文件,它就像管道一样将一个写进程和一个读进程连接在一起,实现两个进程之间的通信。写进程通过写入端往管道文件中写入信息;读进程通过读出端从管道文件中读取信息。两个进程协调不断地进行写和读,便会构成双方通过管道传递信息的流水线。
第二种:消息缓冲通信
多个独立的进程之间可以通过消息缓冲机制来相互通信。这种通信的实现是以消息缓冲区为中间介质,通信双方的发送和接收操作均以消息为单位。在存储器中,消息缓冲区被组织成队列,通常称之为消息队列。消息队列一旦创建后即可由多进程共享,发送消息的进程可以在任意时刻发送任意个消息到指定的消息队列上,并检查是否有接收进程在等待它所发送的消息。若有则唤醒它,而接收消息的进程可以在需要消息的时候到指定的消息队列上获取消息,如果消息还没有到来,则转入睡眠等待状态。
第三种:共享内存通信
针对消息缓冲需要占用CPU进行消息复制的缺点,OS提供了一种进程间直接进行数据交换的通信方式。共享内存,顾名思义这种通信方式允许多个进程在外部通信协议或同步,互斥机制的支持下使用同一个内存段进行通信,它是一种最有效的数据通信方式,其特点是没有中间环节,直接将共享的内存页面通过附接映射到相互通信的进程各自的虚拟地址空间中,从而使多个进程可以直接访问同一个物理内存页面。

Ⅹ Linux进程间通信

进程间通信支持进程之间的通信,Linux支持进程间的多种通信机制,包含信号量、共享内存、消息
队列、管道、UNIX域套接字等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传
递。在实际的Linux应用中,人们更多地趋向于使用UNIX域套接字,而不是System V IPC中的消息队列等
机制。Android内核则新增了Binder进程间通信方式。
Linux内核5个组成部分之间的依赖关系如下。
·进程调度与内存管理之间的关系:这两个子系统互相依赖。在多程序环境下,程序要运行,则必须
为之创建进程,而创建进程的第一件事情,就是将程序和数据装入内存。
·进程间通信与内存管理的关系:进程间通信子系统要依赖内存管理支持共享内存通信机制,这种机
制允许两个进程除了拥有自己的私有空间之外,还可以存取共同的内存区域。
·虚拟文件系统与网络接口之间的关系:虚拟文件系统利用网络接口支持网络文件系统(NFS),也
利用内存管理支持RAMDISK设备。
·内存管理与虚拟文件系统之间的关系:内存管理利用虚拟文件系统支持交换,交换进程定期由调度
程序调度,这也是内存管理依赖于进程调度的原因。当一个进程存取的内存映射被换出时,内存管理向虚
拟文件系统发出请求,同时,挂起当前正在运行的进程。
除了这些依赖关系外,内核中的所有子系统还要依赖于一些共同的资源。这些资源包括所有子系统都
用到的API,如分配和释放内存空间的函数、输出警告或错误消息的函数及系统提供的调试接口等。

热点内容
ios迁移安卓用什么助手 发布:2025-03-16 20:12:42 浏览:720
python异常值处理 发布:2025-03-16 20:12:42 浏览:581
POtn编程 发布:2025-03-16 20:06:11 浏览:776
竞彩资金怎么配置 发布:2025-03-16 20:01:57 浏览:10
在终端中打开编译环境 发布:2025-03-16 19:57:34 浏览:601
sqlserver安装数据库 发布:2025-03-16 19:57:01 浏览:36
如何关掉广告推送安卓 发布:2025-03-16 19:43:32 浏览:959
安卓如何关闭定时开机 发布:2025-03-16 19:23:54 浏览:292
酷派4g网络怎么设置密码 发布:2025-03-16 19:17:53 浏览:664
java培训费用 发布:2025-03-16 19:17:12 浏览:586