
一、Linux下进程间通信概述
UNIX 平台提供了多种进程间通信方式,早期的通信方式包括无名管道(pipe)、有名管道(fifo)和信号(signal) 。AT&T 的贝尔实验室对 UNIX 早期的进程间通信进行了改进和扩充,形成了"System V IPC",其通信进程主要局限于单个计算机,包括共享内存(shared memory)、消息队列(message queue)和信号量(semaphore) 。BSD(加州大学伯克利分校的伯克利软件发布中心)突破了这一限制,形成了基于套接字(socket)的进程间通信机制,即套接字(socket) 。Linux 继承了上述所有的通信方式。
二、进程间通信机制之无名管道pipe
1、无名管道概念
这里所说的管道主要指无名管道,它具有如下特点:**只能用于具有亲缘关系的进程之间的通信,半双工的通信模式,具有固定的读端和写端,形象比喻吸管。**管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。 管道是基于文件描述符的通信方式。
- fd0固定用于读管道。0 是标准输入,就是读
- fd1固定用于写管道。1 是标准输出,是写
下图构成了一个半双工的通道。

管道在内核中创建,并不在用户进程中创建, fork后, 管道并不会变成2个
2、pipe 创建管道
cpp
#include <unistd.h>
int pipe(int pipefd[2]);
函数功能:创建管道, 这个管道在内核中创建, 并不在用户区。创建后, pipefd0 是读端,pipefd1 是写端
函数参数:【pipefd】数组名,也是数组的首地址
函数返回值:成功返回0,失败返回-1并设置errno , 可以使用perror 函数,On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
3、双工通信概念
- 半工通信:水龙头的概念,数据只能向一个方向流动
- 半双工通信:1根吸管的概念,既可以发送也可以接收, 但是不能同时进行
- 全双工通信:双向高速公路的概念 ,或者是2根吸管的概念,UART,发送和接收可以同时进行, 互不影响
- 异步全双工通信:双向高速公路的概念 ,或者是2根吸管的概念,既可以发送也可以接收,发送的时候可以不接受, 接收的时候可以不发送,典型电路UART
- 同步全双工通信:发送的时候必须接收, 接收的时候必须发送,典型电路 SPI总线 , USART
4、实例无名管道实现半双工通信
子进程发送数据,父进程接收数据。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
int ret;
char buf[128];
int pipefd[2]; // 管道文件描述符数组
ret = pipe(pipefd); // 创建管道
if (ret < 0)
{
perror("pipe");
return -1;
}
// pipefd fork 后, 有2个 pipefd 数组 , 2个读 2个写 端
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid == 0) // 子进程 返回值 为 0
{
printf("子进程:pid = %d\n", getpid()); // 子进程的 pid
printf("子进程:ppid = %d\n", getppid()); // 子进程的 ppid
close(pipefd[0]); // 关闭子进程的读端 , 子进程只写
while (1)
{
printf("子进程>:");
memset(buf, 0, sizeof(buf)); // 清空 buf
fgets(buf, sizeof(buf), stdin);
ret = write(pipefd[1], buf, strlen(buf));
if (ret < 0)
{
perror("write");
return -1;
}
usleep(1000*100); // 100ms
}
}
else // 父进程 返回值 为 子进程的 pid
{
printf("父进程:pid = %d\n", getpid()); // 父进程的 pid
printf("父进程:ppid = %d\n", getppid()); // 父进程的 ppid
close(pipefd[1]); // 关闭父进程的写端 , 父进程只读
while (1)
{
ret = read(pipefd[0], buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
printf("父进程>:%s", buf);
memset(buf, 0, sizeof(buf)); // 清空 buf
}
}
return 0;
}

5、实例无名管道实现全双工通信
- 子进程实现流程

- 父进程实现流程

cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
int ret;
char buf[128];
int fd_send[2]; // 管道文件描述符数组
int fd_recv[2]; // 管道文件描述符数组
ret = pipe(fd_send); // 创建管道
if (ret < 0)
{
perror("pipe");
return -1;
}
ret = pipe(fd_recv); // 创建管道
if (ret < 0)
{
perror("pipe");
return -1;
}
// pipefd fork 后, 有2个 pipefd 数组 , 2个读 2个写 端
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid == 0) // 子进程 返回值 为 0
{
printf("子进程:pid = %d\n", getpid()); // 子进程的 pid
printf("子进程:ppid = %d\n", getppid()); // 子进程的 ppid
close(fd_send[0]); // 关闭子进程的读端 , 子进程通过send 写 不读
close(fd_recv[1]); // 关闭子进程的写端 , 子进程通过recv 读 不读
while (1)
{
printf("子进程发送消息>:");
memset(buf, 0, sizeof(buf)); // 清空 buf
fgets(buf, sizeof(buf), stdin);
ret = write(fd_send[1], buf, strlen(buf));
if (ret < 0)
{
perror("write");
return -1;
}
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("子进程退出\n");
break;
}
memset(buf, 0, sizeof(buf)); // 清空 buf
ret = read(fd_recv[0], buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
printf("子进程接收消息>:%s", buf);
}
}
else // 父进程 返回值 为 子进程的 pid
{
printf("父进程:pid = %d\n", getpid()); // 父进程的 pid
printf("父进程:ppid = %d\n", getppid()); // 父进程的 ppid
close(fd_send[1]); // 关闭父进程的写端 , 父进程通过send 写 不读
close(fd_recv[0]); // 关闭父进程的读端 , 父进程通过recv 读 不读
while (1)
{
ret = read(fd_send[0], buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("父进程退出\n");
break;
}
printf("父进程接收消息>:%s", buf);
printf("父进程发送消息>:");
memset(buf, 0, sizeof(buf)); // 清空 buf
fgets(buf, sizeof(buf), stdin);
ret = write(fd_recv[1], buf, strlen(buf));
if (ret < 0)
{
perror("write");
return -1;
}
memset(buf, 0, sizeof(buf)); // 清空 buf
}
}
return 0;
}

