进程之IPC通信一
IPC通信
1.IPC通信 简介
IPC :Internal Procces Communication 进程间通信
 实质:信息(数据)的交换(通信)
 在父进程内定义一个全局变量A,然后再给A进行赋值。然后再让子进程去读取,不就实现通信了吗。
 这个方案:纯属扯淡。**原因:**两个进程的地址空间是独立的(物理地址)。
#include <iostream>
#include  <unistd.h>
#include <sys/types.h>
#include <wait.h>
using namespace std;int main()
{int *p =  new int();*p = 100;pid_t pid = fork();if(pid  < 0){perror("pid_t");}if(pid  == 0){sleep(1);cout << "子进程读取p的值:" << *p << "   p的地址:"<< p << endl;}else if(pid  > 0){*p = 200;cout << "父进程读取p的值:" << *p << "   p的地址:"<< p << endl;wait(nullptr);}return 0;
}
结果是:
父进程读取p的值:200   p的地址:0x648db8855eb0
子进程读取p的值:100   p的地址:0x648db8855eb0
由此可见父进程修改成200之后子进程p还是100,虽然他们的地址一样还是资源不能通信共享,因为这个地址是虚拟地址,他们再实际的物理地址上面还是独自的地址,所有不能通信
如果两个进程要进行通信,必须要把数据放到一个大家都可以访问的地方。
 <文件>:可以支持进程间的通信,大家都可以访问。
#include <iostream>
#include  <unistd.h>
#include <sys/types.h>
#include <wait.h>
#include <fcntl.h>
using namespace std;int main()
{pid_t pid = fork();if(pid  < 0){perror("pid_t");}if(pid  == 0){FILE * fptr = fopen("1.txt",  "a+");if(fptr == nullptr){perror("打开失败");}sleep(1);char a = '\0';fread(&a, 1, 1,fptr );cout << a << endl;fclose(fptr);}else if(pid  > 0){FILE * fptr = fopen("1.txt",  "a+");if(fptr == nullptr){perror("打开失败");}fwrite("a", 1, 1, fptr);fclose(fptr);wait(nullptr);}return 0;
}
可以通信,但有一个很大的问题:速度太慢了。
有没有其他方式?
 在操作系统内核中开辟一段空间(某种机制),进程去访问它。这个内核空间对于进程而言是共享的。
1.1 IPC方式
 
- 管道: - pipe无名管道
- fifo有名管道
 
- 信号: - signal
 
- 消息队列 - System V消息队列
- POSIX消息队列
 
- 信号量 - System V信号量
- POSIX信号量
 
- 共享内存 - System V共享内存
- POSIX共享内存
 
- socket 通信 - unix域协议
 
2.管道
在很久以前,进程间通信方式,都是通过文件。这种方式有个缺点:效率(速度)太低了。但是这种方
 式有一个天大的好处:简单,不需要额外API 函数支持(直接利用文件系统的API 操作)
 因为弊端所有需要改进,首先问题存在于:文件内容是在外设上面,文件系统上====>访问效率低。
管道:管道文件,它的文件内容是在内核/内存中。
2.1 无名管道:pipe
它在文件系统中没有名字( inode ),它的内容存放在内核中,访问pipe的方式是通过文件系统的 API(read/write)。
它不能用open,但是read/write有需要一个文件描述符。所以创建这个pipe的时候,就必须要返回 一个文件描述符!!!!
pipe在创建的时候,在内核中开辟一块区域作为缓冲区,作为pipe的文件的内容区域的存储空间,同 时返回两个文件描述符**(一个是用来读,一个用来写)**
它有如下特点:
- pipe有两端,一端用于写,一端用于读 按顺序读,
- 不支持lseek光标偏移
- 内容读走,就莫有了
- pipe(无名管道)随内核持续性

pipe的API
-  创建pipe管道 Alpha、IA-64、MIPS、SuperH 和 SPARC/SPARC64架构上使用的pipe系统调用的声明/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */struct fd_pair {long fd[2];};struct fd_pair pipe();/* On all other architectures */其他在其他架构上使用 pipe接口- 函数原型
 #include <unistd.h> int pipe(int pipefd[2]);- 描述 - 创建pipe管道
- pipe用于在内核中创建一个无名管道
- pipefd用来保存创建好的无名管道的文件描述符,pipe创建管道,默认“阻塞方式”
 
- 参数 @pipefd:- int类型数组,表示管道文件的描述符
- pipefd[0]表示读端文件描述符
- pipefd[1]表示写端文件描述符
 
