进程间通信概述:
1.数据传输:
一个进程需要将它的数据发送给另一个进程。
2.资源共享:
多个进程之间共享同样的资源 。
3.通知事件 :
一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
4.进程控制 :
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
现在Linux使用的进程间通信方式包括:
1、管道(pipe)和有名管道(FIFO) 2、信号(signal) 3、消息队列 4、共享内存 5、信号量 6、套接字(socket)。
管道通信:
管道是单向的,先进先出的,把一个进程的输入和另一个进程的输出连接出来。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。
管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。
管道包括无名管道和命名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
无名管道:
先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。必须在系统调用fork( )前调用pipe( ),否则子进程将不会继承文件描述符。
无名管道创建:int pipe(int filedis[2]);
当一个管道建立时,它会创建两个文件描述符:filedis[0] 用于读管道,filedis[1] 用于写管道。
代码:
int main()
{
int ret, fd[2] = {0};
pid_t pid;
ret = pipe(fd); //创建一个无名管道
if (-1 == ret)
{
perror("pipe");
exit(1);
}
pid = fork();
if (-1 == pid)
{
perror("fork");
exit(1);
}
else if (0 == pid)
{
close(fd[1]); //关闭写端口
ReadData(fd[0]); //fd[0]读数据(读到空管道堵塞等待管道输入)
}
else
{
close(fd[0]); //关闭读端口
int status;
WriteData(fd[1]); //fd[1]写数据
wait(&status); //等待子进程结束避免子进程成为孤儿进程
}
return 0;
}
命名管道和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用,但是通过命名管道,不相关的进程也能交换数据。
命名管道的创建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode)
pathname:FIFO文件名
mode:属性
代码:
int main()
{
int ret, fd;
char buf[32] = {0}; //从管道读取数据
ret = mkfifo("fifo.tmp", 666); //创建有名管道
if (ret == -1)
{
perror("mkfifo");
exit(1);
}
fd = open("fifo.tmp", O_RDONLY);
if (-1 == fd)
{
perror("open");
exit(1);
}
while (1)
{
ret = read(fd, buf, sizeof(buf));
if (-1 == ret)
{
perror("read");
exit(1);
}
if (!strcmp(buf, "bye"))
{
break;
}
printf("%s\n", buf);
memset(buf, 0, sizeof(buf));
}
close(fd);
unlink("fifo.tmp");//删除管道
return 0;
}
int main()
{
int ret, fd;
char buf[32] = {0}; //管道写数据
fd = open("fifo.tmp", O_WRONLY);
if (-1 == fd)
{
perror("open");
exit(1);
}
while (1)
{
scanf("%s", buf);
ret = write(fd, buf, strlen(buf));
if (-1 == ret)
{
perror("read");
exit(1);
}
if (!strcmp(buf, "bye"))
{
break;
}
memset(buf, 0, sizeof(buf));
}
close(fd);
return 0;
}
共享内存:
是被多个进程共享的一部分物理内存.共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
共享内存实现分为两个步骤:
1.创建共享内存,使用shmget函数。
2.映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
int shmget ( key_t key, int size, int shmflg )
key标识共享内存的键值: 0/IPC_PRIVATE。
当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
返回值:如果成功,返回共享内存标识符;如果失败,返回-1。
char * shmat ( int shmid, char *shmaddr, int flag)
参数:shmid:shmget函数返回的共享存储标识符 。
flag:决定以什么方式来确定映射的地址(通常为0。
返回值:如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回- 1。
当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。
int shmdt ( char *shmaddr )
指针shmaddr指向共享内存映射到进程中的地址。
信号量
信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源. 进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
二值信号灯:信号灯的值只能取0或1,类似于互斥锁。
但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。计数信号灯:信号灯的值可以取任意非负值。
代码(共享资源和信号量结合)
进程1
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SHMKEY 1234
#define SHMSIZE 4096 //以页为单位分配共享内存
#define SEMKEY 1234
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 sem_p(int semid)
{
int ret;
struct sembuf sbuf;
sbuf.sem_num = 0; //第一个 从0开始
sbuf.sem_op = -1; //p操作
sbuf.sem_flg = SEM_UNDO;
ret = semop(semid, &sbuf, 1);
if (-1 == ret)
{
perror("semop");
return;
}
}
void sem_v(int semid)
{
int ret;
struct sembuf sbuf;
sbuf.sem_num = 0; //第一个 从0开始
sbuf.sem_op = 1; //v操作
sbuf.sem_flg = SEM_UNDO;
ret = semop(semid, &sbuf, 1);
if (-1 == ret)
{
perror("semop");
return;
}
}
int main()
{
int shmid, semid, ret;
void *shmaddr;
int count = 0;
shmid = shmget(SHMKEY, SHMSIZE, IPC_CREAT | IPC_EXCL); //创建共享内存
if (-1 == shmid)
{
perror("shmget");
exit(1);
}
semid = semget(SEMKEY, 1, IPC_CREAT | IPC_EXCL); //创建信号量
if (semid == -1)
{
perror("semget");
exit(1);
}
union semun unsem;
unsem.val = 1; //初始化成二值信号量
ret = semctl(semid, 0, SETVAL, unsem); //初始化信号量
if (-1 == ret)
{
perror("semctl");
exit(1);
}
shmaddr = shmat(shmid, NULL, 0); //映射到虚拟地址空间
if (NULL == shmaddr)
{
perror("shmat");
exit(1);
}
*(int *)shmaddr = count; //数据写到共享内存
while (1)
{
sem_p(semid); //p操作 拔钥匙
count = *(int *)shmaddr; //读取数据
usleep(100000);
if (count >= 100)
{
break;
}
printf("Process A : count = %d\n", count);
count++;
*(int *)shmaddr = count; //写回共享内存
sem_v(semid); //v操作 加一操作 插钥匙
}
shmdt(shmaddr); //解除映射
shmctl(shmid, IPC_RMID, NULL);//删除共享资源
semctl(semid, 0, IPC_RMID);//删除信号量
return 0;
}
进程2
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SHMKEY 1234
#define SHMSIZE 4096 //以页为单位分配共享内存
#define SEMKEY 1234
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 sem_p(int semid)
{
int ret;
struct sembuf sbuf;
sbuf.sem_num = 0; //第一个 从0开始
sbuf.sem_op = -1; //p操作
sbuf.sem_flg = SEM_UNDO;
ret = semop(semid, &sbuf, 1);
if (-1 == ret)
{
perror("semop");
return;
}
}
void sem_v(int semid)
{
int ret;
struct sembuf sbuf;
sbuf.sem_num = 0; //第一个 从0开始
sbuf.sem_op = 1; //v操作
sbuf.sem_flg = SEM_UNDO;
ret = semop(semid, &sbuf, 1);
if (-1 == ret)
{
perror("semop");
return;
}
}
int main()
{
int shmid, semid, ret;
void *shmaddr;
int count = 0;
shmid = shmget(SHMKEY, SHMSIZE, 0); //创建共享内存
if (-1 == shmid)
{
perror("shmget");
exit(1);
}
semid = semget(SEMKEY, 1, 0); //创建信号量
if (semid == -1)
{
perror("semget");
exit(1);
}
shmaddr = shmat(shmid, NULL, 0); //映射到虚拟地址空间
if (NULL == shmaddr)
{
perror("shmat");
exit(1);
}
while (1)
{
sem_p(semid); //p操作 拔钥匙
count = *(int *)shmaddr; //读取数据
usleep(100000);
if (count >= 100)
{
break;
}
printf("Process B : count = %d\n", count);
count++;
*(int *)shmaddr = count; //写回共享内存
sem_v(semid); //v操作 加一操作 插钥匙
}
shmdt(shmaddr); //解除映射
return 0;
}
消息队列
系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
创建消息队列:
int msgget (key_t key,int msgflg)
参数key是一个键值,可以由ftok()函数获得,也可以自己宏定义,参数msgflg可以为IPC_CREAT、IPC_EXCL、IPC_NOWAIT。
IPC_CREAT:创建新的消息队列 (IPC_EXCL与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误) 。
IPC_NOWAIT:读写消息队列要求无法得到满足时,不阻塞。
进程1
代码
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MSGKEY 1234
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[64]; /* message data */
};
int main()
{
struct msgbuf mbuf;
int ret;
int msgid = msgget(MSGKEY, IPC_CREAT | IPC_EXCL);
if (-1 == msgid)
{
perror("msgget");
exit(1);
}
pid_t pid = fork();
if (-1 == pid)
{
perror("fork");
exit(1);
}
else if (0 == pid) //子进程发送
{
while (1)
{
memset(&mbuf, 0, sizeof(mbuf));
mbuf.mtype = 1; //消息类型
scanf("%s", mbuf.mtext);
ret = msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);//往消息队列写入数据
if (-1 == ret)
{
perror("msgsnd");
exit(1);
}
if (!strcmp(mbuf.mtext, "bye"))
{
mbuf.mtype = 2;中
msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);
break;
}
}
}
else //父进程接收
{
while (1)
{
memset(&mbuf, 0, sizeof(mbuf));
ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), 2, 0);//读出消息队列中的数据
if (-1 == ret)
{
perror("msgrcv");
exit(1);
}
if (!strcmp(mbuf.mtext, "bye"))
{
kill(pid, 2);
break;
}
printf("\t\t\t%s\n", mbuf.mtext);
memset(&mbuf, 0, sizeof(mbuf));
}
}
sleep(1);//等待进程2结束
msgctl(msgid, IPC_RMID, NULL); //在内核中删除消息队列
return 0;
}
进程2:
代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MSGKEY 1234
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[64]; /* message data */
};
int main()
{
struct msgbuf mbuf;
int ret;
int msgid = msgget(MSGKEY, 0);
if (-1 == msgid)
{
perror("msgget");
exit(1);
}
pid_t pid = fork();
if (-1 == pid)
{
perror("fork");
exit(1);
}
else if (0 == pid) //子进程发送
{
while (1)
{
memset(&mbuf, 0, sizeof(mbuf));
mbuf.mtype = 2; //消息类型
scanf("%s", mbuf.mtext);
ret = msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);//往消息队列找那个写入数据
if (-1 == ret)
{
perror("msgsnd");
exit(1);
}
if (!strcmp(mbuf.mtext, "bye"))
{
mbuf.mtype = 1;
msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);
break;
}
}
}
else //父进程接收
{
while (1)
{
memset(&mbuf, 0, sizeof(mbuf));
ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), 1, 0);//从消息队列中读出数据
if (-1 == ret)
{
perror("msgrcv");
exit(1);
}
if (!strcmp(mbuf.mtext, "bye"))
{
kill(pid, 2);
break;
}
printf("\t\t\t%s\n", mbuf.mtext);
memset(&mbuf, 0, sizeof(mbuf));
}
}
return 0;
}
两个进程实现了互相发送消息的功能。
如果想要查看信号量相关函数具体参数及其功能的可以点开下面的连接: