Linux下的进程概论与编程三(进程间通信的5种方式)

来源:互联网 发布:崛起网络f18团平台图片 编辑:程序博客网 时间:2024/05/16 02:38

Linux下的进程概论与编程一(进程概念与编程)

Linux下的进程概论与编程二(进程控制)

一、进程间通信

1、IPC—-InterProcess Communication
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信
这里写图片描述

2、进程间通信的本质
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到。让不同进程能够看到一份公共资源,此公共资源就可以实现二者之间的通信,公共资源不属于任何进程,它由操作系统内核提供,属于操作系统。

二、管道(pipe)

1、管道,也叫匿名管道,是一种最基本的IPC机制,由pipe函数(系统调用)创建:

#include <unistd.h>int pipe(int filedes[2]);

返回值:pipe函数调用成功返回0,调用失败返回-1。
参数:filedes输出型参数,在pipe内部对filedes进行修改,会改变调用它的函数

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符, filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区

2、开辟管道之后的进程通信步骤
这里写图片描述

  1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

3、管道通信的代码示例
父进程关闭写端,从管道中读数据,子进程关闭读端,向管道中写数据。

  1 /**************************************  2 *文件说明:pipe.c  3 *作者:段晓雪  4 *创建时间:2017年05月23日 星期二 17时00分59秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7 #include<stdio.h>  8 #include<unistd.h>  9 #include<string.h> 10 #include<sys/wait.h> 11 int main() 12 { 13     int _fd[2]; 14     int ret = pipe(_fd);//创建管道 15     if(0 == ret) 16     { 17         printf("create pipe success.\n"); 18     } 19     else 20     { 21         printf("create pipe failure.\n"); 22         return 0; 23     } 24  25     pid_t pid = fork();//创建子进程 26     if(pid == -1) 27     { 28         perror("fork"); 29         return 1; 30     } 31     else if(pid == 0)//child 32     { 33         close(_fd[0]);//子进程关闭管道的读端 34         while(1) 35         { 36             char *str = "i am a child,i am writing."; 37              ssize_t s = write(_fd[1],str,strlen(str)); 38              if(s == -1) 39              { 40                  perror("write"); 41                  return 2; 42              } 43              sleep(1); 44         } 45     } 46     else//father 47     { 48         close(_fd[1]);//父进程关闭管道的写端 49         while(1) 50         { 51             char buff[1024]; 52             printf("i am a father,i am reading.\n"); 53             ssize_t s = read(_fd[0],buff,sizeof(buff)-1); 54             if(s > 0) 55             { 56                 buff[s] = 0; 57                 printf("read success:%s\n",buff); 58             } 59             else if(s == -1) 60             { 61                 printf("read failure:"); 62                 perror("read"); 63                 return 3; 64             } 65         } 66         wait(NULL); 67     }       68     return 0; 69 }

运行结果:
这里写图片描述

4、匿名管道通信的特点
1>两个进程通过一个管道只能进行单向通信
2>只能用于具有血缘关系之间的进程通信(缺点),通常用于父子进程
3>管道的生命周期随进程,随着进程终止而结束
4>管道之间进行通信向上层提供的读写操作面向字节流的通信服务(流:与传送数据时传送数据 的格式无关TCP UDP)
5>管道自带同步机制

5、一些相关概念
临界资源:两个进程同时访问一个资源
临界区:访问临界资源的代码
同步:让不同进程访问临界资源以不同的顺序进行访问称为进程的同步(以互斥基本前提)
二义性:多进程同时访问临界资源可能会产生二义性,即结果的不确定性
互斥:任何时刻有且只能有一个进程在访问临界资源
原子性:一件事情要么做了要么没做,没有中间状态
饥饿:一个进程想要访问某种资源,但是由于优先级或其他原因一直无法访问该资源,此状态称为进程的饥饿状态
常驻进程:一直在内存中运行的内存,最怕内存泄漏

6、与管道相关的4种状态:
1>写端一直在写数据,读端一直不读但又不关闭自己的文件描述符,直到管道写满,写端进行等待
这里写图片描述

运行结果:
这里写图片描述

2>写端不光不写,还不关闭自己的文件描述符,此时读端进行等待
这里写图片描述

运行结果:
这里写图片描述
第一点和第二点验证了管道自带同步机制

3>读端不光不读,还关闭自己的文件描述符,操作系统会终止写端的进程(异常终止)–〉读端进入僵尸状态
这里写图片描述

运行结果:
这里写图片描述

4>写端不光不写 ,还关闭自己的文件描述符,读完管道中的数据之后就返回0值,就像读到文件末尾一样。
这里写图片描述

运行结果:
这里写图片描述

三、命名管道(FIFO)

1、概念
管道的一个不足之处是没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出。

2、创建命名管道
Linux下有两种方式创建命名管道。一是在Shell下交互地建立一个命名管道,二是在程序中使用系统函数建立命名管道。
1>Shell方式下可使用mknod或mkfifo命令创建命名管道
如:

mknod namedpipe

2>创建命名管道的系统函数:mknod和mkfifo

#include <sys/types.h>#include <sys/stat.h>int mknod(const char *path,mode_t mod,dev_t dev);int mkfifo(const char *path,mode_t mode);

参数path为创建的命名管道的全路径名;
mod为创建的命名管道的模式,指明其存取权限;
dev为设备值,该值取决于文件创建的种类,它只在创建设备文件时才会用到。
这两个函数调用成功都返回0,失败都返回-1。

mknod是比较老的函数,而使用mkfifo函数更加简单和规范,所以建议在可能的情况下,尽量使用mkfifo而不是mknod。

3>命名管道创建后就可以使用了,命名管道和管道的使用方法基本是相同的。只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件

需要注意的是,调用open()打开命名管道的进程可能会被阻塞。
但如果同时用读写方式(O_RDWR)打开,则一定不会导致阻塞;
如果以只读方式(O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;
同样以写方式(O_WRONLY)打开也会阻塞直到有读方式打开管道。

3、命名管道的代码举例
打开两个终端,分别模拟客户端和服务器进行管道通信,客户端通过管道不断向服务器发送数据,服务器从管道接收由客户端发来的数据。

client.c:

  1 /**************************************  2 *文件说明:client.c  3 *作者:段晓雪  4 *创建时间:2017年05月23日 星期二 20时35分11秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7 #include<stdio.h>  8 #include<sys/types.h>  9 #include<sys/stat.h> 10 #include<fcntl.h> 11 #include<unistd.h> 12 #include<string.h> 13  14 int main() 15 { 16     umask(0);//设置默认的文件权限为0 17     int p = mkfifo("./fifo",S_IFIFO | 0666);//创建命名管道,成功返回0,失败返回-1\ 18                                           “S_IFIFO|0666”指明创建⼀一个命名管道且存取权限为0    666 19     if(p == -1)//creat named pipe failed 20     { 21         perror("mkfifo error"); 22         return 1; 23     } 24     else//creat named pipe success 25  26     { 27         int fd = open("./fifo",O_RDWR);//以读写方式打开,一定不会阻塞 28         if(-1 == fd)//open failed 29         { 30             perror("open error"); 31             return 2; 32         } 33         else//open success 34         { 35             char buff[1024]; 36             while(1) 37             { 38                 buff[0] = '\0'; 39                 printf("client write#"); 40                 fflush(stdout); 41                 //scanf("%s",buff);//输入不能有空格 42                 ssize_t ss = read(0,buff,sizeof(buff)-1);//从标准输入读数据 43                 if(ss > 0) 44                     buff[ss-1] = 0; 45                 ssize_t s = write(fd,buff,strlen(buff));//write data 46                 if(s > 0)//write success 47                 { 48                     ; 49                 } 50                 else//write failed 51                 { 52                     perror("write error"); 53                     return 3; 54                 } 55                 if(strncmp(buff,"quit",4) == 0) 56                     break; 57             }    58         }        59         close(fd); 60     }                61     return 0;    62 }           

server.c:

  1 /**************************************  2 *文件说明:server.c  3 *作者:段晓雪  4 *创建时间:2017年05月23日 星期二 21时00分21秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7 #include<stdio.h>  8 #include<sys/types.h>  9 #include<sys/stat.h> 10 #include<fcntl.h> 11 #include<unistd.h> 12 #include<string.h> 13  14 int main() 15 { 16     int fd = open("./fifo",O_RDONLY); 17     if(fd == -1)//open failed 18     { 19         perror("open file"); 20         return 1; 21     } 22     else//open success 23     { 24         char buff[1024]; 25         while(1) 26         { 27             memset(buff,'\0',sizeof(buff));//清空缓冲区 28             ssize_t rd = read(fd,buff,sizeof(buff)-1); 29             if(-1 == rd)//read failed 30             { 31                 perror("read"); 32                 break; 33             } 34             else//read success 35             { 36                 printf("server read:%s\n",buff); 37             } 38             if(strncmp(buff,"quit",4) == 0)//结束读  39                 break; 40         } 41     } 42     close(fd); 43     return 0; 44 }

3、运行过程:
1>因为我已经运行过一次了,当我再次运行时,会出现如下结果:
这里写图片描述

查看系统中的管道或ls -al查看当前文件:
这里写图片描述
ps aux | grep -E ‘fifo’:产看系统中名为fifo的命名管道文件
从上图中可以看出管道文件已经存在,要想再次运行程序必须将其删除方可。
删除方法:
1> 用rm命令删除fifo文件
2>在makefile文件clean时删除fifo文件
这里写图片描述

2>再次运行程序:
client:
这里写图片描述

server:
这里写图片描述

四、消息队列

XSI IPC(消息队列,信号量,共享内存)
1、什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息来避免命名管道的同步和阻塞问题。
消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出。消息队列与命名管道有一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。
这里写图片描述

每个消息的最大长度MSGMAX:—->8192
每个消息队列总的字节数MSGMNB:—->16384
系统消息队列的总数MSGMNI:—->32000

2、IPC对象的数据结构
内核为每个IPC对象维护一个数据结构(/usr/include/linux/ipc.h)

struct ipc_perm{key_t __key; /* Key supplied to xxxget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};

key:每个消息队列都有唯一的标识
cuid:创建者ID
cgid:创建者组ID
mode:读写权限

消息队列,共享内存和信号量都有这样一个共同的数据结构。

3、消息队列的数据结构(/usr/include/linux/msg.h)
这里写图片描述

可以看到第一个条目就是IPC结构体,即是共有的,后面的都是消息队列所私有的成员。消息队列是用链表实现的。

4、消息队列的相关函数
1>创建消息队列或取得已存在的消息队列
原型

int msgget(key_t key, int msgflg);

参数
key:可以认为是一个端口号,也可以由函数ftok生成。
msgflg:
IPC_CREAT 如果IPC不存在,则创建一个IPC资源,否则打开操作。
IPC_EXCL:只有在共享内存不存在的时候,新的共享内存才建立,否 则就产生错误。

如果单独使用IPC_CREAT,XXXget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。
如果将IPC_CREAT和IPC_EXCL标志一起使用,XXXget()将返回一个新建的IPC标识符;如果该IPC资源已存在,或者返回-1。
IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。

2>向消息队列读/写消息
原型
msgrcv从队列中取用消息:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

msgsnd将数据放到消息队列中:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数
msqid:消息队列的标识码
msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:

struct msgstru{long mtype; //大于0,消息类型char mtext[用户指定大小];};

msgsz:消息的大小。
msgtyp:从消息队列内读取的消息形态。如果值为零,则表示消息队列中的所有消息都会被读取。
msgflg:用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。

3>设置消息队列的属性
原型

int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );

参数:msgctl 系统调用对msgqid标识的消息队列执行cmd操作,系统定义了3种cmd操作 :
IPC_STAT : 该命令用来获取消息队列对应的 msqid_ds 数据结构,并将其保存到 buf 指 定的地址空间。
IPC_SET : 该命令用来设置消息队列的属性,要设置的属性存储在buf中。
IPC_RMID : 从内核中删除 msqid 标识的消息队列。

4>key_t键
System V IPC使用key_t值作为它们的名字,在Redhat linux(后续验证默认都在该平台下)下key_t被定义为int类型,追溯如下:

/usr/include/sys/ipc.h#ifndef __key_t_definedtypedef __key_t key_t;#define __key_t_defined#endif/usr/include/bits/types.htypedef __DADDR_T_TYPE __daddr_t; /* The type of a disk address. */typedef __SWBLK_T_TYPE __swblk_t; /* Type of a swap block maybe? */typedef __KEY_T_TYPE __key_t; /* Type of an IPC key *//usr/include/bits/typesizes.h#define __KEY_T_TYPE __S32_TYPE/usr/include/bits/types.h#define __S32_TYPE int

