Linux进程间通信
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC 的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程 IPC。
无名管道
它是半双工的(即数据只能在一个方向上流动),具有固定的读端(fd[0
])和写端(fd[1
])。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
函数所需头文件及原型pipe
#include <unistd.h>
int pipe(int pipefd[2]);
返回值:创建失败返回-1,创建成功返回0
无名管道例子:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
int pid;
char buf[128];
if(pipe(fd) == -1){
printf("creat pipe failed\n");
}
pid = fork();
if(pid < 0){
printf("creat child failed\n");
}
else if (pid > 0){
sleep(2);
printf("this is father\n");
close(fd[0]);
write(fd[1],"hello this is a demo",strlen("hello this is a demo"));
}
else{
printf("this is child\n");
close(fd[1]);
read(fd[0],buf,128);
printf("read from father: %s\n",buf);
}
return 0;
}
注意: 管道为半双工通信,读和写操作在同一时间内只能进行一个,所以在读的时候要关闭写端,写的时候关闭读端。
运行结果:
命名管道
和无名管道的主要区别在于,命名管道有一个名字,命名管道的名字对应于一个磁盘索引节点,有了这个文件名,任何进程有相应的权限都可以对它进行访问。
而无名管道却不同,进程只能访问自己或祖先创建的管道,而不能访任意访问已经存在的管道——因为没有名字。
Linux中通过系统调用mknod()或makefifo()来创建一个命名管道。最简单的方式是通过直接使用shell
函数所需头文件及原型fifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname:路径名(例如:./file)
mode:权限(例如:0600)
命名管道例子:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int ret;
ret = mkfifo("./file",0600);
if(ret == 0){
printf("mkfifo success!\n");
}
if(ret == -1){
printf("mkfifo failure!\n");
perror("why");
}
return 0;
}
ps:创建命名管道后会生成一个文件(ls查看)
当 open 一个 FIFO 时,是否设置非阻塞标志(O_NONBLOCK
)的区别:
==若没有指定 O_NONBLOCK
(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
==若指定了 O_NONBLOCK
,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其 errno 置 ENXIO。
例子:
read代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int nread;
mkfifo("./file",0600);
char buf[1024] = {0};
int fd = open("./file",O_RDONLY);
printf("open success\n");
while(1){
nread = read(fd,buf,1024);
printf("read %d byte ,neirong:%s\n",nread,buf);
}
return 0;
}
write代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
mkfifo("./file",0600);
char *str = "this is a fifo demo!";
int fd = open("./file",O_WRONLY);
printf("open success\n");
while(1){
write(fd,str,strlen(str));
sleep(1);
}
return 0;
}
运行read会阻塞,一直到运行write后read才会继续往下执行。
消息队列
1.消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识。
2.消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
3.消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
4.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
函数原型
#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
例子:
get代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readBuf;
int msgId = msgget(0x1236,IPC_CREAT|0777);//0777为可读可写可执行权限
//这里0x1236可用ftok函数创建,具体百度百科。
if(msgId == -1){
printf("msgget failure!\n");
}
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read form que:%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);//移除消息队列,防止占用内存
return 0;
}
send代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf writeBuf = {888,"this is mssage from que!"};
int msgId = msgget(0x1235,IPC_CREAT|0777);
if(msgId == -1){
printf("msgget failure!\n");
}
msgsnd(msgId,&writeBuf,strlen(writeBuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);//移除消息队列,防止占用内存
return 0;
}
共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量 + 共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
函数原型
// 创建或获取一个共享内存:成功返回共享内存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);
例子
shmw代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
int main()
{
int shmid;//类似文件描述符
char *shmaddr;//共享内存的指针
key_t key;
key = ftok(".",1);
shmid = shmget(key,1024*4,IPC_CREAT|0666);//创建获取功效内存
if(shmid == -1){
printf("shmget failure!\n");
}
shmaddr = shmat(shmid,0,0);//连接到共享内存,返回的为共享内存的指针
printf("shmat ok\n");
strcpy(shmaddr,"this is a shm demo");//给共享内存写入数据
sleep(5);
shmdt(shmaddr);//断开连接
shmctl(shmid,IPC_RMID, 0);//清楚共享内存
printf("quit\n");
return 0;
}
shmr代码
#include <sys/ipc.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(".",1);
shmid = shmget(key,1024*4,0);
if(shmid == -1){
printf("shmget failure!\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);
printf("shmat ok\n");
printf("data ;%s\n",shmaddr);
shmdt(shmaddr);
printf("quit\n");
return 0;
}
信号
信号的本质是软件层次上对中断的一种模拟。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。
信号的捕捉:
#include <signal.h>
#include <stdio.h>
void handler(int signum)
{
printf("get signum = %d\n",signum);
printf("never quit\n");
}
int main()
{
signal(SIGINT,handler);
while(1);
return 0;
}
//运行程序后按下ctrl+c程序不会结束,在另一个终端调用kill 9 pid命令来强制结束次程序
//pid查看方法:ps -aux|grep 程序名
发送信号
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
printf("num = %d,pid = %d\n",signum,pid);
kill(pid,signum);
printf("send signal ok!");
return 0;
}
~
信号携带信息实例
//高级
#include <signal.h>
#include<stdio.h>
// int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
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);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO; // be able to get message
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
//send
#include <stdio.h>
#include <signal.h>
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
// int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value);
printf("done\n");
return 0;
}
信号量
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
函数原型
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
参数介绍:当 semget 创建新的信号量集合时,必须指定集合中信号量的个数(即 num_sems
),通常为 1; 如果是引用一个现有的集合,则将 num_sems
指定为 0 。
在 semop 函数中,sembuf 结构的定义如下:
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
在 semctl 函数中的命令有多种,这里就说两个常用的:
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合 semun 的 val 成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
参考:网友精彩博文