Linux 进程间通信机制详解
在 Linux 系统中,每个进程拥有独立的虚拟地址空间,无法直接访问其他进程的内存。若要实现进程间的数据交换,必须借助内核提供的多种通信机制。
管道通信
管道是一种半双工的通信方式,数据只能单向流动。Linux 中的 | 符号就是典型的匿名管道应用:
ps auxf | grep mysql
上述命令将 ps 的输出作为 grep 的输入。匿名管道仅存在于内存中,随进程结束而销毁。
创建匿名管道需要调用 pipe() 系统函数:
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
perror("pipe creation failed");
}
// pipe_fd[0] 为读端,pipe_fd[1] 为写端
通过 fork() 创建子进程后,父子进程可通过复制的文件描述符进行通信:
// 父进程关闭读端,子进程关闭写端
if (pid == 0) {
close(pipe_fd[1]); // 子进程只读
read(pipe_fd[0], buffer, sizeof(buffer));
} else {
close(pipe_fd[0]); // 父进程只写
write(pipe_fd[1], message, strlen(message));
}
命名管道(FIFO)则通过文件系统持久化:
mkfifo /tmp/my_fifo
echo "data" > /tmp/my_fifo # 写入数据(阻塞直到被读取)
cat < /tmp/my_fifo # 读取数据
消息队列
消息队列克服了管道无结构数据流的限制,允许按消息单元传递具有特定类型的数据:
#include <sys/msg.h>
struct msg_buffer {
long msg_type;
char msg_text[100];
};
// 发送消息
struct msg_buffer message;
message.msg_type = 1;
strcpy(message.msg_text, "Hello Queue");
msgsnd(queue_id, &message, sizeof(message.msg_text), 0);
// 接收消息
msgrcv(queue_id, &message, sizeof(message.msg_text), 1, 0);
printf("Received: %s\n", message.msg_text);
消息队列存储于内核中,支持跨进程持久化通信,但存在大小限制(由 MSGMAX 和 MSGMNB 控制)。
共享内存
共享内存提供最高效率的通信方式,多个进程映射同一物理内存区域:
#include <sys/shm.h>
// 创建共享内存段
int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
// 映射到进程地址空间
char *shared_memory = (char*) shmat(shmid, NULL, 0);
// 使用示例
strcpy(shared_memory, "Shared data");
// 解除映射
shmdt(shared_memory);
信号量同步
为避免多个进程同时访问共享资源导致冲突,引入信号量机制:
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// 初始化信号量
union semun arg;
arg.val = 1;
semctl(sem_id, 0, SETVAL, arg);
// P操作(等待资源)
struct sembuf p_op = {0, -1, SEM_UNDO};
semop(sem_id, &p_op, 1);
// V操作(释放资源)
struct sembuf v_op = {0, 1, SEM_UNDO};
semop(sem_id, &v_op, 1);
信号通知
信号用于处理异步事件,如用户中断或系统异常:
#include <signal.h>
void signal_handler(int sig) {
printf("Received signal %d\n", sig);
}
int main() {
signal(SIGINT, signal_handler); // 注册Ctrl+C处理函数
while(1) {
pause(); // 等待信号
}
return 0;
}
套接字通信
Socket 支持本地和网络环境下的进程通信。TCP 流式套接字示例:
// 服务端
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
listen(server_fd, 3);
int client_fd = accept(server_fd, NULL, NULL);
// 客户端
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, (struct sockaddr *)&address, sizeof(address));
send(sock, "Hello", 5, 0);
UDP 数据报套接字无需连接:
// 发送端
sendto(sock, "data", 4, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
// 接收端
recvfrom(sock, buffer, 100, 0, (struct sockaddr*)&src_addr, &addr_len);
本地套接字适用于同一主机进程通信:
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/socket");
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
这些通信机制各有特点:管道适合简单的单向数据流;消息队列支持结构化数据传输;共享内存提供最快的访问速度但需额外同步;信号量确保资源共享的安全性;信号处理异步事件;套接字则构建起分布式系统的基础。