进程间通信方式
早期UNIX进程间通信方式
- 无名管道(pipe)
- 有名管道(fifo)
- 信号(signal)
System V IPC
- 共享队列(share memory)
- 消息队列(message queue)
- 信号灯集(semaphore set)
套接字(socket)
- 网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
无名管道特点
- 只能用于具有亲缘关系的进程之间的通信
- 半双工的通信模式,具有固定的读端和写端
- 无名管道创建时会返回两个文件描述符,分别用于读写管道
无名管道创建 - pipe
#include<unistd.h>
int pipe(int pfd[2]);
- 成功时返回0,失败时返回EOF
- pfd:包含两个元素的整形数组,用来保存文件描述符
- pfd[0]:用于读管道;pfd[1]:用于写管道
测试
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<string.h>
int main(void){
pid_t pid1,pid2;
char buf[32];
int pfd[2];
if(pipe(pfd)<0){
perror("pipe");
exit(-1);
}
if((pid1 = fork())<0){
perror("fork");
exit(-1);
}
else if(pid1 == 0){//子进程1
strcpy(buf,"I'm process 1");
write(pfd[1],buf,32);
exit(0);
}
else{
if((pid2 = fork())<0){//子进程2
perror("fork");
exit(-1);
}
else if(pid2 == 0){
sleep(1);
strcpy(buf,"I'm process 2");
write(pfd[1],buf,32);
}
else{//父进程
wait(NULL);//子进程1先结束
read(pfd[0],buf,32);
printf("%s\n",buf);
wait(NULL);
read(pfd[0],buf,32);
printf("%s\n",buf);
}
}
return 0;
}
无名管道读特性
从管道读出时,读出的部分会从管道中清除。
- 写端存在
- 至少有一个进程可以通过文件描述符来写管道
- 有数据(read返回实际读取字节数)
- 无数据(进程读阻塞)
- 写端不存在
- 有数据(read返回实际读取的字节数)
- 无数据(read返回0)
无名管道写特性
- 读端存在
- 至少有一个进程可以通过文件描述符来读管道
- 有空间(write返回实际写入的字节数)
- 无空间(进程写阻塞)
- 读端不存在
- 有无空间(异常结束即被信号结束称为管道断裂)
测试
计算无名管道的大小
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
int count = 0,pfd[2];
char buf[1024];
if(pipe(pfd)<0){
perror("pipe");
exit(-1);
}
while(1){
write(pfd[1],buf,1024);
printf("wrote %dk bytes\n",++count);
}
return 0;
}
验证管道断裂
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t pid;
int pfd[2],status;
char buf[32];
if(pipe(pfd)<0){
perror("pfd");
exit(-1);
}
close(pfd[0]);
if((pid = fork())<0){
perror("fork");
exit(-1);
}
else if(pid == 0){
write(pfd[1],buf,32);
exit(0);
}
else{
wait(&status);
printf("status = %x\n",status);
}
return 0;
}
有名管道特点
- 对应管道文件,可用于任意进程之间进行通信
- 打开管道时可指定读写方式
- 通过文件I/O操作,内容存在内存中
- 只有读端或写端时,open()会堵塞
有名管道创建 - mkfifo
#include<unistd.h>
#include<fcntl.h>
int mkfifo(const char *path,mode_t mode);
- 成功返回0,失败返回EOF
- path:创建的管道文件路径
- mode:管道文件的权限,如0666;
测试
有名管道读写
//common.h
#ifndef _COMMON_H
#define _COMMON_H
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#endif
//creat_fifo.c
#include"common.h"
int main(void){
if(mkfifo("myfifo",0666)<0){
perror("mkfifo");
exit(-1);
}
return 0;
}
//write_fifo.c
#include"common.h"
#define N 32
int main(void){
char buf[N];
int pfd;
if((pfd = open("myfifo",O_WRONLY))<0){
perror("open");
exit(-1);
}
printf("myfifo is opened\n");
while(1){
fgets(buf,N,stdin);
if(strcmp(buf,"quit\n") == 0)
break;
write(pfd,buf,N);
}
close(pfd);
return 0;
}
//read_fifo.c
#include"common.h"
#define N 32
int main(void){
char buf[N];
int pfd;
if((pfd = open("myfifo",O_RDONLY))<0){
perror("open");
exit(-1);
}
printf("myfifo is opened\n");
while(read(pfd,buf,N)>0){
printf("the lenth of string is %lu\n",strlen(buf));
}
close(pfd);
return 0;
}
信号机制
-
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
-
Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
-
Linux对早期的UNIX信号机制进行了扩展
-
进程对信号有不同的响应方式
- 缺省方式
- 忽略信号
- 捕捉信号
-
kill -l
进行查看
33-64这些信号一般不会采用,这是为了区分可靠信号和不可靠信号而新增加的32个信号
常用信号
全部信号使用用
man 7 signal
查看
信号名 | 含义 | 默认操作 |
---|---|---|
SIGHUP | 该信号在用户终端关闭时产生,通常是发给和该终端关联的会话内的所有进程 | 终止 |
SIGINT | 该信号在用户键入INTR字符(CTRL + C)时产生,内核发送此信号送到当前终端的所有前台进程 | 终止 |
SIGQUIT | 该信号和SIGINT类似,但由QUIT字符(通常时CTRL + \)来产生 | 终止 |
SIGILL | 该信号在一个进程企图执行一条非法指令时产生 | 终止 |
SIGSEV | 该信号在非法访问内存时产生,如野指针,缓冲区溢出 | 终止 |
SIGPIPE | 当进程往一个没有读端的管道中写入时产生,代表“管道断裂” | 终止 |
SIGKILL | 该信号用来结束进程,并且不能被捕捉和忽略 | 终止 |
SIGSTOP | 该信号用于暂停进程,并且不能被捕捉和忽略 | 暂停进程 |
SIGTSTP | 该信号同于暂停进程,用户可键入SUSP字符(通常时CTRL + Z)发出这个信号 | 暂停进程 |
SIGCONT | 该信号让进程进入运行态 | 继续运行 |
SIGALRM | 该信用于通知进程定时器时间已到 | 终止 |
SIGUSR1/2 | 该信号保留给用户程序使用 | 终止 |
kill/killall
-
kill [-signal] pid
- 默认发送15【SIGTERM】
- -sig:可指定信号
- pid:指定对象的进程号
- -a:当处理当前进程时,不限制命令名和进程号的对应关系;
- -l <信息编号>:若不加<信息编号>选项,则-l参数会列出全部的信息名称;
- -p:指定kill 命令只打印相关进程的进程号,而不发送任何信号;
- -s <信息名称或编号>:指定要送出的信息;
- -u:指定用户。
-
killall [-u user | prog ]
- prog:指定进程名
- urer:指定用户名
- -e:对长名称进行精确匹配;
- -l:忽略大小写的不同;
- -p:杀死进程所属的进程组;
- -i:交互式杀死进程,杀死进程前需要进行确认;
- -l:打印所有已知信号列表;
- -q:如果没有进程被杀死。则不输出任何信息;
- -r:使用正规表达式匹配要杀死的进程名称;
- -s:用指定的进程号代替默认信号“SIGTERM”;
- -u:杀死指定用户的进程。
信号发送 - kill/raise
#include<unistd.h>
#include<signal.h>
int kill(pid_t pid,int sig);
int raise(int sig);
- 成功返回0,失败返回EOF
- pid:接收进程的进程号;0代表同组进程;-1代表所有进程
- sig:信号类型
定时器 - alarm/pause
#include <unistd.h>
int alarm(unsigned int seconds);
- 成功返回上个定时器的剩余时间,失败返回EOF
- seconds:定时器的时间(0表示取消当前定时器)
- 一个进程中只能设定一个定时器,时间到时产生SIGALRM
#include <unistd.h>
int pause(void);
- 进程一直阻塞,直到被信号中断
- 被信号中断后返回-1,errno为EINTR
测试
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
alarm(3);
pause();//终止
printf("I have waken up!\n");//不会打印
return 0;
}
alarm经常用于实现超时检测
设置信号响应方式 - signal
#include<unistd.h>
#include<signal.h>
void ( *signal(int signum, void (*handler)(int)) ) (int);
- 成功时返回原先的信号处理函数,失败时返回SIG_ERR
- signo:要设置的信号类型
- handler:指定的信号处理函数(SIG_DFL代表缺省方式;SIG_IGN代表忽略信号)
测试
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handler (int signo){
if(signo == SIGINT){
printf("I have got SIGINT!\n");
}
if(signo == SIGQUIT){
printf("I have got SIGQUIT!\n");
}
}
int main(){
signal(SIGINT,handler);
signal(SIGQUIT,handler);
while(1)
pause();
return 0;
}