进程通信IPC
InterProcess Communication
多个进程间的交互
方式:
1.管道(无名管道和命名管道)pipo FIFO
2.消息队列
3.共享内存
4.信号
5.信号量
6.还有Socket 、Streams用于不同主机间两个进程的通信
管道
1.无名管道:半双工,具有固定读写端。用于具有情缘关系的父子进程或兄弟进程。它可看作特殊文件,对其的读写使用系统调用的read/write,注意它不属于任何文件系统,只存在于内存中。
原型:**int pipe(int pipefd[2]);**成功返回0,失败返回-1;
原理:一个管道建立会创建两个文件描述符,f[0]为读端,f[1]为写端。
demo:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int fd[2];//定义管道所用的文件描述符
char readbuf[30];//定义缓冲区
pid_t pid;
if(pipe(fd)<0)
{
printf("creat pipe fail!!!\n");
exit(-1);
}
pid=fork();
if(pid<0)
{
printf("creat process fail!\n");
close(fd[0]);
close(fd[1]);
exit(-2);
}else if(pid>0){//父进程设置为写
close(fd[0]);//关闭读端
write(fd[1],"test",5);
close(fd[1]);
}else{ //子进程设置为读
close(fd[1]);//关闭写端
read(fd[0],readbuf,5);
close(fd[0]);
}
printf("OK.child process read:%s\n",readbuf);//此处会执行打印两次,父进程那次为乱码,因为readbuf没有初始化。
return 0;
}
2.命名管道FIFO:可在无关进程之间交换数据,命名管道与路径有关,它以特殊设备文件形式存在于文件系统中。
原型:int mkfifo(const char *pathname, mode_t mode);//创建实现管道功能的特殊设备文件。
原理:先创建一个作为管道的特殊设备文件,不同进程可打开设备文件进行读写操作。
demo:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
int ret = mkfifo("./FIFO",0600);
if(ret==0)
{
printf("creat FIFO successful!!!\n");
}else{
printf("have the file OR creat file fail!!!\n");
perror("why");
}
//如果成功创建文件,可以用file指令看看文件的属性。(ls -l)
return 0;
}
/*
if(mkfifo("./FIFO",0600) == -1 && errno ==EEXIST)
{
printf("creat file fail!!!\n");
perror("why");
}else{
if(errno == EEXIST)
{
printf("have the FIFO file.\n");
}else printf("creat FIFO successful!!!\n");
}
*/
基于上方代码创建的FIFO文件,进行进程通信
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
char buf[50]={0};
fd=open("./FIFO",O_RDONLY);
if(fd<0)
{
printf("open FIFO fail!!!\n");
//以读的方式打开的FIFO没有写入会阻塞
//因为默认没有设置非阻塞标志O_NONBLOCK
}
printf("open FIFO successful.\n");
read(fd,buf,sizeof(buf));
printf("BUF:%s\n",buf);
close(fd);
return 0;
}
/*//再写一份写操作的代码,只有进行写打开操作,读打开才会结束阻塞状态。
int fd;
fd=open("./FIFO",O_WRONLY);
if(fd<0)
{
printf("write open FIFO fail!!!\n");
exit(-1);
}else{
write(fd,"This is test.",20);
}
close(fd);
*/
消息队列
特点:
消息队列是消息的链接表,放在内核中,由一个标识符(队列ID)来标识。1.它面向记录,消息具有特定格式和特定优先级。2.它独立于发送与接收端,进程终止时消息队列及其内容不会被删除。3.消息队列可以实现消息的随机查询,不一定按先进先出的次序读取,可以按消息的类型读取。
原型:
int msgget(key_t key, int msgflg);//创建打开消息队列成功返回队列ID,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//添加消息,成功返回0,失败返回-1.
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//读取消息,成功返回消息数据长度,失败返回-1;
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列。成功返回0,失败返回-1
原理:
内核中创建的消息队列,数据放到队列的节点(结构体),再读取和写入后,就由内核机制管理。通过key索引值找相应的队列。通过flag(方式)打开消息队列。
demo:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct msgbuf
{
long mtype;
char mtext[128];
}MSG;
int main(void)
{
MSG buf;
//打开或创建消息队列,两个进程基于同一个消息队列,同一key值进行通信。
int id=msgget(0x1234,IPC_CREAT|0777);
if(id == -1)
{
printf("get msg fail!!!\n");
exit(-1);
}
msgrcv(id,&buf,sizeof(buf.mtext),888,0);//队列获取
printf("read data:%s\n",buf.mtext);
/*
MSG sndbuf={888,"This is test program!"};
msgsnd(id,&sndbuf,strlen(sndbuf.mtext),0);//队列发送
*/
/*
//获取key值和消息队列移除
key_t ftok(const char *fname,int id);//获取key值
fname:指定文件名,一般使用当前目录
id:子序号。范围“1-255”
key_t key;
key=ftok(".",1);
msgctl(id,IPC_RMID,NULL);//移除消息队列,在发送和接收程序中都要调用
*/
return 0;
}
共享内存
特点:创建一段共享内存,必须指定其size;引用已存在的共享内存,则将size指定为0;
原型:
//创建或获取一个共享内存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);
原理:
创建一个共享性质的内存,两个进程中可创建指针去映射到共享内存。是基于同一块内存空间进行数据操作。以实现两个普通进程间的交互。
demo:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
key_t key;
int id;
char *addr;
key=ftok(".",1);
//注意:共享内存的大小以M对齐
id=shmget(key,1024*4,IPC_CREAT|0600);
if(id==-1)
{
printf("creat shm fail!!!\n");
exit(-1);
}
addr=shmat(id,0,0);//共享内存地址映射
//往共享内存中写数据
strcpy(addr,"This is test program!\n");
sleep(3);
shmdt(addr);//断开映射连接
shmctl(id,IPC_RMID,0);//销毁共享内存
printf("quit.\n");
/*
//在共享内存中读数据的程序
id=shmget(key,1024*4,0);
if(id==-1)
{
printf("open shm fail!!!\n");
exit(-1);
}
addr=shmat(id,0,0);
printf("from shm data:%s\n",addr);
shmdt(addr);
*/
return 0;
}
注:共享内存是全双工,所以要避免两进程同时写入数据导致混乱。可使用信号量加以约束
ipcs -m使用该指令查看创建的共享内存。
ipcrm -m 共享内存ID号删除创建的共享内存
信号
特点:
是信号,为Linux提供了一种处理异步事件的方法。比如,终端用户输入了Ctrl+c来中断程序。信号SIG开头。可用 kill -l 来查看信号的名字以及序号,不存在0号信号,kill对于信号又有特殊的应用,信号具有优先级。
信号处理的方法:忽略,捕捉,默认动作
其中的捕捉信号(信号处理函数),默认动作可使用man 7 signal来查看系统定义。杀死进程kill -9 ID号
原型:
信号处理函数注册
入门:**signal---sighandler_t signal(int signum, sighandler_t handler);**
进阶:**sigaction---int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);**
信号发送函数
入门:kill---
进阶:**sigqueue---int sigqueue(pid_t pid, int sig, const union sigval value);**
原理:
信号相当于中断源,用于中断。
demo:
信号捕捉(无效Ctrl C)使用ps -aux|grep a.out 并kill -9 ID号结束
#include <signal.h>
#include <stdio.h>
void handler(int signum)
{
printf("get signal number:%d\n",signum);
switch(signum)
{
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");//理论上这个信号不能被捕捉。
break;
case 10:
printf("SIGUSR1\n");
break;
default:
printf("Don't know.\n");
}
}
int main(void)
{
signal(SIGINT,handler);//信号注册
//如果要忽略信号使用宏:SIGIGN(signal(SIGINT,SIGIGN))
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
信号发送
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
//可以把该程序编译成名为kill的可执行程序,使用方法和kill 指令一样。
int main(int argc,char **argv)
{
int signum;
int pid;
if(argc!=3)
{
printf("input error!!!\n");
exit(-1);
}
signum=atoi(argv[1]);//ASCII转整型
pid=atoi(argv[2]);
printf("num:%d pid:%d\n",signum,pid);
kill(pid,signum);//向相应进程发送相应信号
//另一种写法system();
/*
int i=0;
char cmd[50];
memset(cmd,0,sizeof(cmd));
strcpy(cmd,"kill -");
strcat(cmd,argv[1]);
strcat(cmd," ");
strcat(cmd,argv[2]);
puts(cmd);
system(cmd);
*/
//还有一种
/*
char cmd[50]={0};
sprintf(cmd,"kill -%d %d",signum,pid);
puts(cmd);
system(cmd);
*/
return 0;
}
信号携带信息(进阶)
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
/*
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
*/
//自定义的函数
void handler(int signum ,siginfo_t *info,void *context)
{
printf("get signum:%d\n",signum);
if(context != NULL)//检测是否有数据
{
printf("sigInformation:%d\n",info->si_int);
printf("sigInformation:%d\n",info->si_value.sival_int);
printf("sigInformation:%d\n",info->si_pid);
}
}
int main(void)
{
struct sigaction act;
printf("ID:%d\n",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT,&act,NULL); //注册信号
//第二个参数:收到消息后的动作
//第三个参数:做备份
while(1);
return 0;
}
基于上段代码,发送信号
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
//int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
pid_t pid;
int signum;
union sigval si;
if(argc!=3)
{
printf("input error!!!\n");
return 0;
}
signum=atoi(argv[1]);
pid=atoi(argv[2]);
si.sival_int = 100;//发送信号携带的信息
sigqueue(pid,signum,si);//信号发送
printf("ID:%d\n",getpid());
printf("OK\n");
return 0;
}
信号量(semaphore)
特点:
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。信号基于操作系统的PV操作,程序对信号量操作都是原子操作。对信号量的PV操作中增减值可以是任意非负整数。并且有信号量组。
原型:
//创建或获取信号量组,成功返回信号量集ID,反之返回-1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值:成功返回0,反之返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
//控制信号量的相关信息
int semctl(int semid, int semnum, int cmd, …);
原理:
信号量与其他IPC结构不同,用于实现进程的同步和互斥,与存储进程间通信数据无关,相当于一个计数器。
对信号量的操作:P是拿锁 V是放回锁
信号量对于管理临界资源很有用。
demo:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
} ;
void pkey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("getkey\n");
}
void vkey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("back key\n");
}
int main(int argc,char const *argv[])
{
key_t key;
int semid;
int pid;
union semun initsem;
initsem.val = 0;//信号量,最开始没锁(锁的初始状态)
key=ftok(".",2);//获取key
//获取/创建一个信号量集,只有一个信号量
if((semid=semget(key,1,IPC_CREAT|0666))==-1)
{
printf("creat semaphore fail!!!\n");
return -1;
}
//操作第0个信号量,如果有多个信号量,相当于数组的第一个。SETVAL用来设置信号量的值
//设置为initsem共同体
semctl(semid,0,SETVAL,initsem);//信号量初始化
pid=fork();
if(pid>0)
{
//父进程拿锁
pkey(semid);
printf("father process:%d\n",getpid());
}else if(pid==0){
//子进程执行完放锁,父进程在之前没拿到锁,阻塞
printf("child process:%d\n",getpid());
vkey(semid);
}else{
printf("creat process fail!!!\n");
}
return 0;
}