ftok函数:
函数ftok把一个已存在的路径名和一个整数标识得转换成一个key_t值,称为IPC键:

# include <sys/types.h># include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);

该函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键。

5、消息队列实例
同上,打开两个人终端,分别模拟client和server,刚开始由服务器先给客户端发送数据,客户端接收来自服务器发送的数据,紧接着由客户端发数据,服务器接收数据,后续客户端和服务器交替的进行收发数据。

同命名管道一样,在每次运行之前必须将系统已有的消息队列删除。
ipcs -q:查看系统中的消息队列(ipcs:查看系统的资源 -q:查看消息队列)
ipcrm -q 0:删除0号消息队列
消息队列的生命周期随内核,直到删除或系统关机

common.h:

  1 /**************************************  2 *文件说明:common.h  3 *作者:段晓雪  4 *创建时间:2017年05月24日 星期三 15时39分36秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7 #ifndef  __COMMON__  8 #define __COMMON__  9  10 #include<stdio.h> 11 #include<unistd.h> 12 #include<stdlib.h> 13 #include<sys/ipc.h> 14 #include<sys/msg.h> 15 #include<sys/types.h> 16 #include<string.h> 17 #include<time.h> 18  19 #define MSG_SIZE 1024 20 #define FILEPATH "." 21 #define ID 0 22 #define SERVER_TYPE 1 23 #define CLIENT_TYPE 2 24  25 typedef struct msg_info 26 { 27     long mtype; 28     char mtext[MSG_SIZE]; 29 }msginfo; 30  31 int CreatMessageQueue();//创建消息队列 32 int GetMessageQueue();//获取已经存在的消息队列 33 int DeleteMessageQueue(int msg_id);//删除消息队列 34 int SendDataToMessageQueue(int msg_id,int send_type,char* msg);//向消息队列中发消息 35 int ReceiveDataFromMessageQueue(int mag_id,int receive_type,char* out);//向消息队列里获取消36  37 #endif 38 

common.c:

  1 /**************************************  2 *文件说明:common.c  3 *作者:段晓雪  4 *创建时间:2017年05月24日 星期三 16时42分04秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7 #include"common.h"  8   9 static int CommonMessageQueue(int flags)//创建或获取消息队列,实现代码复用 10 { 11     key_t _key = ftok(FILEPATH,ID);//创建键值 12     if(_key == -1) 13     { 14        perror("ftok error"); 15        return 1; 16     } 17     int _msg_id = msgget(_key,flags);//创建或获取消息队列 18     if(_msg_id < 0) 19     { 20         perror("msgget error"); 21         return 2; 22     } 23     return _msg_id; 24 } 25  26 int CreatMessageQueue()//创建消息队列 27 { 28     return CommonMessageQueue(IPC_CREAT | IPC_EXCL | 0666);//如果将IPC_CREAT和IPC_EXCL标志>    一起\ 29                        使用,XXXget()将返回一个新建的IPC标识符;如果该IPC资源已存在,或者返    回-130 } 31  32 int GetMessageQueue()//获取消息队列 33 { 34     return CommonMessageQueue(IPC_CREAT);//单独使用IPC_CREAT,XXXget()函数要么返回一个\ 35                            已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符 36 } 37  38 int DeleteMessageQueue(int msg_id)//删除消息队列 39 { 40     if(msgctl(msg_id,IPC_RMID,NULL) < 0)//删除失败 41         return -1; 42     return 0;//删除成功 43 } 44  45 int SendDataToMessageQueue(int msg_id,int send_type,char*msg)//发送消息到消息队列 46 { 47     msginfo buff; 48     buff.mtype = send_type; 49     strcpy(buff.mtext,msg); 50     int msg_snd = msgsnd(msg_id,(void*)&buff,sizeof(buff),0);//将数据放到消息队列中 51     if(msg_snd < 0) 52     { 53         perror("msgsnd error"); 54         return -3; 55     } 56     return 0; 57 } 58  59 int ReceiveDataFromMessageQueue(int msg_id,int receive_type,char* out)//从消息队列中取消息 60 { 61     msginfo buff; 62     int msg_rcv = msgrcv(msg_id,(void*)&buff,sizeof(buff),receive_type,0); 63     if(msg_rcv < 0) 64     { 65         perror("msg_rcv error"); 66         return -4; 67     } 68     strcpy(out,buff.mtext); 69     return 0; 70 }                                                                                           

client.c:

  1 /**************************************  2 *文件说明:client.c  3 *作者:段晓雪  4 *创建时间:2017年05月24日 星期三 21时14分41秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 //client:第一次先接收消息,然后再向消息队列中发消息  9 #include"common.h" 10  11 int main() 12 { 13     char buff[MSG_SIZE]; 14     int msg_id = GetMessageQueue();//创建消息队列 15     while(1) 16     { 17         //receive data 18         ReceiveDataFromMessageQueue(msg_id,SERVER_TYPE,buff);//client receive data 19         printf("from server:%s\n",buff); 20  21         //send data 22         printf("client please enter# "); 23         fflush(stdout); 24         ssize_t s = read(0,buff,sizeof(buff)-1);//从标准输入中读数据到buff 25         if(s <= 0) 26         { 27             perror("read error"); 28             return 1; 29         } 30         else 31         { 32             buff[s-1] = 0;//去掉换行符 33             SendDataToMessageQueue(msg_id,CLIENT_TYPE,buff);//client send data 34             printf("data has sended,wait receive......\n"); 35         } 36     } 37     //DeleteMessageQueue(msg_id);//删除消息队列 38     return 0; 39 }

server.c:

  1 /**************************************  2 *文件说明:server.c  3 *作者:段晓雪  4 *创建时间:2017年05月24日 星期三 20时35分32秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 //server:第一次先向消息队列中发送消息,紧接着再接收消息...  9 #include"common.h" 10  11 int main() 12 { 13     char buff[MSG_SIZE]; 14     int msg_id = CreatMessageQueue();//获取消息队列 15  16     while(1) 17     { 18         //send data 19         printf("server please enter# "); 20         fflush(stdout);//刷新缓冲区,打印输入提示 21         ssize_t s = read(0,buff,sizeof(buff)-1);//从标准输入中读数据到消息队列 22         if(s > 0) 23         { 24             buff[s-1] = 0;//去掉换行符 25             SendDataToMessageQueue(msg_id,SERVER_TYPE,buff);//server send data 26             printf("data has sended,wait receive......\n"); 27         } 28         else 29         { 30             perror("read error"); 31             return 1; 32         } 33  34         //receive data 35         ReceiveDataFromMessageQueue(msg_id,CLIENT_TYPE,buff);//server receive data 36         printf("from client: %s\n",buff); 37     } 38     DeleteMessageQueue(msg_id);//删除消息队列 39  40     return 0; 41 }

makefile:

 1 .PHONY:all  2 all:client server  3   4 client:common.c client.c  5     gcc -o $@ $^  6 server:common.c server.c  7     gcc -o $@ $^  8   9 .PHONY:clean 10 clean: 11     rm -rf client server

运行结果:
server:
这里写图片描述

client:
这里写图片描述

五、信号量

1、信号量的引入
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。
当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减
操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性。

2、为什么要使用信号量(保护临界资源)
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它, 也就是说信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。

3、信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1

举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时
第二个进程就可以恢复执行。

4、Linux的信号量机制
1>Linux提供了一组精心设计的信号量接口来对信号量进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。

【信号量的意图在于进程间同步,互斥锁和条件变量的意图则在于线程间同步。但是信号量也可用于线程间,互斥锁和条件变量也可用于进程间。我们应该使用适合具体应用的那组原语。】

2>信号量的相关概念
信号量:进行同步或互斥的一把利器
信号量本质:计数器,记录或统计临界资源中资源数目的个数
二元信号量:该信号量计数器的值要么为0要么为1,有效或无效
互斥锁:同一时间只能有一个进程访问临界资源
信号量本身也是一种临界资源,必须保证信号量的加减操作为原子操作(保护信号量)
P操作:对信号量– V操作:对信号量++ (必须保证原子性)
而在信号量的创建及初始化上,不能保证操作均为原子性。
目的:保护临界区(临界资源)

3>信号量的相关函数
(1)得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符

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

semget函数说明:
返回值:返回信号量集标识符
申请信号量时,申请的基本单位是信号量集(申请一个,nsems=1)看似是集合,实则是数组。

ipcs -s:查看系统的信号量
ipcrm -s 0:删除系统的0号信号量

信号量数组:信号量集的组织形式
信号量集的声明周期随内核(system v的IPC:信号量集、消息队列)

(2)在指定的信号集或信号集内的某个信号上执行控制操作

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);

semctl() 在 semid 标识的信号量集上,或者该集合的第 semnum 个信号量上执行 cmd 指定的控制命令。(信号量集合索引起始于零。)根据 cmd 不同,这个函数有三个或四个参数。当有四个参数时,第四个参数的类型是union。
调用程序必须按照下面方式定义这个联合体:

union semun {int val; // 使⽤用的值struct semid_ds *buf; // IPC_STAT、IPC_SET 使⽤用缓存区unsigned short *array; // GETALL,、SETALL 使⽤用的数组struct seminfo *__buf; // IPC_INFO(Linux特有) 使⽤用缓存区};

注意:该联合体没有定义在任何系统头文件中,因此得用户自己声明。

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid, struct sembuf *sops, size_t nsops);

semop函数操作:
这里写图片描述
这里写图片描述

(4)SEM_UNDO:信号量回滚
信号量回滚:干什么,如何做到对二元信号量的互斥???
UNDO标志就是用来撤销该进程对指定信号量操作的,目的就是为了防止当一个进程获取到一个临界资源后,突然死掉,让其他要使用该资源的进程永远处于等待状态。

IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

一般来说二值信号量(用于互斥),为了防止进程意外退出引起的问题,需要设置SEM_UNDO标志。但是其他的信号量(比如生产者消费者问题中empty和full信号量), 就不能设置SEM_UNDO标志。

举个例子就是,一个进程使用semop()函数对一个信号量的计数器的值增加了1,把另一个信号量的计数器的值减2,该函数又指定了UNDO标志,当进程退出时,就会对第一个信号量减1,对第2个信号量加2。而其他进程对这2个信号量所做的改变却仍然有效。

其实,每个内核对IPC信号量资源所执行的可撤销操作,都存放在一个叫sem_undo的数据结构中,比如上个例子中semop()做的+1,-2这两个操作,在进程结束时,内核就做了一个sem_undo的数据结构中记录的数值的反向操作,来达到IPC信号量计数器回滚的目的。

5、信号量代码举例
两个进程互斥打印AA和BB(二元信号量),目标是AA和BB必须成对打印,比如AAAABB或BB ,即AA和BB要么不打印,要么两个A或两个B同时出现。

1>没有信号量的时候:

  1 /**************************************  2 *文件说明:nosem.c  3 *作者:段晓雪  4 *创建时间:2016年07月26日 星期二 06时56分49秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 #include<stdio.h>  9 #include<unistd.h> 10  11 int main() 12 { 13     pid_t pid = fork();//创建子进程 14  15     if(pid == -1) 16     { 17         perror("fork error"); 18         return -1; 19     } 20     else if(pid == 0)//child 21     { 22        while(1) 23        { 24            printf("A"); 25            fflush(stdout); 26            usleep(123456); 27            printf("A"); 28            fflush(stdout); 29            usleep(12345); 30        } 31     } 32     else//father 33     { 34        while(1) 35        { 36           printf("B"); 37           fflush(stdout); 38           usleep(123456); 39           printf("B"); 40           fflush(stdout); 41           usleep(12345); 42        } 43     } 44     return 0; 45 }

运行结果:
这里写图片描述
程序的打印完全是乱的,并没有按预想的成对出现。

2>用二元信号量对其进行控制
sem.h:

  1 /**************************************  2 *文件说明:sem.h  3 *作者:段晓雪  4 *创建时间:2017年05月26日 星期五 19时10分29秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7 #ifndef __SEM__  8 #define __SEM__  9  10 #include<stdio.h> 11 #include<sys/sem.h> 12 #include<sys/ipc.h> 13 #include<unistd.h> 14 #include<string.h> 15 #include<stdlib.h> 16 #include<errno.h> 17  18 #define KEY_PATH "." 19 #define PROJECT 88 20  21 union semun 22 { 23     int val; // 信号量使用的值 24     struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区 25     unsigned short *array; // GETALL,、SETALL 使用的数组 26     struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区 27 }; 28  29 int create_sem();//创建信号量 30 int init_sem(int which);//初始化信号量 31 int get_sem();//获取信号量 32 int delete_sem();//删除信号量 33 int p_sem(int which);//信号量的P操作 34 int v_sem(int which);//信号量的V操作 35 int show_sem_value(int sem_num);//显示信号量的值 36  37 #endif //__SEM__

sem.c:

  1 /**************************************  2 *文件说明:sem.c  3 *作者:段晓雪  4 *创建时间:2016年07月26日 星期二 04时44分56秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 #include<stdio.h>  9 #include<unistd.h> 10 #include"sem.h" 11  12 int main() 13 { 14     int sem_id = create_sem();//创建信号量 15     init_sem(0);//初始化信号量 16     pid_t pid = fork();//创建子进程 17  18     if(pid == -1) 19     { 20         perror("fork error"); 21         return -1; 22     } 23     else if(pid == 0)//child 24     { 25        while(1) 26        { 27            p_sem(0);//对信号量进行P操作 28            printf("A"); 29            fflush(stdout); 30            usleep(123456); 31            printf("A"); 32            fflush(stdout); 33            usleep(12345); 34            v_sem(0);//对信号量进行V操作 35        } 36     } 37     else//father 38     { 39        while(1) 40        { 41           p_sem(0);//对信号量进行P操作 42           printf("B"); 43           fflush(stdout); 44           usleep(123456); 45           printf("B"); 46           fflush(stdout); 47           usleep(12345); 48           v_sem(0);//对信号量进行V操作 49        } 50     } 51     delete_sem();//删除信号量 52    return 0; 53 }

comm.c

  1 /**************************************  2 *文件说明:comm.c  3 *作者:段晓雪:  4 *创建时间:2017年05月26日 星期五 20时42分40秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 #include"sem.h"  9  10 //success:return the id of semaphore set 11 //failure:return -1 12 int create_sem()//创建信号量 13 { 14      key_t key = ftok(KEY_PATH,PROJECT);//获取键值 15      if(key == -1) 16      { 17          perror("create_sem:ftok error"); 18          return -1; 19      } 20      int ret = semget(key,1,IPC_CREAT | IPC_EXCL | 0666); 21      if(ret == -1) 22      { 23          perror("create_sem:semget error"); 24          return -1; 25      } 26      return ret; 27 } 28  29 int init_sem(int which)//初始化信号量 30 { 31     int sem_id = get_sem(); 32     union semun _semun;//定义联合体 33     _semun.val = 1; 34     int ret = semctl(sem_id,which,SETVAL,_semun); 35     if(ret == -1) 36         perror("init_sem:semctl error"); 37  38     return ret; 39 } 40  41 //success:return sem_id,failure:return -1 42 //semset_num is return semset num 43 int get_sem()//获取信号量 44 { 45      key_t key = ftok(KEY_PATH,PROJECT); 46      if(key == -1) 47      { 48          perror("get_sem:ftok error"); 49          return -1; 50      } 51      return semget(key,0,0); 52 } 53  54 //failure:return -1,success:return errno 55 int delete_sem()//删除信号量 56 { 57     int sem_id = get_sem(); 58     int ret = semctl(sem_id,0,IPC_RMID,NULL); 59     if(ret == -1) 60         perror("delete_sem:semctl errno"); 61  62     return ret; 63 } 64  65 static int op_sem(int op,int which)//操作信号量:P操作、V操作 66 { 67     int sem_id = get_sem(); 68     struct sembuf sem;// 69     memset(&sem, '\0', sizeof(sem)); 70     sem.sem_num = which; 71     sem.sem_op = op; 72     sem.sem_flg = 0;//SEM_UNDO:信号量回滚 73     return semop(sem_id,&sem,1/*sem num*/); 74 } 75  76 int p_sem(int which)//信号量的P操作 77 { 78     int ret = op_sem(-1,which); 79     if(ret == 0) 80         ;//printf("P opreator is successful!\n"); 81     else 82         ;//printf("P operator is failed!\n"); 83     return ret; 84 } 85  86 int v_sem(int which)//信号量的V操作 87 {    88     int ret = op_sem(1,which); 89     if(ret == 0) 90         ;//printf("V operator is successful!\n"); 91     else 92         ;//printf("V operator is failed!\n"); 93     return ret; 94 } 95  96 int show_sem_value(int sem_num)//显示信号量的值 97 {     98     int sem_id = get_sem(); 99     union semun _semun;100     unsigned short *_sem_list=(unsigned short *)malloc(sizeof(unsigned short)*sem_num);101     if( NULL == _sem_list )102     {103         perror("malloc error");104         return -1;105     }106     memset(_sem_list, '\0', sizeof(unsigned short) * sem_num);107     _semun.array=_sem_list;108     int ret = semctl(sem_id, 0, GETALL, _semun);109     if(ret == -1)110         perror("get sem error");111     else112     {113         int i=0;114         for(; i<sem_num; i++)115         {116              printf("sem[%d] is : %d\n",i, _semun.array[i]);117         }118     }119     free(_sem_list);120     _semun.array=NULL;121 return ret;122 }    