- 返回值 - 成功返回0
- 失败返回-1,同时errno被设置。
 
 
示例
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
using namespace std;
int main()
{// 定义一个无名管道int pipefd[2] = {0};pipe(pipefd);// 定义一个进程pid_t pid = fork();// 失败if (pid < 0){perror("创建进程失败");}// 父进程if (pid > 0){// 关闭写端,父进程只负责读close(pipefd[1]);// 父进程负责读for (int i = 0; i < 10; i++){char c = '0';read(pipefd[0], &c, 1);cout << c << endl;}// 等待子进程写端关闭再关闭读端usleep(100);// pipefd[0]就是文件指针close(pipefd[0]);// 等待子进程结束wait(nullptr);}// 子进程if (pid == 0){// 关闭读端,子进程只负责写close(pipefd[0]);// 子进程负责写for (int i = 0; i < 10; i++){char c = '0' + i;write(pipefd[1], &c, 1);}// 写完立刻关闭读端// pipefd[1]就是文件指针close(pipefd[1]);}return 0;
}结果
1
2
3
4
5
6
7
8
9
关闭pipe时候,需要注意,要先关闭写端,再关闭读端。
 pipe(无名管道)只能用于有亲缘关系的进程间通信,它为什么有这个限制?
- 因为它没有名字
- 假设它如果在文件系统中有个一个名字 “ inode”它就可以用于任意进程间的通信
2.2 有名管道:fifo
fifo是在pipe基础上,给它在文件系统中创建一个inode(它会在文件系统中有一个名字),但是
 fifo文件的内容却是在内核中!!!!
- fifo的文件名随文件系统持续性
- fifo的文件内容存在于内核,随内核持续性
fifo同pipe一样,除了fifo在文件系统中有一个文件名外。
fifo 的API 
创建fifo文件
- 函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 描述:
在文件系统中创建一个fifo(有名管道)文件
-  参数 -  pathname:需要创建的fifo文件的名字
-  mode:fifo文件的权限-  (1)宏值设置: -  S_IRUSR 
-  S_IWUSR 
-  S_IXUSR 
-  … 
 
-  
-  (2)八进制: -  0777 
-  0774 
-  0665 
-  … 
 
-  
 
-  
 
-  
-  返回值 @return: -  成功返回0, 
-  失败返回-1,同时 errno被设置。
 
-  
注意:FIFO特殊文件(命名管道)类似于管道,只是它是作为文件系统的一部分访问的。它可以通过 多个进程打开以进行读取或写入。当进程通过FIFO交换数据时内核在内部传递所有数据,而不将其写 入文件系统(交换只在内核处理,不会进入到磁盘里面)。
因此,FIFO特殊文件没有内容在文件系统上;文件系统条目只是作为一个参考点,这样进程就可以访
 问管道使用文件系统中的名称。内核为至少一个进程打开的每个FIFO特殊文件保留一个管道对象。
 这个必须先打开FIFO的两端(读取和写入),然后才能传递数据。通常,打开FIFO一端直到另一端也
 被打开。进程可以在非阻塞模式下打开FIFO。在Linux下,打开FIFO进行读写操作在阻塞和非阻塞模
 式下都会成功。
-  阻塞的读/写 - 读的时候,如果没有数据,则read会阻塞
- 写的时候,如果没有空间,则write会阻塞
 
-  非阻塞的读/写 -  读的时候,如果没有数据,则立即返回,设置对应错误码 
-  写的时候,如果没有空间,则立即返回,设置对应错误码 
 
