管道
管道是一个特殊的共享文件,属于操作系统(也就不是fork()的复制对象),两个进程通过操作系统内核空间进行读写通信
特点:
1.管道的读取属于一次性动作,读取即释放,普通管道只允许半双工通信,若要实现两个进程双向通信,则需要两个管道
2.管道则需要先通过操作系统访问文件再获得内存数据。
匿名管道:
1.只允许有血缘关系之间的进程通信
2.生命周期和进程关联
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#define BUF_SIZE 30
int main(int arcg, char *argv[])
{int fds1[2], fds2[2];char str1[] = "Who are you?";char str2[] = "Thank you for your message";char buf[BUF_SIZE];pid_t pid;pipe(fds2), pipe(fds2);pid = fork();if (pid == 0){write(fds1[1], str1, sizeof(str1));read(fds2[0], buf, BUF_SIZE);printf("child proc output:%s\n", buf);}else{read(fds1[0], buf, BUF_SIZE);printf("Parent proc output:%s\n", buf);write(fds2[1], str2, sizeof(str2));sleep(3);}return 0;
}
命名管道:
1.可以在两个无亲缘关系的进程间通信
2.生命周期独立于进程存在,直到最后一个使用它的进程关闭它或者显示删除该文件
3.需要创建文件用于管道通信
//write.cpp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{// 打开命名管道,O_WRONLY 表示只写模式int fifo_fd = open("./myfifo", O_WRONLY | O_CREAT);if (fifo_fd == -1){perror("Failed to open FIFO for writing");exit(EXIT_FAILURE);}char message[] = "Hello from the writer!\n";ssize_t bytes_written = write(fifo_fd, message, strlen(message));if (bytes_written == -1){perror("Write to FIFO failed");}else{printf("Wrote %ld bytes to FIFO\n", (long)bytes_written);}// 关闭文件描述符close(fifo_fd);return 0;
}
//read.cpp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{// 打开命名管道,O_RDONLY 表示只读模式int fifo_fd = open("./myfifo", O_RDONLY | O_CREAT);if (fifo_fd == -1){perror("Failed to open FIFO for reading");exit(EXIT_FAILURE);}char buffer[100];ssize_t bytes_read = read(fifo_fd, buffer, sizeof(buffer) - 1);if (bytes_read == -1){perror("Read from FIFO failed");}else{buffer[bytes_read] = '\0'; // 添加字符串结束符printf("Read from FIFO: %s\n", buffer);}// 关闭文件描述符close(fifo_fd);return 0;
}
套接字对 socketpair()
特点:通过sendmsg()和recvmsg()通信,可以允许同时发送接收多个缓冲区,已经可以附带文件描述符等辅助信息
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define BUF_SIZE 1024
int main()
{int sockfd[2]; // 存储套接字对unsigned char SendBuffer[2][10] = {{"hellow"}, {"world!"}}; // 消息缓冲区// 创建已连接套接字对if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) == -1){perror("Error creating socket pair");return -1;}// 创建子进程pid_t pid = fork();if (pid == -1){perror("Error forking");return -1;}// 子进程(发送方)if (pid == 0){close(sockfd[0]); // 关闭读套接字// 打开文件并获取文件描述符int file_socket = open("example.txt", O_RDONLY);if (file_socket == -1){perror("Error opening file");return -1;}// 准备消息// 准备I/O向量struct iovec iov[2];iov[0].iov_base = SendBuffer[0];iov[0].iov_len = sizeof(SendBuffer[0]);iov[1].iov_base = SendBuffer[1];iov[1].iov_len = sizeof(SendBuffer[1]);struct msghdr message = {0};message.msg_iov = iov;message.msg_iovlen = 2;// 辅助控制信息char control_data[CMSG_SPACE(sizeof(int))]; // 开辟存储辅助数据内存 辅助信息为一个文件描述符 4字节 = sizeof(int)message.msg_control = control_data;message.msg_controllen = sizeof(control_data);// 构建控制信息头部struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message); // 获取消息头的第一个辅助数据块cmsg->cmsg_len = CMSG_LEN(sizeof(int)); // 一个辅助数据块的总长度cmsg->cmsg_level = SOL_SOCKET; // 源层协议 SOL_SOCKET表示这是与套接字相关的辅助数据cmsg->cmsg_type = SCM_RIGHTS; // 辅助数据的类型 SCM_RIGHTS表示辅助数据用于传递文件描述符。// 将文件描述符复制到辅助数据中*((int *)CMSG_DATA(cmsg)) = file_socket;// 发送消息if (sendmsg(sockfd[1], &message, 0) == -1){perror("Error sending message");close(file_socket);return -1;}close(file_socket); // 关闭文件描述符close(sockfd[1]); // 关闭写端exit(0);}// 父进程(接收方)close(sockfd[1]); // 关闭写端// 准备消息// 设置I/O向量unsigned char RecvBuffer[2][10]; // 消息缓冲区struct iovec iov[2];iov[0].iov_base = RecvBuffer[0];iov[0].iov_len = sizeof(RecvBuffer[0]);iov[1].iov_base = RecvBuffer[1];iov[1].iov_len = sizeof(RecvBuffer[1]);struct msghdr message = {0};message.msg_iov = iov;message.msg_iovlen = 2;char control_data[CMSG_SPACE(sizeof(int))]; // 开辟存储辅助数据内存 辅助信息为一个文件描述符 4字节 = sizeof(int)message.msg_control = control_data;message.msg_controllen = sizeof(control_data);// 接收消息// 接收消息if (recvmsg(sockfd[0], &message, 0) == -1){perror("Error receiving message");return -1;}printf("消息:%s %s\n", RecvBuffer[0], RecvBuffer[1]);// 从辅助信息中获取文件描述符struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message); // 获取消息头的第一个辅助数据块int received_fd;memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int)); // 从数据段首地址复制// 读取文件内容printf("接收的文件描述符:%d\n", received_fd);char buffer[BUF_SIZE];ssize_t bytes_read = read(received_fd, buffer, sizeof(buffer));if (bytes_read == -1){perror("Error reading file");return -1;}// 打印文件内容printf("文件内容: %.*s\n", (int)bytes_read, buffer);close(received_fd); // 关闭收到的文件描述符close(sockfd[0]); // 关闭读端return 0;
}
共享内存
共享内存允许多个毫不相干的进程读取和写入同一块物理内存,当某个进程往共享内存中写入数据时,其它进程就能够立马读取到共享内存中的数据,从而达到进程间通信的目的。这也是所有进程间通信方式中最快的一种。
缺点:
用于进程间通信时,共享内存本身不支持阻塞等待操作。这是因为当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。因为共享内存本质是进程直接访问内存,无法主动停止读取,如果读端不加以限制,那么将持续读取数据。同理,写端也会持续写入数据。换句话说,共享内存本身没有访问控制。
原理:
CPU执行语句的过程大致就是先找到当前进程的PCB(进程控制块)里的进程虚拟地址空间
当A进程需要与B进程通信时,只需要通过页表,将共享区虚拟地址映射到物理地址,对该物理地址直接进行写入;而B进程则是通过页表,取虚拟地址映射到物理地址,从该物理地址直接进行读取。
共享内存的使用
(一)创建共享内存(物理内存)
①key
shmget会根据key
值创建一个共享内存,因此当创建多个共享内存时,每一个key值要独一无二。
获得key值可以使用库函数ftok
专门获取一个独一无二的key_t类型值。
参数pathname
:为路径,必须是真实存在且可以访问的路径。
参数proj_id
:是int类型数字,且必须传入非零值。
返回值 :成功返回key_t
值,失败返回-1。
ftok函数内部会根据路径和proj_id通过算法生成一个独一无二的key_t返回值。
多进程通信时,需要通信双方使用同一个key值,因此双方使用的ftok参数应该一致。
②size
该参数用于确定共享内存大小。
一般而言是4096的整数倍,因为内存的块的大小就是4KB即4096B。因此即便我们需要的空间大小不是块大小的整数倍,操作系统实际上也还是分配块的倍数个。但在使用时,那些超过size大小的多余分配空间不能访问。
③shmflg
该参数用于确定共享内存属性。
使用上为:标志位 | 内存权限
标志位参数有两种:IPC_CREAT、IPC_EXCL
常用的使用方法:
值得注意PC_EXCL无法单独使用。
通常情况下在多进程通信时,创建方使用IPC_CREAT | IPC_EXCL,接收方使用0即可。
④返回值
返回值为int类型,称为shmid。每一个共享内存都会有一个shmid,用于连接与分离时传递参数。
(二)链接
用于将物理共享内存段附加(attach)到当前进程的虚拟地址空间
shmid即shmget返回值。
shmaddr用于确定将共享内存挂在进程虚拟地址哪个位置,一般填nullptr即可代表让内核自己确定位置。(一般在堆区,也可以是其他区域)
shmflg用于确定挂接方式,一般填0。
连接成功返回共享内存在进程中虚拟内存的起始地址,失败返回-1。
(三)分离
shmdt() 函数用于将共享内存段从当前进程的地址空间中分离(detach)。这意味着虽然共享内存段本身仍然存在,并且可能被其他进程使用,但它对调用了 shmdt() 的进程不再直接可见或可访问。分离并不意味着删除共享内存,它仅仅是断开当前进程对该内存区域的访问路径。
shmaddr:之前通过 shmat() 调用返回的指向共享内存段起始地址的指针。传递这个地址给 shmdt() 以告知系统要从当前进程的地址空间中分离哪个共享内存段。
(四).销毁
该接口本身用于控制共享内存,可用于销毁。
shmid:shmget()的返回值,cmd传入IPC_RMID,buf传nullptr。
(五)指令查看删除共享内存
查看指令:ipcs -m
删除:ipcrm -m [shmid]
演示代码:
//writer.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_KEY 123456
#define SHM_SIZE 100void write_to_shared_mem(int shmid) {const char *data = "Hello from Writer!";char *shmaddr = (char*)shmat(shmid, NULL, 0);//连接共享内存和进程空间if (shmaddr == (void*) -1) {perror("shmat");exit(1);}strncpy(shmaddr, data, strlen(data)+1);//直接向虚拟地址写数据printf("Writer: Data written to shared memory.\n");shmdt(shmaddr);//分离共享内存连接
}int main() {key_t key = ftok(".", 131);//创建唯一的keyif (key == -1) {perror("ftok");exit(1);}// 创建共享内存(如果不存在)int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");exit(1);}//向共享内存写数据write_to_shared_mem(shmid);return 0;
}
//reader.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_KEY 123456
#define SHM_SIZE 100void read_from_shared_mem(int shmid) {char *shmaddr = (char*)shmat(shmid, NULL, 0);连接共享内存和进程空间if (shmaddr == (void*) -1) {perror("shmat");exit(1);}printf("Reader: Read from shared memory: %s\n", shmaddr);shmdt(shmaddr);
}int main() {key_t key = ftok(".", 131);//创建keyif (key == -1) {perror("ftok");exit(1);}// 连接到现有的共享内存int shmid = shmget(key, SHM_SIZE, 0666);if (shmid == -1) {perror("shmget");exit(1);}// 等待一段时间以确保写入完成(实际应用中应使用信号量或互斥锁等同步机制)sleep(2); read_from_shared_mem(shmid);//读取共享内存数据return 0;
}