进程通信问题(随时更新)

线程通信相对简单,因为共享空间,所以在线程中已做总结。(同步与互斥)

而进程的相对复杂,所以在此另起炉灶。

UNIX下的基础进程通信分为:无名管道(pipe),有名管道(fifo),和信号(signal)三类

还有三类高级进程间通信SYSTEM V IPC:消息队列,共享内存和信号量,这三类称为IPC对象

Linux IPC继承了System V IPC。

不同点在于: 管道的释放由内核自动控制;
IPC对象因为运行与内核而非文件系统中,所以释放由用户控制。若没有释放,则会一直运行直到重启。

基础进程通信

1.无名管道

特点:
只能在有亲缘关系的进程间通信,通常为父子进程。
单工通信,有固定读端和写端
创建时返回两个文件描述符,分别为读写管道

特性:
写端存在,有数据读正常返回,无数据读阻塞
写端不存在,有数据读正常返回,无数据返回0
读端存在,有空间写正常输入,无空间写阻塞
读端不存在,管道断裂,只进不出,要他何用。

#include <unistd.h>
int pipe(int pfd[2]);
返回值:成功:0;失败:EOF
通过一个整形数组来保存两个文件描述符,pfd[0]为读管道,pfd[1]为写管道

示例:

//省略头文件
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)	//建立进程1
	{
		perror("fork");
		exit(-1);
	}
	else	if(pid1 == 0)			//子进程1
	{	strcpy(buf,"process_1");
		write(pfd[1],buf,32);
		exit(0);
	}
	else					//子进程1的父进程1
	{
		if(pid2 = fork()) < 0)
		{				//父进程1中建立进程2
			perror("fork");
			exit(-1);
		}
		else if(pid2 == 0)		//子进程2
		{
			sleep(1);
			strcpy(buf,"process_2");
			write(pfd[1].buf,32);
		}
		else				//子进程2的父进程2
		{
			wait(NULL);
			read(pfd[0],buf,32);
			printf("%s\n",buf);
			wait(NULL);
			read(pfd[0],buf,32);
			printf("%s\n",buf)
		}
	}
	return 0;
}

1.1白话时间:

以上例程是建立一个进程,产生子进程1和父进程1,然后在父进程1中建立子进程2和父进程2。这样父进程1和父进程2就有了亲缘关系,那么两个子进程也就有了亲缘关系。
例程的过程就是两个子进程分别将内容输入写管道pfd[1],再由父进程通过读管道pfd[0]读取内容打印。
具体使用方法:声明一个两个元素的数组(pfd[2]),再创建管道(pipe(pfd)),之后就可以在每个进程中对两个问件描述符(pfd[0],pfd[1]),进行读写了。

2.有名管道

特点:
有对应管道文件(有名),可用于任意进程通信。
打开管道时指定读写方式,不像无名管道时固定的
通过文件io操作,内容存于内存。
有名管道打开时可能会阻塞

#include <sys/sypes.h>
#include <sys/stat.h>

int mkfifo(const char *path,mode_t mode);
返回值:成功:0;失败:EOF
path:创建的管道文件路径
mode:管道文件权限,如0666

示例:

//创建有名管道:fifo.c
省略头文件
int	main(void)
{
	if(mkfifo("myfifio",0666) < 0)
	{
		perror("mkfifo");
		exit(-1);
	}
	return 0;
}

//其他文件通过有名管道进行读写
//write_fifo.c
int main(void)
{
	int pfd;
	char buf[32];
	if((pfd = open("myfifo",O_WRONLY))< 0 )
	{
		perror("open");
		exit(-1);
	}
	while(1)
	{
		fgets(buf,32,stdin);
		write(pfd,buf,32);
	}
	close(pfd);
	return 0 ;
}

//read_fifo.c
int main(void)
{
	char buf[32];
	int pfd;
	if((pfd = open("myfifo",O_RDONLY)) < 0)
	{
		perror("open");
		exit(-1);
	}
	while(read(pfd,buf,32) >0)
	{
		printf("%d\n",strlen(buf));
	}
	close(pfd);
}

白话部分:

有名管道使用也较为简单,首先创建一个管道文件(mkfifo),之后在别的文件中打开(open)该文件,然后就可以对打开文件的文件描述符(pfd)进行操作了。

3.信号

信号实在软件层面上对中断机制的模拟,是一种异步通信模式。
内核通过信号通知进程,不同信号种类代表不同事件。
进程响应信号有三种方式:缺省,忽略,捕捉。

