进程间通信(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 buf[128];
ret = pipe(fd);
if(ret == -1){
perror("pipe:");
exit(-1);
}
pid_t pid = fork();
if(pid < 0){
perror("create child failed\n");
exit(-1);
}else if(pid > 0){
printf("Please input content:\n");
fgets(buf,128,stdin);
close(fd[0]);
write(fd[1],buf,strlen(buf));
wait();
}else{
memset(buf,0,sizeof(buf));
close(fd[1]);
read(fd[0],buf,128);
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){
perror("error\n");
exit(-1);
}
int fd = open("./fifofile",O_RDONLY);
if(fd < 0){
perror("open fifo:");
exit(-1);
}
while(1){
memset(buf,0,sizeof(buf));
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 buf[128];
int fd = open("./fifofile",O_WRONLY);
if(fd == -1){
perror("fifo:");
exit(-1);
}
while(1){
memset(buf,0,sizeof(buf));
printf("Please input content:\n");
fgets(buf,128,stdin);//从键盘获取内容
write(fd,buf,strlen(buf));
if(strstr(buf,"quit") != NULL)
break;
}
close(fd);
return 0;
}
三、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。
msgsend.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
/* 创建或打开消息队列:int msgget(key_t key, int flag);
添加消息:int msgsnd(int msqid, const void *ptr, size_t size, int flag);
读取消息:int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
控制消息队列:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
*/
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key;
key = ftok(".",1);
int msgid = msgget(key,IPC_CREAT|0777);
if(msgid == -1){
perror("error");
}
struct msgbuf sendbuf ={666,"this is send massage"};
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
msgrcv.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdio.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key;
key = ftok(".",1);
int msgid = msgget(key,IPC_CREAT|0777);
if(msgid == -1){
perror("error");
}
struct msgbuf rcvbuf;
msgrcv(msgid,&rcvbuf,sizeof(rcvbuf.mtext),666,0);
printf("read from quen:%s\n",rcvbuf.mtext);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
四、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
shmw.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 shmget(key_t key, size_t size, int flag);
连接共享内存到当前进程的地址空间:void *shmat(int shm_id, const void *addr, int flag);
断开与共享内存的连接:int shmdt(void *addr);
控制共享内存的相关信息:int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
*/
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".",2);
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;
}
shmr.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;
key = ftok(".",2);
shmid = shmget(key,1024*4,0);
shmaddr = shmat(shmid,0,0);
printf("context: %s\n",shmaddr);
shmdt(shmaddr);
return 0;
}
五、信号
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
入门
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");
}
printf("never quit\n");
}
int main()
{
printf("pid = %d\n",getpid());
signal(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)
{
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);
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]);
union sigval value;
value.sival_int = 100;
sigqueue(pid,signum,value);
printf("pid = %d done\n",getpid());
return 0;
}
六、信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
#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;
semctl(semid,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;
}