三、进程间通信机制之有名管道fifo
1、有名管道的概念
- 无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围
- 有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
- 是7种文件类型的一种(bcd-lsp) 当中的p
- 进程通过文件IO来操作有名管道
- 有名管道遵循先进先出规则, 是一个队列模型
- 不支持如lseek() 操作 , 支持open,close,read,write操作
2、mkfifo 创建有名管道
cpp
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
函数功能:创建管道, 这个管道在文件系统中创建,就是一个文件。创建后, 可以使用open,close,read, write操作这个文件
函数参数:pathname,要创建的管道文件名;mode,创建文件的权限, 例如: 0775 ,0664
函数返回值:成功返回0,失败返回-1并设置errno , 可以使用perror 函数,On success mkfifo() and mkfifoat() return 0. In the case of an error, -1 is returned (in which case, errno is set appropriately).
3、实例有名管道实现半双工通信
fifo_write这个进程从终端读数据发送给fifo_read , fifo_read收到数据并显示
- fifo_write
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// 53-fifo_write myfifo
int main(int argc, char const *argv[])
{
int ret;
char buf[128];
if (argc != 2)
{
printf("Usage: %s <fifo_name>\n", argv[0]);
return -1;
}
// 判断 myfifo 是否存在 如果不存在 则创建
// F_OK 检查文件是否存在 , 文件存在 返回 0
// 文件不存在 返回 -1
if (access(argv[1], F_OK) == -1)
{
ret = mkfifo(argv[1], 0664);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
// 打开 myfifo 写端
int fd = open(argv[1], O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("fd = %d\n", fd);
while (1)
{
printf("请输入要发送的消息>:");
memset(buf, 0, sizeof(buf)); // 清空 buf
fgets(buf, sizeof(buf), stdin);
ret = write(fd, buf, strlen(buf));
if (ret < 0)
{
perror("write");
return -1;
}
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("写端退出\n");
break;
}
}
close(fd);
return 0;
}
- fifo_read
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// 53-fifo_read myfifo
int main(int argc, char const *argv[])
{
int ret;
char buf[128];
if (argc != 2)
{
printf("Usage: %s <fifo_name>\n", argv[0]);
return -1;
}
// 判断 myfifo 是否存在 如果不存在 则创建
// F_OK 检查文件是否存在 , 文件存在 返回 0
// 文件不存在 返回 -1
if (access(argv[1], F_OK) == -1)
{
ret = mkfifo(argv[1], 0664);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
// 打开 myfifo 写端
int fd = open(argv[1], O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("fd = %d\n", fd);
while (1)
{
memset(buf, 0, sizeof(buf)); // 清空 buf
ret = read(fd, buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
printf("read %d bytes: %s", ret, buf);
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("读端退出\n");
break;
}
}
close(fd);
return 0;
}

4、实例有名管道实现全双工通信
cpp
./54-fifo_write send recv // 需要2个管道
cpp
while(1)
{
printf(">:");
fgets(buf,N,stdin);
write(fd_send); ---------------send-----------------> 54-fifo_read 的read(fd-send)
read(fd_recv) ; <--------------recv----------------- 54-fifo_read 的write(fd-recv)
printf("buf=%s\n",buf);
}
cpp
./54-fifo_read send recv // 需要2个管道
cpp
while(1)
{
read(fd_send) ; <--------------send----------------- 54-fifo_write 的write(fd-send)
printf("buf=%s\n",buf);
printf(">:");
fgets(buf,N,stdin);
write(fd_recv); ---------------recv-----------------> 54-fifo_write 的read(fd-recv)
}
- fifo_write
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// 54-fifo_write send recv
int main(int argc, char const *argv[])
{
int ret;
char buf[128];
if (argc != 3)
{
printf("Usage: %s <send_fifo_name> <recv_fifo_name>\n", argv[0]);
return -1;
}
// 判断 myfifo 是否存在 如果不存在 则创建
// F_OK 检查文件是否存在 , 文件存在 返回 0
// 文件不存在 返回 -1
if (access(argv[1], F_OK) == -1)
{
ret = mkfifo(argv[1], 0664);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
if (access(argv[2], F_OK) == -1)
{
ret = mkfifo(argv[2], 0664);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
// 打开 send_fifo 写端
int fd_send = open(argv[1], O_RDWR);
if (fd_send < 0)
{
perror("open");
return -1;
}
printf("fd_send = %d\n", fd_send);
// 打开 recv_fifo 写端
int fd_recv = open(argv[2], O_RDWR);
if (fd_recv < 0)
{
perror("open");
return -1;
}
printf("fd_recv = %d\n", fd_recv);
while (1)
{
printf("请输入要发送的消息>:");
memset(buf, 0, sizeof(buf)); // 清空 buf
fgets(buf, sizeof(buf), stdin);
ret = write(fd_send, buf, strlen(buf));
if (ret < 0)
{
perror("write");
return -1;
}
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("写端退出\n");
break;
}
memset(buf, 0, sizeof(buf)); // 清空 buf
ret = read(fd_recv, buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
printf("read %d bytes: %s", ret, buf);
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("写端退出\n");
break;
}
}
close(fd_send);
close(fd_recv);
return 0;
}
- fifo_read
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// 54-fifo_write send recv
int main(int argc, char const *argv[])
{
int ret;
char buf[128];
if (argc != 3)
{
printf("Usage: %s <send_fifo_name> <recv_fifo_name>\n", argv[0]);
return -1;
}
// 判断 myfifo 是否存在 如果不存在 则创建
// F_OK 检查文件是否存在 , 文件存在 返回 0
// 文件不存在 返回 -1
if (access(argv[1], F_OK) == -1)
{
ret = mkfifo(argv[1], 0664);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
if (access(argv[2], F_OK) == -1)
{
ret = mkfifo(argv[2], 0664);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
// 打开 send_fifo 写端
int fd_send = open(argv[1], O_RDWR);
if (fd_send < 0)
{
perror("open");
return -1;
}
printf("fd_send = %d\n", fd_send);
// 打开 recv_fifo 写端
int fd_recv = open(argv[2], O_RDWR);
if (fd_recv < 0)
{
perror("open");
return -1;
}
printf("fd_recv = %d\n", fd_recv);
while (1)
{
memset(buf, 0, sizeof(buf)); // 清空 buf
ret = read(fd_send, buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -1;
}
printf("read %d bytes: %s", ret, buf);
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("读端退出\n");
break;
}
printf("请输入要发送的消息>:");
memset(buf, 0, sizeof(buf)); // 清空 buf
fgets(buf, sizeof(buf), stdin);
ret = write(fd_recv, buf, strlen(buf));
if (ret < 0)
{
perror("write");
return -1;
}
// quit 退出
if (strncmp(buf, "quit", 4) == 0)
{
printf("读端退出\n");
break;
}
}
close(fd_send);
close(fd_recv);
return 0;
}

5、实例无名管道缓存区的大小
测试无名管道缓存区的大小。最终的结果是: 65536, 就是64KB
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd[2];
int ret = pipe(fd);
if (ret < 0)
{
perror("pipe");
return -1;
}
printf("pipe fd[0] = %d, fd[1] = %d\n", fd[0], fd[1]);
// 管道就是1个队列, 先进先出
// 可以一直往管道写入数据, 不去读, 写到不能写的时候, 就写满了
char buf[128];
int size = 0;
while (1)
{
// 每次写入1个字节
ret = write(fd[1], buf, 1);
if (ret < 0)
{
perror("write");
return -1;
}
size++;
printf("size = %d (%d KB)\n", size, size / 1024);
}
return 0;
}

6、实例有名管道缓存区的大小
测试无名管道缓存区的大小,最终的结果是: 65536, 就是64KB
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int ret = 0;
// 判断 myfifi 是否存在, 不存在创建
if (access("myfifo", F_OK) < 0)
{
// 创建FIFO文件
ret = mkfifo("myfifo", 0666);
if (ret < 0)
{
perror("mkfifo");
return -1;
}
}
// 打开FIFO文件
int fd = open("myfifo", O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("fd = %d\n", fd);
// 管道就是1个队列, 先进先出
// 可以一直往管道写入数据, 不去读, 写到不能写的时候, 就写满了
char buf[128];
int size = 0;
while (1)
{
// 每次写入1个字节
ret = write(fd, buf, 1);
if (ret < 0)
{
perror("write");
return -1;
}
size++;
printf("size = %d (%d KB)\n", size, size / 1024);
}
return 0;
}

7、无名管道与有名管道的总结
|------|--------------------------------------------------------------------------------------|
| 无名管道 | * 缓存区大小是64KB * 读端关闭,写管道, 管道崩溃 * 管道写端被关闭, 读管道返回为0 * 当管道中无数据时,读操作会阻塞 * 管道被写满时, 写操作也会阻塞 |
| 有名管道 | * 缓存区大小是64KB * 当管道中无数据时,读操作会阻塞 * 管道被写满时, 写操作也会阻塞 |
8、实例管道崩裂
测试管道的性能
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd[2];
char buf[128] = {0};
int ret = pipe(fd);
if (ret < 0)
{
perror("pipe");
return -1;
}
printf("pipe fd[0] = %d, fd[1] = %d\n", fd[0], fd[1]);
#if 0
// 读端关闭 写管道,管道崩裂 , 写端会收到SIGPIPE信号
close(fd[0]);
ret = write(fd[1], "hello", 5);
if (ret < 0)
{
perror("write");
return -1;
}
#endif
#if 0
// 写端关闭 读管道会返回0
close(fd[1]);
ret = read(fd[0], buf, 5);
printf("ret=%d\n", ret);
if (ret < 0)
{
perror("read");
return -1;
}
#endif
printf("test pipe broken\n");
return 0;
}

四、进程间通信机制之信号signal
1、信号通信的概念
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。用户进程对信号的响应方式:
- 忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
- 捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数, 但是有两个信号不能处理,即SIGKILL及SIGSTOP。
- 执行缺省操作:Linux对每种信号都规定了默认操作
使用信号的场合
- 后台进程需要使用信号,如xinetd
- 如果两个进程没有亲缘关系,无法使用无名管道
- 如果两个通信进程之一只能使用标准输入和标准输出,则无法使用FIFO
linux系统定义了如下的信号:

系统一共定义了62个信号, 前31个信号是传统信号, 这个信号没有缓存机制, 容易丢失信号.后31个信号是实时(RealTime)信号, 这个信号有缓存机制, 不会丢失信号。信号的解释与默认行为
- SIGINT 值为2 , 默认行为杀死进程,这个信号是 ctrl +c 发出的信号
- SIGQUIT 值为3 , 默认行为杀死进程,这个信号是 ctrl +\ 发出的信号
- SIGKILL 值为9 , 默认行为杀死进程,不能被忽略和捕捉
- SIGUSR1 值为10 , 默认行为杀死进程,给用户使用的自定义信号
- SIGUSR2 值为12 , 默认行为杀死进程,给用户使用的自定义信号
- SIGPIPE 值为13 , 默认行为杀死进程,管道崩裂时发出
- SIGALRM 值为14 , 默认行为杀死进程,使用alarm设置一个时间,定时时间到出发一个信号
- SIGCHLD 值为17 , 默认行为忽略进程,子进程状态改变时(包括退出)发出这个信号
- SIGTSTP 值为20 , 默认行为暂停进程,该信号用于暂停交互进程,Ctrl + Z 发出这个信号
2、kill 发送信号
cpp
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
函数功能:给进程发送指定的信号
函数参数:【pid】,要发送的进程,pid > 0 : 发送给指定的pid进程;pid == 0 : 发送给调用进程组中所有的进程 , 发送给本组所有成员;pid ==-1 : 除了1号进程外,给所有有权限发送的进程都发送指定的信号;pid < -1 : 给本组内的是-pid 进程号的进程发送信号。【sig】,要发送的信号,0 : 表示不发送信号, 用于检查进程组是否存在。非0 的信号: 要发送的信号
函数返回值:成功返回0,失败返回-1并设置errno , 可以使用perror 函数,On success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set appropriately.
3、raise 发送信号
cpp
#include <signal.h>
int raise(int sig);
函数功能:自己给自己发送一个信号 , 等价于 kill(getpid(),sig),这个函数可以使用kill 函数去替代。
函数参数:【sig】,要发送的信号
函数返回值:成功返回0,失败返回非0,raise() returns 0 on success, and nonzero for failure.
4、alarm 发送信号
cpp
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
函数功能:也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALRM信号。
函数参数:【seconds】,闹钟定时时间
函数返回值:成功返回剩余时间,失败返回无,alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm.
5、pause 挂起进程
cpp
#include <unistd.h>
int pause(void);
函数功能:函数是用于将调用进程挂起直到收到信号为止,就是睡眠与唤醒的概念。
函数参数:无
函数返回值:只有被信号唤醒后才返回 , 返回-1 并设置 errno 为EINTR,pause() returns only when a signal was caught and the signal-catching function returned. In this case, pause() returns -1, and errno is set to EINTR.
6、signal 信号的处理
信号的处理就是捕捉一个信号,特定的信号是与相应的事件相联系的,一个进程可以设定对信号的相应方式,信号处理的主要方法有两种:使用简单的signal()函数和使用信号集函数组。使用signal函数处理时,需指定要处理的信号和处理函数,使用简单、易于理解。signal 信号处理函数的原型
cpp
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数功能:设置信号的处理函数, 信号发生后要调用哪一个函数去处理这个信号
函数参数:【signum】, 要设置(捕捉)的信号;【handler】,函数类型的变量, 传参函数名SIG_IGN,表示忽略这个信号;SIG_DFL,信号的默认行为
函数返回值:成功返回上一个的信号处理函数地址,返回SIG_ERR 并设置 errno。signal() returns the previous value of the signal handler, or SIG_ERR on error. In the event of an error, errno is set to indicate the cause. The signals SIGKILL and SIGSTOP cannot be caught or ignored.
7、实例捕捉信号
捕捉 SIGINT , SIGQUIT , SIGTSTP , SIGUSR1
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
// 定义信号处理函数
void sighandler(int signum)
{
printf("signum = %d\n", signum);
}
int main(int argc, char const *argv[])
{
signal(SIGINT, sighandler); // 捕捉 singint 信号 , ctrl + c 发出
signal(SIGQUIT, sighandler); // 捕捉 singquit 信号 , ctrl + \ 发出
signal(SIGTSTP, sighandler); // 捕捉 singtstp 信号 , ctrl + z 发出
signal(SIGUSR1, sighandler);// sigusr1 信号 , 自定义信号 , 可以用 kill -s SIGUSR1 pid 发送
while (1)
{
sleep(1);
}
return 0;
}

8、实例信号练习
编写一段程序,使用系统调用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按ctrl+c键),子进程忽略这个信号,当捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号(sigusr1),子进程捕捉到父进程发来的信号后,分别输出下列信息后终止:
Child process 1 is killed by parent!
Child process 2 is killed by parent!
父进程等待两个子进程终止后(SIGCHLD),输出以下信息后终止:
Parent process exit!
使用kill 与 signal 函数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
pid_t child1_pid = 0, child2_pid = 0;
// 定义信号处理函数
void parent_sighandler(int signum)
{
if (signum == SIGINT)
{
// 给子进程1 发送 sigusr1 信号
kill(child1_pid, SIGUSR1);
// 给子进程2 发送 sigusr1 信号
kill(child2_pid, SIGUSR1);
}
else if (signum == SIGCHLD)
{
printf("Parent process exit!\n");
exit(0); // 父进程退出
}
}
void child1_sighandler(int signum)
{
if (signum == SIGUSR1)
{
printf("Child process 1 is killed by parent!\n");
exit(0); // 子进程1 退出 , 向父进程发送 SIGCHLD 信号
}
}
void child2_sighandler(int signum)
{
if (signum == SIGUSR1)
{
printf("Child process 2 is killed by parent!\n");
exit(0); // 子进程2 退出 , 向父进程发送 SIGCHLD 信号
}
}
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid == 0) // 子进程1
{
signal(SIGINT, SIG_IGN); // 子进程 忽略 SIGINT 信号
signal(SIGUSR1, child1_sighandler); // 子进程1 捕捉 sigusr1 信号
while (1)
{
sleep(1);
}
}
else
{
child1_pid = pid; // 记录子进程1的pid
// 子进程2
pid = fork();
if (pid < 0)
{
perror("fork");
return -1;
}
else if (pid == 0) // 子进程2
{
signal(SIGINT, SIG_IGN); // 子进程 忽略 SIGINT 信号
signal(SIGUSR1, child2_sighandler); // 子进程2 捕捉 sigusr1 信号
while (1)
{
sleep(1);
}
}
else // 父进程
{
child2_pid = pid; // 记录子进程2的pid
signal(SIGINT, parent_sighandler); // 捕捉 singint 信号 , ctrl + c 发出
signal(SIGCHLD, parent_sighandler); // 捕捉sigchld 信号
while (1)
{
sleep(1);
}
}
}
return 0;
}

