【IO进程线程】
创始人
2024-06-02 15:54:50
0

进程间通信

进程进通信简介

假如A进程和B进程想要通信,按照之间讲解的进程的原理是无法完成的,A和B进程的内存空间是独立的0-3G。如果想要实现通信就必须借助内核空间才能完成,因为内核空间是共用的。在linux系统中进程间通信的方式有7种。
(1)传统进程间通信(3种)
​ 1.无名管道
​ 2.有名管道
​ 3.信号
(2)system V版本引入IPC进程间通信(3种)
​ 1.消息队列
​ 2.共享内存
​ 3.信号灯集
(3)BSD(伯克利分校)版本中引入的socket通信
​ 1.套接字通信(通过网络实现进程间通信)
(4)Android系统中
​ 1.binder机制

无名管道

无名管道通信的原理

无名管道只能由于亲缘关系的进程间通信,无名管道是在内核中实现的。如果A和B进程想要通过无名管道通信,A向管道的一端发送消息,B进程从管道的另外一端读取消息即可。无名管道的通信方式不支持使用lseek函数。无名管道的大小是64K。如果A一直往管道中写数据,B不读取。A写满的时候就会阻塞。如果A没有写,B取读取数据,B进程阻塞。无名管道是半双工的通信方式。

A和B的通信方式:
单工: A------------->B
半双工:在同一时刻内只能一边发一边收
​ A------>B A<-------B
全双工:在同一时刻两边都可以收发
​ A<-------->B
在这里插入图片描述

无名管道的API

int pipe(int pipefd[2]);
功能:Pipe()创建一个管道,这是一个可以用于进程间通信的单向数据通道。数组pipefd用于返回引用管道两端的两个文件描述符。Pipefd [0]是指管道的读端。Pipefd[1]指管道的写端。写入到管道写端的数据由内核进行缓冲,直到从管道读端读取。
参数:@pipefd :返回管道两端的文件描述符pipefd[0]读端pipefd[1]写端
返回值:成功返回0,失败返回-1置位错误码

无名管道的实例

#include int main(int argc, const char* argv[])
{pid_t pid;int pipefd[2];char buf[128] = { 0 };// 1.创建无名管道if (pipe(pipefd))PRINT_ERR("pipe error");// 2.创建进程pid = fork();if (pid == -1) {PRINT_ERR("fork error");} else if (pid == 0) {close(pipefd[0]); // 关闭子进程的读端// 子进程向管道中写数据 pipefd[1]while (1) {fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = '\0';write(pipefd[1], buf, strlen(buf));if (strcmp(buf, "quit") == 0)break;}close(pipefd[1]);exit(0);} else {close(pipefd[1]); // 关闭父进程的写端// 从管道中读数据 pipefd[0]while (1) {memset(buf, 0, sizeof(buf));read(pipefd[0], buf, sizeof(buf));if (strcmp(buf, "quit") == 0)break;printf("buf = %s\n", buf);}close(pipefd[0]);wait(NULL);}return 0;
}

无名管道的特点

1.只能用于亲缘关系的进程的通信
2.无名管道是半双工的通信方式
3.不支持使用lseek函数
4.无名管道的大小是64K

#include int main(int argc, const char* argv[])
{pid_t pid;int pipefd[2];char buf[128] = { 0 };// 1.创建无名管道if (pipe(pipefd))PRINT_ERR("pipe error");for(int i=0;i<65537;i++){write(pipefd[1],"a",1); //写第65537个字符的时候,因为管道满了,写阻塞printf("i = %d\n",i);}printf("********************\n"); //这句话不打印return 0;
}

无名管道读写的特点

读端存在,写管道 :有多少写多少,直到写满(64K)位置。

#include int main(int argc, const char* argv[])
{pid_t pid;int pipefd[2];char buf[128] = { 0 };// 1.创建无名管道if (pipe(pipefd))PRINT_ERR("pipe error");for(int i=0;i<65537;i++){write(pipefd[1],"a",1);printf("i = %d\n",i);}printf("********************\n");return 0;
}

读端不存在,写管道:管道破裂,收到SIGPIPE信号,杀死进程