makefile:

  1 .PHONY:all  2 all:sem nosem  3 sem:sem.c comm.c  4     gcc -o $@ $^  5 nosem:nosem.c  6     gcc -o $@ $^  7 .PHONY:clean  8 clean:  9     rm -rf sem nosem

运行结果:
这里写图片描述
AA实现了BB成对出现。

六、共享内存

1、共享内存通信的原理
这里写图片描述

能通信的原因:将同一个映射到不同的地址空间
特点:公共资源在物理内存
共享内存是所有进程通信中速度最快的(至少少了两次数据拷贝)
共享内存是不带任何同步与互斥的通信机制
共享内存属于系统IPC,生命周期随内核

查看共享内存:ipcs -m
删除共享内存:ipcrm -m 0

2、相关函数
1>创建共享内存
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符

#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);

函数说明:
这里写图片描述
这里写图片描述

2>管理共享内存:完成对共享内存的控制

 #include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);

函数说明:
这里写图片描述这里写图片描述

3>(关联)挂接:
把共享内存区对象映射到调用进程的地址空间,类似malloc,平白无故多了一块内存。 连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

#include <sys/types.h>#include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);int shmdt(const void *shmaddr);

函数说明:
这里写图片描述这里写图片描述

4>(去关联)分离
断开共享内存连接,与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存