程序在运行时, 有时会出现如下问题

9、信号回收僵尸进程
回收僵尸进程的办法
【第1种】 回收进程来避免产生僵尸进程
- 适用于需要获取子进程退出时的状态
- 使用子进程退出时会发出一个信号SIGCHLD,捕捉这信号即可实现实时回收 , 这个有问题, 会丢失信号
- 更好的解决办法是使用SIGRTMIN信号 , 去解决丢失的问题
【第2种】 创建子进程父进程退出,让子进程变成孤儿进程,系统1号进程托管 (服务就是这个思路创建的)
【第3种】创建子进程后就脱离父子关系(使用信号的机制来实现)
- 使用信号忽略的机制来处理
- 适用于不需要获取子进程退出时的状态
cppsignal(SIGCHLD,SIG_IGN) ; // 脱离父子关系, 子进程让系统(1号进程)进行托管
10、实例回收进程避免僵尸进程
使用SIGRTMIN信号 , 去解决丢失的问题
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
// 定义信号处理函数
void sighandler(int signum)
{
pid_t pid = waitpid(-1, NULL, 0);
printf("回收子进程pid=%d\n", pid);
}
int main(int argc, char const *argv[])
{
// 第1种 回收进程
// SIGCHLD 这个信号没有缓存机制, 多了会发生丢失, 有bug , 解析思路用SIGRT信号
// signal(SIGCHLD, sighandler); // 捕捉sigchld 信号
// 第2种 回收进程
// 设置带缓存的信号
signal(SIGRTMIN, sighandler); // 捕捉sigrtmin 信号
for (int i = 0; i < 10; i++)
{
if (fork() == 0) // 子进程
{
printf("子进程pid=%d\n", getpid());
sleep(10);
kill(getppid(), SIGRTMIN); // 向父进程发送 SIGRTMIN 信号
exit(0);
}
}
printf("main process is running pid=%d\n", getpid());
while (1)
{
sleep(1);
}
return 0;
}

