〖Linux〗Linux高级编程 - 进程间通信(Interprocess Communication)

[转自: http://blog.csdn.net/Paradise_for_why/article/details/5550619]

这一章就是著名的IPC,这个东西实际的作用和它的名字一样普及。例如我们浏览网页,打印文章,等等。

IPC总共有五种类型:

  1. 共享内存(Shared Memory):最容易理解的一种,就像一个特工把情报放在特定地点(内存),另一个特工再过来取走一样。
  2. 内存映射(Mapped Memory):和共享内存几乎相同,除了特工们把地点从内存改成了文件系统。
  3. 管道(Pipes):从一个进程到另一个进程的有序通信,用电话来比喻再恰当不过了。
  4. FIFOs:和管道和类似,唯一的区别是FIFOs比管道更神通一些,允许没有关系的进程之间的有序通信。
  5. 套接字(Sockets):为什么说浏览网页也是IPC?就是因为它。

5.1 共享内存(Shared Memory)

  • 共享内存是最快捷的进程间通信方式。访问共享内存的效率和访问进程自己的非共享内存的效率是相同的,而且这种通信方式不需要任何额外的系统调用。
  • 系统不会自动为共享内存处理同步问题,这个问题必须由用户自己解决。
  • 共享内存的步骤通常是:
    • 一个进程申请一块共享内存,即在它的页表中加入新的一项
    • 所有进程Attach该共享内存,即从申请内存的进程中拷贝对应的页表
    • 使用该内存进行通讯
    • 结束后所有进程detach该共享内存
    • 申请共享内存的进程在确定所有进程都detach后,释放该内存
  • 由于共享内存是通过页表来实现的,我们可以得出一个结论:共享内存的大小是页面大小的整数倍,页面的大小可以通过getpagesize()来得到,通常在Linux下该值是4KB
  • 相关的API函数:
    • 申请共享内存:shmget,返回共享内存segment的id
    • Attach,Detach函数:shmat,shmdt。需要共享内存segment的id
    • 释放申请的内存:shmctl。一定要记得释放!调用exit和exec会自动detach,但不会自动释放。
  • 使用 ipcs -m来观看当前系统存在的共享内存

  例子:原程序的例子找不到了...摘抄后修改的...

  1 /*
  2  * =============================================================================
  3  *
  4  *       Filename:  sharememory_read.c
  5  *
  6  *    Description:
  7  *
  8  *        Version:  1.0
  9  *        Created:  2014年11月04日 19时52分28秒
 10  *       Revision:  none
 11  *       Compiler:  gcc
 12  *
 13  *         Author:  lwq (28120), [email protected]
 14  *   Organization:
 15  *
 16  * =============================================================================
 17  */
 18 #include <stdlib.h>
 19
 20 /**********************************************************
 21 *实验要求:   创建两个进程,通过共享内存进行通讯。
 22 *功能描述:   本程序申请和分配共享内存,然后轮训并读取共享中的数据,直至
 23 *           读到“end”。
 24 *日    期:   2010-9-17
 25 *作    者:   国嵌
 26 **********************************************************/
 27 #include <unistd.h>
 28 #include <stdlib.h>
 29 #include <stdio.h>
 30 #include <string.h>
 31 #include <sys/types.h>
 32 #include <sys/ipc.h>
 33 #include <sys/shm.h>
 34 #include <getopt.h>
 35 #include "sharememory.h"
 36
 37 void read_shm(struct shared_use_st *shared_stuff);
 38 void write_shm(struct shared_use_st *shared_stuff);
 39 void del_shm();
 40
 41 void usage(){
 42     fprintf(stderr, "\nusage: %s -r|-w\n\n"
 43             "-r read mode\n"
 44             "-w write mode\n"
 45             "\n", "shared_memory");
 46     exit(1);
 47 }
 48
 49 #define READ (1)
 50 #define WRITE (2)
 51 #define OPTNONE (0)
 52
 53 // 全局变量
 54 void *shared_memory=(void *)0;
 55
 56 /*
 57  * 程序入口
 58  * */
 59 int main(int argc, char **argv)
 60 {
 61     int running=RUNNING;
 62     struct shared_use_st *shared_stuff;
 63     int shmid;
 64     int operation=OPTNONE;                      /* 读/写操作 */
 65
 66     /*-----------------------------------------------------------------------------
 67      *  getopt start
 68      *----------------------------------------------------------------------------*/
 69     int choice;
 70     while (1)
 71     {
 72         static struct option long_options[] =
 73         {
 74             /* Use flags like so:
 75             {"verbose",    no_argument,    &verbose_flag, ‘V‘}*/
 76             /* Argument styles: no_argument, required_argument, optional_argument */
 77             {"version", no_argument,    0,    ‘v‘},
 78             {"help",    no_argument,    0,    ‘h‘},
 79             {"read",    no_argument,    0,  ‘r‘},
 80             {"write",   no_argument,    0,  ‘w‘},
 81             {0,0,0,0}
 82         };
 83
 84         int option_index = 0;
 85
 86         /* Argument parameters:
 87             no_argument: " "
 88             required_argument: ":"
 89             optional_argument: "::" */
 90
 91         choice = getopt_long( argc, argv, "vhrw",
 92                     long_options, &option_index);
 93
 94         if (choice == -1)
 95             break;
 96
 97         switch( choice )
 98         {
 99             case ‘v‘:
100
101                 break;
102
103             case ‘h‘:
104                 usage();
105                 break;
106
107             case ‘r‘:
108                 operation=READ;
109                 break;
110
111             case ‘w‘:
112                 operation=WRITE;
113                 break;
114
115             case ‘?‘:
116                 /* getopt_long will have already printed an error */
117                 usage();
118                 break;
119
120             default:
121                 /* Not sure how to get here... */
122                 return EXIT_FAILURE;
123         }
124     }
125     if (operation == OPTNONE) {
126         usage();
127     }
128     /*-----------------------------------------------------------------------------
129      *  getopt end
130      *----------------------------------------------------------------------------*/
131
132     /*创建共享内存*/
133     shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
134     if(shmid==-1) {
135         fprintf(stderr,"shmget failed\n");
136         exit(EXIT_FAILURE);
137     }
138
139     /*映射共享内存*/
140     shared_memory=shmat(shmid,(void *)0,0);
141     if(shared_memory==(void *)-1) {
142         fprintf(stderr,"shmat failed\n");
143         exit(EXIT_FAILURE);
144     }
145
146     printf("Memory attached at 0%08x\n",(int)((intptr_t)shared_memory));
147
148     /*让结构体指针指向这块共享内存*/
149     shared_stuff=(struct shared_use_st *)shared_memory;
150
151     /*控制读写顺序*/
152     // lwq: 使之能读取上一条消息
153     if (operation == READ && shared_stuff->written_by_you != HADWROTE)
154         shared_stuff->written_by_you=HADREAD;
155
156     switch(operation) {
157         case READ:
158             read_shm(shared_stuff);
159             break;
160
161         case WRITE:
162             write_shm(shared_stuff);
163             break;
164
165         default:
166             usage();
167             break;
168     }
169
170     del_shm();
171     exit(EXIT_SUCCESS);
172 }
173
174 // 读取共享内存
175 void read_shm(struct shared_use_st *shared_stuff){
176     while(1) {
177        if(shared_stuff->written_by_you == HADWROTE) {
178            printf("You wrote:%s",shared_stuff->some_text);
179            shared_stuff->written_by_you=HADREAD;
180            if(strncmp(shared_stuff->some_text,"end",3)==0) {
181                break;
182            }
183        }
184        else {
185            usleep(100000);
186        }
187     }
188 }
189
190 // 写入共享内存
191 void write_shm(struct shared_use_st *shared_stuff){
192     char buffer[BUFSIZ] = {0};
193     while(1) {
194         while(shared_stuff->written_by_you!=HADREAD); /* 等待读写完成 */
195         printf("Enter some text:");
196         fgets(buffer,BUFSIZ,stdin);
197         strncpy(shared_stuff->some_text,buffer,TEXT_SZ); /* 复制进去 */
198         shared_stuff->written_by_you=HADWROTE;
199         if(strncmp(buffer,"end",3)==0) {
200             break;
201         }
202     }
203 }
204
205 // 删除共享内存
206 void del_shm(){
207     /*删除共享内存*/
208     if(shmdt(shared_memory)==-1) {
209         fprintf(stderr,"\nshmdt failed\n");
210         exit(EXIT_FAILURE);
211     }
212     else {
213         fprintf(stderr, "\ndelete shared_memory: 0x%08x\n", (int)((intptr_t)shared_memory));
214     }
215 }

sharememory.c

  编译:gcc sharememory.c -o sharememory

  执行:

    1. 以读取模式打开程序(进程1): ./sharememory -r

    2. 以写入模式打开程序(进程2): ./sharememory -w

5.2 进程信号量

  • 信号量(Semaphore)的概念前面已经介绍过了。Linux对用来同步进程的信号量采取了一种特别的实现方式。这些信号量也就被称为进程信号量(Process Semaphore)。(这一节下面所提到的所有信号量默认都是指进程信号量)
  • 相关的API函数:
    • 申请:semget
    • 释放:semctl。需要注意的是信号量不会被自动释放,我们必须显式释放它。
    • Wait和Post:semop
  • 使用ipcs -s来观看当前系统存在的信号量

5.3 内存映射

  • 内存映射使得不同的进程可以通过一个共享文件来互相通信。
  • 相关的API函数:
    • 映射:mmap
    • 同步:msync。用来指定对文件的修改是否被buffer。
    • 释放:munmap。在程序结束的时候会自动unmap
  • mmap的其他用法:
    • 可以替代read和write,有时使用内存映射后的效率比单纯使用I/O操作来的更快
    • 在内存映射文件中构建structure,修改structure再次将文件映射到内存中可以快速的将structure恢复到原来的状态
    • 把/dev/zero文件映射到内存中。该文件可以提供无限的0,并且写到该文件的所有内容将被直接丢弃

5.4 管道(Pipes)

  • 管道是单向的,即一个线程写,另一个线程读,无法互换
  • 如果写的速度太快,造成管道满了,那么写的线程就会被block;如果读的速度太快,造成管道空了,那么读的进程就会被block。因此事实上我们可以说管道自动实现了同步机制
  • 我们可以通过调用pipe函数来生成一对pipe file description。(为什么是一对?因为一个读一个写)。可是,生成的pipe file description无法传送给不相关的进程(因为做为file descriptor即使它拿到了也没法用)。但是我们注意到fork之后父进程所有的file descriptor在子进程中依然有效,因此管道最大的作用是在父子进程之间通信。或者更确切的说,是在有共同祖先的进程之间通信。
  • 典型的创建管道的流程如下:
    • 用pipe生成2个pipe file description(简称fds)。然后调用fork
    • 在父进程关闭fds[0](或fds[1]),并以只读(或只写)方式打开fds[1](或fds[0])。在子进程中关闭fds[1](或fds[0]),并以只写(或只读)方式打开fds[0](或fds[1])。打开的函数是fdopen。
    • 开始通信。结束后用close函数关闭剩下的fds。
  • 这里有一个技巧:可以利用管道来达成重定向stdin, stdout和stderr。注意到dup2这个API可以把一个file descriptor复制到另一个上。
  • 事实上,我们有一对更为简洁的函数popen/pclose来完成上面的一系列复杂的操作。popen有两个参数:
    • 第一个参数接受一个exec,子进程将执行这个exec
    • 第二个参数为”w”或者”r”,”w”表示父进程写子进程读,”r”则反之
    • 返回值为管道的一端,也就是一个file descriptor
    • pclose用来关闭popen返回的file descriptor
  • FIFO(First In First Out)文件事实上是一个有名字的管道,换句话说,他可以用来让“不相干”的程序互相通信。
    • 我们使用mkfifo函数来创建一个FIFO文件
    • 我们可以使用任何的低级I/O函数(open, write, read, close等)以及C库I/O函数(fopen, fprintf, fscanf, fclose等)来操作FIFO文件。
  • Linux的管道和Windows下的命名管道(Named Pipes)的区别
    • Windows的命名管道更像一个套接字(sockets),它可以通过网络让不同主机上的程序进行通信
    • Linux的管道允许有多个reader和writer,每个reader和writer进行读/写的最大容量为 PIPE_BUF(4KB),如果有多个writer同时写,他们写的东西会被分为一个一个的chunk(每个4KB)并允许交错写。(例如进程A有两个 chunk,A1,A2。进程B也有两个chunk,B1,B2。A和B同时写,则顺序可能为A1,B1,A2,B2) Windows的管道允许在同一个管道上有多个reader/writer对,他们之间读写的数据没有交叉。

5.5 套接字(Sockets)

  • 套接字的特点:

    • 它是双向通信的
    • 它是进程间通信的,包括其他机器上的进程
  • 套接字有三个参数:
    • 通讯类型(communication style)

      • 连接(connection)类型:保证所有的包按发送的顺序到达接受方。(类似于电话)如果包丢失或者抵达顺序错误,会自动重发。
      • datagram类型:所有包单独发送,可能会出现丢失或者晚发早到的现象。(类似于邮寄)
    • 命名空间(namespace):描述套接字的地址是如何表示的,例如本地就是文件名,internet上就是ip地址。
    • 协议(protocol):通讯协议,常用的有TCI/IP,AppleTalk等。
  • 相关的API(套接字也是通过file descriptor来表示的):
    • socket:创建一个socket
    • closes:销毁一个socket
    • connect:在两个socket之间创建一个连接。这个API通常由客户端调用。
    • bind:给服务器的一个套接字绑定一个地址,服务器端调用。
    • listen:让一个套接字开始侦听,准备接受请求,服务器端调用。
    • accept:接受一个连接请求,并且为该连接创建一个新的套接字,服务器端调用。
  • 服务器端的生命流程:
    • 创建一个connection类型的socket
    • 给该socket绑定一个地址
    • 调用listen来enable该socket(listen可以指定最多有多少个请求在等待队列中,如果等待队列满了,又有新的请求到达的时候,则该请求被拒绝)
    • 对于收到的连接请求调用accept来接受
    • 关闭socket
  • 本地socket(local socket)
    • 如果是同一台电脑上的两个进程需要通信的话,可以使用本地socket。这种情况下socket的地址是文件路径。注意进程必须对该路径拥有可写权限,否则无法建立连接
    • 完成之后使用unlink来关闭一个socket
时间: 2024-10-10 13:48:29

〖Linux〗Linux高级编程 - 进程间通信(Interprocess Communication)的相关文章

Linux C高级编程——文件操作之系统调用

Linux C高级编程文件操作之系统调用 宗旨:技术的学习是有限的,分享的精神的无限的! 库函数是一些完成特定功能的函数,一般由某个标准组织制作发布,并形成一定的标准.使用库函数编写的函数一般可以应用于不同的平台而不需要做任何修改,具有很好的可移植性. 系统调用函数与操作系统直接相关,不同的操作系统所使用的系统调用可能不太一样,因此,如果两个操作系统差异很大,系统调用函数的可移植性就不高.例如windows采用的系统调用的应用程序不能直接在Linux下编译运行. 之所以使用系统调用是因为系统资源

Linux环境高级编程--介绍

从今天开始,将开启Linux环境高级编程(Advanced Programming Of Linux Enviroment)的学习笔记或者说总结,我将持续和大家分享自己的学习成果.本系列博客依托于lilin老师的课程,代码也基本上来自于课程的内容,在得到老师的同意和允许情况,我在github上新建了一个仓库和大家分享代码,仓库名称为APLE .有任何建议或者想法的可以一起参加进来改进代码. 首先,我们介绍下系列博客的基本内容,APLE系列主要是在掌握基本Linux API的基础上,封装了一套C+

Linux C高级编程——网络编程基础(1)

Linux高级编程--BSD socket的网络编程 宗旨:技术的学习是有限的,分享的精神是无限的. 一网络通信基础 TCP/IP协议簇基础:之所以称TCP/IP是一个协议簇,是由于TCP/IP包括TCP .IP.UDP.ICMP等多种协议.下图是OSI模型与TCP/IP模型的对照.TCP/IP将网络划分为4层模型:应用层.传输层.网络层和网络接口层(有些书籍将其分为5层,即网络接口层由链路层和物理层组成) (1)网络接口层:模型的基层.负责数据帧的发送已接收(帧是独立的网络信息传输单元).网络

Linux C高级编程——网络编程(1)

Linux高级编程--BSD socket的网络编程 宗旨:技术的学习是有限的,分享的精神的无限的. 一网络通信基础 TCP/IP协议簇基础:之所以称TCP/IP是一个协议簇,是因为TCP/IP包含TCP .IP.UDP.ICMP等多种协议.下图是OSI模型与TCP/IP模型的对比,TCP/IP将网络划分为4层模型:应用层.传输层.网络层和网络接口层(有些书籍将其分为5层,即网络接口层由链路层和物理层组成) (1)网络接口层:模型的基层,负责数据帧的发送已接收(帧是独立的网络信息传输单元).网络

Linux 系统应用编程——进程间通信(下)

在前面,我们学习了传统的进程间通信方式--无名管道(pipe).有名管道(fifo)和信号(signal). 下面我们来学习 System V  IPC 对象: 1.共享内存(share memory): 2.信号灯(semaohore): 3.消息队列(message queue):        IPC对象是活动在内核级别的一种进程间通信的工具.存在的IPC对象通过它的标识符来引用和访问,这个标识符是一个非负整数,它唯一的标识了一个IPC对象,这个IPC对象可以是消息队列或信号量或共享存储器

Linux C高级编程——网络编程之以太网(2)

Linux网络编程--以太网 宗旨:技术的学习是有限的,分享的精神是无限的. 1.以太网帧格式 源地址和目的地址是指网卡的硬件地址(也叫MAC地址),长度是48位,是在网卡出厂时固化的.用ifconfig命令查看," 硬件地址 00:0c:29:cf:7e:1a " .协议字段有三种值,分别相应IP. ARP. RARP.帧末尾是CRC校验码. ARP和RARP数据包的长度不够46字节.要在后面补填充位. 最大值1500称为以太网的最大传输单元( MTU),不同的网络类型有不同的MTU

Linux 系统应用编程——进程间通信(上)

现在再Linux应用较多的进程间通信方式主要有以下几种: 1)无名管道(pipe)及有名管道(fifo):无名管道可用于具有亲缘关系进程间的通信:有名管道除具有管道相似的功能外,它还允许无亲缘关系进程使用: 2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程某事件发生.一个进程收到一个信号与处理器收到一个中断请求处理的过程类似: 3)消息队列(message queue):消息队列是消息的链接表,包括POSIX消息队列和System V 消息队

Linux C高级编程——网络编程(3)

Linux网络编程(三)--TCP 宗旨:技术的学习是有限的,分享的精神是无限的. 1.TCP段格式 和UDP协议一样也有源端口号和目的端口号,通讯的双方由IP地址和端口号标识.32位序号.32位确认序号.窗口大小.4位首部长度和IP协议头类似,表示TCP协议头的长度,以4字节为单位,因此TCP协议头最长可以是4x15=60字节,如果没有选项字段, TCP协议头最短20字节.URG. ACK. PSH. RST. SYN. FIN是六个控制位,本节稍后将解释SYN. ACK. FIN. RST四

Linux Shell 高级编程技巧3----运行级别脚本介绍

3.运行级别脚本介绍    3.1.运行级别        运行级别介绍:            0    关机            1    单用户模式            2    多用户模式,没有NFS服务            3    多用户模式            4    目前还没有使用            5    X windows 的启动模式            6    重启计算机        运行级别对应的目录(/etc/rcN.d,N是数字0.1.2...)