#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);int shmdt(const void *shmaddr);

函数说明:
这里写图片描述

3、共享内存的代码实例
通过共享内存让父子进程进行通信,打印26个英文字母。
shm.h:

  1 /**************************************  2 *文件说明:shm.h  3 *作者:段晓雪  4 *创建时间:2016年07月26日 星期二 08时10分02秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 #ifndef __SHM__  9 #define __SHM__ 10  11 #include<stdio.h> 12 #include<unistd.h> 13 #include<sys/shm.h> 14 #include<sys/ipc.h> 15 #include<sys/wait.h> 16  17 #define PATH "." 18 #define PROJECT 8888 19 #define SHM_SIZE 4*1024 //4K对齐 20  21 int get_shm();//创建共享内存  22 char* at_shm(int shm_id);//关联共享内存 23 int delete_shm(int shm_id);//删除共享内存 24 int dt_shm(char* addr);//去关联 25  26 #endif //__SHM__

shm.c:

  1 /**************************************  2 *文件说明:shm.c  3 *作者:段晓雪  4 *创建时间:2016年07月26日 星期二 08时27分17秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 #include"shm.h"  9  10 int get_shm()//创建共享内存 11 { 12     key_t key = ftok(PATH,PROJECT); 13     if(key == -1) 14     { 15         perror("ftok error"); 16         return -1; 17     } 18     int shm_id = shmget(key,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);//创建共享内存 19     if(shm_id == -1) 20         perror("shmget error"); 21     return shm_id; 22 } 23  24 char* at_shm(int shm_id)//关联共享内存 25 { 26     return (char*)shmat(shm_id,NULL,0);//以读写方式挂接 27 } 28  29 int delete_shm(int shm_id)//删除共享内存 30 { 31     int ret = shmctl(shm_id,IPC_RMID,NULL); 32     if(ret == -1) 33         perror("shmctl error"); 34     return ret; 35 } 36  37 int dt_shm(char* addr)//去关联 38 { 39     return shmdt(addr); 40 }

test.c:

  1 /**************************************  2 *文件说明:test.c  3 *作者:段晓雪  4 *创建时间:2016年07月26日 星期二 09时00分26秒  5 *开发环境:Kali Linux/g++ v6.3.0  6 ****************************************/  7   8 #include"shm.h"  9  10 int main() 11 { 12     int shm_id = get_shm();//创建共享内存 13     pid_t pid = fork(); 14  15     if(pid == -1) 16     { 17         perror("fork error"); 18         return -1; 19     } 20     else if(pid == 0)//child 21     { 22         char* buff = at_shm(shm_id);//关联共享内存 23         int i = 0; 24         while(i < 26) 25         { 26             buff[i] = 'A'+i; 27             ++i; 28         } 29         buff[25] = '\0'; 30         dt_shm(buff);//去关联 31     } 32     else//father 33     { 34         char* buff = at_shm(shm_id);//关联共享内存 35         sleep(1); 36         printf("%s\n",buff); 37         dt_shm(buff);//去关联 38         waitpid(pid,NULL,0); 39         delete_shm(shm_id);//删除共享内存 40     } 41     return 0; 42 }

