当前位置: 首页 > news >正文

进程间的通信1.(管道,信号)

1.进程间的通信

        进程空间是独立的,包含文本段、数据段和系统数据段,多个进程没有共享的用户空间,进程是操作系统资源分配的最小单元,可以利用Linux内核实现多个进程间的通信。

              

2.进程间常见的通信方式

        传统Unix系统中的通信方式:管道,信号。

        SYS V分支中的通信方式:消息队列,共享内存,信号灯。

        BSD分支中的通信方式:本地域套接字。

3.管道

管道操作特性:

        1. 管道中至少有一个写端: 读取数据时,如果管道中有数据则直接读出 读取数据时,如果管道中没有数据则阻塞等待有数据写入才能读出。

        2. 管道中没有写端: 读取数据时,管道中有数据则直接读出 读取数据时,管道中没有数据不阻塞等待直接向下执行。

        3. 管道中至少有一个读端: 写入数据时,如果管道没有写满则直接写入 写入数据时,如果管道存满则阻塞等待有数据读出才能继续写入。

        4. 管道中没有读端:写入数据时,会产生管道破裂的信号导致进程任务异常退出。

3.1无名管道(只能用于有亲缘关系的进程间)

        无名管道是一段内核缓存区,父进程通过pipe创建无名管道,只有该父进程的子进程才能继承得到这两个文件描述符,才能实现通信,因此只能用于具有亲缘关系的进程间通信。

         

3.1.1函数接口pipe:创建一个无名管道(内核缓存区)。

原型:int pipe(int pipefd[2]);
功能:
创建一个无名管道(内核缓存区)
参数:
pipefd[0]:读管道文件描述符
pipefd[1]:写管道文件描述符
返回值:
成功返回0 
失败返回-1

无名管道在父子进程间的应用:

#include "head.h"int main(void)
{int pipefd[2];      // 定义管道文件描述符数组,pipefd[0]为读端,pipefd[1]为写端int ret = 0;        // 用于存储函数返回值pid_t pid;          // 进程ID变量char tmpbuff[4096] = {0}; // 缓冲区,用于读写数据// 创建匿名管道ret = pipe(pipefd);if (-1 == ret)      // 检查管道是否创建成功{perror("fail to pipe"); // 打印错误信息return -1;}// 创建子进程pid = fork();if (-1 == pid)      // 检查fork是否成功{perror("fail to fork");return -1;}// 子进程逻辑if (0 == pid){strcpy(tmpbuff, "hello world");              // 子进程写入数据到缓冲区write(pipefd[1], tmpbuff, strlen(tmpbuff));  // 向管道写端写入数据sleep(1);                                    // 等待1秒(确保父进程先读)read(pipefd[0], tmpbuff, sizeof(tmpbuff));   // 从管道读端读取父进程的回复printf("子读 = %s\n", tmpbuff);              // 打印读取的数据}// 父进程逻辑else if (pid > 0){read(pipefd[0], tmpbuff, sizeof(tmpbuff));   // 从管道读端读取子进程的数据printf("父读 = %s\n", tmpbuff);              // 打印读取的数据write(pipefd[1], "thank you", 10);           // 向管道写端回复数据}// 关闭管道的两端(父子进程各自关闭自己的文件描述符)close(pipefd[0]);close(pipefd[1]);return 0;
}

3.2有名管道

        有名管道与无名管道区别:有名管道有名字,可以通过名字找到该管道,可以用于任意进程间的通信,是进程间通信最简单、易实现的方式,且有名管道必须读写两端同时加入才能继续向下执。

                 

3.2.1函数接口mkfifo:创建有名管道

原型:int mkfifo(const char *pathname, mode_t mode);
功能:
创建有名管道
参数:
pathname:管道文件名
mode:权限
返回值:
成功返回0 
失败返回-1 

3.2.2操作方式