#include int main(int argc, const char* argv[])
{pid_t pid;int pipefd[2];char buf[128] = { 0 };pid = fork();if (pid == -1) {PRINT_ERR("fork error");} else if (pid == 0) {if (pipe(pipefd))PRINT_ERR("pipe error");close(pipefd[0]); // 关闭读端write(pipefd[1], "a", 1); // 写管道while (1);}else{int wstatus;wait(&wstatus);if (WIFSIGNALED(wstatus)) { // 信号结束的子进程printf("signal = %d\n", WTERMSIG(wstatus)); //打印让子进程退出的信号号//子进程收到了管道破裂的信号,被杀死了。父进程收到了它的退出状态。// signal=13 ,13就是管道破裂的信号}}return 0;
}

写端存在,读管道 :有多少读多少,没有数据的时候读阻塞

#include int main(int argc, const char* argv[])
{int pipefd[2];char buf[128] = { 0 };if (pipe(pipefd))PRINT_ERR("pipe error");write(pipefd[1], "hello world", 11); // 写管道while (1){read(pipefd[0],buf,sizeof(buf));printf("buf = %s\n",buf);}return 0;
}

写不存在,读管道 :管道中有多少数据就读走多少数据,没有数据的时候,立即返回。

#include int main(int argc, const char* argv[])
{int pipefd[2];char buf[128] = { 0 };if (pipe(pipefd))PRINT_ERR("pipe error");write(pipefd[1], "hello world", 11); // 写管道close(pipefd[1]);while (1){memset(buf,0,sizeof(buf));read(pipefd[0],buf,sizeof(buf));printf("buf = %s\n",buf);sleep(1);}return 0;
}

有名管道

有名管道通信的原理

有名管道可以在任意进程间进程通信,因为在创建有名管道的时候在用户空间会产生一个有名管道的文件,如果A和B进程想要通信,只需要让A和B进程打开这个文件即可得到文件描述符,此时两者就可以通信的。有名管道的文件在内存存储并没有在硬盘存储(掉电丢失)。有名管道的大小也是64K。有名管道也是不支持使用lseek。
在这里插入图片描述

有名管道的API

int mkfifo(const char *pathname, mode_t mode);
功能:创建有名管道
参数:@pathname:管道文件的路径及名字@mode:管道文件的权限(mode & (~(umask)))
返回值:成功返回0,失败返回-1置位错误码

有名管道的实例

01mkfifo.c

#include 
#define FIFO_NAME "./myfifo"int main(int argc,const char * argv[])
{if(mkfifo(FIFO_NAME,0666))PRINT_ERR("mkfifo error");//阻塞等待getchar();//删除管道文件char buf[128] = {0};snprintf(buf,sizeof(buf),"rm -rf %s",FIFO_NAME);system(buf); return 0;
}

02write.c

#include 
#define FIFO_NAME "./myfifo"
int main(int argc, const char* argv[])
{int fd;char buf[128] = {0};if ((fd = open(FIFO_NAME, O_WRONLY)) == -1)PRINT_ERR("open error");while (1) {printf("input > ");fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = '\0';write(fd, buf, strlen(buf));if (strcmp(buf, "quit") == 0)break;}close(fd);return 0;
}

03read.c

#include 
#define FIFO_NAME "./myfifo"
int main(int argc, const char* argv[])
{int fd;char buf[128] = { 0 };if ((fd = open(FIFO_NAME, O_RDONLY)) == -1)PRINT_ERR("open error");while (1) {memset(buf, 0, sizeof(buf));read(fd, buf, sizeof(buf));if (strcmp(buf, "quit") == 0)break;printf("buf = %s\n", buf);}close(fd);return 0;
}

有名管道读写的特点

1.有名管道可以用于任意进程间的通信
2.有名管道半双工的通信方式
3.有名管道的大小也是64K
4.有名管道也是不支持使用lseek
5.有名管道读写特点

读端存在,写管道:有多少写多少,直到写满位置(64K),写阻塞。
读端不存在,写管道
​   读端没有打开过,写管道:此时写端在open的位置阻塞
​   读端先打开后关闭,写管道:管道破裂,收到SIGPIPE信号,杀死进程
写端存在,读管道:有多少读多少,没有数据可读的时候读阻塞。
写端不存在,读管道
​   写端没有打开过,读管道:此时读端在open的位置阻塞
​   写端先打开后关闭,读管道:有多少读多少,如果没有数据可读的时候立即返回。

信号

信号的工作原理

在进程执行的时候,用户可以给进程发送信号,进程收到信号后,对信号的处理有三种方式,分别是:捕捉,忽略,默认,大部分进程信号的默认是杀死进程,信号的发送和执行是异步过程

信号的查看命令kill -l(小写的L)

1-31为稳定信号,发出就一定会处理,34-64为不稳定信号,内核不使用
在这里插入图片描述

发信号的命令

kill -信号号 PID

常用的信号

image-20221114144343069
在这里插入图片描述
SIGCHLD:当子进程退出的时候,父进程会收到这个信号

注:在所有的信号中,只有SIGKILL/SIGSTOP两个信号不能被捕捉,也不能被忽略。只能执行默认的动作。

进程对信号处理的API

#include typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:注册一个信号处理函数
参数:@signum:信号号@handler:信号处理的方式SIG_IGN:忽略SIG_DFL:默认自己填写信号处理函数:捕捉void signal_handle(int signo){}
返回值:成功返回handler(handler是一个函数指针,返回它的地址),失败返回值SIG_ERR,并置位错误码

进程对信号处理的三种方式实例

#include void signal_handle(int signo)
{if (signo == SIGINT) {printf("我收到了一个ctrl+c的信号\n");}
}
int main(int argc, const char* argv[])
{// 1.对SIGINT进行捕捉,如果安下ctrl+c,会执行信号处理函数//  if(signal(SIGINT,signal_handle)==SIG_ERR)//      PRINT_ERR("signal error");// 2.对SIGINT进行忽略// if (signal(SIGINT, SIG_IGN) == SIG_ERR)//     PRINT_ERR("signal error");// 3.对SIGINT进行默认处理if (signal(SIGINT, SIG_DFL) == SIG_ERR)PRINT_ERR("signal error");while (1);return 0;
}

信号的练习实例

1.尝试对SIGUSR1信号进行忽略、默认、捕捉
#include void signal_handle(int signo)
{if (signo == SIGUSR1) {printf("我收到了一个SIGUSR1的信号\n");}
}
int main(int argc, const char* argv[])
{// 1.对SIGUSR1进行捕捉,//  if(signal(SIGUSR1,signal_handle)==SIG_ERR)//      PRINT_ERR("signal error");// 2.对SIGUSR1进行忽略// if (signal(SIGUSR1, SIG_IGN) == SIG_ERR)//     PRINT_ERR("signal error");// 3.对SIGUSR1进行默认处理if (signal(SIGUSR1, SIG_DFL) == SIG_ERR)PRINT_ERR("signal error");while (1);return 0;
}
//发信号  kill -10  pid
2.尝试捕捉管道破裂的信号(SIGPIPE)
#include void handle(int signo)
{if(signo == SIGPIPE){printf("我收到了管道破裂的信号\n");}if(signo == SIGINT){printf("我收到了一个ctrl+c的信号\n");}}
int main(int argc, const char* argv[])
{int pipefd[2];char buf[128] = { 0 };if(signal(SIGPIPE,handle)==SIG_ERR)PRINT_ERR("signal error");if(signal(SIGINT,handle)==SIG_ERR)PRINT_ERR("signal error");if (pipe(pipefd))PRINT_ERR("pipe error");close(pipefd[0]); // 关闭读端write(pipefd[1], "a", 1); // 写管道while (1);return 0;
}
3.尝试使用非阻塞的方式回收子进程(借助SIGCHLD信号完成)
#include 
void handle(int signo)
{waitpid(-1, NULL,WNOHANG); 
}
int main(int argc, const char* argv[])
{pid_t pid;pid = fork();if (pid == -1) {PRINT_ERR("fork error");} else if (pid == 0) {sleep(1);printf("child1 :pid = %d\n", getpid());} else {if(signal(SIGCHLD,handle)==SIG_ERR)PRINT_ERR("signal error");while(1);}return 0;
}

发信号相关的函数

int raise(int sig);
功能:给自己发信号
参数:@sig:信号号
返回值:成功返回0,失败返回非0int kill(pid_t pid, int sig);
功能:给指定pid的进程发送信号
参数:@pid:进程号pid > 0 :给pid号的进程发信号pid = 0 :给同组的进程发送信号pid = -1:给所有有权限的进程发送信号pid <-1:首先会对pid取绝对值,给和这个绝对值相同的组的进程发送信号@信号号
返回值:成功返回0,失败返回-1置位错误码unsigned int alarm(unsigned int seconds);
功能:当seconds倒计时为0的时候发送SIGALRM信号
参数:@seconds:秒钟数,如果填写为0,取消挂起的信号
返回值:如果alarm是第一次调用,返回0.如果alarm不是第一次调用,返回上一次调用的剩余秒钟数

raise实例

#include 
void handle(int signo)
{//回收子进程的资源waitpid(-1, NULL,WNOHANG); //给自己发送杀死的信号raise(SIGKILL);
}
int main(int argc, const char* argv[])
{pid_t pid;pid = fork();if (pid == -1) {PRINT_ERR("fork error");} else if (pid == 0) {sleep(1);printf("child1 :pid = %d\n", getpid());} else {if(signal(SIGCHLD,handle)==SIG_ERR)PRINT_ERR("signal error");while(1);}return 0;
}

kill函数实例

test.c

#include int main(int argc,const char * argv[])
{while(1);return 0;
}

kill.c

#include 
//./a.out pid
int main(int argc, const char* argv[])
{if(kill(atoi(argv[1]),SIGKILL))PRINT_ERR("kill error");return 0;
}

alarm函数实例

#include void handle(int signo)
{printf("我收到了一个闹钟信号\n");
}
int main(int argc,const char * argv[])
{if(signal(SIGALRM,handle)==SIG_ERR)PRINT_ERR("signal error");//第一次调用alarm返回值是上一次剩余的秒钟数,但是之前没有调用过alarm,//所以返回值就是0printf("alarm ret = %d\n",alarm(5));//等待2秒sleep(2);//第二调用alarm函数返回的上一次剩余的秒钟数3.它会把上一次剩余的秒钟数刷新为//5秒.5秒之后执行闹钟的信号处理函数printf("alarm ret = %d\n",alarm(5));while(1);return 0;
}
使用alarm模拟斗地主倒计时
#include void handle(int signo)
{printf("系统自动出牌了\n");alarm(4);
}
int main(int argc, const char* argv[])
{if (signal(SIGALRM, handle) == SIG_ERR)PRINT_ERR("signal error");alarm(4);char ch;while (1) {ch = getchar();getchar();printf("用户手动出牌=%c\n", ch);alarm(4);}return 0;
}

上一篇:类和对象 - 中

下一篇:Kafka最佳实践

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...