进程间通信 IPC interprocess communication

来源:互联网 发布:the essence of sql 编辑:程序博客网 时间:2024/05/21 16:47

1,管道,FIFO
2, 信号
3,消息队列
4,共享类存

5.文件映射

6.socket


1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
  (
2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
  (
3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
  (
4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
  (
5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  (
6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
  (
7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  (
8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

通信就是说进程之间传递数据。常见的方法有   pipe(管道),FIFO(命名管道),socket(套接字),SysVIPC   的  shm(共享内存)、msg   queue(消息队列),mmap(文件映射)。以前还有   STREAM,不过现在比较少见了(好像)。  
   
  同步的意思是说,让不同进程能够在同时到达一个已知的特定状态之前等待另一方的执行。Linux   下常见的同步方法有SysVIPC   的  sem(信号量)、file   locking   /   record   locking(通过   fcntl  设定的文件锁、记录锁)、futex(基于共享内存的快速用户态互斥锁)。针对线程(pthread)的还有   pthread_mutex   和  pthread_cond(条件变量)。  
  除了这些特定的同步对象之外,还有一些同步方法是与通信方法不可分离的,包括:对  pipe/FIFO/socket   和   msg   queue  的阻塞等待、对子进程退出事件的等待(wait族)、对线程退出时间的等待(pthread_join)  
   
  另外还有一个不能不提的,就是信号。Unix   信号是异步处理的、可以终端接收进程执行过程的特殊   IPC   方式——其实既可以算同步也可以选通讯了。

#####################################################################################################################

  linux下进程间通信的几种主要手段简介:  
   
        1.   管道(Pipe)及有名管道(named   pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通 信;  
        2.  信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数  sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal  函数);  
        3.  报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system  V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。  
        4.   共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使 用,来达到进程间的同步及互斥。  
        5.   信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。  
        6.  套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System   V的变种都支持套接字。

#####################################################################################################

用于进程间通讯(IPC)的四种不同技术:   
1. 消息传递(管道,FIFO,posix和system v消息队列)
2. 同步(互斥锁,条件变量,读写锁,文件和记录锁,Posix和System V信号灯)
3. 共享内存区(匿名共享内存区,有名Posix共享内存区,有名System V共享内存区)
4. 过程调用(Solaris门,Sun RPC)
消息队列和过程调用往往单独使用,也就是说它们通常提供了自己的同步机制.相反,共享内存区通常需要由应用程序提供的某种同步形式才能正常工作.解决某个特定问题应使用哪种IPC不存在简单的判定,应该逐渐熟悉各种IPC形式提供的机制,然后根据特定应用的要求比较它们的特性.

必须考虑的四个前提:
1. 联网的还是非联网的.IPC适用于单台主机上的进程或线程间的.如果应用程序有可能分布到多台主机上,那就要考虑使用套接字代替IPC,从而简化以后向联网的应用程序转移的工作.
2. 可移植性.
3. 性能,在具体的开发环境下运行测试程序,比较几种IPC的性能差异.
4. 实时调度.如果需要这一特性,而且所用的系统也支持posix实时调度选项,那就考虑使用Posix的消息传递和同步函数.

各种IPC之间的一些主要差异:
1. 管道和FIFO是字节流,没有消息边界.Posix消息和System V消息则有从发送者向接受者维护的记录边界(eg:TCP是没有记录边界的字节流,UDP则提供具有记录边界的消息).
2. 当有一个消息放置到一个空队列中时,Posix消息队列可向一个进程发送一个信号,或者启动一个新的线程.System V则不提供类似的通知形式.
3. 管道和FIFO的数据字节是先进先出的.Posix消息和System V消息具有由发送者赋予的优先级.从一个Posix消息队列读出时,首先返回的总是优先级最高的消息.从一个System V消息队列读出时,读出者可以要求想要的任意优先级的消息.
4. 在众多的消息传递技术—管道,FIFO,Posix消息队列和System V消息队列—中,可从一个信号处理程序中调用的函数只有read和write(适用于管道和FIFO).

比较不同形式的消息传递时,我们感兴趣的有两种测量尺度:
1. 带宽(bandwidth):数据通过IPC通道转移的速度.为测量该值,我们从一个进程向另一个进程发送大量数据(几百万字节).我们还给不同大小的I/O操作(例如管道和FIFO的write和read操作)测量该值,期待发现带宽随每个I/O操作的数据量的增长而增长的规律.
2. 延迟(latency):一个小的IPC消息从一个进程到令一个进程再返回来所花的时间.我们测量的是只有一个1个字节的消息从一个进程到令一个进程再回来的时间(往返时间)

在现实世界中,带宽告诉我们大块数据通过一个IPC通道发送出去需花多长时间,然而IPC也用于传递小的控制信息,系统处理这些小消息所需的时间就由延迟提供.这两个数都很重要.

#########################################################################################################################

程序员必须让拥有依赖关系的进程 集协调,这样才能达到进程的共同目标。可以使用两种技术来达到协调。第一种技术在具有通信依赖关系的两个进程间传递信息。这种技术称做进程间通信(interprocess communication)。第二种技术是同步,当进程间相互具有合作依赖时使 用。这两种类型的依赖关系可以同时存在。

一般而言,进程有单独的地址空 间。我们可以了解下可执行程序被装载到内存后建立的一系列映射等理解这一点。如此以来意味着如果我们有两个进程(进程A和进程B),那么,在进程A中声明的数据对于进程B是不可用的。而且,进程B看不到进程A中发生的事件,反之亦然。如果进程AB一起工作来完成某个任务,必须有一个在两个进程间通信信息和时间的方法。我们这里可以去看看基本的进程组件。注意进程有一个文本、数据以及堆栈片断。进程可能也有从自由存储空间中分配的其它内存。进程所占有的数据一般位于数据片断、堆栈片断或进程的动态分配内存中。数据对于其它进程来说是受保护的。为了让一个进程访问另外一个进程的数据,必须最终使用操作系统调用。与之类似,为了让一个进程知道另一个进程中文本片断中发生的事件,必须在进程间建立一种通信方式。这也需要来自操作系统API的帮助。当进程将数据发送到另一进程时,称做IPCinterprocess communication,进程间通信)。下面先列举几种不同类型的进程间通信 方式:

 

进程间通信                                 描 述

环境变量/文件描述符            子进程接受父进程环境数据的拷贝以及所有文件描述符。父进程可以在它的数据片断或环境中设置一定的变量,同时子进程接收这 些值。父进程可以打开文件,同时推进读/写指针的位置,而且子进程使用相同的偏移访问该文件。

 

命令行参数                      在调用exec或派生函数期间,命令行参数可以传递给子进程。

 

管道                        用于相关和无关进程间的通信,而且形成两个进程间的一个通信通道,通常使用文件读写程序访问。

 

共享内存                        两个进程之外的内存块,两个进程均可以访问它。

 

DDE                           (动态数据交换Dynamic data exchange

                                使用客户机/服务器模型(C/S),服务器对客户的数据 或动作请求作出反应。

 

一、 环境变量、文件描述符:

当创建一个子进程时,它接受了父进程许多资源的拷贝。子进程接受了父进程的文本、堆栈

以及数据片断的拷贝。子进程也接受了父进程的环境数据以及所有文件描述符的拷贝。子进

程从父进程继承资源的过程创造了进程间通信的一个机会。父进程可以在它的数据片断或环

境中设置一定的变量,子进程于是接受这些值。同样,父进程也可以打开一个文件,推进到

文件内的期望位置,子进程接着就可以在父进程离开读/写指针的准确位置访问该文件。

 

这类通信的缺陷在于它是单向的、一次性的通信。也就是说,除了文件描述外,如果子进程

继承了任何其它数据,也仅仅是父进程拷贝的所有数据。 一旦创建了子进程,由子进程对

这些变量的任何改变都不会反映到父进程的数据中。同样,创建子进程后,对父进程数据的

任何改变也不会反映到子进程中。所以,这种类型的进程间通信更像指挥棒传递。一旦父进

程传递了某些资源的拷贝,子进程对它的使用就是独立的,必须使用原始传递资源。

 

二、命令行参数:

通过命令行参数(command-line argument)可以完成另一种单向、一次性的进程间通信

我前面的文章已经提到过使用命令行参数。命令行参数在调用一个exec或派生调用操作系

统时传递给子进程。命令行参数通常在其中一个参数中作为NULL终 止字符串传递给exec

或派生函数调用。这些函数可以按单向、一次性方式给子进程传递值。WINDOWS有调用执行

exe程序的API。大家可以去参考一下ShellExecuteA函数相关。

 

除了文件描述符外,继承资源是IPC的单向、一次 性形式。

传递命令参数也是单向、一次性的IPC方 法。

这些方法也只有限制于关联进程,如 果不关联,命令行参数和继承资源不能使用。

还有另一种结构,称做管道(Pipe), 它 可以用于在关联进程间以及无关联进程间进行通信。

 

三、管道通信:

继承资源以及命令行参数是最简单形式的进程间通信。它们同时有两个主要限制。

管道是一种数据结构,像一个序列化文件一样访问。它形成了两个进程间的一种通信渠道。

管道结构通过使用文本和写方式来访问。如果进程A希望通过管道发送数据给进程B,那么

进程A向管道写入数据。为了让进程B接收此数据,进程B必须读取管道,与命令行参数的

IPC形式不一样。管道可以双向通信。两进程间的数据流是双向通信的。管道可以在程序的

整个执行期间使用,在进程间发送和接收数据。所以,管道充当可访问管道的进程间的一种

可活链接,有两种基本管道类型:

1.  匿名管道

2.  命名管道

匿名管道

上面的图可以看出在没有管道时,两进程是不能互写的。

匿名管道

建立管道后就可以相互通信了。

只有关联进程可以使用匿名管道来通信。

无关联进程必须使用命名管道。

匿名管道:通过文件描述符或文件句柄提供对匿名管道的访问。对系统API的调用创建一个管道,并返回一个文件描述符。

这个文件描述符是用作read()write()函数的一个参数。

当通过文件描述符调用read()write()时,数据的源和目标就是管道。

例如,在OS/2环境中使用操作系统函数DosCreatePipe()创建匿名管道:

int mian( void )

{

    PFHILE readHandle;

PFHILE writeHandle;

DosCreatePipe( readHandle, writeHandle, size );

}

WINDOWS下例如我写的ASM集 成环境通过管道与DOS命令行通信的MFC下 代码块:

void CIDEManager::Commond( CString cmd, char* buf, unsigned int bufsize )

{

    SECURITY_ATTRIBUTES sa;

    HANDLE hRead, hWrite;

    sa.nLength              = sizeof( SECURITY_ATTRIBUTES );

    sa.lpSecurityDescriptor = NULL;

sa.bInheritHandle       = TRUE;

 

    if ( !CreatePipe( &hRead, &hWrite, &sa, 0 ) )  // 创建管道

    {

        return;

    }

 

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    si.cb = sizeof( STARTUPINFO );

    GetStartupInfo( &si );

    si.hStdError   = hWrite;

    si.hStdOutput  = hWrite;

    si.wShowWindow = SW_HIDE;

    si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

 

if ( !CreateProcess( NULL, ( LPTSTR )( LPCTSTR )cmd, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi ) )

    {

        return;

    }

 

    CloseHandle( hWrite );

 

    DWORD bytesRead;

    while ( TRUE )

    {

        memset( buf, 0, bufsize );

        if ( ReadFile( hRead, buf, bufsize, &bytesRead, NULL ) != NULL )

        {

            break;

        }

        Sleep( 200 );

    }

    CloseHandle( hRead );

    return;

}

 

命名管道:将管道用作两个无关联进程间的通信渠道,程序员必须使用命名管道,它可以看作一种具有某名字的特殊类型文件。进程可以根据它的名字访问这个管道。通过匿名管道,父和子进程可以单独使用文件描述符来访问他们所共享的管道,因为子进程继承了父进程的文件描述符,同时文件描述符用read()write()函数的参数。因为无关进 程不能访问彼此的文件描述符,所以不能使用匿名管道。由于命名管道提供该管道的一个等价文件名,任何知道此管道名字的进程都可以访问它。

下面是命名管道相对于匿名管道的优点

命名管道可以被无关联进程使用。

命名管道可以持久。创建它的程序退出后,它们仍然可以存在。

命名管道可以在网络或分布式环境中使用。

命名管道容易用于多对一关系中。

与访问匿名管道一样,命名管道也是通过read()write()函数来访问。

两者之间的主要区别在于命名管道的创建方式以及谁可以访问它们。

命名管道可以建立一个进程间通信的C/S模型。

访问命名管道的进程可能都位于同一台机器上,或位于通过网络通信的不同 机器上。由于管道的名字可以通过管道所在服务器的逻辑名,所以能够跨网络访问管道。例如,////ServerName//Pipe//MyPipe(不 区分大小写)可以作为一个管道名字。假如Server1是网络服务器的名字。当打开或访问这个管道 的调用解析文件名时,首先应该定位Server1,然后访问MyPipe。 例子如下:

 

服务器端:

int main( void )

{

    HANDLE pipehandle;

    char buf[ 256 ];

 DWORD bytesRead;

 

     if( ( pipehandle = CreateNamedPipe( "////.//Pipe//cao",PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 5000,NULL ) ) == INVALID_HANDLE_VALUE )

    {

        printf( "CreateNamedPipe failed with error %d/n", GetLastError() );

        system( "pause" );

        return 0;

    }

 

    printf( "server is running/n" ); 

    if( ConnectNamedPipe( pipehandle, NULL ) == 0 )

 

    {

        printf( "connectNamedPipe failed with error %d/n", GetLastError() );

        CloseHandle( pipehandle );

        system( "pause" );

        return 0;

    }    

 

    if( ReadFile( pipehandle, buf, sizeof( buf ), &bytesRead, NULL ) == 0 )

 

    {

        printf( "ReadFile failed with error %d/n", GetLastError() );

        CloseHandle( pipehandle );

        system( "pause" );

        return 0;

    }

 

    printf( "%s/n", buf );      

    if ( DisconnectNamedPipe( pipehandle ) == 0 )

 

    {

        printf( "DisconnectNamedPipe failed with error %d/n", GetLastError() );

        CloseHandle( pipehandle );

        system( "pause" );

        return 0;

    }

 

    system( "pause" );

    return 0;

}

 

 

客户端:

int main( void )

{

    HANDLE pipehandle;

    DWORD writesbytes;

    char buff[ 256 ];

 

    if( WaitNamedPipe( "////.//Pipe//cao", NMPWAIT_WAIT_FOREVER ) == 0 )

    {

        printf( "WaitNamedPipe failed with error %d/n", GetLastError() );

        system( "pause" );

        return 0;

    }

 

    if( ( pipehandle = CreateFile( "////.//Pipe//cao", GENERIC_READ |GENERIC_WRITE, 0, ( LPSECURITY_ATTRIBUTES )NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, ( HANDLE )NULL ) ) == INVALID_HANDLE_VALUE )

    {

        printf( "CreateFile failed with error %d/n", GetLastError() );

        system( "pause" );

        return 0;

    }

 

    ZeroMemory( &buff, sizeof( buff ) );

    gets( buff );

 

    if( WriteFile( pipehandle, buff, sizeof( buff ), &writesbytes, NULL ) == 0 )

    {

        printf( "WriteFile failed with error %d/n", GetLastError() );

        CloseHandle( pipehandle );

        system( "pause" );

        return 0;

    }    

    printf( "write %d bytes", writesbytes );

    CloseHandle( pipehandle );

 

    system( "pause" );

    return 0;

}

 

命名管道不仅可用于无关联进程间、位于不同机器上的两进程间的通信,而且可用于多对一通信,可以建立服务器进程,允许同时通过多个客户访问命名管道。命名管道常常用于多线程服务器。累死了。。

之前写了上,由于工作原因和忙到写自己的毕业设计。一直拖到现在。今晚突然有这个冲动把下面的写完。其实写到这里也就是做个笔记,留个思绪 - -

前面提到了几种方式,还要下面几种方式实现进程间的通信:

四、 共享内存

共享内存也可以实现进程间的通信。进程需要可以被其他进程浏览的内存块。希望访问这个内存块的其他进程请求对它的访问,或由创建它的进程授予访问内存块的权限。可以访问特定内存块的所有进程对它具有即时可见性。共享内存被映射到使用它的每个进程的地址空间。所以,它看起来像是另一个在进程内声明的变量。当一个进程写共享内存,所有的进程都立即知道写入的内容,而且可以访问。

进程间共享内存的关系与函数间全局变量的关系相似。程序中的所有函数都可以使用全局变量的值。同样,共享内存块可以被正在执行的所有进程访问。内存 块可能共享一个逻辑地址,进程也可以共享某些物理地址。

共享内存块的创建必须由一个系统API调用来完成。在WIN32环境中,使用CreateFileMapping()、 MapViewOfFile()以及MapViewOfFileEx() API能很好地完成。

共享内存分配位于WIN32系统中2~3GB地址范围内。一旦调用MapViewOfFile()和MapViewOfFileEx(),共享文件 映射对象的所有进程都可以立即访问此内存块,而且在需要时,可以读写此内存块。

五、动态数据交换

动态数据交换( dynamic data exchange ) 是当今可用的进程间通信最强大和完善的形式之一。

动态数据交换使用消息传递共享内存事务协议客户/服务器范畴、同步规则以及会话协议来让数据和控制 信息在进程间流动。

动态数据交换对话( dynamic data exchange session, DDE )的基本模型是客户、服务器。

服务器对来自客户的数据或动作作出反应。

客户和服务器可以以多种关系来通信。

一个服务器可以与任意数量的客户通信。一个客户也可以与任意数量的服务器通信。

单个DDE代理既可以是客户,也可以是服务器。也就是说,进程可以从 一个正为另一个进程执行服务的DDE代理请求服务。

这里就简单介绍到这里,只是阐述个形。 - -  睡觉。

原创粉丝点击