😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
本文未经允许,不得转发!!!
目录
- 🎄一、POSIX消息队列
- ✨1.1 POSIX消息队列介绍
- ✨1.2 例子
- 🎄二、POSIX信号量
- ✨2.1 POSIX信号量介绍
- ✨2.2 例子
- 🎄三、 POSIX共享内存
- ✨3.1 POSIX共享内存介绍
- ✨3.2 例子
- 🎄四、信号
- 🎄五、套接字
- 🎄六、总结
🎄一、POSIX消息队列
✨1.1 POSIX消息队列介绍
POSIX消息队列与System V消息队列有一定的相似之处, 信息交换的基本单位是消息, 但也有显著的区别。Linux实现里POSIX消息队列的句柄本质是文件描述符,所以可以使用I/O多路复用系统调用(select、 poll或epoll
等) 来监控这个文件描述符。其次, POSIX消息队列提供了通知功能, 当消息队列中有消息可用时, 就会通知到进程。
编程步骤:
- 1、创建/获取消息队列
name:必须以#include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <mqueue.h> mqd_t mq_open(const char *name, int oflag); mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
/
打头, 而且后续字符不允许出现/
,最大长度为255个字符;
oflag:允许的标志位包括O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_NONBLOCK
mode:mode设置的是访问权限,创建时有效;
attr:attr设置的是消息队列的属性,创建时有效。int mq_fd = mq_open("/mqPosix", O_RDWR | O_CREAT, 0666, NULL); //创建 int mq_fd = mq_open("/mqPosix", O_RDWR); // 获取
- 2、发送/获取数据
服务端发送:mq_send(mq_fd, buf, strlen(buf), i);
客户端获取:mq_receive(mq_fd, buf, mqAttr.mq_msgsize, &prio);
获取数据之前,可能需要先获取消息队列的属性,mq_getattr(mq_fd,&attr)
。- 3、关闭消息队列句柄
mq_close(mq_fd);
- 4、删除消息队列
mq_unlink("/mqPosix");
✨1.2 例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <mqueue.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
int main()
{
// 创建子进程
pid_t pid = fork();
if(pid == 0) {// 子进程
printf("子进程[%d]开始执行, 创建POSIX消息队列,循环往里写数据\n", getpid());
// 创建消息队列
int mq_fd = mq_open("/mqPosix", O_RDWR|O_CREAT|O_EXCL, 0664, NULL);
if(mq_fd < 0)
{
if (errno == EEXIST)
{
printf("/mqPosix EEXIST\n");
mq_unlink("/mqPosix");
mq_fd = mq_open("/mqPosix", O_RDWR | O_CREAT, 0666, NULL);
}
else
{
perror("mq_open failed");
exit(-1);
}
}
// 发送数据
int i = 9;
while(i>=0)
{
char buf[256] = {0,};
sprintf(buf, "hello-%d", i);
if (mq_send(mq_fd, buf, strlen(buf), i) < 0)
{
perror("mq_send failed");
exit(-1);
}
printf("子进程[%d]写入数据:hello-%d\n", getpid(), i);
i--;
sleep(1);
}
mq_close(mq_fd);
printf("子进程[%d]退出\n", getpid());
return 0;
}
else if(pid > 0)// 父进程
{
sleep(3); //延时一会,让子进程先运行
printf("父进程[%d]开始执行, 获取消息队列,读取数据\n", getpid());
int mq_fd = mq_open("/mqPosix", O_RDWR);
if(mq_fd == -1)
{
perror("mq_open failed");
exit(1);
}
struct mq_attr mqAttr;
mq_getattr(mq_fd, &mqAttr);
printf("mq_msgsize=%ld\n",mqAttr.mq_msgsize);
char *buf = (char*)malloc(mqAttr.mq_msgsize);
while(1)
{
unsigned prio = 0;
int res = mq_receive(mq_fd, buf, mqAttr.mq_msgsize, &prio);//阻塞
printf("res=%d, 消息:%s, prio:%u\n", res, buf, prio);
if(res == -1)
{
perror("mq_receive failed");
break;
}
}
mq_close(mq_fd);
mq_unlink("/mqPosix");
printf("父进程[%d]退出\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
保存上面代码,运行gcc mq_posix.c -lrt
运行结果:
🎄二、POSIX信号量
✨2.1 POSIX信号量介绍
POSIX信号量和System V信号量的作用是相同的, 都是用于同步进程之间及线程之间的操作, 以达到无冲突地访问共享资源的目的。
POSIX提供了两类信号量: 有名信号量和无名信号量。
无名信号量, 又称为基于内存的信号量
, 由于其没有名字, 没法通过open操作直接找到对应的信号量, 所以很难直接用于没有关联的两个进程之间。 无名信号量多用于线程之间的同步
。
有名信号量由于其有名字, 多个不相干的进程可以通过名字来打开同一个信号量
, 从而完成同步操作, 所以有名信号量的操作要方便一些, 适用范围也比无名信号量更广。
有名信号量编程步骤:
- 1、创建/获取有名信号量
name:可以以1个或多个#include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
/
打头, 也可以不以/
打头。打头的一个或多个/字符不计入长度。最大长度为255个字符;
oflag:oflag标志位支持的标志包括O_CREAT和O_EXCL标志位。 如果带了O_CREAT标志位,则表示要创建信号量;
mode:mode设置的是访问权限,创建时有效;
value:value是新建信号量的初始值,创建时有效。sem_t *sem_p = sem_open("/semPosix", O_RDWR|O_CREAT|O_EXCL, 0664, 1);//创建 sem_t *sem_p = sem_open("/semPosix", O_RDWR); // 获取
- 2、申请该资源
当申请该资源时, 需要先调用sem_wait函数; 当发布该资源或使用完毕释放该资源时,则调用sem_post函数。可能还有使用下面两个等待信号量的函数:// 使用资源,数量 -1 sem_wait(sem_p); // 使用资源... // 释放资源,数量 +1 sem_post(sem_p);
int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
- 3、关闭信号量句柄
sem_close(sem_p);
- 4、删除信号量
sem_close(sem_p);
无名信号量编程步骤和上面的基本差不多,就创建和销毁不一样:
- 初始化无名信号量
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value);
- 销毁无名信号量
#include <semaphore.h> int sem_destroy(sem_t *sem);
✨2.2 例子
// gcc sem_posix.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/stat.h>
int main()
{
// 2 创建子进程
pid_t pid = fork();
if(pid == 0) {// 子进程
printf("子进程[%d]开始执行, 创建信号量,使用资源\n", getpid());
// 创建信号量集
sem_t *sem_p = sem_open("/semPosix", O_RDWR|O_CREAT|O_EXCL, 0664, 1);
if(SEM_FAILED == sem_p)
{
if (errno == EEXIST)
{
printf("/semPosix EEXIST\n");
sem_unlink("/semPosix");
sem_p = sem_open("/semPosix", O_RDWR | O_CREAT, 0666, 1);
}
else
{
perror("sem_open failed");
exit(-1);
}
}
// 使用资源,数量 -1
sem_wait(sem_p);
printf("子进程[%d]访问共享资源\n", getpid());
sleep(20);
printf("子进程[%d]完成共享资源的访问\n",getpid());
// 释放资源,数量 +1
sem_post(sem_p);
sem_close(sem_p);
printf("子进程[%d]退出\n", getpid());
return 0;
}
else if(pid > 0)// 父进程
{
sleep(3); //延时一会,让子进程先运行
printf("父进程[%d]开始执行, 获取信号量,准备使用资源\n", getpid());
sem_t *sem_p = sem_open("/semPosix", O_RDWR);
if(SEM_FAILED == sem_p)
{
perror("sem_open failed");
exit(1);
}
// 使用资源,数量 -1
sem_wait(sem_p);
printf("父进程[%d]访问共享资源\n", getpid());
sleep(3);
printf("父进程[%d]完成共享资源的访问\n",getpid());
// 释放资源,数量 +1
sem_post(sem_p);
sem_close(sem_p);
sem_unlink("/semPosix");
printf("父进程[%d]退出\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
注意,编译时需要加-lpthread
,保持上面代码,运行gcc sem_posix.c -lpthread
编译,
运行结果如下:
🎄三、 POSIX共享内存
✨3.1 POSIX共享内存介绍
Linux系统中,POSIX共享内存的本质是将一个文件通过mmap函数将共享内存映射到进程的地址空间。
编程步骤:
- 1、创建/获取共享内存句柄
name:可以以1个或多个#include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> int shm_open(const char *name, int oflag, mode_t mode);
/
打头, 也可以不以/
打头。打头的一个或多个/字符不计入长度。最大长度为255个字符;
oflag:oflag标志位要包含O_RDONLY或O_RDWR,另外,可以选择的标志位还有O_CREAT(表示创建) 、 O_EXCL(配合O_CREAT表示排他创建)、O_TRUNC(表示将共享内存的size截断成0)。
mode:mode设置的是访问权限;创建时,需要调用int shmid = shm_open("/shmPosix", O_RDWR|O_CREAT|O_EXCL, 0666);//创建 int shmid = shm_open("/shmPosix", O_RDWR, 0666); // 获取
ftruncate(shmid, SHM_SIZE);
调整共享内存文件大小。- 2、映射共享内存,得到虚拟地址
使用mmap函数映射共享内存,得到虚拟地址后,可以直接操作。void *p = mmap(NULL, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, shmid, 0);
- 3、使用后,按需解除映射
munmap(p, SHM_SIZE);
- 4、销毁共享内存
shm_unlink("/shmPosix")
✨3.2 例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHM_SIZE 8192
int main()
{
// 创建子进程
pid_t pid = fork();
if(pid == 0) {// 子进程
printf("子进程[%d]开始执行, 创建共享内存段,使用创建共享内存\n", getpid());
// 2.1 创建共享内存段
int shmid = shm_open("/shmPosix", O_RDWR|O_CREAT|O_EXCL, 0666);
if(shmid == -1)
{
perror("shm_open failed");
exit(1);
}
ftruncate(shmid, SHM_SIZE); // 调整文件大小为 8192, 最好为页的整数倍
// 2.2 映射共享内存,得到虚拟地址
void *p = mmap(NULL, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, shmid, 0);
if((void *)MAP_FAILED == p)
{
perror("mmap failed");
exit(2);
}
// 2.3 读写共享内存
int *pi = p;
*pi = 0xaaaaaaaa;
*(pi+1) = 0x55555555;
printf("子进程[%d]写入%x, %x\n", getpid(), *pi, *(pi+1));
// 2.4 解除映射
if(munmap(p, SHM_SIZE) == -1)
{
perror("munmap failed");
exit(3);
}
printf("子进程[%d]解除映射, 结束进程\n\n", getpid());
return 0;
}
else if(pid > 0)// 父进程
{
sleep(3); //延时一会,让子进程先运行
printf("父进程[%d]开始执行, 获取共享内存段,准备使用资源\n", getpid());
// 3.1 获取共享内存段
int shmid = shm_open("/shmPosix", O_RDWR, 0666);
if(shmid == -1)
{
perror("shm_open failed");
exit(1);
}
// 3.2 映射共享内存,得到虚拟地址
void *p = mmap(NULL, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, shmid, 0);
if((void *)MAP_FAILED == p)
{
perror("mmap failed");
exit(2);
}
// 3.3 读写共享内存
int x = *((int *)p);
int y = *((int *)p + 1);
printf("父进程[%d]读取数据:x=%#x y=%#x\n",getpid(), x, y);
// 3.4 解除映射
if(munmap(p, SHM_SIZE) == -1)
{
perror("munmap failed");
exit(3);
}
printf("父进程[%d]解除映射\n", getpid());
// 3.5 销毁共享内存
if( shm_unlink("/shmPosix") == -1)
{
perror("shm_unlink");
exit(4);
}
printf("父进程[%d]销毁共享内存, 结束进程\n", getpid());
return 0;
}
else
{
printf("Error in fork\n");
exit(1);
}
return 0;
}
编译:gcc shm_posix.c -lrt
运行结果:
🎄四、信号
信号也可以勉强算作进程间通信的一种方式。信号是一种事件通知机制,当接收到该信号的进程会执行相应的操作。
进程可以通过kill函数给另一进程发送信号,收到信号的进程,对信号的处理有三种方式:忽略、捕捉和默认动作。
关于进程间的信号的,在前面的文章有介绍了,这里不再赘述。可以参看文章:
进程间通信 | 信号 (带C语言例子,8352字详细讲解)
🎄五、套接字
前面提到的通信方式都是在同一台主机上进行进程间通信,如果想要在不同主机上的进程进行通信,则需要用到socket(套接字)。
Socket可以在不同主机之间的进程进行通信,当然也可以在同一主机的不同进程进行通信。
Socket是操作系统提供给程序员操作网络的接口,根据底层不同的实现方式,通信方式也不同。
关于socket的内容,在后面的文章会介绍,这里先提一下,有个了解,知道它也是进程间通信的重要方式之一就行了。
🎄六、总结
本文介绍进程间通信的五种方式:POSIX消息队列、POSIX信号量、POSIX共享内存、信号、套接字。
想了解另外5种方式的,可以看上篇文章:
Linux 进程间通信的10种方式(1)
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