IPC (管道、FIFO、消息队列、共享内存、信号、信号量)
进程间通信(IPC,InterProcess Communication)的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。
一、管道
管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
①半双工通信模式。
②只用于具有亲缘关系的进程之间的通信。如父子进程、兄弟进程、
③可看成是一种特殊的文件。
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
//int pipe(int fd[2]);
int main()
{
int fd[2];
int ret;
char *str="Hellow word";
char buf[30]={
0};
ret = pipe(fd);//创建管道
if(ret == -1){
printf("have failed\n");
perror("pipe:");
exit(-1);
}
pid_t pid = fork();//创建子进程
if(pid < 0){
perror("create child failed\n");
exit(-1);
}else if(pid > 0){
perror("create child failed\n");
close(fd[0]);
write(fd[1],srt,strlen(str));//将str内容写入管道
wait();
}else{
close(fd[1]);
read(fd[0],buf,30);//将管道内容读到buf中去
printf("read from parent:%s\n",buf);
exit(0);
}
return 0;
}
二、FIFO
命名管道,又称为有名管道,它可以使不相关的进程实现彼此通信。
①命名管道(FIFO)是在文件系统中作为一个特殊的设备文件而存在的。
②不同祖先的进程之间可以通过管道共享数据。
③当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。
读端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
// int mkfifo(const char *pathname,mode_t mode);
int main()
{
char buf[128];
if(mkfifo("./file",0600) == -1 && errno != EEXIST){
//创建一个文件名为file的管道
perror("error\n");
exit(-1);
}
int fd = open("./file",O_RDONLY);//以只读方式打开file管道
if(fd < 0){
perror("open fifo:");
exit(-1);
}
while(1){
read(fd,buf,128);
printf("read from write.c: %s",buf);
if(strstr(buf,"quit") != NULL)
break;
}
close(fd);
return 0;
}
写端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char *str="Hellow word";
int fd = open("./file",O_WRONLY);//只写方式打开file管道
if(fd == -1){
perror("fifo:");
exit(-1);
}
while(1){
sleep(1);
write(fd,str,strlen(str));
}
close(fd);
return 0;
}
三、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1、特点
(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
(2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
(3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2、原型
#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
在以下两种情况下,msgget将创建一个新的消息队列:
(1)如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
(2)key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
(1)type == 0,返回队列中的第一个消息;
(2)type > 0,返回队列中消息类型为 type 的第一个消息;
(3)type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。
msgrcv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
struct msg_form {
long mtype;
char mtext[256];// 消息结构
};
int main()
{
int msqid;
key_t key;
struct msg_form msg;
// 获取key值
if((key = ftok(".",'z')) < 0)// 用于创建一个唯一的key
{
perror("ftok error");
exit(1);
}
printf("Message Queue - Server key is: %x.\n", key); //16进制方式打印key值
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) // 创建消息队列
{
perror("msgget error");
exit(1);
}
// 打印消息队列ID及进程ID
printf("My msqid is: %d.\n", msqid);
printf("My pid is: %d.\n", getpid());
// 循环读取消息
for(;;)
{
msgrcv(msqid, &msg, 256, 888, 0);// 返回类型为888的第一个消息、256代表读取的大小
printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
printf("Server: receive msg.mtype is: %d.\n", msg.mtype);
msg.mtype = 999; // 客户端接收的消息类型
sprintf(msg.mtext, "hello, I'm server %d", getpid());//写入到msg.mtext中信息
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);//发送出去
}
return 0;
}
msgsnd.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
struct msg_form {
long mtype;
char mtext[256];// 消息结构
};
int main()
{
int msqid;
key_t key;
struct msg_form msg;
if ((key = ftok(MSG_FILE, 'z')) < 0) // 获取key值
{
perror("ftok error");
exit(1);
}
printf("Message Queue - Client key is: %x\n", key); //16进制形式打印key值
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) // 打开消息队列
{
perror("msgget error");
exit(1);
}
printf("My msqid is: %d.\n", msqid); // 打印消息队列ID及进程ID
printf("My pid is: %d.\n", getpid());
msg.mtype = 888; // 添加消息,类型为888
sprintf(msg.mtext, "hello, I'm client %d", getpid());//写入到msg.mtext中信息
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);//发送出去
msgrcv(msqid, &msg, 256, 999, 0); // 读取类型为999的消息
printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
msgctl(msgqid,IPC_RMID,0);//移除消息队列
return 0;
}
五、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
(1)共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
(2)因为多个进程可以同时操作,所以需要进行同步。
(3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2、原型
#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
sever.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
if ((key = ftok(".", 'z')) < 0) // 获取key值
{
perror("ftok error");
exit(1);
}
shmid = shmget(key,1024*4,IPC_CREAT|0666);//创建共享内存(创建的内存大小必须为兆的整数倍)
if(shmid == -1){
printf("create failed\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);//连接共享内存
strcpy(shmaddr,"helloha");//输入信息
sleep(5);//休息五秒,等待接受信息
shmdt(shmaddr);//断开连接
shmctl(shmid,IPC_RMID,NULL);//卸载共享内存
return 0;
}
client.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
if ((key = ftok(".", 'z')) < 0) // 获取key值
{
perror("ftok error");
exit(1);
}
shmid = shmget(key,1024*4,0);//获取共享内存
shmaddr = shmat(shmid,0,0);//连接共享内存
printf("context: %s\n",shmaddr);//打印共享内存信息
shmdt(shmaddr);//断开连接
return 0;
}
查看所有共享内存
ipcs -m
移除共享内存
ipcrm -m (key)
五、信号
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
入门级
signal
#include <signal.h>
#include <stdio.h>
void handler(int signum)
{
printf("get signum %d\n",signum);//打印捕获的信号
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
}
}
int main()
{
printf("pid = %d\n",getpid());
signal(SIGKILL,handler);//捕获SIGKILL信号到handler处理
signal(SIGINT,handler);
while(1);
return 0;
}
高级
sigaction
接受信息
#include <signal.h>
#include <stdio.h>
void handler(int signum,siginfo_t *info, void *context)//info包含很多信息,context表示有无信息传递过来
{
printf("get signum %d\n",signum);
if(context != NULL){
printf("get data: %d\n",info->si_int);//指令信息
printf("get data: %d\n",info->si_value.sival_int);//信息
printf("from %d\n",info->si_pid);//哪个进程发的
}
}
int main()
{
struct sigaction act;
printf("pid = %d\n",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;//必须这样配置去获取信息
sigaction(SIGUSR1,&act,NULL);//捕获信号SIGUSR1,null是代表要不要备份数据
while(1);
return 0;
}
sendsigaction
发送信息
#include <signal.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int signum = atoi(argv[1]);//将命令字符串转换成数字
int pid = atoi(argv[2]);//将pid字符串转换成数字
union sigval value;
value.sival_int = 100;//信息
sigqueue(pid,signum,value);//发送信息
printf("pid = %d done\n",getpid());
return 0;
}
六、信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
(1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
(2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
(3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
无论何时,进程想要使用资源需要调用p操作或者wait操作,当进程使用完资源需要调用v操作或者signal操作。如果s变为0则则进程不得不执行wait等待操作直到信号量s的值变为正数。举个栗子,假设有p1,p2,p3,p4四个进程并且它们都对信号量s执行了P操作(信号量s的值初始化为4),如果另一个进程p5想要进入临界区想要使用资源,它需要等到p1,p2,p3,p4这四个进程中的任意一个执行v操作使得信号量s的值变为正数。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
/* 创建或获取一个信号量组:int semget(key_t key, int num_sems, int sem_flags);
对信号量组进行操作,改变信号量的值:int semop(int semid, struct sembuf semoparray[], size_t numops);
控制信号量的相关信息:int semctl(int semid, int sem_num, int cmd, ...);
*/
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pgetkey(int id)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;//拿锁
sops.sem_flg = SEM_UNDO;//等待
semop(id, &sops, 1);
printf("get key\n");
}
void vputkey(int id)
{
struct sembuf sopss;
sopss.sem_num = 0;
sopss.sem_op = 1;//放回锁
sopss.sem_flg = SEM_UNDO;
semop(id, &sopss, 1);
// printf("put key\n");
}
int main(int argc,char **argv)
{
int semid;
key_t key;
key = ftok(".",6);
semid = semget(key, 1, IPC_CREAT|0666);
union semun initsem;
initsem.val = 0;//锁的总量,运用PV操作使得其在0与正数之间变化
semctl(semid,0,SETVAL,initsem);//获取创建信号量
//'0'代表操作第一个信号量,和数组一样0表示第一个信号量
//SETVAL设置信号量的值,设置为initsem
int pid = fork();
if(pid > 0){
pgetkey(semid);//拿锁
printf("this is father\n");
vputkey(semid);//放锁
semctl(semid,0,IPC_RMID);//摧毁锁
}
else if(pid == 0){
printf("this is child\n");
vputkey(semid);//放一把锁
}else{
printf("fork error\n");
}
return 0;
}