1. 进程一使用open以读、写、读写方式打开管道文件
2. 进程二使用open以读、写、读写方式打开管道文件
3. 两个进程可以通过向管道文件中读写数据实现进程的通信

        write向管道1写入字符串,read读取管道1的内容打印在终端并向管道二写入字符串,再由write读取管道二的内容并打印:

//read
#include "head.h"
int main(void)
{int fd1 = 0;int fd2 = 0;char tmpbuff[4096] = {0};mkfifo("./myfifo1", 0777);fd1 = open("./myfifo1", O_RDWR);if (-1 == fd1){perror("fail to open");return -1;}mkfifo("./myfifo2", 0777);fd2 = open("./myfifo2", O_RDWR);if (-1 == fd2){perror("fail to open");return -1;}while (1){memset(tmpbuff, 0, sizeof(tmpbuff));read(fd1, tmpbuff, sizeof(tmpbuff));printf("RECV:%s\n", tmpbuff);memset(tmpbuff, 0, sizeof(tmpbuff));m_fgets(tmpbuff);write(fd2, tmpbuff, strlen(tmpbuff)); }close(fd1);close(fd2);return 0;
}
//write
#include "head.h"
int main(void)
{int fd1 = 0;int fd2 = 0;char tmpbuff[4096] = {0};mkfifo("./myfifo1", 0777);fd1 = open("./myfifo1", O_RDWR);if (-1 == fd1){perror("fail to open");return -1;}mkfifo("./myfifo2", 0777);fd2 = open("./myfifo2", O_RDWR);if (-1 == fd2){perror("fail to open");return -1;}while(1){memset(tmpbuff, 0, sizeof(tmpbuff));m_fgets(tmpbuff);write(fd1, tmpbuff, strlen(tmpbuff));  memset(tmpbuff, 0, sizeof(tmpbuff));read(fd2, tmpbuff, sizeof(tmpbuff));printf("RECV:%s\n", tmpbuff);}close(fd1);close(fd2);return 0;
}

        这串代码只能实现两个进程间轮流发送并打印,无法做到某个进程一直向对方发送并打印,因此我们可以加入两个线程,搭配管道实现聊天功能:

      

//clientA.c
#include "head.h"
void *thread1(void *arg)
{int fd1 = 0;fd1 = open("./myfifo1", O_WRONLY);char tmpbuff[4096] = {0};while(1){memset(tmpbuff, 0, sizeof(tmpbuff));m_fgets(tmpbuff);write(fd1, tmpbuff, strlen(tmpbuff));}close(fd1);return NULL;
}
void *thread2(void *arg)
{int fd2 = 0;fd2 = open("./myfifo2", O_RDONLY);char tmpbuff[4096] = {0};while (1) {read(fd2, tmpbuff, sizeof(tmpbuff));printf("收到的消息: %s\n", tmpbuff);memset(tmpbuff, 0, sizeof(tmpbuff));}close(fd2);return NULL;
}
int main(void)
{mkfifo("./myfifo1", 0777);mkfifo("./myfifo2", 0777);pthread_t tid[2] = {0};pthread_create(&tid[0], NULL, thread1, NULL);pthread_create(&tid[1], NULL, thread2, NULL);pthread_join(tid[0],NULL);pthread_join(tid[1],NULL);return 0;
}
//clientB.c
#include "head.h"
void *thread1(void *arg)
{int fd1 = 0;fd1 = open("./myfifo1", O_RDONLY);char tmpbuff[4096] = {0};while(1){read(fd1, tmpbuff, sizeof(tmpbuff));printf("收到的消息: %s\n", tmpbuff);memset(tmpbuff, 0, sizeof(tmpbuff));}close(fd1);return NULL;
}
void *thread2(void *arg)
{int fd2 = 0;fd2 = open("./myfifo2", O_WRONLY);char tmpbuff[4096] = {0};while (1) {memset(tmpbuff, 0, sizeof(tmpbuff));m_fgets(tmpbuff);write(fd2, tmpbuff, strlen(tmpbuff));}close(fd2);return NULL;
}
int main(void)
{mkfifo("./myfifo1", 0777);mkfifo("./myfifo2", 0777);pthread_t tid[2] = {0};pthread_create(&tid[0], NULL, thread1, NULL);pthread_create(&tid[1], NULL, thread2, NULL);pthread_join(tid[0],NULL);pthread_join(tid[1],NULL);return 0;
}

