Linux进程与线程之 进程间通信IPC

管道 PIPE

通常指无名管道, 是 UNIX 系统 IPC 最古老的形式
管道是由内核管理的一个缓冲区,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。
一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。
特点

  1. 半双工(即数据只能在一个方向上流动) , 具有固定的写端和读端
  2. 需要双方通信时,需要建立起两个管道;
  3. 它只能用于具有亲缘关系的进程之间的(父子进程或兄弟进程之间)
  4. 万物皆文件, 可以被看成是一种特殊的文件, 可以使用普通的读些 write,read
    等函数。 但它不是普通文件, 并不属于其他文件系统,单独构成一种文件系统,并且只存在与内存中。
    管道只能在本地计算机中使用,而不可用于网络间的通信。
    函数原型:
#include <unistd.h>
int pipe(int fd[2]); //返回值, 若成功返回 0, 失败返回-1
eg.int fd[2]  
int result = pipe(fd);  

当一个管道建立时, 它会创建两个文件描述符: fd[0]为读而打开, fd[1]为写而打
开,关闭管道只需将这两个文件描述符关闭即可。当两个进程都终结的时候,管道也自动消失。
写入与读取的顺序原则是 先进先出

命名管道 FIFO

命名管道是一种特殊类型的文件,它在系统中以文件形式存在。
这样克服了管道的弊端,他可以 允许没有亲缘关系的进程间通信。
特点:

  1. FIFO 可以在无关的进程之间交换数据, 与无名管道不同。
  2. FIFO 有路径名与之相关联, 它以一种特殊设备文件形式存在于文件系统中。
    函数原型:
 #include <sys/types.h>   
 #include <sys/stat.h>
 //建立一个名字为filename的命名管道,并指定权限;函数返回值: 成功返回 0, 出错返回-1
int mkfifo(const char *filename,mode_t mode); 
eg.mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );  

FIFO 的通信方式类似于在进程中使用文件来传输数据, 只不过 FIFO 类
型文件同时具有管道的特性。
在数据读出时, FIFO 管道中同时清除数据, 而且==“先进先出”== 。

一般来说FIFO和PIPE一样总是处于阻塞状态。
也就是说如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并向管道写入数据。
这个阻塞动作反过来也是成立的。
如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK标志,以关闭默认的阻塞操作。

信号 signal

信号机制是unix系统中最为古老的进程之间的通信机制,用于一个或几个进程之间传递异步信号。信号可以有各种异步事件产生,比如键盘中断等。shell也可以使用信号将作业控制命令传递给它的子进程。

#include <sys/types.h>   
#include <signal.h>   
void (*signal(int sig,void (*func)(int)))(int); //用于截取系统信号,第一个参数为信号,第二个参数为对此信号挂接用户自己的处理函数指针。返回值为以前信号处理程序的指针。  
eg.int ret = signal(SIGSTOP, sig_handle);  

int kill(pid_t pid,int sig); //kill函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig。  
int raise(int sig);//向当前进程中自举一个信号sig, 即向当前进程发送信号。  
#include <unistd.h>   
unsigned int alarm(unsigned int seconds); 
//alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。使用alarm函数的时候要注意alarm函数的覆盖性,即在一个进程中采用一次alarm函数则该进程之前的alarm函数将失效。  
int pause(void); //使调用进程(或线程)睡眠状态,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。 
消息队列 Message queues

消息队列是消息的链接表, 存放在内核中。 一个消息队列由一个标识符(即队列ID) 来标识。

#include <sys/types.h>   
#include <sys/stat.h>   
#include <sys/msg.h>   

在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。

消息队列的优势在于
1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

特点
1、消息队列是面向记录的, 其中的消息具有特定的格式以及特定的优先级。
2、消息队列独立于发送与接收进程, 进程终于时, 消息队列及其内容并不会被删除。
3、 消息队列可以实现消息的随即查询, 消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

信号量 Semaphore
#include <sys/types.h>   
#include <sys/stat.h>  
#include <sys/sem.h>   

int semget(key_t key, int num_sems, int sem_flags); //semget函数用于创建一个新的信号量集合 , 或者访问一个现有的集合(不同进程只要key值相同即可访问同一信号量集合)。第一个参数key是ftok生成的键值,第二个参数num_sems可以指定在新的集合应该创建的信号量的数目,第三个参数sem_flags是打开信号量的方式。  
eg.int semid = semget(key, 0, IPC_CREATE | IPC_EXCL | 0666);//第三个参数参考消息队列int msgget(key_t key,int msgflag);第二个参数。  
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops); //semop函数用于改变信号量的值。第二个参数是要在信号集合上执行操作的一个数组,第三个参数是该数组操作的个数 。  
eg.struct sembuf sops = {0, +1, IPC_NOWAIT};//对索引值为0的信号量加一。  
semop(semid, &sops, 1);//以上功能执行的次数为一次。  
int semctl(int sem_id, int sem_num, int command,...); //semctl函数用于信号量集合执行控制操作,初始化信号量的值,删除一个信号量等。 类似于调用msgctl(), msgctl()是用于消息队列上的操作。第一个参数是指定的信号量集合(semget的返回值),第二个参数是要执行操作的信号量在集合中的索引值(例如集合中第一个信号量下标为0),第三个command参数代表要在集合上执行的命令。
``

