LINUX-并发程序设计进程间通信篇

进程间通信方式

早期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;
}

2
验证管道断裂

#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;
}

3

有名管道特点

  • 对应管道文件,可用于任意进程之间进行通信
  • 打开管道时可指定读写方式
  • 通过文件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;
}

4

信号机制

  • 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

  • Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件

  • Linux对早期的UNIX信号机制进行了扩展

  • 进程对信号有不同的响应方式

    • 缺省方式
    • 忽略信号
    • 捕捉信号
  • kill -l进行查看

5

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;
}

6

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;
}

7

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值