4.信号

        Linux系统中的信号主要实现应用层和内核层之间的信号通知,应用层与内核层之间通信的信号根据含义分为很多不同的信号,信号主要用于多个进程任务间的事件通知,信号可以用于异步通信。

                        ​​​​​​​        

4.1信号的类型

        使用kill -l 查看信号类型:

linux@ubuntu:~$ kill -l1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX
SIGINT:中止信号(可以从键盘输入)        ctrl + c
SIGQUIT:退出信号(可以从键盘输入)       ctrl + \
SIGKILL:杀死进程信号
SIGSEGV:段错误信号
SIGPIPE:管道破裂信号
SIGALARM:定时时间到达信号
SIGCHLD:子进程结束时,父进程回收到该信号
SIGCONT:继续执行进程任务
SIGSTOP:停止进程任务
SIGTSTP:挂起信号(可以从键盘输入)       ctrl + z
SIGIO:异步IO信号

4.2信号的处理方式

缺省接到信号,按照默认方式处理
忽略接到信号,不处理信号,忽略信号
捕捉接到信号,按照用户自己定义的方式处理信号

        需要注意:9号信号(SIGKILL)和19号信号(SIGSTOP)不能被忽略和捕捉的。

4.3函数接口

4.3.1signal设置signum对应信号的处理方式

原型:
typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);
功能:设置signum对应信号的处理方式
参数:signum:信号的编号handler:信号的处理方式SIG_IGN:忽略信号SIG_DFL:缺省处理信号
返回值:成功返回之前处理信号的方式失败返回SIG_ERR

运行程序等待终端的不同信号打印不同的字符串:

#include "head.h"/* 信号处理函数1:处理SIGINT信号(Ctrl+C触发) */
void handler1(int signo)
{printf("SIGINT信号来了!\n");  // 打印信号提示return;
}
/* 信号处理函数2:处理SIGQUIT信号(Ctrl+\触发) */
void handler2(int signo)
{printf("SIGQUIT信号来了!\n"); // 打印信号提示return;
}
/* 信号处理函数3:处理SIGTSTP信号(Ctrl+Z触发) */
void handler3(int signo)
{printf("SIGTSTP信号来了!\n"); // 打印信号提示return;
}
int main(void)
{// 注册信号处理函数signal(SIGINT, handler1);signal(SIGQUIT, handler2);signal(SIGTSTP, handler3);// 保持程序运行,等待信号while (1){}return 0;
}
#if 0 
/* 优化版本:使用单一处理函数通过参数区分不同信号 */
#include "../head.h"/* 通用信号处理函数 */
void handler(int signo)
{if (SIGINT == signo)        // 判断是否为SIGINT信号{printf("SIGINT信号来了\n");}else if (SIGQUIT == signo)  // 判断是否为SIGQUIT信号{printf("SIGQUIT信号来了\n");}else if (SIGTSTP == signo)  // 判断是否为SIGTSTP信号{printf("SIGTSTP信号来了\n");}return;
}int main(void)
{// 注册信号处理函数(全部使用handler)signal(SIGINT, handler);signal(SIGQUIT, handler);signal(SIGTSTP, handler);while (1){}return 0;
}
#endif

        ​​​​​​​        ​​​​​​​        ​​​​​​​       ​​​​​​​

可以用ps -ef | grep filename查看进程,kill -9 pid杀死进程结束当前程序:

        ​​​​​​​        

4.3.2alarm设置第一个定时,过seconds秒后,给进程发送SIGALRM信号

