linux C语言编程----进程间通信

转载来源:http://book.51cto.com/art/200912/169555.htm

进程间通信

一个大型的应用软件往往需要众多进程协作,进程间通信(IPC)的重要性显而易见。Linux系统下的进程通信机制基本上是从UNIX平台上的进程通信机制移植而来的。主要的进程间通信机制有以下几种。

无名管道(Pipe)及命名管道(Named pipe):管道可用于具有父子关系进程间的通信,命名管道用于无父子关系的进程之间通信。无父子关系的进程可将信息发送到某个命名管道中,并通过管道名读取信息。

信号(Signal):信号是进程间高级的通信方式,用于通知其他进程有何种事件发生。此外,进程可以向自身发送信号,还可以获得Linux内核发出的信号。Linux支持UNIX系统早期信号函数sigal(),并从BSD引入了信号函数sigaction()。sigaction()函数不仅提供了更为有效的通信机制,并保持了接口的统一,已替代sigal()函数。

报文(Message)队列:报文队列又称为消息队列,是以Posix和System V为标准的通信机制。报文队列克服了信号的数据结构过于简单的问题,同时也解决了管道数据流无格式和缓冲区长度受限等问题。报文队列规定了每个进程的权限,避免了仿冒信息的出现。

共享内存:共享内存是让多个进程访问同一个内存空间,适合于数据量极大和数据结构极为复杂的进程间通信。但这种方式牺牲了系统的安全性,所以通常与其他进程间通信形式混合使用,并避免以根用户权限执行。

信号量(Semaphore):信号量是用于解决进程的同步和相关资源抢占而设计的。

套接字(Socket):套接字是一种数据访问机制,不仅可用于进程间通信,还可用于网络通信。使用套接字最大的好处在于,Linux下的程序能快速移植到其他类UNIX平台上。很多高级的进程间通信机制以套接字为基础实现。

D-Bus:D-Bus是一种高级的进程间通信机制,以前述机制为基础实现。它提供了丰富的接口和功能,简化了程序设计难度。

 进程间使用管道通信

本节将以管道方式为例讲解进程间通信的使用方法。管道本身是一种数据结构,遵循先进先出原则。先进入管道的数据,也能先从管道中读出。数据一旦读取后,就会在管道中自动删除。管道通信以管道数据结构作为内部数据存储方式,以文件系统作为数据存储媒体。Linux系统中有两种管道,分别是无名管道和命名管道。pipe系统调用可创建无名管道,open系统调用可创建命名管道。下面介绍这两种管道的实现方式。


pipe系统调用

系统调用pipe用来建立管道。与之相关的函数只有一个,即pipe()函数,该函数被定义在头文件unistd.h中,它的一般形式是:

  
  
  
  
  1. int pipe(int filedes[2]);  

pipe系统调用需要打开两个文件,文件标识符通过参数传递给pipe()函数。文件描述符filedes[0]用来读数据,filedes[1]用来写数据。调用成功时,返回值为0,错误时返回-1。管道的工作方式可以总结为以下3个步骤。

1.将数据写入管道

将数据写入管道使用的是write()函数,与写入普通文件的操作方法一样。与文件不同的是,管道的长度受到限制,管道满时写入操作会被阻塞。执行写操作的进程进入睡眠状态,直到管道中的数据被读取。fcntl()函数可将管道设置为非阻塞模式,管道满时,write()函数的返回值为0。如果写入数据长度小于管道长度,则要求一次写入完成。如果写入数据长度大于管道长度,在写完管道长度的数据时,write()函数将被阻塞。

2.从管道读取数据

读取数据使用read()函数实现,读取的顺序与写入顺序相同。当数据被读取后,这些数据将自动被管道清除。因此,使用管道通信的方式只能是一对一,不能由一个进程同时向多个进程传递同一数据。如果读取的管道为空,并且管道写入端口是打开的,read()函数将被阻塞。读取操作的进程进入睡眠状态,直到有数据写入管道为止。fcntl()函数也可将管道读取模式设置为非阻塞。

3.关闭管道

