linux---进程---进程间通信IPC

Linux环境进程间通信.pdf

https://blog.csdn.net/ucan23/article/details/46003713

2、信号(Signal)

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

3、报文(Message)队列(消息队列)

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

4、共享内存(最常用且最高效)

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

消息队列和管道基本上都是4次拷贝,而共享内存(mmap, shmget)只有两次。
4次:1,由用户空间的buf中将数据拷贝到内核中。2,内核将数据拷贝到内存中。3,内存到内核。4,内核到用户空间的buf;
2次: 1,用户空间到内存。 2,内存到用户空间。

6、套接口(Socket)

更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

以下三种类型的IPC合称为System V IPC:
System V 消息队列
System V 信号量
System V 共享内存区
在这里插入图片描述

1、管道(Pipe)及有名管道(named pipe)

在Linux文本流中,我们提到可以使用管道将一个进程的输出和另一个进程的输入连接起来,从而利用文件操作API来管理进程间通信。在shell中,我们经常利用管道将多个进程连接在一起,从而让各个进程协作,实现复杂的功能。
传统IPC (interprocess communication)。我们主要是指消息队列(message queue),信号量(semaphore),共享内存(shared memory)。这些IPC的特点是允许多进程之间共享资源,这与多线程共享heap和global data相类似。由于多进程任务具有并发性 (每个进程包含一个进程,多个进程的话就有多个线程),所以在共享资源的时候也必须解决同步的问题 (参考Linux多线程与同步)。
(1)日常使用少,只有大型程序才能用上
(2)更为复杂,属于linux应用编程中难度最大的部分
(3)细节多
(4)面试较少涉及,对找工作帮助不大
(5)建议后续深入学习时再来实际写代码详细探讨

管道容量64k

管道实现进程间通信

(1)父进程创建管道,得到两个件描述符指向管道的两端
(2)父进程fork出子进程,子进程也有两个文件描述符指向同管道。
(3)父进程关闭fd[0],子进程关闭fd[1],即子进程关闭管道读端,父进程关闭管道写端(因为管道只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
父进程写,子进程读的实例
在这里插入图片描述
管道读写的4类情况:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

pipe管道特点

1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。
2.管道只允许单向通信。(半双工)
3.管道内部保证同步机制,从而保证访问数据的一致性。
4.面向字节流
5.管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。

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

无名管道:亲缘关系进程间的通信(如父子进程之间的通信)

(1)管道通信的原理:内核维护的一块内存,有读端和写端(管道是单向通信的)
(2)管道通信的方法:父进程创建管理后fork子进程,子进程继承父进程的管道fd
(3)管道通信的限制:只能在父子进程间通信、半双工
(4)管道通信的函数:pipe、write、read、close

由pipe()函数创建:
#include <unistd.h>
int pipe(int fd[2]);
参数fd为整数数组名,管道创建成功后系统为管道分配的两个文件描述符将通过这个数组返回到用户进程中: fd[0]为读而打开, fd[1]为写而打开。 fd [1]的输出是 fd[0]的输入。
无名管道不能保证写入的原子性,需要注意的是向管道中写入数据时,必须关闭管道的读取端,反之,从管道中读取数据时,也必须关闭管道的写端,代码示例:

#define INPUT 0 
#define OUTPUT 1 
 
void main() { 
int file_descriptors[2]; 
/*定义子进程号 */ 
pid_t pid; 
char buf[256]; 
int returned_count; 
/*创建无名管道*/ 
pipe(file_descriptors); 
/*创建子进程*/ 
if((pid = fork()) == -1) { 
printf("Error in fork/n"); 
exit(1); 
} 
/*执行子进程*/ 
if(pid == 0) { 
printf("in the spawned (child) process.../n"); 
/*子进程向父进程写数据,关闭管道的读端*/ 
close(file_descriptors[INPUT]); 
write(file_descriptors[OUTPUT], "test data", strlen("test data")); 
exit(0); 
} else { 
/*执行父进程*/ 
printf("in the spawning (parent) process.../n"); 
/*父进程从管道读取子进程写的数据,关闭管道的写端*/ 
close(file_descriptors[OUTPUT]);       
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); 
printf("%d bytes of data received from spawned process: %s/n", 
returned_count, buf); 
} 
} 

有名管道fifo:任何两个进程之间的通信

