IPC:进程间通信
1、古老的通信方式
无名管道 有名管道 信号
2、IPC对象通信
消息队列(用的相对少,这里不讨论)
共享内存
信号量集
3、socket通信
网络通信
线程信号,posix() sem_init(信号量)
特例:古老的通信方式中信号是唯一的异步通信
所有的通信方式中共享内存是唯一的最高效
管道:
管道-->无名管道、有名管道
无名管道 --->pipe --->只能给有亲缘关系进程通信
有名管道 --->fifo --->可以给任意单机进程通信
管道的特性:
1、管道是 半双工的工作模式(两条管道,一根只能收,一根只能发,对单一进程来说)
2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetc,
open,read,write,close;
1,读端存在,一直向管道中去写,超过64k,写会阻塞。
2,写端是存在的,读管道,如果管道为空的话,读会阻塞。
3.管道破裂(进程退出),读端关闭,写管道。
4. read 0 ,写端关闭,如果管道没有内容,read 0 ;
有读没有写,读阻塞
有写没有读,管道破裂
使用框架:
创建管道 ==》读写管道 ==》关闭管道
无名管道
1、无名管道 ===》管道的特例 ===>pipe函数
特性:
1.1 亲缘关系进程使用
1.2 有固定的读写端
流程:
创建并打开管道: pipe函数
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
pipefd[1] ==>无名管道的固定写端
返回值:成功 0
失败 -1;
注:
注意事项:
1、无名管道的架设应该在fork之前进行。(因为只有创建管道的进程(父进程或子进程)会有管道的文件描述符,而另一个进程则无法访问管道,从而无法进行通信)
无名管道的读写:===》文件IO的读写方式。
读: read()
写: write()
关闭管道: close();
问题:
1.父子进程是否都有fd[0] fd[1]? 都有
如果在单一进程中写fd[1]能否直接从fd[0]中读到?
可以,写fd[1]可以从fd[0]读(操作系统操作好了,其实管道只有一个实体,只是读写操作分成了fd[0]与fd[1])
----->>>
2.管道的数据存储方式是什么样的(管道数据是保存在内核管理的内存缓冲区中)
数据是否一直保留?(不会一直保留,在管道被关闭时释放)
栈:
局部变量:栈用于存储函数的局部变量、返回地址、函数参数等。
生命周期:栈上的数据通常在函数调用结束时销毁。
大小限制:栈的大小通常是有限的,并且由操作系统或程序设置。
管道:
内核管理:管道的缓冲区是由操作系统内核管理的,不直接与进程的栈相关。
生命周期:管道缓冲区的生命周期与管道本身相同,通常在管道被关闭时释放。
大小限制:管道缓冲区的大小通常是固定的,并且与特定的操作系统实现有关。
堆:
大小:堆的大小不是固定的,它可以根据程序的需要动态增长或缩小。
生命周期:堆内存的生命周期由分配和释放操作决定,与函数调用的生命周期无关。
访问方式:堆内存的访问是随机的,没有固定的顺序。
多线程共享:在多线程程序中,堆内存是共享的,所有线程都可以访问堆上的数据。
3、管道的数据容量是多少,有没有上限值。
操作系统的建议值: 512* 8 = 4k(4kb是1页的大小,管道大小不能小于1页)
代码测试实际值: 65536byte= 64k(16页,管道的大小必须为4k的整数倍,少了会向上补全)
4、管道的同步效果如何验证?读写同步验证。
读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止(管道破裂)
写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0(如果写端是开启的,read将会被阻塞)
结论:读写端必须同时存在,才能进行(系统内部的读写阻塞)
管道的读写。
5、固定的读写端是否就不能互换?
能否写fd[0] 能否读fd[1]? 不可以,是固定读写端。
有名管道
有名管道===》fifo ==》有文件名称的管道。
文件系统中可见
框架:
创建有名管道 ==》打开有名管道 ==》读写管道
==》关闭管道 ==》卸载有名管道
1、创建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
remove();
2、打开有名管道 open
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定
当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
3、管道的读写: 文件IO
读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));(写端看情况可以strlen)
问题:
1、是否需要同步,以及同步的位置。
读端关闭 是否可以写,不能写什么原因。
写端关闭 是否可以读。
结论:有名管道执行过程过必须有读写端同时存在。
如果有一端没有打开,则默认在open函数部分阻塞。
2、有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。
信号通信
kill -xx xxxx(终端下命令)(使用kill -l可以看到所有的信号列表)(ps aux命令查看进程号)
发送进程 信号 接收进程号
kill -9 1000
a.out 9 1000
1、发送端
#include <sys/types.h>
#include <signal.h>
信号列表:
函数:
int kill(pid_t pid, int sig);
功能:通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
sig 当前程序要发送的信号编号 《=== kill -l
返回值:成功 0
失败 -1;
int raise(int sig)== kill(getpid(),int sig);
功能:给进程自己发送sig信号
unsigned int alarm(unsigned int seconds);SIGALAM
功能:定时由系统给当前进程发送信号
也称为闹钟函数
闹钟只有一个,定时只有一次有效,
但是必须根据代码逻辑是否执行判断。
int pause(void);
功能:进程暂停,不再继续执行,除非
收到其他信号。
接收端
每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、默认处理
2、忽略处理 9,19
3、自定义处理 9,19 捕获
以上三种方式的处理需要在如下函数上实现。
信号注册函数原型:
void ( *signal(int signum, void (*handler)(int)) ) (int);
-->signal函数第一个参数为信号号数,第二个参数为函数指针(返回值void*,参数为int)
signal函数外面套着的返回值,因为其返回值是一个函数指针(返回值void*,参数为int)
在系统中使用了typedef来定义
-------------------------------------------------------------------------------------------------------------------------
typedef void (*sighandler_t)(int);
===》void (*xx)(int); == void fun(int);
===》xx是 void fun(int) 类型函数的函数指针
===》typedef void(*xx)(int) sighandler_t; ///错误写法
typedef int myint;
===>sighandler_t signal(int signum, sighandler_t handler);
===> signal(int sig, sighandler_t fun);
===> signal(int sig, xxx fun);
===>fun 有三个宏表示:SIG_DFL 表示默认处理
SIG_IGN 表示忽略处理
fun 表示自定义处理(自定义函数,也就是函数指针void (*fun)(int))
注:
编写信号注册函数,并测试所有的32个系统信号
是否能全部被忽略? 如果不能,则找出信号编号。
结论: 9 19 不能被忽略也不能被自定义(停止和暂停)
1、必须事先定义自定义函数,必须是如下格式:
void fun(int sig) sig 接收到的信息编号
{
}
2、在所有的信号中有如下两个特列:
10 SIGUSR1
12 SIGUSR2
专门预留给程序员使用的未定义信号。