-  
在使用open的时候,默认是阻塞方式,非阻塞则需要或上一个宏:NONBLOCK
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <wait.h>
#include <fcntl.h>
using namespace std;
int main()
{// 创建mkfifo文件mkfifo("fifo", S_IXUSR| 0777) ;// 打开文件int fifo = open("fifo", O_RDWR);if(fifo == -1){perror("fifo"); return -1;}// 创建一个子进程pid_t pid = fork();if(pid < 0){perror("pid");close(fifo);return -1;}// 父进程 读if(pid > 0){for(int i = 0; i < 10; i++){char c = '0';read(fifo,&c,1);cout << c << endl;}// 等待子进程写端关闭再关闭读端usleep(200);close(fifo);// 等待子进程结束wait(nullptr);}// 子进程 写if(pid ==  0){// 写入  for(int i = 0; i < 10; i++){char c = '0' + i;write(fifo,&c,1);}close(fifo);}return 0;
}3.信号
信号是进程间通信一种方式,这个种方式没有传输数据。只是在内核中传一个信号(整数),信号的表 示是一个整数,不同信号值,代表不同含义,当然用户可以自定义信号,以及自定义信号的含义解释权 归用户所有。
Signal     Value     Action   Comment──────────────────────────────────────────────────────────────────────SIGHUP        1       Term    Hangup detected on controlling terminalor death of 
controlling process/*控制终端的挂起操作,或者是控制进程死亡时,控制终端上的所有
进程都会收到SIGHUP信号*/SIGINT        2       Term    Interrupt from keyboard/*从键盘接收一个中断信号,如:ctrl + c*/SIGQUIT       3       Core    Quit from keyboard/*Core:输出信息,然后中止如:ctrl + zctrl + d*/SIGILL        4       Core    Illegal Instruction/*非法指令*/SIGABRT       6       Core    Abort signal from abort(3)/*调用abort函数时候,进程会收到SIGABRT这个信号。*/SIGFPE        8       Core    Floating-point exception/*浮点运算异常的时候,产生SIGFPE信号*/SIGKILL       9       Term    Kill signal/*杀死信号,中止*/SIGSEGV      11       Core    Invalid memory reference/*非法内存引用的时候,会收到SIGSEGV信号*/SIGPIPE      13       Term    Broken pipe: write to pipe with noreaders; see 
pipe(7)/*当往一个管道中写入数据的时候,没有读端进程的时候就会产生
SIGPIPE信号*/SIGALRM      14       Term    Timer signal from alarm(2)/*定时信号,进程在调用alarm的时候,会在超时的时候产生
SIGALRM信号*/SIGTERM      15       Term    Termination signalSIGUSR1   30,10,16    Term    User-defined signal 1/*用户自定义信号1*/SIGUSR2   31,12,17    Term    User-defined signal 2/*用户自定义信号2*/SIGCHLD   20,17,18    Ign     Child stopped or terminated/*当子进程停止或者中止的时候,父进程会收到SIGCHLD信号。*/SIGCONT   19,18,25    Cont    Continue if stoppedSIGSTOP   17,19,23    Stop    Stop processSIGTSTP   18,20,24    Stop    Stop typed at terminalSIGTTIN   21,21,26    Stop    Terminal input for background processSIGTTOU   22,22,27    Stop    Terminal output for background process进程在收到一个信号的时候,通常会有三种处理方式:
捕捉信号:
-  把一个信号 与 用户自定义的信号处理函数关联起来 
-  那么在收到该信号的时候,就会自动调用该函数处理 
-  默认行为: -  收到一个信号的时候,采用操作系统默认的行为 
-  大部分信号的默认行为,是会直接干掉进程。 
-  只有一个信号 SIGCHLD是被忽略的。忽略该信号 
 
-  
3.1 信号的处理过程
通过 “ 软件中断/软中断 ” 来实现,信号处理函数起始在 “ 中断上下文 ” 执行,信号处理函数----->" 软中断服务函数 "
 进程上下文:
-  进程在大环境下 , “ 时间片轮转 ” 
-  一个进程的执行状态又分为: -  用户态:执行用户自己的代码的时候 
-  内核态:进入操作系统执行内核代码的时候 
 
-  
-  在状态切换的时候,要保存用户自己代码的运行到的位置,保存的这个玩意就是上下文。 
-  文简单来说,就是当前任务(进程或线程)执行时的“状态”和“环境”。 它包含以下关键内容: - 程序计数器(PC):指示下一条要执行的指令的地址。
- 寄存器内容:像通用寄存器、状态寄存器等,用于存储临时数据和指令执行状态。
- 栈指针:指向当前任务的栈顶位置。
 当发生中断或任务切换时,系统会保存当前上下文信息,以便之后能准确恢复并继续执行任务。 
-  什么是中断? - 中断是一种计算机系统中的机制,用于让 CPU 暂停当前正在执行的程序,转而处理其他紧急或重要的事件
 
-  中断与函数区别总结如下: -  定义: - 中断:异步事件处理机制,由硬件或软件触发,使 CPU 暂停当前程序,处理重要事件。
- 函数:程序中的代码块,实现特定功能,由程序员定义和调用。
 
-  触发方式: - 中断:外部设备或软件指令触发,随机性强。
- 函数:程序调用语句触发,顺序确定。
 
-  执行流程: - 中断:CPU 暂停进程,执行中断处理程序,完成后恢复进程。
- 函数:按调用顺序执行,完成后返回调用处。
 
-  作用及目的: - 中断:处理外部事件、多任务、提升响应速度。
- 函数:组织代码、复用代码、提高可读性。
 
-  与进程的关系: - 中断:可打断进程正常执行。
- 函数:在进程控制下执行,不主动打断进程。
 
 
-  
3.2 linux下信号相关的API
 
3.2.1 发送信号
- kill函数原型
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
-  描述: - 给指定的进程发送一个指定的信号
 