原型:unsigned int alarm(unsigned int seconds);
功能:
设置第一个定时,过seconds秒后,给进程发送SIGALRM信号
参数:
seconds:秒数
返回值:
成功返回上次定时剩余的秒数
如果之前没有定时返回0 

应用示例:

#include "head.h"  // 包含自定义头文件
/* SIGALRM信号处理函数 */
void handler(int signo)
{printf("SIGALRM信号来了\n");  // 打印信号到达提示alarm(5);                    // 重新设置5秒后再次触发SIGALRMreturn;
}
int main(void)
{// 注册SIGALRM信号的处理函数signal(SIGALRM, handler);// 设置第一个5秒定时器,5秒后发送SIGALRM信号alarm(5);while (1){printf("正在运行...\n");sleep(1);}return 0;
}

          ​​​​​​​        ​​​​​​​

4.3.3kill给pid对应的进程发送sig信号

原型:int kill(pid_t pid, int sig);
功能:
给pid对应的进程发送sig信号
参数:
pid:进程的ID号
sig:信号的编号
返回值:
成功返回0 
失败返回-1 
  1. Ctrl+C → 子进程收到 SIGINT → 子进程向父进程发送 SIGUSR1 → 父进程打印消息

  2. Ctrl+\ → 父进程收到 SIGQUIT → 父进程向子进程发送 SIGUSR2 → 子进程打印消息

#include "head.h"pid_t pid;void handler_parent(int signo)
{if (SIGQUIT == signo){kill(pid, SIGUSR2);}else if (SIGUSR1 == signo){printf("父进程:收到子进程发送的信号了\n");}return;
}
void handler_child(int signo)
{if (SIGINT == signo){kill(getppid(), SIGUSR1);}else if (SIGUSR2 == signo){printf("子进程:收到父进程发送的信号了\n");}return;
}
int main(void)
{pid = fork();if (-1 == pid){perror("fail to fork");return -1;}if (0 == pid){signal(SIGUSR2, handler_child);signal(SIGINT, handler_child);signal(SIGQUIT, SIG_IGN);}else if (pid > 0){signal(SIGUSR1, handler_parent);signal(SIGQUIT, handler_parent);signal(SIGINT, SIG_IGN);}while (1){}return 0;
}
http://www.xdnf.cn/news/1328077.html

相关文章:

  • LINUX 软件编程 -- 线程
  • 决策树(续)
  • LeetCode100-560和为K的子数组
  • 决策树1.1
  • 项目一系列-第5章 前后端快速开发
  • 项目管理.管理理念学习
  • react-quill-new富文本编辑器工具栏上传、粘贴截图、拖拽图片将base64改上传服务器再显示
  • LeetCode算法日记 - Day 16: 连续数组、矩阵区域和
  • 第4章 React状态管理基础
  • 算法训练营day56 图论⑥ 108. 109.冗余连接系列
  • 项目过程管理的重点是什么
  • Ansible 角色管理
  • 点大餐饮独立版系统源码v1.0.3+uniapp前端+搭建教程
  • GStreamer无线图传:树莓派到计算机的WiFi图传方案
  • GEO 优化专家孟庆涛:技术破壁者重构 AI 时代搜索逻辑
  • RESTful API 开发实践:淘宝商品详情页数据采集方案
  • Apache IoTDB:大数据时代时序数据库选型的技术突围与实践指南
  • 从0到1认识Rust通道
  • Redis-缓存-击穿-分布式锁
  • 无人机场景 - 目标检测数据集 - 山林野火烟雾检测数据集下载「包含VOC、COCO、YOLO三种格式」
  • 国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—ARM + FPGA通信案例
  • 如何免费给视频加字幕
  • Linux的ALSA音频框架学习笔记
  • Spring AOP 和 Spring 拦截器
  • LeetCode 100 -- Day2
  • JVM垃圾收集器
  • ts 引入类型 type 可以省略吗
  • sfc_os!SfcValidateDLL函数分析之cache文件版本
  • python的社区互助养老系统
  • 【实时Linux实战系列】实时平台下的图像识别技术