进程间的通信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
Ctrl+C → 子进程收到
SIGINT
→ 子进程向父进程发送SIGUSR1
→ 父进程打印消息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;
}