makefile:

  1 .PHONY:all  2 all:shm_test  3   4 shm_test:test.c shm.c  5     gcc -o $@ $^ -g  6   7 .PHONY:clean  8 clean:  9     rm -rf shm_test

运行结果:
这里写图片描述

七、进程通信的总结

1、为什么要进行进程间通信?
1>数据传输
一个进程需要将它的数据发送给另一个进程。
2>资源共享
多个进程之间共享同样的资源。
3>通知事件
一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
4>进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。

Linux进程间通信(IPC)由以下几部分发展而来:
1>UNIX进程间通信
2>基于System V进程间通信
3>POSIX进程间通信

2、posix 和 system V
Linux继承的进程间通信
这里写图片描述

最初Unix IPC包括:管道、FIFO、信号;
System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;
Posix IPC包括: Posix消息队列、Posix信号灯、Posix共享内存区

3、linux下进程间通信的几种主要手段简介:
1 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

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

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

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

5 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5、进程间通信各种方式效率比较
这里写图片描述
注:无连接: 指无需调用某种形式的OPEN,就有发送消息的能力流控制:
如果系统资源短缺或者不能接收更多消息,则发送进程能进行流量控制

6、各种通信方式的比较和优缺点
管道:速度慢,容量有限,只有父子进程能通讯

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

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

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

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

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

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

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

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

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

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

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

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

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

阅读全文
0 1
原创粉丝点击