11、实例忽略SIGCHLD避免僵尸进程
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
// 定义信号处理函数
// void sighandler(int signum)
// {
// pid_t pid = waitpid(-1, NULL, 0);
// printf("回收子进程pid=%d\n", pid);
// }
int main(int argc, char const *argv[])
{
// 第1种 回收进程
// SIGCHLD 这个信号没有缓存机制, 多了会发生丢失, 有bug , 解析思路用SIGRT信号
// signal(SIGCHLD, sighandler); // 捕捉sigchld 信号
// 第2种 回收进程
// 设置带缓存的信号
//signal(SIGRTMIN, sighandler); // 捕捉sigrtmin 信号
// 第3种 脱离父子关系 , 缺点是没有办法获取子进程退出的状态
signal(SIGCHLD, SIG_IGN); // 忽略 SIGCHLD 信号
for (int i = 0; i < 10; i++)
{
if (fork() == 0) // 子进程
{
printf("子进程pid=%d\n", getpid());
sleep(10);
//kill(getppid(), SIGRTMIN); // 向父进程发送 SIGRTMIN 信号
exit(0);
}
}
printf("main process is running pid=%d\n", getpid());
while (1)
{
sleep(1);
}
return 0;
}

五、进程间通信机制之共享内存
1、共享内存
**【共享内存理解】**共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 。
**【共享内存分类】**共享内存区,按标准可分为Posix共享内存区和System V共享内存区,两者在概念上类似。Posix 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称。System V,曾经也被称为 AT&T System V,是Unix操作系统众多版本中的一支。它最初由 AT&T 开发,在1983年第一次发布。一共发行了4个 System V 的主要版本:版本1、2、3 和 4。
**【共享内存区对象】**共享内存区对象(shared-memory object),由shm_open函数打开一个Posix.1 IPC名字,所返回的描述符由mmap函数映射到当前进程的地址空间。
2、shm_open 创建/打开共享内存
cpp
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);
函数功能:如果不存在就创建, 如果存在就打开;创建或打开一段内存内存用于进程间通信
函数参数:【name】,要创建或打开的文件名;【oflag】,操作标志,包含:O_RDONLY,O_RDWR,O_CREAT,O_EXCL,O_TRUNC。其中O_RDONLY和O_RDWR标志必须且仅能存在一项。mode,用于设置创建的共享内存区对象的权限属性。和open函数不同的是,该参数必须一直存在
函数返回值:成功返回文件描述符,失败返回-1,On success, shm_open() returns a nonnegative file descriptor. On failure, shm_open() returns -1.
3、shm_unlink 删除共享内存
cpp
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_unlink(const char *name);
函数功能:删除共享内存
函数参数:【name】,要删除的共享内存文件名
函数返回值:成功返回0,失败返回-1 并设置 errno。shm_unlink() returns 0 on success, or -1 on error.
4、mmap 映射共享内存
cpp
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
函数功能:映射共享内存
函数参数:【addr】,指向想要映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。【length】,映射到进程地址空间的字节数,它从被映射文件开头的第offset个字节处开始,offset通常被设置为0。【prot】,映射区域的保护方式。可以为以下几种方式的组合,PROT_READ:数据可读;PROT_WRITE:数据可写;PROT_EXEC:数据可执行;PROT_NONE:数据不可访问。【flags】,设置内存映射区的类型标志,POSIX标志定义了以下三个标志:MAP_SHARED该标志表示,调用进程对被映射内存区的数据所做的修改对于共享该内存区的所有进程都可见;MAP_PRIVATE调用进程对被映射内存区的数据所做的修改只对该进程可见,而不改变其底层支撑对象;MAP_FIXED该标志表示准确的解释start参数,一般不建议使用该标志;MAP_ANON 在Linux中也定义了一些非标准的标志,例如MAP_ANONYMOUS(MAP_ANON)。【fd】,有效的文件描述符,如果设定了MAP_ANONYMOUS(MAP_ANON)标志,在Linux下面会忽略fd参数。【offset】, 相对文件的起始偏移
函数返回值:成功返回映射后的地址,失败返回-1,On success, mmap() returns a pointer to the mapped area. On error, the value MAP_FAILED (that is, (void *) -1) is returned, and errno is set to indicate the cause of the error.
5、munmap 取消映射共享内存
cpp
#include <sys/mman.h>
int munmap(void *addr, size_t length);
函数功能:取消一个映射关系
函数参数: 【addr】,被映射到的进程地址空间的内存区的起始地址,即mmap返回的地址。【length】,映射区的大小。
函数返回值:成功返回0,失败返回-1 并设置 errno,On success, munmap() returns 0. On failure, it returns -1, and errno is set to indicate the cause of the error (probably to EINVAL).
6、msync 映射同步
对于一个MAP_SHARED的内存映射区,内核的虚拟内存算法会保持内存映射文件和内存映射区的同步,也就是说,对于内存映射文件所对应内存映射区的修改,内核会在稍后的某个时刻更新该内存映射文件。如果我们希望硬盘上的文件内容和内存映射区中的内容实时一致,那么我们就可以调用msync开执行这种同步。
cpp
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
函数功能:同步磁盘和共享内存的内容
函数参数:【addr】,被映射到的进程地址空间的内存区的起始地址,即mmap返回的地址。length,映射区的大小。【flags】,同步标志,有以下三个标志:MS_ASYNC异步写,一旦写操作由内核排入队列,就立刻返回;MS_SYNC同步写,要等到写操作完成后才返回;MS_INVALIDATE:使该文件的其他内存映射的副本全部失效。
函数返回值:成功返回0,失败返回-1 并设置 errno,On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
7、ftruncate 调整共享内存大小
Linux下的内存是采用页式管理机制。通过mmap进行内存映射,内核生成的映射区的大小都是以页面大小PAGESIZE为单位,即为PAGESIZE的整数倍。如果mmap映射的长度不是页面大小的整数倍,那么多余空间也会被闲置浪费。可以使用getpagesize函数获取页的大小
cpp
#include <unistd.h>
int getpagesize(void);
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("pagesize=%d\n",getpagesize());
return 0;
}