-  参数 - pid:指定信号接收者(可能是多个进程)- pid> 0 :- pid表示接收者进程 一对一发送
- pid= 0 :发送信号给与调用者同组的所有进程 一对同组多个
- pid=-1 :发送信号给所有进程(有权限发送的所有进程) 一对所有
- *pid<-1* :发送信号给组- id等于``pid绝对值的所有进程。 一对指定组多个
 
- *sig:*- 要发送的信号
 
 
-  返回值 @return: - 成功返回0,(表示至少有一个进程成功接收到了信号)
- 失败返回-1,同时errno被设置
 
-  raisel函数原型
#include <signal.h>int raise(int sig);
- 作用: - 发送信号给自己
 
- 参数 - sig:信号值
 
- raise(SIGINT) <=====> kill(getpid(),SIGINT)
3.2.2 捕获信号
- 函数原型
 #include <signal.h>typedef void (*sighandler_t)(int);-  函数作用 -  类型重定义语句 
-  将指向一个无返回值,带有一个int类型参数的函数指针类型重定义成新类型,类型名 sighandler_t
 
-  
-  函数原型 
 sighandler_t signal(int signum, sighandler_t handler);
-  作用:
 捕获一个信号,执行用户自定义的处理函数
-   参数: -  signum:要捕获那个信号值
-   handler: 信号的处理方式,(用户捕获到那个信号之后,应该要执行的函数)- (1**)自定义函数**
 void my_handler_function(int sig) { // 写你想干的事情 }-  ( 2) SIG_IGN忽略该信号
-  ( 3) SIG_DEL:default- 采用系统默认的处理方式
 
 
 
-  
- 返回值 @return: - 成功返回该信号的上一次处理方式(函数指针)
- 失败返回SIG_ERR,同时errno被设置
 
示例 中断信号
#include <iostream>
#include <signal.h>
using namespace std;
void mySingalHandler(int sig)
{static int count = 0;if(count < 10){cout<< "你ctrl + c是没有办法杀死我的" << endl;count++;}else{cout << "好吧,你赢了" << endl;exit(0);}
}int main()
{//捕获信号 中断信号signal(SIGINT, mySingalHandler);while(1);return 0;
}
示例 内存访问违规信号
#include <iostream>
#include <signal.h>
using namespace std;// 自定义信号处理函数
void mySingalHandler(int sig)
{cout << "非法内存访问" << endl;//非法内存访问信息exit(0);}int main()
{//捕获信号 内存访问违规信号 signal(SIGSEGV,mySingalHandler );// 访问非法内存内存int *p =nullptr;*p = 1;while(1);return 0;
}
3.2.3 设置闹钟信息
- 函数原型
#include <unistd.h>unsigned int alarm(unsigned int seconds);-   作用: - 设置一个闹钟信号,在多少秒之后发送一个闹钟信号
 
-  返回值 - **seconds:**多少秒之后,发送一个闹钟信号
 
-   返回值 @return: - 返回上一个闹钟的剩余秒数
 
如:
 alarm(5);int r = alarm(10); // 10秒之后接收一个 闹钟信号
// 前面的闹钟会被替换/取消 r就是表示上一个闹钟的剩余秒数
....alarm(0); // 取消闹钟
alarm :定时发送一个闹钟信号给本进程,“ 闹钟 ” 每一个进程都有一个属于自己的 “ 闹钟 ”。闹钟的时
 间到了,进程就会收到一个SIGALRM 的信号,但是同一时刻一个进程只有一个 “ 闹钟 ”生效。
 大部分的信号的默认行为,是把收到信号的进程Kill掉。那么如果要改变接收到信号之后的行为:捕获信
 号。
示例
#include <iostream>
#include <signal.h>
using namespace std;// 自定义信号处理函数
void mySingalHandler(int sig)
{cout << "非法内存访问" << endl; // 非法内存访问信息alarm(2);
}int main()
{// 捕获信号 内存访问违规信号signal(SIGALRM, mySingalHandler);// 设置一个2秒的闹钟,闹钟到期时会触发SIGALRM信号alarm(2);while (1);return 0;
}
3.2.4 等待信号
- 函数原型
#include <unistd.h>int pause(void);
-  pause函数描述: - 让进程停在那里等待一个信号的到来
 
-  作用: - 等待一个信号的到来
 
-  返回值 @return: - 成功返回信号标号
- 失败返回-1。
 示例 #include <iostream> #include <signal.h> using namespace std;// 自定义信号处理函数 void mySingalHandler(int sig) {cout << "非法内存访问" << endl; // 非法内存访问信息alarm(2); }int main() {// 捕获信号 内存访问违规信号signal(SIGALRM, mySingalHandler);// 设置一个2秒的闹钟,闹钟到期时会触发SIGALRM信号alarm(2);//让进程停在那里等待一个信号的到来pause();return 0; }
