以下是阿鲤对Linux下进程间通信的总结,希望对大家有所帮助;若有误请慷慨指出。
因为每一个进程都要有一个独立的虚拟地址空间,在保证了进程的独立性同时,却使得进程间无法通信;所以必须要借助一定的方法进行进程间通信,阿鲤在这里主要介绍以下几种通信方式(以下均为SystemV标准)
1:管道 -- 用于进程间的数据传输
2:共享内存 -- 用于进程间的数据共享
3:消息队列 -- 用于进程间的数据传输
4:信号量 -- 用于时间进程间控制
注:以下的代码实现均为centos7环境;
一:管道 -- 用于进程间的数据传输
1:本质:通过让多个进程都能访问到同一块内核中的缓冲区,通过半双工通信实现数据传输 (半双工通信:方向可选择的单项通信)
2:分类: 匿名管道 和 命名管道
3:匿名管道:这块内核中的缓冲区没有标识符
3.1:因为匿名管道没有标识符,故只能用于具有亲缘关系之间的进程通信;
3.2:在创建管道时,操作系统会提供两个操作句炳(文件描述符),其中一个用于从管道读取数据,一个用于向管道写入数据;但是我们在使用时往往会关闭一个使用一个
3.3:我们都知道子进程是通过复制父进程进行创建的所以,子进程也会复制到父进程所创建管道的操作句柄,故父子进程便可以通过父进程所创建的匿名管道进行进程间通信。
3.4:int pipe(int pipefd[2]):
创建一个匿名管道,通过参数pidefd向用户返回管道的操作句柄,其中pipedf[0]用于向管道读取数据,pipedf[1]用于向管道写入数据。返回值 0代表成功 -1代表失败
3.5 eg:
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int pipefd[2] = {0};//创建匿名进程
int ret = pipe(pipefd);
if(ret < 0)
{
perror("pipe erroe");
return -1;
}
int pid = fork();//创建子进程
if(pid < 0)
{
perror("fork error");
return -1;
}
else if(pid == 0)//子进程
{
//sleep(1);
//close(pipefd[1]);//用于关闭写端,父子进程均有写端,所以均需要关闭
//close(pipefd[0]);//用于关闭读端
//pause();//用于关闭读端
char buf[1024] = {0};
int ret = read(pipefd[0], buf, 1023);//子进程用于读
printf("buf:[%s]-[%d]\n",buf,ret);
}
else//父进程
{
//close(pipefd[1]);//用于关闭写端
//sleep(1);
//close(pipefd[0]);//用于关闭读端
char *ptr = "BelongAl";
write(pipefd[1], ptr, strlen(ptr));//父进程用于写
}
return 0;
}
3.6:匿名管道的特性:
1:管道自带同步与互斥(同步:若管道写满了则write阻塞;若管道中没有数据则read阻塞;互斥:在对管道进行数据操作的大小不超过PIPE_BUF=4096字节的时候,则保证操作的原子性(原子性:在一个任务未完成前不可被打断))。
2:若管道所有写端被关闭,read读完管道中的数据后就不会阻塞,而是返回0。
3:若管道所有读端被关闭,若继续写入(write)会触发异常程序退出。
3.7匿名管道的应用
我们在查看某个信息时候会采用这样的操作:比如:ps -ef | grep ssh 这个的命令来对信息进行过滤,那么它的原理是怎样实现的呢?
这起始就是采用管道进行实现的,ps -ef将信息写入管道中,grep再对管道中的信息进行过滤打印;实现代码如下(兄弟进程)
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
int pipefd[2] = {0};//创建管道
int ret = pipe(pipefd);
if(ret < 0)
{
perror("pipe error");
return -1;
}
int pid1 = fork();//为ps创建子进程
if(pid1 == 0)
{
dup2(pipefd[1], 1);//重定向
execl("/usr/bin/ps", "ps", "-ef", NULL);
exit(0);
}
int pid2 = fork();//为grep创建子进程
if(pid2 == 0)
{
close(pipefd[1]);//关闭写端则读端退出返回0
dup2(pipefd[0], 0);//重定向
execl("/usr/bin/grep", "grep", "sshd", NULL);
exit(0);
}
close(pipefd[0]);//关闭读端
close(pipefd[1]);//关闭写端
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
return 0;
}
4:命名管道:
内核中的缓冲区具有标识符,其他进程可以通过标识符找到这块缓冲区,进而实现进程间通信
4.1:这个标识符是一个可见于文件系统的管道文件
4.2:命名管道的创建
mkfifo命令创建,如下 test.fifo
int mkfifo(const char *pathname, mode_t mode);接口创建 (pathname:管道文件名称 mpde:文件权限 返回值:成功返回0 失败返回-1) 例子如下
4.3:命名管道的特性:
1:若管道以只读的方式打开,则会阻塞,直到这个管道被以写的方式打开。
2:命名管道不会再创建时就开辟缓冲区,而是等有人写的时候才会开辟缓冲区。
3:若管道以只写的方式打开,则会阻塞,直到这个管道以读的方式打开。
4:若以读写的方式打开就不会阻塞。
5:管道的声明周期随进程
6:提供字节流服务--有序,链接,可靠的字节流传输
4.5:命名管道举例
读端代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<errno.h>
#include<fcntl.h>
int main()
{
umask(0);
char *file = "./test.fifo";//创建命名管道
int ret = mkfifo(file, 0664);
if(ret < 0 && errno != EEXIST)//如管道文件已经存在便直接退出
{
perror("mkfifo erroe");
return -1;
}
int fd = open(file, O_RDONLY); //打开管道文件
if(fd < 0)
{
perror("open error");
return -1;
}
printf("open fifo succes\n");
while(1)
{
char buf[1024] = {0};
ret = read(fd, buf, 1023);
if(ret < 0)
{
perror("read error");
return -1;
}
else if(ret == 0)//如果写端被全部关闭,则读端返回0 直接退出避免死循环
{
printf("All write ends are closed\n");
return -1;
}
printf("buf[%s]\n",buf);
}
return 0;
}
写端代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
int main()
{
umask(0);
char *file = "./test.fifo";//创建命名管道
int ret = mkfifo(file, 0664);
if(ret < 0 && errno != EEXIST)//如管道文件已经存在便直接退出
{
perror("mkfifo erroe");
return -1;
}
int fd = open(file, O_WRONLY);
if(fd < 0)
{
perror("open error");
return -1;
}
printf("open fifo succes\n");
while(1)
{
char buf[1024] = {0};
scanf("%s",buf);
ret = write(fd, buf, strlen(buf));
if(ret < 0)
{
perror("write error");
return -1;
}
}
return 0;
}
二:共享内存 -- 用于进程间的数据共享
1:共享内存的原理
1.1:在物理内存上开辟一块内存空间
1.2:将这块物理内存映射到进程的虚拟地址空间
1.3:进程就可以通过虚拟地址直接访问这块物理内存
1.4:多个进程要是同时映射一块物理内存,就可以通过这块内存实现数据共享
2:为什么共享内存时最快的进程间通信?
因为共享内存直接通过虚拟地址映射访问物理内存,而其他方式因为都是内核中的缓冲区,因此通信时会涉及用户态和内核态之间的两次数据拷贝。因为少了两次用户态与内核态之间的拷贝,因此通信速度最快。
3:共享内存的操作流程
3.1:创建共享内存(开辟具有标识符的物理内存)
int shmget(key_t key, size_t size, int shmflg);
key:内存空间的标识;
size:共享内存的大小,以内存页为单位;
shmflg:选项参数,IPC_CREAT | IPC_EXCL(存在则报错) | 权限
return:成功返回操作句柄,失败返回-1
3.2:映射到虚拟地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:共享内存操作句柄
shmaddr:映射到虚拟地址空间的首地址,通常置NULL
shmflg:通常置0-可读可写 SHM_RDONLY-只读 取决于权限
return:成功返回映射的虚拟空间首地址,通过对这个地址内存进行操作;失败返回-1
3.3:内存操作
3.4:解除映射
int shmdt(const void *shmaddr);
shmaddr:映射到虚拟地址空间的首地址
return:成功返回-1,失败返回0
3.5:删除共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:操作句柄
cmd:具体的操作 常用的为IPC_RMID删除共享内存
buf:共享内存的一些描述信息
return:成功返回0,失败返回-1;
注:当删除共享内存的时候,共享内存并不会立即被删除(因为有可能会造成正在访问的进程崩溃),而时将key修改为0,表示这块内存将不再继续接收映射链接,当这块共享内存的映射连接数为0的时候,则自动释放。
4:eg:
写端代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x12345678
int main()
{
umask(0);
int shmid = shmget(IPC_KEY, 32, IPC_CREAT|0664);//创建共享内存
//key_t key = ftok("./", IPC_KEY) //比较鸡肋的造key
if(shmid < 0)
{
perror("shmget error");
return -1;
}
void *shmstart = shmat(shmid, NULL, 0);//建立映射
if(shmstart == (void*)-1)
{
perror("shmat error");
return -1;
}
int i = 0;
while(1)
{
sprintf(shmstart, "%s-%d\n", "share memory", i++);
sleep(1);
}
shmdt(shmstart);//解除映射
shmctl(shmid,IPC_KEY, NULL);//删除共享内存
return 0;
}
读端代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x12345678
int main()
{
umask(0);
int shmid = shmget(IPC_KEY, 32, IPC_CREAT|0664);//创建共享内存或打开共享内存
if(shmid < 0)
{
perror("shmget error");
return -1;
}
void *shmstart = shmat(shmid, NULL, 0);//建立映射
if(shmstart == (void*)-1)
{
perror("shmat error");
return -1;
}
int i = 0;
while(1)
{
printf("%s",shmstart);
sleep(1);
}
shmdt(shmstart);//解除映射
shmctl(shmid,IPC_KEY, NULL);//删除共享内存
return 0;
}
5:注意事项及特性
5.1:最快的进程间通信方式
5.2:生命周期随内核
5.3:共享内存操作不安全(并不会自动具备同步与互斥关系,需要操作用户自己控制)
三:消息队列 -- 用于进程间的数据传输
1:原理
本质上是内核中的一个优先级队列,多个进程通过向同一个队列中添加节点和获取节点实现通信。传输一个有类型(优先级)的数据快。
2:特性
自带同步与互斥,生命周期随内核,数据传输自带优先级
3:接口
msgget:创建
msgsnd:添加节点
msgrcv:获取节点
msgctl:操作-删除消息队列
注意:ipcs查看进程间通信资源 ipcm 删除进程间通信资源
-m:查看共享内存
-q:查看消息队列
-s:查看信号量
来源:CSDN
作者:belongAL
链接:https://blog.csdn.net/belongHWL/article/details/103652778