从上述的运行结果,可以很明显的看出,当前系统的页大小为4k字节。
cpp
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
函数功能:修改共享内存的大小
函数参数:【fd】,要操作的文件;【length】,映射区的大小
函数返回值:成功返回0,失败返回-1 并设置 errno,On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
8、实例共享内存读写
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
int main(int argc, char const *argv[])
{
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
int ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(shmaddr, 0, 64 + 1); // 初始化共享内存
memset(shmaddr, 'O', 32); // 初始化共享内存
memset(shmaddr + 32, '-', 32); // 初始化共享内存
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}

9、实例共享内存读写的问题
多个进程访问共享内存的时候,共享内存没有互斥的机制时会发生逻辑混乱
- shm_write
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
int main(int argc, char const *argv[])
{
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
int ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(shmaddr, 0, 64 + 1); // 初始化共享内存
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
while (1)
{
memset(shmaddr, 'O', 32); // 初始化共享内存
usleep(1000); // 1ms
memset(shmaddr + 32, 'O', 32); // 初始化共享内存
usleep(1000); // 1ms
}
}
else
{
// 父进程
while (1)
{
memset(shmaddr, '-', 32); // 初始化共享内存
usleep(1000); // 1ms
memset(shmaddr + 32, '-', 32); // 初始化共享内存
usleep(1000); // 1ms
}
}
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}
- shm_read
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
int main(int argc, char const *argv[])
{
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
int ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(shmaddr, 0, 64 + 1); // 初始化共享内存
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
while (1)
{
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
sleep(1); // 1s
}
}
else
{
// 父进程
while (1)
{
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
sleep(1); // 1s
}
}
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}