管道虽然有2个端口,但只有一个端口能被打开,这样避免了同时对管道进行读和写的操作。关闭端口使用的是close()函数,关闭读端口时,在管道上进行写操作的进程将收到SIGPIPE信号。关闭写端口时,进行读操作的read()函数将返回0。如下例所示:

  
  
  
  
  1. #include <unistd.h>                     // 标准函数库  
  2. #include <sys/types.h>                  // 该头文件提供系统调用的标志  
  3. #include <sys/wait.h>                   // wait系统调用相关函数库  
  4. #include <stdio.h>                      // 基本输入输出函数库  
  5. #include <string.h>                     // 字符串处理函数库  
  6. int main()  
  7. {  
  8.    int fd[2], cld_pid, status;          // 创建文件标识符数组  
  9.    char buf[200], len;                  // 创建缓冲区  
  10.    if (pipe(fd) == -1) {                // 创建管道  
  11.       perror("创建管道出错");  
  12.       exit(1);  
  13.    }  
  14.    if ((cld_pid=fork()) == 0) {         // 创建子进程, 判断进程自身是否是子进程  
  15.       close(fd[1]);                     // 关闭写端口  
  16.       len = read(fd[0], buf, sizeof(buf));      // 从读 端口中读取管道内数据  
  17.       buf[len]=0;                               // 为缓 冲区内的数据加入字符串  
  18.                                                 // 结束符  
  19.       printf("子进程从管道中读取的数据是:%s ",buf);  //  输出管道中的数据  
  20.       exit(0);                                  // 结束子进程  
  21.    }  
  22.    else {  
  23.       close(fd[0]);                             // 关闭读端口  
  24.       sprintf(buf, "父进程为子进程(PID=%d)创建该数据", cld_pid);  
  25.                                                 // 在缓 冲区创建字符串信息  
  26.       write(fd[1], buf, strlen(buf));           // 通过 写端口向管道写入数据  
  27.       exit(0);                                  // 结束父进程  
  28.    }  
  29.    return 0;  

程序中,首先创建了一个管道,并且将管道的文件标识符传递给fp[]数组。该数组有2个元素,fd[0]是读取管道的端口,fd[1]是写入管道的端口。然后,通过fork()系统调用创建了一个子进程。父进程的操作是向管道写入数据,子进程的操作是读取管道内的数据,最后子进程将所读取的数据显示到终端上。


dup系统调用

系统调用dup用来复制一个文件描述符,该操作是通过对u区中文件描述符复制实现的。因此,系统调用dup能让多个文件描述符指向同一文件,便于管道操作。与该调用相关的函数有两个,分别是dup()函数和dup2()函数,一般形式如下:

  
  
  
  
  1. int dup(int oldfd);  
  2. int dup2(int oldfd, int newfd); 

其中,oldfd是原有的文件描述符,newfd为指定的新文件描述符。这两个函数的区别为,dup()函数自动分配新文件描述符,并保证该文件描述符没有被使用。dup2()函数使用newfd参数指定新文件描述符,如果该文件描述符已存在,则覆盖对应的文件描述符。新旧文件描述符可交换使用,并共享文件锁、文件指针和文件状态。调用成功时,函数返回值为新文件描述符,否则返回-1。如下例所示:

  
  
  
  
  1. #include <unistd.h>                         // 标准函数库  
  2. #include <stdio.h>                          // 基本输入输出函数库  
  3. #include <sys/types.h>                      // 该头文件提供系统调用的标志  
  4. #include <sys/stat.h>                       // 进程状态及相关操作函数库  
  5. #include <fcntl.h>                          // 该头文件包含文件I/O操作相关  
  6.                                             // 标志  
  7. int main()  
  8. {  
  9.    int fd;  
  10.    if ((fd = open("output", O_CREAT|O_RDWR,0644)) == -1) {  
  11.                                             // 打开或创建文件  
  12.       perror("打开或创建文件出错");  
  13.       return 1;  
  14.    }  
  15.    close(1);                                    // 关闭标准输出  
  16.    dup(fd);                                 // 复制fd到文件描述符1上  
  17.    close(fd);                               // 关闭文件描述符fd  
  18.    puts("该行数据将输出到文件中");  
  19.    return 0;  

代码中,标准输出(文件描述符为1)关闭,并将一个普通文件output的文件描述符复制到标准输出上。因为刚关闭了文件描述符1,文件描述符表的第一个空表项是1,dup()函数调用将fd的文件描述符复制到该位置上。所以,程序以后的向标准输出写的内容都写到了文件output中。

进程间使用D-Bus通信

D-Bus是一种高级的进程间通信机制,它由freedesktop.org项目提供,使用GPL许可证发行。D-Bus最主要的用途是在Linux桌面环境为进程提供通信,同时能将Linux桌面环境和Linux内核事件作为消息传递到进程。D-Bus的主要概率为总线,注册后的进程可通过总线接收或传递消息,进程也可注册后等待内核事件响应,例如等待网络状态的转变或者计算机发出关机指令。目前,D-Bus已被大多数Linux发行版所采用,开发者可使用D-Bus实现各种复杂的进程间通信任务。

  D-Bus的基本概念

D-Bus是一个消息总线系统,其功能已涵盖进程间通信的所有需求,并具备一些特殊的用途。D-Bus是三层架构的进程间通信系统,其中包括:

接口层:接口层由函数库libdbus提供,进程可通过该库使用D-Bus的能力。

总线层:总线层实际上是由D-Bus总线守护进程提供的。它在Linux系统启动时运行,负责进程间的消息路由和传递,其中包括Linux内核和Linux桌面环境的消息传递。

包装层:包装层一系列基于特定应用程序框架的Wrapper库。

D-Bus具备自身的协议,协议基于二进制数据设计,与数据结构和编码方式无关。该协议无需对数据进行序列化,保证了信息传递的高效性。无论是libdbus,还是D-Bus总线守护进程,均不需要太大的系统开销。

总线是D-Bus的进程间通信机制,一个系统中通常存在多条总线,这些总线由D-Bus总线守护进程管理。最重要的总线为系统总线(System Bus),Linux内核引导时,该总线就已被装入内存。只有Linux内核、Linux桌面环境和权限较高的程序才能向该总线写入消息,以此保障系统安全性,防止有恶意进程假冒Linux发送消息。

会话总线(Session Buses)由普通进程创建,可同时存在多条。会话总线属于某个进程私有,它用于进程间传递消息。

进程必须注册后才能收到总线中的消息,并且可同时连接到多条总线中。D-Bus提供了匹配器(Matchers)使进程可以有选择性的接收消息,另外运行进程注册回调函数,在收到指定消息时进行处理。匹配器的功能等同与路由,用于避免处理无关消息造成进程的性能下降。除此以外,D-Bus机制的重要概念有以下几个。

对象:对象是封装后的匹配器与回调函数,它以对等(peer-to-peer)协议使每个消息都有一个源地址和一个目的地址。这些地址又称为对象路径,或者称之为总线名称。对象的接口是回调函数,它以类似C++的虚拟函数实现。当一个进程注册到某个总线时,都要创建相应的消息对象。

消息:D-Bus的消息分为信号(signals)、方法调用(method calls)、方法返回(method returns)和错误(errors)。信号是最基本的消息,注册的进程可简单地发送信号到总线上,其他进程通过总线读取消息。方法调用是通过总线传递参数,执行另一个进程接口函数的机制,用于某个进程控制另一个进程。方法返回是注册的进程在收到相关信息后,自动做出反应的机制,由回调函数实现。错误是信号的一种,是注册进程错误处理机制之一。

服务:服务(Services)是进程注册的抽象。进程注册某个地址后,即可获得对应总线的服务。D-Bus提供了服务查询接口,进程可通过该接口查询某个服务是否存在。或者在服务结束时自动收到来自系统的消息。

安装D-Bus可在其官方网站下载源码编译,地址为http://dbus.freedesktop.org。或者在终端上输入下列指令:

  
  
  
  
  1. yum install dbus dbus-devel dbus-doc 
安装后,头文件位于"/usr/include/dbus-<版本号>/dbus"目录中,编译使用D-Bus的程序时需加入编译指令"`pkg-config --cflags --libs dbus-1`"。


D-Bus的用例

在使用GNOME桌面环境的Linux系统中,通常用GLib库提供的函数来管理总线。在测试下列用例前,首先需要安装GTK+开发包(见22.3节)并配置编译环境。该用例一共包含两个程序文件,每个程序文件需单独编译成为可执行文件。

1.消息发送程序

"dbus-ding-send.c"程序每秒通过会话总线发送一个参数为字符串Ding!的信号。该程序的源代码如下:

  
  
  
  
  1. #include <glib.h>                               // 包含glib库  
  2. #include <dbus/dbus-glib.h>                     // 包含 glib库中D-Bus管理库  
  3. #include <stdio.h> 
  4. static gboolean send_ding(DBusConnection *bus);// 定义 发送消息函数的原型  
  5. int main ()  
  6. {  
  7.    GMainLoop *loop;                             // 定义 一个事件循环对象的指针  
  8.    DBusConnection *bus;                         // 定义 总线连接对象的指针  
  9.    DBusError error;                             // 定义 D-Bus错误消息对象  
  10.    loop = g_main_loop_new(NULL, FALSE);         // 创建新事件循环对象  
  11.    dbus_error_init (&error);                    // 将错误消 息对象连接到D-Bus  
  12.                                                 // 错误消息对象  
  13.    bus = dbus_bus_get(DBUS_BUS_SESSION, &error);// 连接到总线  
  14.    if (!bus) {                              // 判断是否连接错误  
  15. g_warning("连接到D-Bus失败: %s", error.message);  
  16.                                         // 使用GLib输出错误警告信息  
  17.       dbus_error_free(&error);              // 清除错误消息  
  18.       return 1;  
  19.    }  
  20.    dbus_connection_setup_with_g_main(bus, NULL);  
  21.                                             // 将总线设为 接收GLib事件循环  
  22.    g_timeout_add(1000, (GSourceFunc)send_ding, bus);  
  23.                                     // 每隔1000ms调用一次 send_ding()函数  
  24.                                             // 将总线指针作为参数  
  25.    g_main_loop_run(loop);                   // 启动事件循环  
  26.    return 0;  
  27. }  
  28. static gboolean send_ding(DBusConnection *bus)  // 定义发 送消息函数的细节  
  29. {  
  30.    DBusMessage *message;                        // 创建消息对象指针  
  31.    message = dbus_message_new_signal("/com/burtonini/dbus/ding",   
  32.                                        "com.burtonini.dbus.Signal",  
  33.                                        "ding");     //  创建消息对象并标识路径  
  34.    dbus_message_append_args(message,  
  35.                             DBUS_TYPE_STRING, "ding!",  
  36.                             DBUS_TYPE_INVALID);     //  将字符串Ding!定义为消息  
  37.    dbus_connection_send(bus, message, NULL);    // 发送该消息  
  38.    dbus_message_unref(message);                 // 释放消息对象  
  39.    g_print("ding!\n");                          // 该函数 等同与标准输入输出                                      // 库的printf()  
  40.    return TRUE;  

main()函数创建一个GLib事件循环,获得会话总线的一个连接,并将D-Bus事件处理集成到GLib事件循环之中。然后它创建了一个名为send_ding()函数作为间隔为一秒的计时器,并启动事件循环。send_ding()函数构造一个来自于对象路径"/com/burtonini/dbus/ding"和接口"com.burtonini.dbus.Signal"的新的Ding信号。然后,字符串Ding!作为参数添加到信号中并通过总线发送。在标准输出中会打印一条消息以让用户知道发送了一个信号。

2.消息接收程序

dbus-ding-listen.c程序通过会话总线接收dbus-ding-send.c程序发送到消息。该程序的源代码如下:

  
  
  
  
  1. #include <glib.h>                               // 包含glib库  
  2. #include <dbus/dbus-glib.h>                     // 包含glib库中D-Bus管理库  
  3. static DBusHandlerResult signal_filter      // 定义接收消息函数的原型  
  4.       (DBusConnection *connection, DBusMessage *message, void *user_data);  
  5. int main()  
  6. {  
  7.    GMainLoop *loop;                             // 定义 一个事件循环对象的指针  
  8.    DBusConnection *bus;                         // 定义
  9. 总线连接对象的指针  
  10.    DBusError error;                             // 定义 D-Bus错误消息对象  
  11.    loop = g_main_loop_new(NULL, FALSE);         // 创建新 事件循环对象  
  12.    dbus_error_init(&error);                     // 将错误消 息对象连接到D-Bus  
  13.                                                 // 错误消息对象  
  14.    bus = dbus_bus_get(DBUS_BUS_SESSION, &error);    // 连接到总线  
  15.    if (!bus) {                              // 判断是否连接错误  
  16. g_warning("连接到D-Bus失败: %s", error.message);  
  17.                                         // 使用GLib输出错误警告信息  
  18.       dbus_error_free(&error);              // 清除错误消息  
  19.       return 1;  
  20.   }  
  21.    dbus_connection_setup_with_g_main(bus, NULL);      
  22.                                             // 将总线设 为接收GLib事件循环  
  23.    dbus_bus_add_match(bus, "type='signal',interface ='com.burtonini.dbus.Signal'");  // 定义匹配器  
  24.    dbus_connection_add_filter(bus, signal_filter, loop, NULL);  
  25.                                             // 调用函数接收消息  
  26.    g_main_loop_run(loop);                   // 启动事件循环  
  27.    return 0;  
  28. }  
  29. static DBusHandlerResult                    // 定义接收消息函数的细节  
  30. signal_filter (DBusConnection *connection,  DBusMessage *message, void *user_data)  
  31. {  
  32.    GMainLoop *loop = user_data;             // 定义事件 循环对象的指针,并与主函                                      // 数中的同步  
  33.    if (dbus_message_is_signal               // 接收连接 成功消息,判断是否连接  
  34.                                             // 失败  
  35.         (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")) {  
  36.       g_main_loop_quit (loop);              // 退出主循环  
  37.       return DBUS_HANDLER_RESULT_HANDLED;  
  38.    }  
  39.    if (dbus_message_is_signal(message, "com.burtonini.dbus.Signal",   
  40.    "Ping")) {  
  41.                                             // 指定 消息对象路径,判断是否成功  
  42.       DBusError error;                      // 定义错误对象  
  43.       char *s;  
  44. dbus_error_init(&error);                // 将错误消息对 象连接到D-Bus错误  
  45.                                         // 消息对象  
  46.       if (dbus_message_get_args                 // 接 收消息,并判断是否有错误  
  47.          (message, &error, DBUS_TYPE_STRING, &s,  DBUS_TYPE_INVALID)) {  
  48.          g_print("接收到的消息是: %s\n", s);   // 输出接收到的消息  
  49.          dbus_free (s);                     // 清除该消息  
  50.       }   
  51.       else {                                    // 有 错误时执行下列语句  
  52.          g_print("消息已收到,但有错误提示: %s\n", error.message);  
  53.          dbus_error_free (&error);  
  54.       }  
  55.       return DBUS_HANDLER_RESULT_HANDLED;  
  56.    }  
  57.    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;  

该程序侦听dbus-ping-send.c程序正在发出的信号。main()函数和前面一样启动,创建一个到总线的连接。然后它声明愿意在使用com.burtonini.dbus.Signal接口的信号被发送时得到通知,将signal_filter()函数设置为通知函数,然后进入事件循环。当满足匹配的消息被发送时,signal_func()函数会被调用。

如果需要确定在接收消息时如何处理,可通过检测消息头实现。若收到的消息为总线断开信号,则主事件循环将被终止,因为监听的总线已经不存在了。若收到其他的消息,首先将收到的消息与期待的消息进行比较,两者相同则输出其中参数,并退出程序。两者不相同则告知总线并没有处理该消息,这样消息会继续保留在总线中供别的程序处理。


你可能感兴趣的:(linux C语言编程----进程间通信)