信号相关命令:
kill [-signal] pid -sig:指定信号类型,pid:发送对象(进程号)
killall [-u user | prog] prog:进程名,user:用户名

发送信号函数:

#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,int sig);
int raise(int sig);
返回值:成功:0,失败:EOF
pid:接收进程进程号,0:同组进程;-1:所有进程
sig:信号类型

int alarm(unisigned int seconds);
返回值:成功:返回上个计时器剩余时间,失败:EOF
seconds:定时时间
一个进程只能设定一个定时器,时间到产生SIGALRM信号
通常被用于超时检测

int pause(void);
进程阻塞,直到被信号中断
终端后返回-1,errno为EINTR;

示例:

int main(void)
{
	alarm(3);
	pause();
	printf("wake up\n");
	return 0;
}

//信号响应
#include <unistd.h>
#include <signal.h>

void(*signal(int signo,void(*handler)(int)))(int);
虽然看上去很复杂,但实际上就是将信号与函数关联起来
返回值:成功:返回原先的信号处理函数,失败:SIG_ERR
sign0:要设置的信号类型
handler:指令的信号处理函数,SIG_DFL表示缺省,SIG_IGN表示忽略

示例:

int mian(void)
{
	signal(SIGINT,handler);
	while(1)
	{
		pause();
	}
	return 0;
}
void handler(int signo)
{
	if(signo == SIGINT)
		printf("I have got SIGINT")
}

白话部分

信号可能比前两个管道略复杂,主要使用的函数便是(signal)将信号与函数联系起来。得到信号,进行相应的处理。

高级进程通信

SYSTEM V IPC:
共享内存(Shm_get),消息队列(Msg_get)和信号灯集(Sem_get)
每个IPC对象有唯一ID
IPC对象创建后一直存在直到被显式删除
每个IPC对象有一个关联的KEY
相关命令ipcs/ipcrm

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *psth,int proj_id);
返回值:成功:合法的key值,失败:EOF
path:可访问的文件路径
proj_id:用于生成key的数字,不等于0
ftok函数详解:
https://blog.csdn.net/andylauren/article/details/78821655

1.共享内存

特点:
最为高效的进程间通信方式,可直接读写内存而不需拷贝数据
在内核空间创建,映射到用户空间访问,使用灵活
同线程类似,由于多个进程可同时访问,所以需要同步和互斥机制
相关命令:
查看共享内存大小限制:ipcs -l;cat/proc/sys/kernel/shmmax
查看共享内存删除时间点:chamctl(shmid,IPC_RMID,NULL)添加删除标记;当nattach变为0时真正删除

使用步骤:
打开/创建共享内存
映射共享内存,将指定的共享内存映射到进程的地址空间
读写
撤销映射
删除共享内存

//创建:由ftok/IPC_PRIVATE获得shmid
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,int size,int shmflg);
返回值:成功:共享内存id(shmid),失败:EOF
key由函数(ftok)或者IPC_PRIVATE生成
shmflg:共享内存标志位 IPC_CREAT|0666

//映射:由shmid获得共享内存映射地址(shmaddr)
#include <sys/type.h>
#include <sys/shm.h>
void *shmat(int shmid,const void shmaddr,int shmflg);
返回值:成功:映射后地址,失败:(void
)-1
shmid:映射的共享内存id,由shmget获得。
shmaddr:映射后地址,NULL表示自动映射
shmflg:标志位;0:可读写;SHM_RDONLY:只读

//读写:通过地址指针进行访问
fgets(shmaddr,N,stdin);

//撤销映射:通过地址撤销映射,但共享内存还在。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);
返回值:成功:0;失败:EOF
不使用共享内存时应撤销
进程结束时自动撤销

//共享内存控制
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
返回值:成功:0;失败:EOF
shmid:操作的共享内存id
cmd:执行的操作:IPC_STAT;IPC_SET;IPC_RMID
buf:要保存或设置共享内存属性的地址

白话部分:

当需要多个进程访问一片空间时可采用共享内存,就像多个线程共享一个空间一样。使用时先通过(ftok)获得key值,再通过(key值+shmget())获得shmid,再由(shmid+shmat)映射获得一个地址(addr),通过(*addr)进行读写操作,操作完毕通过(shmdt)撤销映射,最后由(shmctl)来决定对其设置或者删除。

消息队列msg