六、进程间通信机制之信号量
1、信号量的概念
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。信号量种类:
- posix有名信号量(基于文件实现) , 用于进程间通信
- posix无名信号量(基于内存实现) , 用于线程间同步与互斥
- System V信号量(IPC对象)
2、sem_open 创建信号量
cpp
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#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);
函数功能:创建或打开一个信号量,如果不存在就创建,如果存在就打开,同open函数实现
函数参数:【name】,要创建信号量的文件名 , 这个文件名创建在 /dev/shm目录内;【oflag】,同open权限位;【mode】,信号量的权限 , 例如 0664;【value】,信号量的值
函数返回值:成功返回信号量的地址,失败返回SEM_FAILED 并设置 errno,On success, sem_open() returns the address of the new semaphore; this address is used when calling other semaphore-related functions. On error, sem_open() returns SEM_FAILED, with errno set to indicate the error.
3、sem_close 关闭信号量
cpp
#include <semaphore.h>
int sem_close(sem_t *sem);
函数功能:关闭一个信号量
函数参数:【sem】,要关闭的信号量
函数返回值:成功返回0,失败返回-1 并设置 errno,On success sem_close() returns 0; on error, -1 is returned, with errno set to indicate the error.
4、sem_wait 申请信号量
cpp
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
函数功能:申请一个信号量,P操作
函数参数:【sem】,要申请的信号量
函数返回值:成功返回0,失败返回-1 并设置 errno,On success sem_close() returns 0; on error, -1 is returned, with errno set to indicate the error.
5、sem_post 释放信号量
cpp
#include <semaphore.h>
int sem_post(sem_t *sem);
函数功能:释放一个信号量,V操作
函数参数:【sem】,要释放的信号量
函数返回值:成功返回0,失败返回-1 并设置 errno,sem_post() returns 0 on success; on error, the value of the semaphore is left unchanged, -1 is returned, and errno is set to indicate the error.
6、sem_getvalue 获取信号量的值
cpp
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
函数功能:获取信号量的值
函数参数:【sem】,要获取的信号量;sval ,保存获取的值 , save value
函数返回值:成功返回0,失败返回-1 并设置 errno,em_getvalue() returns 0 on success; on error, -1 is returned and errno is set to indicate the error.
7、sem_unlink 删除信号量
cpp
#include <semaphore.h>
int sem_unlink(const char *name);
函数功能:删除信号量,删除文件名 /dev/shm/xxx
函数参数:【name】,信号量的名字
函数返回值:成功返回0,失败返回-1 并设置 errno,On success sem_unlink() returns 0; on error, -1 is returned, with errno set to indicate the error.
8、实例有名信号量实现共享内存互斥
- shm_mutex_write
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
#define SEM_NAME "mysem"
int main(int argc, char const *argv[])
{
int ret;
// 判断信号量的文件时候存在 , 如果存在就删除
char filename[128] = {0};
sprintf(filename, "/dev/shm/sem.%s", SEM_NAME);
ret = access(filename, F_OK);
if (ret == 0)
{
ret = sem_unlink(SEM_NAME);
if (ret == -1)
{
perror("sem_unlink");
exit(1);
}
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem = sem_open(SEM_NAME, O_CREAT | O_RDWR, 0666, 1); // 初始值为1
if (sem == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 读取信号量的值
int sval;
ret = sem_getvalue(sem, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sval = %d\n", sval);
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(shmaddr, 0, 64 + 1); // 初始化共享内存
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
while (1)
{
sem_wait(sem); // 信号量值减1
memset(shmaddr, 'O', 32); // 初始化共享内存
usleep(1000); // 1ms
memset(shmaddr + 32, 'O', 32); // 初始化共享内存
usleep(1000); // 1ms
sem_post(sem); // 信号量值加1
}
}
else
{
// 父进程
while (1)
{
sem_wait(sem); // 信号量值减1
memset(shmaddr, '-', 32); // 初始化共享内存
usleep(1000); // 1ms
memset(shmaddr + 32, '-', 32); // 初始化共享内存
usleep(1000); // 1ms
sem_post(sem); // 信号量值加1
}
}
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}
- shm_mutex_read
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
#define SEM_NAME "mysem"
int main(int argc, char const *argv[])
{
int ret;
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem = sem_open(SEM_NAME, O_RDWR);
if (sem == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 读取信号量的值
int sval;
ret = sem_getvalue(sem, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sval = %d\n", sval);
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
//memset(shmaddr, 0, 64 + 1); // 初始化共享内存
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
while (1)
{
sem_wait(sem); // 信号量值减1
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
sem_post(sem); // 信号量值加1
sleep(1); // 1s
}
}
else
{
// 父进程
while (1)
{
sem_wait(sem); // 信号量值减1
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
sem_post(sem); // 信号量值加1
sleep(1); // 1s
}
}
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}

9、实例有名信号量实现共享内存同步
- shm_sync_write
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
#define SEM_NAME_W1 "w1"
#define SEM_NAME_W2 "w2"
#define SEM_NAME_R1 "r1"
#define SEM_NAME_R2 "r2"
int main(int argc, char const *argv[])
{
int ret;
// 判断信号量的文件时候存在 , 如果存在就删除
char filename[128] = {0};
sprintf(filename, "/dev/shm/sem.%s", SEM_NAME_W1);
ret = access(filename, F_OK);
if (ret == 0)
{
ret = sem_unlink(SEM_NAME_W1);
if (ret == -1)
{
perror("sem_unlink");
exit(1);
}
}
sprintf(filename, "/dev/shm/sem.%s", SEM_NAME_W2);
ret = access(filename, F_OK);
if (ret == 0)
{
ret = sem_unlink(SEM_NAME_W2);
if (ret == -1)
{
perror("sem_unlink");
exit(1);
}
}
sprintf(filename, "/dev/shm/sem.%s", SEM_NAME_R1);
ret = access(filename, F_OK);
if (ret == 0)
{
ret = sem_unlink(SEM_NAME_R1);
if (ret == -1)
{
perror("sem_unlink");
exit(1);
}
}
sprintf(filename, "/dev/shm/sem.%s", SEM_NAME_R2);
ret = access(filename, F_OK);
if (ret == 0)
{
ret = sem_unlink(SEM_NAME_R2);
if (ret == -1)
{
perror("sem_unlink");
exit(1);
}
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_w1 = sem_open(SEM_NAME_W1, O_CREAT | O_RDWR, 0666, 1); // 初始值为1
if (sem_w1 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_w2 = sem_open(SEM_NAME_W2, O_CREAT | O_RDWR, 0666, 0); // 初始值为0
if (sem_w2 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_r1 = sem_open(SEM_NAME_R1, O_CREAT | O_RDWR, 0666, 0); // 初始值为0
if (sem_r1 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_r2 = sem_open(SEM_NAME_R2, O_CREAT | O_RDWR, 0666, 0); // 初始值为0
if (sem_r2 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 读取信号量的值
int sval;
ret = sem_getvalue(sem_w1, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_w1 = %d\n", sval);
ret = sem_getvalue(sem_w2, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_w2 = %d\n", sval);
ret = sem_getvalue(sem_r1, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_r1 = %d\n", sval);
ret = sem_getvalue(sem_r2, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_r2 = %d\n", sval);
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(shmaddr, 0, 64 + 1); // 初始化共享内存
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
while (1)
{
sem_wait(sem_w1); // 信号量值减1
memset(shmaddr, 'O', 32); // 初始化共享内存
usleep(1000); // 1ms
memset(shmaddr + 32, 'O', 32); // 初始化共享内存
usleep(1000); // 1ms
sem_post(sem_r1); // 信号量值加1
}
}
else
{
// 父进程
while (1)
{
sem_wait(sem_w2); // 信号量值减1
memset(shmaddr, '-', 32); // 初始化共享内存
usleep(1000); // 1ms
memset(shmaddr + 32, '-', 32); // 初始化共享内存
usleep(1000); // 1ms
sem_post(sem_r2); // 信号量值加1
}
}
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}
- shm_sync_read
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#define SHM_NAME "myshm"
#define SHM_SIZE 4096
#define SEM_NAME_W1 "w1"
#define SEM_NAME_W2 "w2"
#define SEM_NAME_R1 "r1"
#define SEM_NAME_R2 "r2"
int main(int argc, char const *argv[])
{
int ret;
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_w1 = sem_open(SEM_NAME_W1, O_RDWR);
if (sem_w1 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_w2 = sem_open(SEM_NAME_W2, O_RDWR);
if (sem_w2 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_r1 = sem_open(SEM_NAME_R1, O_RDWR);
if (sem_r1 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 如果信号量不存在,就创建
// 如果信号量存在,就打开
// 信号量的位置 : /dev/shm 目录内
sem_t *sem_r2 = sem_open(SEM_NAME_R2, O_RDWR);
if (sem_r2 == SEM_FAILED)
{
perror("sem_open");
exit(1);
}
// 读取信号量的值
// 读取信号量的值
int sval;
ret = sem_getvalue(sem_w1, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_w1 = %d\n", sval);
ret = sem_getvalue(sem_w2, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_w2 = %d\n", sval);
ret = sem_getvalue(sem_r1, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_r1 = %d\n", sval);
ret = sem_getvalue(sem_r2, &sval);
if (ret == -1)
{
perror("sem_getvalue");
exit(1);
}
printf("sem_r2 = %d\n", sval);
// 创建共享内存
// 不存在则创建,存在则打开
// 共享内存的位置 :/dev/shm 目录内
int shmid = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shmid == -1)
{
perror("shm_open");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ls -l /dev/shm"); // 查看共享内存
ret = ftruncate(shmid, SHM_SIZE); // 调整共享内存的大小
if (ret == -1)
{
perror("ftruncate");
exit(1);
}
system("ls -l /dev/shm"); // 查看共享内存
// 映射共享内存
void *shmaddr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if (shmaddr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
// memset(shmaddr, 0, 64 + 1); // 初始化共享内存
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
while (1)
{
sem_wait(sem_r1); // 信号量值减1
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
sem_post(sem_w2); // 信号量值加1
sleep(1); // 1s
}
}
else
{
// 父进程
while (1)
{
sem_wait(sem_r2); // 信号量值减1
printf("shmaddr=%s\n", (char *)shmaddr); // 打印共享内存
sem_post(sem_w1); // 信号量值加1
sleep(1); // 1s
}
}
munmap(shmaddr, SHM_SIZE); // 取消映射共享内存
shm_unlink(SHM_NAME); // 删除共享内存
system("ls -l /dev/shm"); // 查看共享内存
return 0;
}

七、进程间通信机制之消息队列
1、消息队列的概念
在应用开发中,生产者,消费者的模型非常常见,一方产生数据并把数据放入队列中,而另一方从队列中取数据,先进先出。同样,在操作系统内核中,也实现了类似的功能,队列中存放的是"消息"。称之为消息队列,消息也可理解为数据。主要用途是进程间通信(IPC),所谓通信,就是进行数据交互。

2、mq_open 创建或打开消息队列
cpp
#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);
函数功能:创建或打开一个消息队列,没有会创建1个文件, 有就会打开这个文件
函数参数:【name】,表示消息队列的名字。【oflag】:表示打开的方式,和open函数的类似,必须的选项:O_RDONLY,O_WRONLY,O_RDWR;可选的选项:O_NONBLOCK,O_CREAT,O_EXCL。【mode】:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,才需要提供该参数。可以参考open。【attr】:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才需要。如果是NULL,那么就采用默认属性。
函数返回值:成功返回消息队列的描述符,失败返回-1 并设置 errno,On success, mq_open() returns a message queue descriptor for use by other message queue functions. On error, mq_open() returns (mqd_t) -1,with errno set to indicate the error.
3、mq_close 关闭消息队列
cpp
#include <mqueue.h>
int mq_close(mqd_t mqdes);
函数功能:关闭消息队列,关闭1个文件
函数参数:【mqdes】,消息队列的描述符
函数返回值:成功返回0,失败返回-1 并设置 errno,On success mq_close() returns 0; on error, -1 is returned, with errno set to indicate the error.
4、mq_unlink 删除消息队列
cpp
#include <mqueue.h>
int mq_unlink(const char *name);
函数功能:删除消息队列,删除1个文件
函数参数:【name】,消息队列的名称
函数返回值:成功返回0,失败返回-1 并设置 errno,On success mq_unlink() returns 0; on error, -1 is returned, with errno set to indicate the error.
5、mq_getattr 获取息队列属性
cpp
#include <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
函数功能:获取息队列属性
函数参数:【mqdes】,要获取消息队列的描述符;【attr】,要消息队列属性结构体
cpp
struct mq_attr
{
long mq_flags; /* Flags: 0 or O_NONBLOCK */
long mq_maxmsg; /* Max. # of messages on queue */
long mq_msgsize; /* Max. message size (bytes) */
long mq_curmsgs; /* # of messages currently in queue */
};
函数返回值:成功返回0,失败返回-1 并设置 errno,On success mq_getattr() and mq_setattr() return 0; on error, -1 is returned, with errno set to indicate the error.
6、mq_setattr 设置息队列属性
cpp
#include <mqueue.h>
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
函数功能:设置息队列属性
函数参数:【mqdes】,要设置消息队列的描述符;【newattr】, 新的属性;【oldattr】,旧的属性,用于保存修改前的消息队列的属性,可以为空
cpp
struct mq_attr
{
long mq_flags; /* Flags: 0 or O_NONBLOCK */
long mq_maxmsg; /* Max. # of messages on queue */
long mq_msgsize; /* Max. message size (bytes) */
long mq_curmsgs; /* # of messages currently in queue */
};
函数返回值:成功返回0,失败返回-1 并设置 errno,On success mq_getattr() and mq_setattr() return 0; on error, -1 is returned, with errno set to indicate the error.
7、mq_send 发送消息
cpp
#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
#include <time.h>
#include <mqueue.h>
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
函数功能:发送一个消息
函数参数:【mqdes】,要发送消息队列的描述符;【msg_ptr】,指向消息体缓冲区的指针;【msg_len】, 消息体的长度;【msg_prio】,消息的优先级;它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。
函数返回值:成功返回0,失败返回-1 并设置 errno,On success, mq_send() and mq_timedsend() return zero; on error, -1 is returned, with errno set to indicate the error.
8、mq_receive 接收消息
cpp
#include <mqueue.h>
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
#include <time.h>
#include <mqueue.h>
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout);
函数功能:从消息队列中接收一个消息
函数参数:【mqdes】,要接收消息队列的描述符;【msg_ptr】,要接收消息结构体;【msg_len】,消息体的长度,其中【mq_receive】的该参数不能小于能写入队列中消息的最大大小。不管消息的实际大小是什么,msg_len(即 msg_ptr 指向的缓冲区的大小)必须要大于或等于队列的 mq_msgsize特性,否则 mq_receive()就会失败并返回 EMSGSIZE 错误。【msg_prio】, 接收消息的类型, 这个就是mtype 中的类型
函数返回值:成功返回实际读回来消息正文的字节数,失败返回-1 并设置 errno,On success, mq_receive() and mq_timedreceive() return the number of bytes in the received message; on error, -1 is returned, with errno set to indicate the error.
9、mq_notify 建立或者删除通知事件
cpp
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
函数功能:建立或者删除消息到达通知事件
函数参数:【mqdes】,消息队列的描述符;【sevp】,要操作的命令
cpp
struct sigevent
{
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value; /* Data passed with notification */
void (*sigev_notify_function) (union sigval);/* Function used for thread notification (SIGEV_THREAD) */
void *sigev_notify_attributes;/* Attributes for notification thread (SIGEV_THREAD) */
pid_t sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID) */
}
Posix消息队列允许异步事件通知,以告知何时有一个消息放置到某个空消息队列中,这种通知有两种方式可以选择:
- 创建一个线程来执行一个指定的函数,在新的线程中处理消息.
cpp
struct sigevent sev = { 0 };
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = (void *)p; //线程入口函数
sev.sigev_notify_attributes = NULL;//线程属性,根据需求设置
sev.sigev_value.sival_ptr = (void *)mqdes; // 传递给线程的参数
- 产生一个信号,通过信号处理函数进行处理消息.
cpp
struct sigevent sev = { 0 };
signal(signo,p); //设置信号处理函数
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = signo ; //信号值
函数返回值:成功返回0,失败返回-1 并设置 errno,On success, IPC_STAT, IPC_SET, and IPC_RMID return 0. On error, -1 is returned with errno indicating the error.
10、实例消息队列的使用
使用1个消息队列实现数据的单向发送,子进程发送,父进程接收
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <mqueue.h>
#include <fcntl.h>
#include <unistd.h>
#define MQ_NAME "/mymq"
#define NSize 128
int main(int argc, char const *argv[])
{
char buf[NSize] = {0};
mqd_t mqfd = mq_open(MQ_NAME, O_RDWR | O_CREAT, 0666, NULL);
if (mqfd == -1)
{
perror("mq_open");
exit(1);
}
printf("mqfd=%d\n", mqfd);
system("ls -l /dev/mqueue"); // 查看消息队列
// 获取消息队列的属性
struct mq_attr attr;
int ret = mq_getattr(mqfd, &attr);
if (ret == -1)
{
perror("mq_getattr");
exit(1);
}
printf("attr.mq_maxmsg = %ld\n", attr.mq_maxmsg);
printf("attr.mq_msgsize = %ld\n", attr.mq_msgsize);
printf("attr.mq_flags = %ld\n", attr.mq_flags);
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
printf("child pid=%d\n", getpid());
while (1)
{
printf("child >:");
memset(buf, 0, NSize);
fgets(buf, NSize, stdin);
buf[strlen(buf)-1] = 0 ;// 去除 '\n'
ret = mq_send(mqfd, buf, NSize, 1); // 发送消息
if (ret == -1)
{
perror("mq_send");
exit(1);
}
usleep(1000*100); // 100ms
}
}
else
{
// 父进程
printf("parent pid=%d\n", getpid());
int prio; // 消息优先级
while(1)
{
ret = mq_receive(mqfd, buf, attr.mq_msgsize, &prio); // 接收消息
if (ret == -1)
{
perror("mq_receive");
exit(1);
}
printf("parent :%s, prio=%d\n", buf, prio);
memset(buf, 0, NSize);
}
}
mq_close(mqfd); // 关闭消息队列
return 0;
}

11、消息队列实现双向通信
创建2个消息队列,分别是send与recv
- mq_write
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <mqueue.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#define MQ_SEND "/send"
#define MQ_RECV "/recv"
#define NSize 128
int main(int argc, char const *argv[])
{
char buf[NSize] = {0};
mqd_t mqfd_send = mq_open(MQ_SEND, O_RDWR | O_CREAT, 0666, NULL);
if (mqfd_send == -1)
{
perror("mq_open");
exit(1);
}
printf("mqfd_send=%d\n", mqfd_send);
mqd_t mqfd_recv = mq_open(MQ_RECV, O_RDWR | O_CREAT, 0666, NULL);
if (mqfd_recv == -1)
{
perror("mq_open");
exit(1);
}
printf("mqfd_recv=%d\n", mqfd_recv);
system("ls -l /dev/mqueue"); // 查看消息队列
// 获取消息队列的属性
struct mq_attr attr_send, attr_recv;
int ret = mq_getattr(mqfd_send, &attr_send);
if (ret == -1)
{
perror("mq_getattr");
exit(1);
}
printf("attr_send.mq_msgsize = %ld\n", attr_send.mq_msgsize);
ret = mq_getattr(mqfd_recv, &attr_recv);
if (ret == -1)
{
perror("mq_getattr");
exit(1);
}
printf("attr_recv.mq_msgsize = %ld\n", attr_recv.mq_msgsize);
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
printf("child pid=%d\n", getpid());
while (1)
{
printf("child >:");
memset(buf, 0, NSize);
fgets(buf, NSize, stdin);
buf[strlen(buf) - 1] = 0; // 去除 '\n'
ret = mq_send(mqfd_send, buf, NSize, 1); // 发送消息
if (ret == -1)
{
perror("mq_send");
exit(1);
}
// quit
if (strcmp(buf, "quit") == 0)
{
break;
}
usleep(1000 * 100); // 100ms
}
kill(getppid(), SIGINT); // 发送信号给父进程
mq_close(mqfd_recv); // 关闭消息队列
mq_close(mqfd_send); // 关闭消息队列
}
else
{
// 父进程
printf("parent pid=%d\n", getpid());
int prio; // 消息优先级
while (1)
{
ret = mq_receive(mqfd_recv, buf, attr_recv.mq_msgsize, &prio); // 接收消息
if (ret == -1)
{
perror("mq_receive");
exit(1);
}
// quit
if (strcmp(buf, "quit") == 0)
{
break;
}
printf("parent :%s, prio=%d\n", buf, prio);
memset(buf, 0, NSize);
}
kill(pid, SIGINT); // 发送信号给子进程
mq_close(mqfd_recv); // 关闭消息队列
mq_close(mqfd_send); // 关闭消息队列
}
return 0;
}
- mq_read
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <mqueue.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#define MQ_SEND "/send"
#define MQ_RECV "/recv"
#define NSize 128
int main(int argc, char const *argv[])
{
char buf[NSize] = {0};
mqd_t mqfd_send = mq_open(MQ_SEND, O_RDWR | O_CREAT, 0666, NULL);
if (mqfd_send == -1)
{
perror("mq_open");
exit(1);
}
printf("mqfd_send=%d\n", mqfd_send);
mqd_t mqfd_recv = mq_open(MQ_RECV, O_RDWR | O_CREAT, 0666, NULL);
if (mqfd_recv == -1)
{
perror("mq_open");
exit(1);
}
printf("mqfd_recv=%d\n", mqfd_recv);
system("ls -l /dev/mqueue"); // 查看消息队列
// 获取消息队列的属性
struct mq_attr attr_send, attr_recv;
int ret = mq_getattr(mqfd_send, &attr_send);
if (ret == -1)
{
perror("mq_getattr");
exit(1);
}
printf("attr_send.mq_msgsize = %ld\n", attr_send.mq_msgsize);
ret = mq_getattr(mqfd_recv, &attr_recv);
if (ret == -1)
{
perror("mq_getattr");
exit(1);
}
printf("attr_recv.mq_msgsize = %ld\n", attr_recv.mq_msgsize);
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
else if (pid == 0)
{
// 子进程
printf("child pid=%d\n", getpid());
while (1)
{
printf("child >:");
memset(buf, 0, NSize);
fgets(buf, NSize, stdin);
buf[strlen(buf) - 1] = 0; // 去除 '\n'
ret = mq_send(mqfd_recv, buf, NSize, 1); // 发送消息
if (ret == -1)
{
perror("mq_send");
exit(1);
}
// quit
if (strcmp(buf, "quit") == 0)
{
break;
}
usleep(1000 * 100); // 100ms
}
kill(getppid(), SIGINT); // 发送信号给父进程
mq_close(mqfd_recv); // 关闭消息队列
mq_close(mqfd_send); // 关闭消息队列
}
else
{
// 父进程
printf("parent pid=%d\n", getpid());
int prio; // 消息优先级
while (1)
{
ret = mq_receive(mqfd_send, buf, attr_recv.mq_msgsize, &prio); // 接收消息
if (ret == -1)
{
perror("mq_receive");
exit(1);
}
// quit
if (strcmp(buf, "quit") == 0)
{
break;
}
printf("parent :%s, prio=%d\n", buf, prio);
memset(buf, 0, NSize);
}
kill(pid, SIGINT); // 发送信号给子进程
mq_close(mqfd_recv); // 关闭消息队列
mq_close(mqfd_send); // 关闭消息队列
}
return 0;
}

八、进程间通信机制之套接字(socket)
- socket 既可以做进程间通信,也可以做网络通信
- 这一部分内容在第九篇