(1)有名管道的原理:实质也是内核维护的一块内存,表现形式为一个有名字的文件
(2)有名管道的使用方法:固定一个文件名,2个进程分别使用mkfifo创建fifo文件,然后分别open打开获取到fd,然后一个读一个写
(3)管道通信限制:半双工(注意不限父子进程,任意2个进程都可)
(4)管道通信的函数:mkfifo、open、write、read、close

在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:
     方式一:mkfifo(“myfifo”,“rw”);
     方式二:mknod myfifo p
生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。

删除命名管道的方法是:
int unlink(const char *pathname);//pathname是要删除的命名管道的全路径。

代码示例:

/* 进程一:读有名管道*/ 
#include <stdio.h> 
#include <unistd.h> 
void main() { 
FILE * in_file; 
int count = 1; 
char buf[80]; 
in_file = fopen("mypipe", "r");      //在这里设置为默认的阻塞方式
if (in_file == NULL) { 
printf("Error in fdopen./n"); 
exit(1); 
} 
while ((count = fread(buf, 1, 80, in_file)) > 0) 
printf("received from pipe: %s/n", buf); 
fclose(in_file); 
} 
 /* 进程二:写有名管道*/ 
#include <stdio.h> 
#include <unistd.h> 
void main() { 
FILE * out_file; 
int count = 1; 
char buf[80]; 
out_file = fopen("mypipe", "w");             //在这里设置为默认的阻塞方式
if (out_file == NULL) { 
printf("Error opening pipe."); 
exit(1); 
} 
sprintf(buf,"this is test data for the named pipe example/n"); 
fwrite(buf, 1, 80, out_file); 
fclose(out_file); 
} 

5、信号量(semaphore):进程间or线程间

信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

信号量工作原理

由于信号量只能进行两种操作等待P(sv)和发送V(sv)信号
(1P(sv,Proberen测试,为阻塞原语):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
	1、测试控制该资源的信号量
	2、信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位
	3、若此时信号量的值为0,则进程进入挂起状态(进程状态改变),直到信号量的值大于0,若进程被唤醒则返回至第一步。
(2V(sv,Verhogen增加,为唤醒原语):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的

二元信号量

二元信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。

消息队列:

消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。
事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。
3共享内存:就是多个进程将同一块内存区域映射到自己的进程空间中,以此来实现数据的共享和传输。
第一步:创建共享内存:
首先要用的函数是shmget,它获得一个共享存储标识符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);
其中第一个参数是:共享内存的键值,可以由用户指定,也可以调用ftok()来生成
第二个参数是:共享内存的大小
第三个参数是:创建共享内存并设定其存取权限
代码示例:

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <stdlib.h>
#include <stdlio.h>
#include <unistd.h>
 
#define SHM_SIZE   1024
int main()
{
   int shmid;
   key_t key;
   key=ftok();           // key_t ftok( char * fname, int id )   生成共享内存的键值  fname就是你指定的文件名(已经存在的文件名),一般使用当    //前目录,如:key_t key;key = ftok(".", 1); 这 样就是将fname设为当前目录。
   if(key < 0)
   {
   perrror("ftok error\n");
   exit(1);
   }
    shmid = shmget(key,SHM_SIZE,IPC_CREAT | 0666 );  //创建一块共享内存
    if(shmid < 0)
    {
    perrror("shmget error\n");
    exit(1);
    }
else {
           printf(""创建共享内存完成!\n);}
return 0;
}

创建 完共享内存后使用ipcs -m命令来查看系统中的共享内存。
第二步:读写共享内存:在读写之前必须使用shmat()函数将共享内存映射到进程的地址空间中才可以进行访问。
void *shmat(int shmid, void *addr, int flag);
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。

3.4.19.SystemV IPC介绍
3.4.19.1、SystemV IPC的基本特点
(1)系统通过一些专用API来提供SystemV IPC功能
(2)分为:信号量、消息队列、共享内存
(3)其实质也是内核提供的公共内存
3.4.19.2、消息队列
(1)本质上是一个队列,队列可以理解为(内核维护的一个)FIFO
(2)工作时A和B2个进程进行通信,A向队列中放入消息,B从队列中读出消息。
3.4.19.3、信号量
(1)实质就是个计数器(其实就是一个可以用来计数的变量,可以理解为int a)
(2)通过计数值来提供互斥和同步
3.4.19.4、共享内存
(1)大片内存直接映射
(2)类似于LCD显示时的显存用法
3.4.19.5、剩余的2类IPC
(1)信号
(2)Unix域套接字 socket

猜你喜欢

转载自blog.csdn.net/qq_42024067/article/details/107450454