顾名思义,就是消息的列表,用户可以通过消息列表来添加与读取消息,使用它我们可以按照类型来发送/接收消息

使用步骤:
1,打开/创建消息队列
2.向消息队列发送消息
3.从消息队列接收消息
4.控制消息队列

/创建/打开
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/msg.h>
int msgget(key_t key,int msgflg);
返回值:成功:消息队列id;失败:-1;
key:通过ftok或者IPC_PRIVATE获得
msgflg:标志位IPC_CREAT|0666

//发送
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/msg.h>
int msgsnd(int msgid,const void *msgp,size_t size,int msgflg);
返回值:成功:0;失败:-1;
msgid:消息队列id
msgp:消息队列缓冲区地址
size:消息正文长度
msgflg:标志位0或者IPC_NOWAIT

//接收:与发送格式相同,只是多了需要接收的消息类型
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/msg.h>
int msgrcv(int msgid,const void *msgp,size_t size,long msgtype,int msgflg);
返回值:成功:0,失败:-1;
size:指定接受的消息长度
msgtype:指定接收的消息类型

//控制:设置和删除
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/msg.h>
int msgctl(int msgid,int cmd,struct msqid_ds *buf);
返回值:成功:0;失败:-1
cmd:IPC_STAT/IPC_SET/IPC_RMID
buf:存放消息队列属性的地址

示例:

typedef struct{
	long mtype;
	char mtex[64];
}MSG;			//定义消息队列结构体,类型+正文
#define LEN (sizeof(MSG) - sizeof(long))	//正文长度
int main(void)
{
	//创建
	int msgid;
	ket_t key;
	if((key = ftok(".",'q')) == -1 )
	{
		perror("ftok");
		exit(-1);
	}
	if((msgid = msgget(key,IPC_CREAT|0666)) < 0)
	{
		perror("msgget");
		exit(-1);
	}
	//发送
	MSG buf_snd,buf_rcv;		//一个名为buf的结构体
	buf.mtype = 100;	//定义消息类型为100
	fgets(buf.mtext,64,stdin);	//从键盘输入数据
	msgsnd(msgid,&buf_snd,LEN,0);	//发送消息

	//接收
	if(msgrcv(msgid,&buf_rcv,LEN,200,0) < 0)	//接收消息类型为200的消息,存入接收缓存区中
	{
		perror("msgrcv");
		exit(-1);
	}
}

信号灯(量)集

信号灯也可以叫信号量,之前在线程中也由,但线程中主要是通过p/v操作来实现线程的同步,在进程中也可以通过信号量来实现同步与互斥
“集”表示了这里的system V信号量是由一个或多个信号量组成,也可以同时对多个进行操作,注意使用多个时避免死锁。

使用步骤:
1.打开/创建
2.初始化(信号量特有)
3.p/v操作
4.删除

//创建/打开
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
返回值:成功:信号灯id;失败:-1;
ket:管理的key,IPC_PRIVATE或ftok
nsems:包含的信号灯的个数
semflg:标志位IPC_CREAT|0666 IPC_EXCL

//初始化
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/sem.h>
int semctl(int semid,int semnum,int cmd,…);
返回值:成功:0;失败:EOF
semid:信号灯集id
semnum:在要操作信号灯集中操作的信号灯的编号
cmd:执行的操作 SETVAL(初始化) IPC_RMID(删除)
union semun:与cmd有关,通常用于设置和返回信号量的信息

Union semun
{
  int val;
  struct semid_ds *buf;
  unsigned short *array;
  struct seminfo *__buf;
  void *__pad;
}

示例:

//集合中包括两个信号灯:一个初始化为3,一个初始化为0
union semun myun;
myun.val = 3;
if(semctl(semid,0,SETVAL,myun) < 0)
{
	perror("semctl");
	exit(-1);
}
myun.val = 0;
if(semctl(semid,0,SETVAL,myun) < 0)
{
	perror("semctl");
	exit(-1);
}

//p/v操作
#include <sys/types.h>
#include <sys/ipc.h>
#incldue <sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned nsops);
返回值:成功:0;失败:-1
semid:操作的信号灯集id
sops:对信号灯操作的结构体(数组)
nsops:操作的信号灯的个数

struct sembuf
{
	short semnum;		//信号灯编号
	short sem_op;		//-1:p(申请资源) 1:v(申请资源)
	short sem_flg;		//0/IPC_NOWAIT
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值