信号量与已经介绍过的 IPC 结构不同, 它是一个计数器。
信号量常用于实现进程间的互斥与同步, 而不是用于存储进程间通信数据。
特点
 1.  信号量用于进程间同步, 若要在进程间传递数据需要结合共享内存
 2. 信号量基于擦做系统的 PV 操作, 程序对信号量的操作都是原子操作。
 3. 每次信号量的 PV 操作不仅限于对信号量值加 1 或减 1, 而且可以加减任意正整数支持信号量组。
最简单的信号量是只能读取 01 的变量, 这也是信号量最常见的一种形式, 叫做二值信号量。 
而可以取多个正整数的信号量被称为通用信号量。
linux 下的信号量函数都是在通用的信号量数组上进行操作, 而不是在一个单一
的二值信号量上进行操作.

#### 共享内存 Share Memory
 指的一个或多个进程共享一个给定的存储区。
```c
#include <sys/types.h>   
#include <sys/stat.h>  
#include <sys/shm.h>   
int shmget(key_t key,size_t size,int shmflg);  //shmget函数用来创建一个新的共享内存段, 或者访问一个现有的共享内存段(不同进程只要key值相同即可访问同一共享内存段)。第一个参数key是ftok生成的键值,第二个参数size为共享内存的大小,第三个参数sem_flags是打开共享内存的方式。  
eg.int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);//第三个参数参考消息队列int msgget(key_t key,int msgflag);  
void *shmat(int shm_id,const void *shm_addr,int shmflg); //shmat函数通过shm_id将共享内存连接到进程的地址空间中。第二个参数可以由用户指定共享内存映射到进程空间的地址,shm_addr如果为0,则由内核试着查找一个未映射的区域。返回值为共享内存映射的地址。  
eg.char *shms = (char *)shmat(shmid, 0, 0);//shmid由shmget获得  
int shmdt(const void *shm_addr); //shmdt函数将共享内存从当前进程中分离。 参数为共享内存映射的地址。  
eg.shmdt(shms);  
int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//shmctl函数是控制函数,使用方法和消息队列msgctl()函数调用完全类似。参数一shm_id是共享内存的句柄,cmd是向共享内存发送的命令,最后一个参数buf是向共享内存发送命令的参数。

特点

  1. 共享内存是最快的一种 IPC(进程间通信) , 因为进程是直接对内存进行存取。
  2. 因为多个进程可以同时操作, 所以需要进行同步。
  3. 信号量+共享内存通常结合在一起操作, 信号量用来同步对共享内存的访问。

比较:

1. 管道: 速度慢, 容量有限, 只有父子进程能通讯
2. FIFO(命名管道): 任何进程间都能通讯, 但速度慢
3. 消息队列: 容量受到系统限制, 且要注意第一次读的时候, 要考虑上一次没有读完数据的问题
4. 信号量: 不能传递复杂消息, 只能用来同步
5. 共享内存: 能够很容易控制容量, 速度快, 但是要保持同步, 比如一个进程在写的时候,另一个进程要注意读的问题, 相当于线程中的线程安全。 当然, 共享内存区同样可以用作线程间通讯, 不过没这个必要, 线程间本来就已经共享了用一进程的一块内存。 

消息队列、信号量以及共享内存的相似之处:

它们被统称为XSI IPC,它们在内核中有相似的IPC结构
(消息队列的msgid_ds,信号量的semid_ds,共享内存的shmid_ds)
,而且都用一个非负整数的标识符加以引用(消息队列的msg_id,信号量的sem_id,共享内存的shm_id,分别通过msgget、semget以及shmget获得),标志符是IPC对象的内部名,每个IPC对象都有一个键(key_t key)相关联,将这个键作为该对象的外部名。

XSI IPC和PIPE、FIFO的区别:

1、XSI IPC的IPC结构是在系统范围内起作用,没用使用引用计数。
如果一个进程创建一个消息队列,并在消息队列中放入几个消息,进程终止后,即使现在已经没有程序使用该消息队列,消息队列及其内容依然保留。
而PIPE在最后一个引用管道的进程终止时,管道就被完全删除了。
对于FIFO最后一个引用FIFO的进程终止时,虽然FIFO还在系统,但是其中的内容会被删除。

2、和PIPE、FIFO不一样,XSI IPC不使用文件描述符,
所以不能用ls查看IPC对象,不能用rm命令删除,不能用chmod命令删除它们的访问权限。
只能使用ipcs和ipcrm来查看可以删除它们。

内存映射文件 Memory Map

内存映射文件,是由一个文件到一块内存的映射.
使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作。 每一个使用该机制的进程通过把同一个共享的文件映射到自己的进程地址空间来实现多个进程间的通信(这里类似于共享内存,只要有一个进程对这块映射文件的内存进行操作,其他进程也能够马上看到)。
使用内存映射文件不仅可以实现多个进程间的通信,还可以用于 处理大文件提高效率。

因为我们普通的做法是 把磁盘上的文件先拷贝到内核空间的一个缓冲区再拷贝到用户空间(内存),用户修改后再将这些数据拷贝到缓冲区再拷贝到磁盘文件,一共四次拷贝。
如果文件数据量很大,拷贝的开销是非常大的。
那么问题来了,系统在在进行内存映射文件就不需要数据拷贝?
mmap()确实没有进行数据拷贝,真正的拷贝是在在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,所以只进行一次数据拷贝。效率高于read/write

#include <sys.mman.h> 
void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); 
//mmap函数将一个文件或者其它对象映射进内存。 第一个参数为映射区的开始地址,设置为0表示由系统决定映射区的起始地址,第二个参数为映射的长度,第三个参数为期望的内存保护标志,第四个参数是指定映射对象的类型,第五个参数为文件描述符(指明要映射的文件),第六个参数是被映射对象内容的起点。成功返回被映射区的指针,失败返回MAP_FAILED[其值为(void *)-1]。

int munmap(void* start,size_t length); 
//munmap函数用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。如果解除映射成功则返回0,否则返回-1,错误原因存于errno中错误代码EINVAL。 
int msync(void *addr,size_t len,int flags); 
//msync函数实现磁盘文件内容和共享内存取内容一致,即同步。第一个参数为文件映射到进程空间的地址,第二个参数为映射空间的大小,第三个参数为刷新的参数设置。

共享内存和内存映射文件的区别:

内存映射文件是利用虚拟内存把文件映射到进程的地址空间中去,在此之后进程操作文件,就像操作进程空间里的地址一样了,
比如使用c语言的memcpy等内存操作的函数。
这种方法能够很好的应用在需要频繁处理一个文件或者是一个大文件的场合,这种方式处理IO效率比普通IO效率要高。
共享内存是内存映射文件的一种特殊情况,内存映射的是一块内存,而非磁盘上的文件。
共享内存的主语是进程(Process),操作系统默认会给每一个进程分配一个内存空间,每一个进程只允许访问操作系统分配给它的哪一段内存,而不能访问其他进程的。而有时候需要在不同进程之间访问同一段内存,怎么办呢?操作系统给出了 创建访问共享内存的API,需要共享内存的进程可以通过这一组定义好的API来访问多个进程之间共有的内存,各个进程访问这一段内存就像访问一个硬盘上的文件一样。

内存映射文件与虚拟内存的区别和联系:

内存映射文件和虚拟内存都是操作系统内存管理的重要部分,两者有相似点也有不同点。


联系:虚拟内存和内存映射都是将一部分内容加载到内存,另一部放在磁盘上的一种机制。对于用户而言都是透明的。
区别:
虚拟内存是硬盘的一部分,是内存和硬盘的数据交换区,许多程序运行过程中把暂时不用的程序数据放入这块虚拟内存,节约内存资源。
内存映射是一个文件到一块内存的映射,这样程序通过内存指针就可以对文件进行访问。

虚拟内存的硬件基础是分页机制。另外一个基础就是局部性原理(时间局部性和空间局部性),这样就可以将程序的一部分装入内存,其余部分留在外存,当访问信息不存在,再将所需数据调入内存。而内存映射文件并不是局部性,而是使虚拟地址空间的某个区域银蛇磁盘的全

套接字socket通信

套接字机制不但可以单机的不同进程通信,而且使得跨网机器间进程可以通信。

套接字的创建和使用与管道是有区别的,套接字明确地将客户端与服务器 区分开来,可以实现多个客户端连到同一服务器。

#include <sys/types.h> 
#include <sys/socket.h> 
int socket(it domain,int type,int protocal); 
int bind(int socket,const struct sockaddr *address,size_t address_len); 
int listen(int socket,int backlog); 
int accept(int socket,struct sockaddr *address,size_t *address_len); 
int connect(int socket,const struct sockaddr *addrsss,size_t address_len);
连接过程
服务器套接字连接过程描述: 
首先,服务器应用程序用socket创建一个套接字,它是系统分配服务器进程的类似文件描述符的资源。
 接着,服务器调用bind给套接字命名。这个名字是一个标示符,它允许linux将进入的针对特定端口的连接转到正确的服务器进程。 
 然后,系统调用listen函数开始接听,等待客户端连接。
 listen创建一个队列并将其用于存放来自客户端的进入连接。 
 当客户端调用connect请求连接时,服务器调用accept接受客户端连接,accept此时会创建一个新套接字,用于与这个客户端进行通信。 

客户端套接字连接过程描述: 
客户端首先调用socket创建一个未命名套接字,让后将服务器的命名套接字作为地址来调用connect与服务器建立连接。 
只要双方连接建立成功,我们就可以像操作底层文件一样来操作socket套接字实现通信。 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程一时爽Cxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值