Linux进程间通信(IPC)机制深度解析与实践指南
引言
在Linux系统中,进程间通信(Inter-Process Communication, IPC)是操作系统核心功能之一,它允许不同进程之间进行数据交换和同步操作。随着现代软件系统越来越复杂,多进程架构已成为常态,理解并掌握各种IPC机制对于开发高性能、高可靠性的系统至关重要。
Linux提供了丰富的IPC机制,每种机制都有其特定的应用场景和优缺点。从简单的管道通信到复杂的套接字编程,从同步原语到共享内存,不同的IPC方式满足了不同场景下的通信需求。本文将深入探讨Linux下的各种IPC机制,通过丰富的代码示例和详细的原理解析,帮助读者全面掌握这一关键技术。
进程间通信不仅是技术实现的工具,更是系统架构设计的重要组成部分。合理选择IPC机制可以显著提升系统性能、增强系统稳定性,并降低维护成本。接下来,让我们逐一深入分析各种IPC机制的原理、实现和应用。
1. 管道(Pipe)
1.1 匿名管道
匿名管道是Linux中最基本的IPC机制,它提供了一种单向通信的通道,通常用于具有亲缘关系的进程间通信。
基本原理
匿名管道通过pipe()系统调用创建,返回两个文件描述符:一个用于读取,一个用于写入。数据在管道中以先进先出(FIFO)的方式流动。
c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1024
int main() {
int pipefd[2];
pid_t pid;
char buffer[BUFFER_SIZE];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程 - 读取数据
close(pipefd[1]); // 关闭写端
ssize_t bytes_read;
while ((bytes_read = read(pipefd[0], buffer, BUFFER_SIZE - 1)) > 0) {
buffer[bytes_read] = '\0';
printf("Child received: %s", buffer);
}
close(pipefd[0]);
_exit(0);
} else {
// 父进程 - 写入数据
close(pipefd[0]); // 关闭读端
const char *messages[] = {
"Hello from parent!\n",
"This is a test message.\n",
"Pipe communication works!\n",
NULL
};
for (int i = 0; messages[i] != NULL; i++) {
write(pipefd[1], messages[i], strlen(messages[i]));
sleep(1); // 模拟处理延迟
}
close(pipefd[1]); // 关闭写端,子进程read返回0
wait(NULL); // 等待子进程退出
}
return 0;
}
管道缓冲区管理
管道的性能很大程度上取决于缓冲区的大小和管理策略。我们可以通过fcntl函数来查询和设置管道的缓冲区大小。
c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
void check_pipe_buffer() {
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
return;
}
// 获取管道缓冲区大小
long pipe_size = fpathconf(pipefd[0], _PC_PIPE_BUF);
printf("Pipe buffer size: %ld bytes\n", pipe_size);
// 设置非阻塞模式
int flags = fcntl(pipefd[1], F_GETFL);
fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);
// 测试写入大量数据
char buffer[65536];
ssize_t written;
while ((written = write(pipefd[1], buffer, sizeof(buffer))) > 0) {
printf("Written: %zd bytes\n", written);
}
if (written == -1 && errno == EAGAIN) {
printf("Pipe buffer full, would block\n");
}
close(pipefd[0]);
close(pipefd[1]);
}
int main() {
check_pipe_buffer();
return 0;
}
1.2 命名管道(FIFO)
命名管道(FIFO)与匿名管道的主要区别在于它有一个关联的文件名,可以在不相关的进程间使用。
创建和使用FIFO
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE 1024
// FIFO写入进程
int fifo_writer() {
int fd;
char buffer[BUFFER_SIZE];
// 创建FIFO(如果不存在)
if (mkfifo(FIFO_NAME, 0666) == -1 && errno != EEXIST) {
perror("mkfifo");
return 1;
}
printf("Writer: opening FIFO...\n");
fd = open(FIFO_NAME, O_WRONLY);
if (fd == -1) {
perror("open");
return 1;
}
printf("Writer: connected. Type messages (Ctrl+D to exit):\n");
while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
ssize_t bytes_written = write(fd, buffer, strlen(buffer));
if (bytes_written == -1) {
perror("write");
break;
}
printf("Writer: sent %zd bytes\n", bytes_written);
}
close(fd);
return 0;
}
// FIFO读取进程
int fifo_reader() {
int fd;
char buffer[BUFFER_SIZE];
printf("Reader: opening FIFO...\n");
fd = open(FIFO_NAME, O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
printf("Reader: connected. Waiting for messages...\n");
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer, BUFFER_SIZE - 1)) > 0) {
buffer[bytes_read] = '\0';
printf("Reader received: %s", buffer);
}
if (bytes_read == -1) {
perror("read");
}
close(fd);
// 清理FIFO文件
unlink(FIFO_NAME);
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [reader|writer]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "writer") == 0) {
return fifo_writer();
} else if (strcmp(argv[1], "reader") == 0) {
return fifo_reader();
} else {
printf("Invalid argument. Use 'reader' or 'writer'\n");
return 1;
}
}
使用select进行多路复用的FIFO
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/select.h>
#define FIFO_NAME "/tmp/select_fifo"
#define BUFFER_SIZE 1024
int main() {
int fd;
fd_set readfds;
char buffer[BUFFER_SIZE];
// 创建FIFO
if (mkfifo(FIFO_NAME, 0666) == -1) {
perror("mkfifo");
return 1;
}
fd = open(FIFO_NAME, O_RDWR | O_NONBLOCK);
if (fd == -1) {
perror("open");
return 1;
}
printf("FIFO server started. Waiting for data (Ctrl+C to exit)...\n");
while (1) {
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
FD_SET(STDIN_FILENO, &readfds);
// 设置超时
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int max_fd = (fd > STDIN_FILENO) ? fd : STDIN_FILENO;
int ready = select(max_fd + 1, &readfds, NULL, NULL, &timeout);
if (ready == -1) {
perror("select");
break;
} else if (ready == 0) {
printf("Timeout occurred, no data received.\n");
continue;
}
if (FD_ISSET(STDIN_FILENO, &readfds)) {
if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
printf("You typed: %s", buffer);
}
}
if (FD_ISSET(fd, &readfds)) {
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer, BUFFER_SIZE - 1)) > 0) {
buffer[bytes_read] = '\0';
printf("Received from FIFO: %s", buffer);
}
if (bytes_read == 0) {
printf("Client disconnected\n");
}
}
}
close(fd);
unlink(FIFO_NAME);
return 0;
}
2. 消息队列(Message Queue)
2.1 System V消息队列
System V消息队列提供了一种结构化的消息传递机制,支持消息类型和优先级。
基本消息队列操作
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>
#define MAX_MSG_SIZE 1024
#define MSG_TYPE_TEXT 1
#define MSG_TYPE_EXIT 2
struct message {
long mtype;
char mtext[MAX_MSG_SIZE];
};
// 创建或获取消息队列
int create_message_queue() {
key_t key = ftok("/tmp", 'A');
if (key == -1) {
perror("ftok");
return -1;
}
int msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
return -1;
}
printf("Message queue created with ID: %d\n", msgid);
return msgid;
}
// 发送消息
int send_message(int msgid, const char *text, long mtype) {
struct message msg;
msg.mtype = mtype;
strncpy(msg.mtext, text, MAX_MSG_SIZE - 1);
msg.mtext[MAX_MSG_SIZE - 1] = '\0';
if (msgsnd(msgid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
perror("msgsnd");
return -1;
}
printf("Sent message (type %ld): %s\n", mtype, text);
return 0;
}
// 接收消息
int receive_message(int msgid, long mtype, int flags) {
struct message msg;
ssize_t bytes = msgrcv(msgid, &msg, MAX_MSG_SIZE, mtype, flags);
if (bytes == -1) {
if (errno != ENOMSG) {
perror("msgrcv");
}
return -1;
}
msg.mtext[bytes] = '\0';
printf("Received message (type %ld): %s\n", msg.mtype, msg.mtext);
return 0;
}
// 消息队列服务器
int message_queue_server() {
int msgid = create_message_queue();
if (msgid == -1) {
return 1;
}
printf("Server started. Waiting for messages...\n");
while (1) {
struct message msg;
ssize_t bytes = msgrcv(msgid, &msg, MAX_MSG_SIZE, 0, 0);
if (bytes == -1) {
perror("msgrcv");
break;
}
msg.mtext[bytes] = '\0';
printf("Server received (type %ld): %s\n", msg.mtype, msg.mtext);
if (msg.mtype == MSG_TYPE_EXIT) {
printf("Exit message received. Shutting down...\n");
break;
}
// 回显消息
char response[MAX_MSG_SIZE];
snprintf(response, MAX_MSG_SIZE, "Echo: %s", msg.mtext);
send_message(msgid, response, msg.mtype + 100);
}
// 清理消息队列
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
perror("msgctl");
}
return 0;
}
// 消息队列客户端
int message_queue_client() {
int msgid = create_message_queue();
if (msgid == -1) {
return 1;
}
// 发送测试消息
send_message(msgid, "Hello from client!", MSG_TYPE_TEXT);
send_message(msgid, "This is a test message.", MSG_TYPE_TEXT);
// 接收回显
printf("Waiting for echoes...\n");
for (int i = 0; i < 2; i++) {
receive_message(msgid, MSG_TYPE_TEXT + 100, 0);
}
// 发送退出消息
send_message(msgid, "Exit", MSG_TYPE_EXIT);
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [server|client]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "server") == 0) {
return message_queue_server();
} else if (strcmp(argv[1], "client") == 0) {
return message_queue_client();
} else {
printf("Invalid argument. Use 'server' or 'client'\n");
return 1;
}
}
带优先级的消息队列
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <time.h>
#define MAX_MSG_SIZE 256
#define MSG_TYPE_HIGH 1
#define MSG_TYPE_MEDIUM 2
#define MSG_TYPE_LOW 3
struct priority_message {
long mtype;
char mtext[MAX_MSG_SIZE];
int priority;
};
void send_priority_message(int msgid, const char *text, int priority) {
struct priority_message msg;
msg.mtype = priority; // 使用优先级作为消息类型
msg.priority = priority;
strncpy(msg.mtext, text, MAX_MSG_SIZE - 1);
msg.mtext[MAX_MSG_SIZE - 1] = '\0';
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
perror("msgsnd");
return;
}
printf("Sent message with priority %d: %s\n", priority, text);
}
void receive_priority_messages(int msgid) {
printf("Receiving messages in priority order...\n");
// 按优先级接收消息
struct priority_message msg;
// 先接收高优先级消息
while (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), MSG_TYPE_HIGH, IPC_NOWAIT) != -1) {
printf("High priority: %s\n", msg.mtext);
}
// 然后接收中优先级消息
while (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), MSG_TYPE_MEDIUM, IPC_NOWAIT) != -1) {
printf("Medium priority: %s\n", msg.mtext);
}
// 最后接收低优先级消息
while (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), MSG_TYPE_LOW, IPC_NOWAIT) != -1) {
printf("Low priority: %s\n", msg.mtext);
}
}
int main() {
key_t key = ftok("/tmp", 'B');
int msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
return 1;
}
// 发送不同优先级的消息
send_priority_message(msgid, "Low priority task", MSG_TYPE_LOW);
send_priority_message(msgid, "High priority task", MSG_TYPE_HIGH);
send_priority_message(msgid, "Medium priority task", MSG_TYPE_MEDIUM);
send_priority_message(msgid, "Another low priority", MSG_TYPE_LOW);
send_priority_message(msgid, "Critical task", MSG_TYPE_HIGH);
sleep(1); // 确保所有消息都已发送
receive_priority_messages(msgid);
// 清理
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
2.2 POSIX消息队列
POSIX消息队列提供了更现代和灵活的接口,支持异步通知和消息优先级。
基本POSIX消息队列操作
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <errno.h>
#define QUEUE_NAME "/my_posix_queue"
#define MAX_MSG_SIZE 1024
#define MAX_MSG_COUNT 10
void posix_mq_sender() {
mqd_t mq;
struct mq_attr attr;
char buffer[MAX_MSG_SIZE];
// 设置消息队列属性
attr.mq_flags = 0;
attr.mq_maxmsg = MAX_MSG_COUNT;
attr.mq_msgsize = MAX_MSG_SIZE;
attr.mq_curmsgs = 0;
// 创建或打开消息队列
mq = mq_open(QUEUE_NAME, O_CREAT | O_WRONLY, 0644, &attr);
if (mq == (mqd_t)-1) {
perror("mq_open");
return;
}
printf("POSIX MQ Sender started. Type messages (empty line to exit):\n");
while (fgets(buffer, MAX_MSG_SIZE, stdin) != NULL) {
if (strlen(buffer) == 1 && buffer[0] == '\n') {
break;
}
// 发送消息
if (mq_send(mq, buffer, strlen(buffer), 0) == -1) {
perror("mq_send");
break;
}
printf("Sent: %s", buffer);
}
mq_close(mq);
}
void posix_mq_receiver() {
mqd_t mq;
struct mq_attr attr;
char buffer[MAX_MSG_SIZE];
unsigned int priority;
mq = mq_open(QUEUE_NAME, O_RDONLY);
if (mq == (mqd_t)-1) {
perror("mq_open");
return;
}
// 获取队列属性
if (mq_getattr(mq, &attr) == -1) {
perror("mq_getattr");
mq_close(mq);
return;
}
printf("POSIX MQ Receiver started. Waiting for messages...\n");
printf("Queue stats: max messages=%ld, message size=%ld, current messages=%ld\n",
attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);
while (1) {
ssize_t bytes_read = mq_receive(mq, buffer, MAX_MSG_SIZE, &priority);
if (bytes_read == -1) {
if (errno != EAGAIN) {
perror("mq_receive");
}
break;
}
buffer[bytes_read] = '\0';
printf("Received (priority %u): %s", priority, buffer);
if (strcmp(buffer, "exit\n") == 0) {
printf("Exit message received.\n");
break;
}
}
mq_close(mq);
mq_unlink(QUEUE_NAME);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [sender|receiver]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "sender") == 0) {
posix_mq_sender();
} else if (strcmp(argv[1], "receiver") == 0) {
posix_mq_receiver();
} else {
printf("Invalid argument. Use 'sender' or 'receiver'\n");
return 1;
}
return 0;
}
使用信号通知的POSIX消息队列
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#define NOTIFY_QUEUE_NAME "/notify_queue"
#define NOTIFY_MAX_MSG_SIZE 256
#define NOTIFY_MAX_MSG_COUNT 10
static volatile int notification_received = 0;
// 通知处理函数
void notification_handler(union sigval sv) {
mqd_t *mq = sv.sival_ptr;
struct mq_attr attr;
char buffer[NOTIFY_MAX_MSG_SIZE];
ssize_t bytes_read;
printf("Notification received! Reading messages...\n");
// 获取队列属性
if (mq_getattr(*mq, &attr) == -1) {
perror("mq_getattr");
return;
}
// 读取所有可用消息
while ((bytes_read = mq_receive(*mq, buffer, NOTIFY_MAX_MSG_SIZE, NULL)) != -1) {
buffer[bytes_read] = '\0';
printf("Async received: %s", buffer);
}
if (errno != EAGAIN) {
perror("mq_receive");
}
notification_received = 1;
}
void posix_mq_async_receiver() {
mqd_t mq;
struct mq_attr attr;
struct sigevent sev;
// 设置队列属性
attr.mq_flags = 0;
attr.mq_maxmsg = NOTIFY_MAX_MSG_COUNT;
attr.mq_msgsize = NOTIFY_MAX_MSG_SIZE;
attr.mq_curmsgs = 0;
// 创建消息队列
mq = mq_open(NOTIFY_QUEUE_NAME, O_CREAT | O_RDONLY | O_NONBLOCK, 0644, &attr);
if (mq == (mqd_t)-1) {
perror("mq_open");
return;
}
// 设置异步通知
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = notification_handler;
sev.sigev_notify_attributes = NULL;
sev.sigev_value.sival_ptr = &mq;
if (mq_notify(mq, &sev) == -1) {
perror("mq_notify");
mq_close(mq);
mq_unlink(NOTIFY_QUEUE_NAME);
return;
}
printf("Async receiver started. Waiting for notifications...\n");
// 主循环
while (1) {
sleep(1);
// 重新注册通知(每次通知后需要重新注册)
if (notification_received) {
printf("Re-registering notification...\n");
if (mq_notify(mq, &sev) == -1) {
perror("mq_notify re-register");
break;
}
notification_received = 0;
}
printf("Main thread working...\n");
}
mq_close(mq);
mq_unlink(NOTIFY_QUEUE_NAME);
}
void posix_mq_async_sender() {
mqd_t mq = mq_open(NOTIFY_QUEUE_NAME, O_WRONLY);
if (mq == (mqd_t)-1) {
perror("mq_open");
return;
}
const char *messages[] = {
"First async message\n",
"Second async message\n",
"Third async message\n",
"exit\n",
NULL
};
for (int i = 0; messages[i] != NULL; i++) {
printf("Sending: %s", messages[i]);
if (mq_send(mq, messages[i], strlen(messages[i]), 0) == -1) {
perror("mq_send");
break;
}
sleep(2); // 给接收者处理时间
}
mq_close(mq);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [sender|receiver]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "sender") == 0) {
posix_mq_async_sender();
} else if (strcmp(argv[1], "receiver") == 0) {
posix_mq_async_receiver();
} else {
printf("Invalid argument. Use 'sender' or 'receiver'\n");
return 1;
}
return 0;
}
3. 共享内存(Shared Memory)
3.1 System V共享内存
System V共享内存允许多个进程共享同一块内存区域,是实现高性能进程间通信的有效方式。
基本共享内存操作
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#define SHM_SIZE 1024
#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
// 简单的信号量操作
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int create_semaphore() {
int semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget");
return -1;
}
// 初始化信号量值为1(二进制信号量)
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl");
return -1;
}
return semid;
}
void semaphore_wait(int semid) {
struct sembuf sb = {0, -1, 0};
if (semop(semid, &sb, 1) == -1) {
perror("semop wait");
}
}
void semaphore_signal(int semid) {
struct sembuf sb = {0, 1, 0};
if (semop(semid, &sb, 1) == -1) {
perror("semop signal");
}
}
// 共享内存写入进程
int shm_writer() {
int shmid, semid;
char *shm_ptr;
// 创建共享内存段
shmid = shmget(SHM_KEY, SHM_SIZE, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 附加共享内存
shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (char *)-1) {
perror("shmat");
return 1;
}
// 创建信号量
semid = create_semaphore();
if (semid == -1) {
return 1;
}
printf("Writer attached to shared memory at %p\n", shm_ptr);
const char *messages[] = {
"Hello from writer!",
"This is shared memory communication.",
"Multiple processes can access this memory.",
"Goodbye!",
NULL
};
for (int i = 0; messages[i] != NULL; i++) {
semaphore_wait(semid); // 获取锁
// 写入数据到共享内存
snprintf(shm_ptr, SHM_SIZE, "%s", messages[i]);
printf("Writer wrote: %s\n", messages[i]);
semaphore_signal(semid); // 释放锁
sleep(2); // 让读者有时间读取
}
// 标记结束
semaphore_wait(semid);
snprintf(shm_ptr, SHM_SIZE, "END");
semaphore_signal(semid);
// 分离共享内存
if (shmdt(shm_ptr) == -1) {
perror("shmdt");
}
printf("Writer finished.\n");
return 0;
}
// 共享内存读取进程
int shm_reader() {
int shmid, semid;
char *shm_ptr;
char buffer[SHM_SIZE];
// 获取共享内存段
shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 附加共享内存
shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (char *)-1) {
perror("shmat");
return 1;
}
// 获取信号量
semid = semget(SEM_KEY, 1, 0666);
if (semid == -1) {
perror("semget");
return 1;
}
printf("Reader attached to shared memory at %p\n", shm_ptr);
printf("Reader started. Waiting for data...\n");
while (1) {
semaphore_wait(semid); // 获取锁
// 从共享内存读取数据
strncpy(buffer, shm_ptr, SHM_SIZE - 1);
buffer[SHM_SIZE - 1] = '\0';
semaphore_signal(semid); // 释放锁
if (strcmp(buffer, "END") == 0) {
printf("Reader received end signal.\n");
break;
}
if (strlen(buffer) > 0) {
printf("Reader read: %s\n", buffer);
}
sleep(1); // 等待新数据
}
// 分离共享内存
if (shmdt(shm_ptr) == -1) {
perror("shmdt");
}
// 清理资源
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
printf("Reader finished.\n");
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [writer|reader]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "writer") == 0) {
return shm_writer();
} else if (strcmp(argv[1], "reader") == 0) {
return shm_reader();
} else {
printf("Invalid argument. Use 'writer' or 'reader'\n");
return 1;
}
}
共享内存与进程间同步
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
#define SHM_KEY 0x9999
#define SEM_KEY 0x8888
#define NUM_PROCESSES 3
#define NUM_ITERATIONS 5
struct shared_data {
int counter;
int process_completed[NUM_PROCESSES];
char messages[NUM_PROCESSES][256];
};
int create_semaphore_set() {
int semid = semget(SEM_KEY, 2, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget");
return -1;
}
// 初始化信号量
union semun arg;
unsigned short values[2] = {1, 0}; // sem0: mutex, sem1: barrier
arg.array = values;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("semctl SETALL");
return -1;
}
return semid;
}
void semaphore_op(int semid, int sem_num, int op) {
struct sembuf sb = {sem_num, op, 0};
if (semop(semid, &sb, 1) == -1) {
perror("semop");
}
}
void worker_process(int process_id, int shmid, int semid) {
struct shared_data *shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (struct shared_data *)-1) {
perror("shmat");
exit(1);
}
printf("Process %d started\n", process_id);
for (int i = 0; i < NUM_ITERATIONS; i++) {
// 进入临界区
semaphore_op(semid, 0, -1);
// 修改共享数据
shm_ptr->counter++;
snprintf(shm_ptr->messages[process_id], 256,
"Process %d: iteration %d, counter=%d",
process_id, i, shm_ptr->counter);
printf("Process %d: counter=%d\n", process_id, shm_ptr->counter);
// 离开临界区
semaphore_op(semid, 0, 1);
sleep(1); // 模拟工作
}
// 标记进程完成
semaphore_op(semid, 0, -1);
shm_ptr->process_completed[process_id] = 1;
semaphore_op(semid, 0, 1);
// 检查是否所有进程都完成
int all_done;
do {
semaphore_op(semid, 0, -1);
all_done = 1;
for (int i = 0; i < NUM_PROCESSES; i++) {
if (!shm_ptr->process_completed[i]) {
all_done = 0;
break;
}
}
semaphore_op(semid, 0, 1);
if (!all_done) {
sleep(1);
}
} while (!all_done);
printf("Process %d finished\n", process_id);
shmdt(shm_ptr);
}
int main() {
int shmid, semid;
pid_t pids[NUM_PROCESSES];
// 创建共享内存
shmid = shmget(SHM_KEY, sizeof(struct shared_data), 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 初始化共享内存
struct shared_data *shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (struct shared_data *)-1) {
perror("shmat");
return 1;
}
memset(shm_ptr, 0, sizeof(struct shared_data));
shmdt(shm_ptr);
// 创建信号量集
semid = create_semaphore_set();
if (semid == -1) {
return 1;
}
// 创建子进程
for (int i = 0; i < NUM_PROCESSES; i++) {
pids[i] = fork();
if (pids[i] == 0) {
// 子进程
worker_process(i, shmid, semid);
exit(0);
} else if (pids[i] < 0) {
perror("fork");
return 1;
}
}
// 等待所有子进程完成
for (int i = 0; i < NUM_PROCESSES; i++) {
waitpid(pids[i], NULL, 0);
}
// 读取最终结果
shm_ptr = shmat(shmid, NULL, 0);
printf("\nFinal results:\n");
printf("Final counter value: %d\n", shm_ptr->counter);
for (int i = 0; i < NUM_PROCESSES; i++) {
printf("Message from process %d: %s\n", i, shm_ptr->messages[i]);
}
// 清理资源
shmdt(shm_ptr);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
printf("All processes completed successfully.\n");
return 0;
}
3.2 POSIX共享内存
POSIX共享内存提供了更简单、更符合现代编程习惯的接口。
基本POSIX共享内存操作
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#define SHM_NAME "/posix_shared_mem"
#define SEM_NAME "/posix_shared_sem"
#define SHM_SIZE 4096
struct shared_data {
int counter;
char message[256];
int data_ready;
};
int posix_shm_writer() {
int shm_fd;
struct shared_data *shm_ptr;
sem_t *semaphore;
// 创建共享内存对象
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
perror("shm_open");
return 1;
}
// 设置共享内存大小
if (ftruncate(shm_fd, SHM_SIZE) == -1) {
perror("ftruncate");
return 1;
}
// 内存映射
shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (shm_ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// 创建命名信号量
semaphore = sem_open(SEM_NAME, O_CREAT, 0666, 1);
if (semaphore == SEM_FAILED) {
perror("sem_open");
return 1;
}
printf("POSIX SHM Writer started.\n");
const char *messages[] = {
"Hello POSIX shared memory!",
"This is more efficient than System V",
"POSIX API is simpler to use",
"Goodbye!",
NULL
};
for (int i = 0; messages[i] != NULL; i++) {
sem_wait(semaphore); // 获取锁
// 写入数据
shm_ptr->counter = i + 1;
strncpy(shm_ptr->message, messages[i], 255);
shm_ptr->message[255] = '\0';
shm_ptr->data_ready = 1;
printf("Writer: counter=%d, message=%s\n", shm_ptr->counter, shm_ptr->message);
sem_post(semaphore); // 释放锁
sleep(2);
}
// 标记结束
sem_wait(semaphore);
shm_ptr->data_ready = -1;
sem_post(semaphore);
// 清理
munmap(shm_ptr, SHM_SIZE);
close(shm_fd);
sem_close(semaphore);
printf("Writer finished.\n");
return 0;
}
int posix_shm_reader() {
int shm_fd;
struct shared_data *shm_ptr;
sem_t *semaphore;
// 打开共享内存对象
shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
if (shm_fd == -1) {
perror("shm_open");
return 1;
}
// 内存映射
shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (shm_ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// 打开命名信号量
semaphore = sem_open(SEM_NAME, 0);
if (semaphore == SEM_FAILED) {
perror("sem_open");
return 1;
}
printf("POSIX SHM Reader started.\n");
while (1) {
sem_wait(semaphore);
if (shm_ptr->data_ready == -1) {
sem_post(semaphore);
break;
}
if (shm_ptr->data_ready) {
printf("Reader: counter=%d, message=%s\n", shm_ptr->counter, shm_ptr->message);
shm_ptr->data_ready = 0; // 标记为已读
}
sem_post(semaphore);
sleep(1);
}
// 清理
munmap(shm_ptr, SHM_SIZE);
close(shm_fd);
sem_close(semaphore);
// 删除共享内存和信号量
shm_unlink(SHM_NAME);
sem_unlink(SEM_NAME);
printf("Reader finished.\n");
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [writer|reader]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "writer") == 0) {
return posix_shm_writer();
} else if (strcmp(argv[1], "reader") == 0) {
return posix_shm_reader();
} else {
printf("Invalid argument. Use 'writer' or 'reader'\n");
return 1;
}
}
4. 信号量(Semaphore)
4.1 System V信号量
System V信号量提供了复杂的信号量操作,支持信号量集和原子操作。
生产者-消费者问题
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <string.h>
#include <time.h>
#define BUFFER_SIZE 5
#define NUM_ITEMS 10
// 定义信号量索引
#define MUTEX 0 // 互斥信号量
#define EMPTY 1 // 空槽位信号量
#define FULL 2 // 满槽位信号量
struct buffer {
int items[BUFFER_SIZE];
int in;
int out;
};
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// 初始化信号量
int init_semaphore(int semid, int semnum, int value) {
union semun arg;
arg.val = value;
return semctl(semid, semnum, SETVAL, arg);
}
// P操作(等待)
void sem_wait(int semid, int semnum) {
struct sembuf sb = {semnum, -1, 0};
semop(semid, &sb, 1);
}
// V操作(信号)
void sem_signal(int semid, int semnum) {
struct sembuf sb = {semnum, 1, 0};
semop(semid, &sb, 1);
}
// 生产者进程
void producer(int semid, struct buffer *buf) {
printf("Producer started (PID: %d)\n", getpid());
for (int i = 1; i <= NUM_ITEMS; i++) {
// 生产一个项目
sleep(rand() % 3 + 1); // 模拟生产时间
sem_wait(semid, EMPTY); // 等待空槽位
sem_wait(semid, MUTEX); // 进入临界区
// 将项目放入缓冲区
buf->items[buf->in] = i;
printf("Producer produced: %d at position %d\n", i, buf->in);
buf->in = (buf->in + 1) % BUFFER_SIZE;
sem_signal(semid, MUTEX); // 离开临界区
sem_signal(semid, FULL); // 增加满槽位计数
}
printf("Producer finished\n");
}
// 消费者进程
void consumer(int semid, struct buffer *buf) {
printf("Consumer started (PID: %d)\n", getpid());
for (int i = 0; i < NUM_ITEMS; i++) {
sem_wait(semid, FULL); // 等待满槽位
sem_wait(semid, MUTEX); // 进入临界区
// 从缓冲区取出项目
int item = buf->items[buf->out];
printf("Consumer consumed: %d from position %d\n", item, buf->out);
buf->out = (buf->out + 1) % BUFFER_SIZE;
sem_signal(semid, MUTEX); // 离开临界区
sem_signal(semid, EMPTY); // 增加空槽位计数
// 消费项目
sleep(rand() % 3 + 1); // 模拟消费时间
}
printf("Consumer finished\n");
}
int main() {
srand(time(NULL));
key_t key = ftok("/tmp", 'S');
int shmid, semid;
struct buffer *buf;
// 创建共享内存
shmid = shmget(key, sizeof(struct buffer), 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 附加共享内存
buf = shmat(shmid, NULL, 0);
if (buf == (struct buffer *)-1) {
perror("shmat");
return 1;
}
// 初始化缓冲区
memset(buf, 0, sizeof(struct buffer));
buf->in = 0;
buf->out = 0;
// 创建信号量集
semid = semget(key, 3, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget");
return 1;
}
// 初始化信号量
if (init_semaphore(semid, MUTEX, 1) == -1 ||
init_semaphore(semid, EMPTY, BUFFER_SIZE) == -1 ||
init_semaphore(semid, FULL, 0) == -1) {
perror("semctl");
return 1;
}
// 创建生产者进程
pid_t prod_pid = fork();
if (prod_pid == 0) {
producer(semid, buf);
exit(0);
} else if (prod_pid < 0) {
perror("fork producer");
return 1;
}
// 创建消费者进程
pid_t cons_pid = fork();
if (cons_pid == 0) {
consumer(semid, buf);
exit(0);
} else if (cons_pid < 0) {
perror("fork consumer");
return 1;
}
// 等待子进程完成
waitpid(prod_pid, NULL, 0);
waitpid(cons_pid, NULL, 0);
// 清理资源
shmdt(buf);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
printf("Producer-consumer simulation completed.\n");
return 0;
}
4.2 POSIX信号量
POSIX信号量提供了更简洁的API,分为命名信号量和未命名信号量。
命名信号量示例
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#define NUM_THREADS 5
#define SEM_NAME "/my_named_sem"
// 线程函数
void* worker_thread(void* arg) {
int thread_id = *(int*)arg;
sem_t *semaphore;
// 打开命名信号量
semaphore = sem_open(SEM_NAME, 0);
if (semaphore == SEM_FAILED) {
perror("sem_open");
pthread_exit(NULL);
}
printf("Thread %d: waiting for semaphore...\n", thread_id);
// 获取信号量
if (sem_wait(semaphore) {
perror("sem_wait");
pthread_exit(NULL);
}
printf("Thread %d: acquired semaphore. Working...\n", thread_id);
sleep(2); // 模拟工作
printf("Thread %d: work completed. Releasing semaphore.\n", thread_id);
// 释放信号量
if (sem_post(semaphore)) {
perror("sem_post");
}
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
sem_t *semaphore;
// 创建命名信号量
semaphore = sem_open(SEM_NAME, O_CREAT, 0666, 3); // 允许3个线程同时访问
if (semaphore == SEM_FAILED) {
perror("sem_open");
return 1;
}
printf("Created named semaphore with value 3\n");
// 创建线程
for (int i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i + 1;
if (pthread_create(&threads[i], NULL, worker_thread, &thread_ids[i])) {
perror("pthread_create");
return 1;
}
}
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
// 关闭和删除信号量
sem_close(semaphore);
sem_unlink(SEM_NAME);
printf("All threads completed.\n");
return 0;
}
未命名信号量示例
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/mman.h>
#define NUM_THREADS 4
#define NUM_ITERATIONS 3
struct shared_data {
sem_t mutex;
sem_t barrier;
int counter;
int waiting;
};
void* worker(void* arg) {
struct shared_data *data = (struct shared_data*)arg;
for (int i = 0; i < NUM_ITERATIONS; i++) {
// 使用互斥锁保护共享数据
sem_wait(&data->mutex);
data->counter++;
printf("Thread %lu: counter = %d (iteration %d)\n",
pthread_self(), data->counter, i + 1);
sem_post(&data->mutex);
// 屏障同步
sem_wait(&data->mutex);
data->waiting++;
if (data->waiting == NUM_THREADS) {
// 最后一个到达的线程释放屏障
for (int j = 0; j < NUM_THREADS - 1; j++) {
sem_post(&data->barrier);
}
data->waiting = 0;
printf("--- Barrier released ---\n");
} else {
sem_post(&data->mutex);
sem_wait(&data->barrier);
}
sem_post(&data->mutex);
sleep(1); // 模拟工作
}
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
struct shared_data *data;
// 使用共享内存分配数据(用于进程间共享)
data = mmap(NULL, sizeof(struct shared_data),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (data == MAP_FAILED) {
perror("mmap");
return 1;
}
// 初始化未命名信号量(进程间共享)
if (sem_init(&data->mutex, 1, 1) == -1) {
perror("sem_init mutex");
return 1;
}
if (sem_init(&data->barrier, 1, 0) == -1) {
perror("sem_init barrier");
return 1;
}
data->counter = 0;
data->waiting = 0;
printf("Starting %d threads with barrier synchronization...\n", NUM_THREADS);
// 创建线程
for (int i = 0; i < NUM_THREADS; i++) {
if (pthread_create(&threads[i], NULL, worker, data)) {
perror("pthread_create");
return 1;
}
}
// 等待线程完成
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Final counter value: %d\n", data->counter);
// 清理
sem_destroy(&data->mutex);
sem_destroy(&data->barrier);
munmap(data, sizeof(struct shared_data));
return 0;
}
5. 信号(Signal)
信号是Linux系统中用于进程间异步通信的基本机制。
信号处理示例
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
volatile sig_atomic_t signal_received = 0;
volatile sig_atomic_t usr1_count = 0;
volatile sig_atomic_t usr2_count = 0;
// SIGINT处理函数
void sigint_handler(int sig) {
signal_received = sig;
printf("\nReceived SIGINT (Ctrl+C). Cleaning up...\n");
}
// SIGUSR1处理函数
void sigusr1_handler(int sig) {
usr1_count++;
printf("Received SIGUSR1 (%d times)\n", usr1_count);
}
// SIGUSR2处理函数
void sigusr2_handler(int sig) {
usr2_count++;
printf("Received SIGUSR2 (%d times)\n", usr2_count);
}
// SIGALRM处理函数
void sigalrm_handler(int sig) {
printf("Alarm! Timeout occurred.\n");
}
// 信号集操作演示
void demonstrate_signal_sets() {
sigset_t set, oldset;
// 初始化信号集
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
// 阻塞信号
if (sigprocmask(SIG_BLOCK, &set, &oldset) == -1) {
perror("sigprocmask");
return;
}
printf("SIGINT and SIGQUIT are now blocked for 5 seconds...\n");
printf("Try pressing Ctrl+C or Ctrl+\\ - they won't work.\n");
sleep(5);
// 恢复原来的信号掩码
if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
perror("sigprocmask");
}
printf("Signals unblocked. Now they will work.\n");
}
// 发送信号给其他进程
void signal_sender() {
printf("Signal sender started (PID: %d)\n", getpid());
pid_t my_pid = getpid();
// 给自己发送信号
for (int i = 0; i < 3; i++) {
sleep(2);
if (kill(my_pid, SIGUSR1) == -1) {
perror("kill");
}
}
for (int i = 0; i < 2; i++) {
sleep(2);
if (kill(my_pid, SIGUSR2) == -1) {
perror("kill");
}
}
}
// 实时信号处理
void realtime_signal_demo() {
struct sigaction sa;
// 设置实时信号处理
sa.sa_sigaction = (void (*)(int, siginfo_t *, void *))sigusr1_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO; // 使用sa_sigaction而不是sa_handler
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
perror("sigaction");
return;
}
printf("Real-time signal handler installed for SIGRTMIN\n");
}
int main() {
struct sigaction sa_int, sa_usr1, sa_usr2, sa_alrm;
printf("Signal demonstration program (PID: %d)\n", getpid());
// 设置SIGINT处理
sa_int.sa_handler = sigint_handler;
sigemptyset(&sa_int.sa_mask);
sa_int.sa_flags = 0;
if (sigaction(SIGINT, &sa_int, NULL) == -1) {
perror("sigaction SIGINT");
return 1;
}
// 设置SIGUSR1处理
sa_usr1.sa_handler = sigusr1_handler;
sigemptyset(&sa_usr1.sa_mask);
sa_usr1.sa_flags = 0;
if (sigaction(SIGUSR1, &sa_usr1, NULL) == -1) {
perror("sigaction SIGUSR1");
return 1;
}
// 设置SIGUSR2处理
sa_usr2.sa_handler = sigusr2_handler;
sigemptyset(&sa_usr2.sa_mask);
sa_usr2.sa_flags = 0;
if (sigaction(SIGUSR2, &sa_usr2, NULL) == -1) {
perror("sigaction SIGUSR2");
return 1;
}
// 设置SIGALRM处理
sa_alrm.sa_handler = sigalrm_handler;
sigemptyset(&sa_alrm.sa_mask);
sa_alrm.sa_flags = 0;
if (sigaction(SIGALRM, &sa_alrm, NULL) == -1) {
perror("sigaction SIGALRM");
return 1;
}
// 演示信号集
demonstrate_signal_sets();
// 设置定时器
printf("Setting alarm for 10 seconds...\n");
alarm(10);
// 主循环
printf("Entering main loop. Send signals or press Ctrl+C to exit.\n");
printf("You can send signals from another terminal: kill -USR1 %d\n", getpid());
while (!signal_received) {
printf("Main loop running... (SIGUSR1: %d, SIGUSR2: %d)\n",
usr1_count, usr2_count);
sleep(3);
}
printf("Program exiting gracefully.\n");
return 0;
}
6. 套接字(Socket)
6.1 Unix域套接字
Unix域套接字提供了同一台主机上进程间通信的高效方式。
流式套接字示例
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define SOCKET_PATH "/tmp/unix_socket_example"
#define BUFFER_SIZE 1024
void unix_socket_server() {
int server_fd, client_fd;
struct sockaddr_un server_addr, client_addr;
socklen_t client_len;
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
// 创建Unix域套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 删除可能已存在的socket文件
unlink(SOCKET_PATH);
// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_fd);
exit(1);
}
// 开始监听
if (listen(server_fd, 5) == -1) {
perror("listen");
close(server_fd);
exit(1);
}
printf("Unix socket server listening on %s\n", SOCKET_PATH);
while (1) {
// 接受客户端连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
printf("Client connected\n");
// 处理客户端请求
while ((bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1)) > 0) {
buffer[bytes_read] = '\0';
printf("Received: %s", buffer);
// 回显消息
if (write(client_fd, buffer, bytes_read) == -1) {
perror("write");
break;
}
// 检查退出条件
if (strncmp(buffer, "exit", 4) == 0) {
break;
}
}
if (bytes_read == -1) {
perror("read");
}
printf("Client disconnected\n");
close(client_fd);
}
close(server_fd);
unlink(SOCKET_PATH);
}
void unix_socket_client() {
int sockfd;
struct sockaddr_un server_addr;
char buffer[BUFFER_SIZE];
ssize_t bytes_read, bytes_written;
// 创建Unix域套接字
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 连接服务器
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
close(sockfd);
exit(1);
}
printf("Connected to server. Type messages (type 'exit' to quit):\n");
while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
// 发送消息
bytes_written = write(sockfd, buffer, strlen(buffer));
if (bytes_written == -1) {
perror("write");
break;
}
// 接收回显
bytes_read = read(sockfd, buffer, BUFFER_SIZE - 1);
if (bytes_read == -1) {
perror("read");
break;
}
buffer[bytes_read] = '\0';
printf("Echo: %s", buffer);
if (strncmp(buffer, "exit", 4) == 0) {
break;
}
}
close(sockfd);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [server|client]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "server") == 0) {
unix_socket_server();
} else if (strcmp(argv[1], "client") == 0) {
unix_socket_client();
} else {
printf("Invalid argument. Use 'server' or 'client'\n");
return 1;
}
return 0;
}
数据报套接字示例
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define DGRAM_SOCKET_PATH "/tmp/unix_dgram_socket"
#define CLIENT_SOCKET_PATH "/tmp/unix_dgram_client"
#define BUFFER_SIZE 1024
void unix_dgram_server() {
int sockfd;
struct sockaddr_un server_addr, client_addr;
socklen_t client_len;
char buffer[BUFFER_SIZE];
ssize_t bytes_received;
// 创建数据报套接字
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, DGRAM_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 删除可能已存在的socket文件
unlink(DGRAM_SOCKET_PATH);
// 绑定套接字
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(sockfd);
exit(1);
}
printf("Unix datagram server listening on %s\n", DGRAM_SOCKET_PATH);
while (1) {
// 接收数据
client_len = sizeof(client_addr);
bytes_received = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received == -1) {
perror("recvfrom");
continue;
}
buffer[bytes_received] = '\0';
printf("Received from %s: %s",
client_addr.sun_path[0] ? client_addr.sun_path : "unknown",
buffer);
// 发送响应
if (sendto(sockfd, buffer, bytes_received, 0,
(struct sockaddr*)&client_addr, client_len) == -1) {
perror("sendto");
}
if (strncmp(buffer, "exit", 4) == 0) {
break;
}
}
close(sockfd);
unlink(DGRAM_SOCKET_PATH);
}
void unix_dgram_client() {
int sockfd;
struct sockaddr_un server_addr, client_addr;
char buffer[BUFFER_SIZE];
ssize_t bytes_sent, bytes_received;
// 创建数据报套接字
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
// 设置客户端地址(可选,用于接收响应)
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sun_family = AF_UNIX;
strncpy(client_addr.sun_path, CLIENT_SOCKET_PATH, sizeof(client_addr.sun_path) - 1);
unlink(CLIENT_SOCKET_PATH);
if (bind(sockfd, (struct sockaddr*)&client_addr, sizeof(client_addr)) == -1) {
perror("bind");
close(sockfd);
exit(1);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, DGRAM_SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
printf("Datagram client started. Type messages (type 'exit' to quit):\n");
while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
// 发送消息
bytes_sent = sendto(sockfd, buffer, strlen(buffer), 0,
(struct sockaddr*)&server_addr, sizeof(server_addr));
if (bytes_sent == -1) {
perror("sendto");
break;
}
// 接收响应
bytes_received = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0, NULL, NULL);
if (bytes_received == -1) {
perror("recvfrom");
break;
}
buffer[bytes_received] = '\0';
printf("Response: %s", buffer);
if (strncmp(buffer, "exit", 4) == 0) {
break;
}
}
close(sockfd);
unlink(CLIENT_SOCKET_PATH);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [server|client]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "server") == 0) {
unix_dgram_server();
} else if (strcmp(argv[1], "client") == 0) {
unix_dgram_client();
} else {
printf("Invalid argument. Use 'server' or 'client'\n");
return 1;
}
return 0;
}
7. 内存映射(Memory Mapping)
内存映射是一种高效的IPC机制,它允许不同进程通过映射同一文件到各自的地址空间来共享数据。
基本内存映射示例
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#define SHARED_FILE "/tmp/shared_memory_file"
#define FILE_SIZE 4096
struct shared_data {
int counter;
char message[256];
int data_ready;
};
void mmap_writer() {
int fd;
struct shared_data *shared_mem;
// 创建共享文件
fd = open(SHARED_FILE, O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("open");
exit(1);
}
// 设置文件大小
if (ftruncate(fd, FILE_SIZE) == -1) {
perror("ftruncate");
close(fd);
exit(1);
}
// 内存映射
shared_mem = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shared_mem == MAP_FAILED) {
perror("mmap");
close(fd);
exit(1);
}
close(fd); // 映射后可以关闭文件描述符
// 初始化共享数据
shared_mem->counter = 0;
shared_mem->data_ready = 0;
printf("MMAP Writer started. Writing data...\n");
const char *messages[] = {
"Hello from mmap writer!",
"This is shared through memory mapping",
"Memory mapping is efficient for large data",
"Goodbye!",
NULL
};
for (int i = 0; messages[i] != NULL; i++) {
shared_mem->counter = i + 1;
strncpy(shared_mem->message, messages[i], 255);
shared_mem->message[255] = '\0';
shared_mem->data_ready = 1;
printf("Writer: counter=%d, message=%s\n",
shared_mem->counter, shared_mem->message);
// 等待读者读取
while (shared_mem->data_ready == 1) {
usleep(100000); // 100ms
}
sleep(1);
}
// 标记结束
shared_mem->data_ready = -1;
// 取消映射
if (munmap(shared_mem, FILE_SIZE) == -1) {
perror("munmap");
}
// 删除共享文件
unlink(SHARED_FILE);
printf("Writer finished.\n");
}
void mmap_reader() {
int fd;
struct shared_data *shared_mem;
// 打开共享文件
fd = open(SHARED_FILE, O_RDWR);
if (fd == -1) {
perror("open");
exit(1);
}
// 内存映射
shared_mem = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shared_mem == MAP_FAILED) {
perror("mmap");
close(fd);
exit(1);
}
close(fd);
printf("MMAP Reader started. Waiting for data...\n");
while (1) {
if (shared_mem->data_ready == -1) {
break;
}
if (shared_mem->data_ready == 1) {
printf("Reader: counter=%d, message=%s\n",
shared_mem->counter, shared_mem->message);
shared_mem->data_ready = 0; // 标记为已读
}
usleep(500000); // 500ms
}
// 取消映射
if (munmap(shared_mem, FILE_SIZE) == -1) {
perror("munmap");
}
printf("Reader finished.\n");
}
// 使用匿名内存映射的进程间通信
void anonymous_mmap_example() {
int *shared_var;
pid_t pid;
// 创建匿名内存映射
shared_var = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (shared_var == MAP_FAILED) {
perror("mmap");
exit(1);
}
*shared_var = 0;
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// 子进程
for (int i = 0; i < 5; i++) {
(*shared_var)++;
printf("Child: shared_var = %d\n", *shared_var);
sleep(1);
}
exit(0);
} else {
// 父进程
for (int i = 0; i < 5; i++) {
(*shared_var) += 10;
printf("Parent: shared_var = %d\n", *shared_var);
sleep(1);
}
wait(NULL);
}
printf("Final value: %d\n", *shared_var);
// 取消映射
if (munmap(shared_var, sizeof(int)) == -1) {
perror("munmap");
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [writer|reader|anonymous]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "writer") == 0) {
mmap_writer();
} else if (strcmp(argv[1], "reader") == 0) {
mmap_reader();
} else if (strcmp(argv[1], "anonymous") == 0) {
anonymous_mmap_example();
} else {
printf("Invalid argument. Use 'writer', 'reader', or 'anonymous'\n");
return 1;
}
return 0;
}
高级内存映射特性
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#define LARGE_FILE "large_file.bin"
#define FILE_SIZE (100 * 1024 * 1024) // 100MB
// 创建大文件并演示部分映射
void large_file_mmap() {
int fd;
char *mapped_addr;
struct stat sb;
printf("Creating large file: %s (%d MB)\n", LARGE_FILE, FILE_SIZE / (1024 * 1024));
// 创建文件
fd = open(LARGE_FILE, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
perror("open");
return;
}
// 设置文件大小
if (ftruncate(fd, FILE_SIZE) == -1) {
perror("ftruncate");
close(fd);
return;
}
// 映射整个文件
mapped_addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped_addr == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
printf("File mapped at address: %p\n", mapped_addr);
// 写入数据
printf("Writing data to mapped memory...\n");
for (off_t i = 0; i < FILE_SIZE; i += 4096) {
mapped_addr[i] = (char)(i % 256);
}
// 同步到磁盘
if (msync(mapped_addr, FILE_SIZE, MS_SYNC) == -1) {
perror("msync");
}
printf("Data synchronized to disk.\n");
// 取消映射
if (munmap(mapped_addr, FILE_SIZE) == -1) {
perror("munmap");
}
close(fd);
}
// 演示部分文件映射
void partial_mmap() {
int fd;
char *mapped_addr;
const size_t MAP_SIZE = 8192; // 8KB
const off_t MAP_OFFSET = 4096; // 从4KB处开始映射
fd = open(LARGE_FILE, O_RDWR);
if (fd == -1) {
perror("open");
return;
}
// 映射文件的特定部分
mapped_addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, MAP_OFFSET);
if (mapped_addr == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
printf("Partial mapping: address=%p, offset=%ld, size=%zu\n",
mapped_addr, MAP_OFFSET, MAP_SIZE);
// 读取和修改映射的数据
printf("First 100 bytes of mapped region:\n");
for (int i = 0; i < 100; i++) {
printf("%02x ", (unsigned char)mapped_addr[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
// 修改数据
strncpy(mapped_addr, "PARTIAL_MMAP_TEST", 17);
// 同步修改
if (msync(mapped_addr, 17, MS_SYNC) == -1) {
perror("msync");
}
// 取消映射
if (munmap(mapped_addr, MAP_SIZE) == -1) {
perror("munmap");
}
close(fd);
}
// 使用madvise优化内存映射性能
void madvise_example() {
int fd;
char *mapped_addr;
const size_t MAP_SIZE = 1024 * 1024; // 1MB
fd = open(LARGE_FILE, O_RDONLY);
if (fd == -1) {
perror("open");
return;
}
mapped_addr = mmap(NULL, MAP_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (mapped_addr == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
// 提供访问模式建议给内核
if (madvise(mapped_addr, MAP_SIZE, MADV_SEQUENTIAL) == -1) {
perror("madvise");
}
printf("Using madvise for sequential access pattern\n");
// 顺序读取数据
for (size_t i = 0; i < MAP_SIZE; i += 4096) {
volatile char c = mapped_addr[i]; // 强制读取
(void)c; // 避免未使用变量警告
}
// 取消映射
if (munmap(mapped_addr, MAP_SIZE) == -1) {
perror("munmap");
}
close(fd);
}
int main() {
printf("=== Large File Memory Mapping ===\n");
large_file_mmap();
printf("\n=== Partial File Mapping ===\n");
partial_mmap();
printf("\n=== MADVISE Optimization ===\n");
madvise_example();
// 清理
unlink(LARGE_FILE);
return 0;
}
8. 各种IPC方式对比
IPC机制性能比较
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#define TEST_DATA_SIZE 1024
#define NUM_ITERATIONS 10000
// 时间测量函数
double get_time() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
// 管道性能测试
void test_pipe_performance() {
int pipefd[2];
pid_t pid;
char buffer[TEST_DATA_SIZE];
double start_time, end_time;
if (pipe(pipefd) == -1) {
perror("pipe");
return;
}
pid = fork();
if (pid == -1) {
perror("fork");
return;
}
if (pid == 0) {
// 子进程 - 读取端
close(pipefd[1]);
for (int i = 0; i < NUM_ITERATIONS; i++) {
read(pipefd[0], buffer, TEST_DATA_SIZE);
}
close(pipefd[0]);
exit(0);
} else {
// 父进程 - 写入端
close(pipefd[0]);
memset(buffer, 'A', TEST_DATA_SIZE);
start_time = get_time();
for (int i = 0; i < NUM_ITERATIONS; i++) {
write(pipefd[1], buffer, TEST_DATA_SIZE);
}
end_time = get_time();
close(pipefd[1]);
wait(NULL);
printf("Pipe: %.6f seconds (%.0f operations/sec)\n",
end_time - start_time, NUM_ITERATIONS / (end_time - start_time));
}
}
// System V消息队列性能测试
void test_sysv_msgq_performance() {
key_t key = ftok("/tmp", 'P');
int msgid;
pid_t pid;
struct {
long mtype;
char mtext[TEST_DATA_SIZE];
} message;
msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
return;
}
pid = fork();
if (pid == -1) {
perror("fork");
return;
}
if (pid == 0) {
// 子进程 - 接收端
for (int i = 0; i < NUM_ITERATIONS; i++) {
msgrcv(msgid, &message, TEST_DATA_SIZE, 1, 0);
}
exit(0);
} else {
// 父进程 - 发送端
message.mtype = 1;
memset(message.mtext, 'B', TEST_DATA_SIZE);
double start_time = get_time();
for (int i = 0; i < NUM_ITERATIONS; i++) {
msgsnd(msgid, &message, TEST_DATA_SIZE, 0);
}
double end_time = get_time();
wait(NULL);
// 清理
msgctl(msgid, IPC_RMID, NULL);
printf("System V Message Queue: %.6f seconds (%.0f operations/sec)\n",
end_time - start_time, NUM_ITERATIONS / (end_time - start_time));
}
}
// 共享内存性能测试
void test_shm_performance() {
key_t key = ftok("/tmp", 'S');
int shmid;
pid_t pid;
char *shm_ptr;
struct sembuf sb;
int semid;
// 创建共享内存
shmid = shmget(key, TEST_DATA_SIZE, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
return;
}
// 创建信号量用于同步
semid = semget(key, 1, 0666 | IPC_CREAT);
if (semid == -1) {
perror("semget");
return;
}
// 初始化信号量
semctl(semid, 0, SETVAL, 1);
shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (char *)-1) {
perror("shmat");
return;
}
pid = fork();
if (pid == -1) {
perror("fork");
return;
}
if (pid == 0) {
// 子进程 - 读取端
for (int i = 0; i < NUM_ITERATIONS; i++) {
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = 0;
semop(semid, &sb, 1);
// 读取数据
volatile char c = shm_ptr[0];
(void)c;
sb.sem_op = 1;
semop(semid, &sb, 1);
}
exit(0);
} else {
// 父进程 - 写入端
memset(shm_ptr, 'C', TEST_DATA_SIZE);
double start_time = get_time();
for (int i = 0; i < NUM_ITERATIONS; i++) {
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = 0;
semop(semid, &sb, 1);
// 写入数据
shm_ptr[0] = 'D';
sb.sem_op = 1;
semop(semid, &sb, 1);
}
double end_time = get_time();
wait(NULL);
// 清理
shmdt(shm_ptr);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
printf("Shared Memory: %.6f seconds (%.0f operations/sec)\n",
end_time - start_time, NUM_ITERATIONS / (end_time - start_time));
}
}
// Unix域套接字性能测试
void test_unix_socket_performance() {
int sockfd[2];
pid_t pid;
char buffer[TEST_DATA_SIZE];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) == -1) {
perror("socketpair");
return;
}
pid = fork();
if (pid == -1) {
perror("fork");
return;
}
if (pid == 0) {
// 子进程
close(sockfd[0]);
for (int i = 0; i < NUM_ITERATIONS; i++) {
read(sockfd[1], buffer, TEST_DATA_SIZE);
write(sockfd[1], buffer, TEST_DATA_SIZE);
}
close(sockfd[1]);
exit(0);
} else {
// 父进程
close(sockfd[1]);
memset(buffer, 'E', TEST_DATA_SIZE);
double start_time = get_time();
for (int i = 0; i < NUM_ITERATIONS; i++) {
write(sockfd[0], buffer, TEST_DATA_SIZE);
read(sockfd[0], buffer, TEST_DATA_SIZE);
}
double end_time = get_time();
close(sockfd[0]);
wait(NULL);
printf("Unix Domain Socket: %.6f seconds (%.0f operations/sec)\n",
end_time - start_time, NUM_ITERATIONS / (end_time - start_time));
}
}
int main() {
printf("IPC Mechanism Performance Comparison\n");
printf("Data size: %d bytes, Iterations: %d\n\n", TEST_DATA_SIZE, NUM_ITERATIONS);
test_pipe_performance();
test_sysv_msgq_performance();
test_shm_performance();
test_unix_socket_performance();
return 0;
}
IPC特性对比表
| 特性 | 管道 | 消息队列 | 共享内存 | 信号量 | 信号 | 套接字 |
|---|---|---|---|---|---|---|
| 通信方向 | 单向 | 双向 | 双向 | 同步 | 异步 | 双向 |
| 通信模式 | 流/消息 | 消息 | 字节流 | 同步原语 | 事件 | 流/消息 |
| 速度 | 中等 | 中等 | 快 | 快 | 快 | 中等 |
| 容量 | 有限 | 有限 | 大 | N/A | 小 | 大 |
| 持久性 | 进程 | 内核 | 进程 | 内核 | 进程 | 进程 |
| 访问控制 | 无 | 有 | 有 | 有 | 无 | 有 |
| 复杂性 | 低 | 中等 | 高 | 中等 | 低 | 中等 |
9. 如何选择合适的IPC方式
选择合适的IPC机制需要考虑多个因素:
选择指南
c
#include <stdio.h>
#include <string.h>
// IPC选择决策函数
void recommend_ipc(const char *scenario, int need_high_throughput,
int need_low_latency, int need_synchronization,
int need_persistence, int need_broadcast,
int processes_related, int data_size_large) {
printf("Scenario: %s\n", scenario);
printf("Requirements: ");
if (need_high_throughput) printf("High-Throughput ");
if (need_low_latency) printf("Low-Latency ");
if (need_synchronization) printf("Synchronization ");
if (need_persistence) printf("Persistence ");
if (need_broadcast) printf("Broadcast ");
if (processes_related) printf("Related-Processes ");
if (data_size_large) printf("Large-Data ");
printf("\n");
printf("Recommended IPC: ");
if (need_high_throughput && data_size_large) {
printf("Shared Memory (with proper synchronization)\n");
} else if (need_low_latency && processes_related) {
printf("Unix Domain Sockets or Pipes\n");
} else if (need_synchronization) {
printf("Semaphores or Mutexes with Shared Memory\n");
} else if (need_persistence) {
printf("Message Queues or Memory Mapped Files\n");
} else if (need_broadcast) {
printf("Signals or Message Queues with multiple readers\n");
} else if (processes_related) {
printf("Pipes or Unix Domain Sockets\n");
} else {
printf("Message Queues or Network Sockets\n");
}
printf("\n");
}
int main() {
printf("IPC Mechanism Selection Guide\n\n");
recommend_ipc("High-performance data processing", 1, 1, 1, 0, 0, 1, 1);
recommend_ipc("Inter-process synchronization", 0, 1, 1, 0, 0, 1, 0);
recommend_ipc("Persistent message storage", 0, 0, 0, 1, 0, 0, 0);
recommend_ipc("Broadcast notifications", 0, 1, 0, 0, 1, 0, 0);
recommend_ipc("Simple parent-child communication", 0, 0, 0, 0, 0, 1, 0);
recommend_ipc("Unrelated process communication", 0, 0, 0, 0, 0, 0, 0);
return 0;
}
10. 实战建议
最佳实践示例
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <signal.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define SHM_SIZE 4096
// 错误处理宏
#define CHECK(expr, msg) \
do { \
if ((expr) == -1) { \
perror(msg); \
exit(1); \
} \
} while(0)
// 资源清理函数
void cleanup_resources(int shmid, int semid) {
if (shmid != -1) {
shmctl(shmid, IPC_RMID, NULL);
}
if (semid != -1) {
semctl(semid, 0, IPC_RMID);
}
}
// 信号处理用于优雅退出
volatile sig_atomic_t graceful_shutdown = 0;
void shutdown_handler(int sig) {
graceful_shutdown = 1;
printf("\nShutdown signal received. Cleaning up...\n");
}
// 安全的共享内存访问
struct safe_shm {
int shmid;
int semid;
char *data;
};
int init_safe_shm(struct safe_shm *shm, size_t size) {
// 创建共享内存
shm->shmid = shmget(SHM_KEY, size, 0666 | IPC_CREAT | IPC_EXCL);
if (shm->shmid == -1) {
if (errno == EEXIST) {
// 已经存在,直接获取
shm->shmid = shmget(SHM_KEY, size, 0666);
CHECK(shm->shmid, "shmget existing");
} else {
perror("shmget new");
return -1;
}
}
// 附加共享内存
shm->data = shmat(shm->shmid, NULL, 0);
CHECK(shm->data == (char*)-1 ? -1 : 0, "shmat");
// 创建信号量
shm->semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
CHECK(shm->semid, "semget");
// 初始化信号量
union semun arg;
arg.val = 1;
CHECK(semctl(shm->semid, 0, SETVAL, arg), "semctl SETVAL");
return 0;
}
void lock_shm(struct safe_shm *shm) {
struct sembuf sb = {0, -1, 0};
CHECK(semop(shm->semid, &sb, 1), "semop lock");
}
void unlock_shm(struct safe_shm *shm) {
struct sembuf sb = {0, 1, 0};
CHECK(semop(shm->semid, &sb, 1), "semop unlock");
}
void destroy_safe_shm(struct safe_shm *shm) {
if (shm->data != NULL && shm->data != (char*)-1) {
shmdt(shm->data);
}
cleanup_resources(shm->shmid, shm->semid);
}
// 生产者-消费者模式实现
struct ring_buffer {
int head;
int tail;
int count;
int capacity;
char data[0]; // 柔性数组
};
void producer_worker(struct safe_shm *shm) {
struct ring_buffer *rb = (struct ring_buffer*)shm->data;
int message_id = 0;
while (!graceful_shutdown) {
lock_shm(shm);
// 检查缓冲区是否满
if (rb->count < rb->capacity) {
// 生产数据
int len = snprintf(rb->data + rb->head * 256, 256,
"Message %d from producer", message_id++);
rb->head = (rb->head + 1) % rb->capacity;
rb->count++;
printf("Produced: %s (count=%d)\n", rb->data + (rb->head * 256) % (rb->capacity * 256), rb->count);
}
unlock_shm(shm);
usleep(100000); // 100ms
}
}
void consumer_worker(struct safe_shm *shm) {
struct ring_buffer *rb = (struct ring_buffer*)shm->data;
while (!graceful_shutdown) {
lock_shm(shm);
// 检查缓冲区是否有数据
if (rb->count > 0) {
// 消费数据
printf("Consumed: %s (count=%d)\n", rb->data + rb->tail * 256, rb->count);
rb->tail = (rb->tail + 1) % rb->capacity;
rb->count--;
}
unlock_shm(shm);
usleep(150000); // 150ms
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [producer|consumer]\n", argv[0]);
return 1;
}
// 设置信号处理
struct sigaction sa;
sa.sa_handler = shutdown_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
struct safe_shm shm = {-1, -1, NULL};
size_t shm_size = sizeof(struct ring_buffer) + 10 * 256; // 10个256字节的消息
if (init_safe_shm(&shm, shm_size) == -1) {
return 1;
}
// 初始化环形缓冲区
struct ring_buffer *rb = (struct ring_buffer*)shm.data;
if (shm.shmid != -1) { // 只有创建者初始化
rb->head = 0;
rb->tail = 0;
rb->count = 0;
rb->capacity = 10;
}
printf("%s started (PID: %d)\n", argv[1], getpid());
printf("Press Ctrl+C to exit gracefully\n");
if (strcmp(argv[1], "producer") == 0) {
producer_worker(&shm);
} else if (strcmp(argv[1], "consumer") == 0) {
consumer_worker(&shm);
} else {
printf("Invalid role. Use 'producer' or 'consumer'\n");
destroy_safe_shm(&shm);
return 1;
}
printf("Clean shutdown completed.\n");
destroy_safe_shm(&shm);
return 0;
}
11. 常见问题与解决方案
11.1 管道阻塞问题
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL");
return -1;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
return -1;
}
return 0;
}
// 处理SIGPIPE信号,避免进程终止
void handle_sigpipe(int sig) {
printf("SIGPIPE received: Broken pipe. Continuing...\n");
}
// 安全的管道写入函数
ssize_t safe_pipe_write(int fd, const void *buf, size_t count) {
ssize_t bytes_written;
size_t total_written = 0;
const char *ptr = buf;
while (total_written < count) {
bytes_written = write(fd, ptr + total_written, count - total_written);
if (bytes_written == -1) {
if (errno == EINTR) {
continue; // 被信号中断,重试
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 管道缓冲区满,等待
usleep(10000); // 10ms
continue;
} else {
perror("write");
return -1;
}
}
total_written += bytes_written;
}
return total_written;
}
// 演示管道阻塞问题的解决方案
void pipe_blocking_demo() {
int pipefd[2];
pid_t pid;
char buffer[1024];
// 设置SIGPIPE处理
signal(SIGPIPE, handle_sigpipe);
if (pipe(pipefd) == -1) {
perror("pipe");
return;
}
// 设置写端为非阻塞
if (set_nonblocking(pipefd[1]) == -1) {
close(pipefd[0]);
close(pipefd[1]);
return;
}
pid = fork();
if (pid == -1) {
perror("fork");
close(pipefd[0]);
close(pipefd[1]);
return;
}
if (pid == 0) {
// 子进程 - 读取端
close(pipefd[1]);
// 缓慢读取,制造缓冲区满的情况
while (1) {
ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer));
if (bytes_read == -1) {
if (errno == EAGAIN) {
usleep(100000); // 100ms
continue;
}
perror("read");
break;
} else if (bytes_read == 0) {
printf("Child: pipe closed\n");
break;
} else {
printf("Child: read %zd bytes\n", bytes_read);
sleep(1); // 故意慢读
}
}
close(pipefd[0]);
exit(0);
} else {
// 父进程 - 写入端
close(pipefd[0]);
memset(buffer, 'X', sizeof(buffer));
for (int i = 0; i < 10; i++) {
ssize_t bytes_written = safe_pipe_write(pipefd[1], buffer, sizeof(buffer));
if (bytes_written == -1) {
printf("Parent: write failed\n");
break;
} else {
printf("Parent: wrote %zd bytes (iteration %d)\n", bytes_written, i + 1);
}
}
close(pipefd[1]);
wait(NULL);
}
}
int main() {
printf("Pipe Blocking Problem Demonstration\n");
pipe_blocking_demo();
return 0;
}
11.2 消息队列持久化
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#define MSG_KEY 0x7777
#define MAX_MSG_SIZE 1024
#define PERSISTENT_FILE "/tmp/msgq_persistent"
struct persistent_message {
long mtype;
char mtext[MAX_MSG_SIZE];
time_t timestamp;
int sequence;
};
// 持久化消息队列状态
void persist_msgq_state(int msgid) {
FILE *fp = fopen(PERSISTENT_FILE, "w");
if (fp == NULL) {
perror("fopen");
return;
}
struct msqid_ds buf;
if (msgctl(msgid, IPC_STAT, &buf) == -1) {
perror("msgctl");
fclose(fp);
return;
}
fprintf(fp, "msgid: %d\n", msgid);
fprintf(fp, "messages: %ld\n", buf.msg_qnum);
fprintf(fp, "last_send: %ld\n", buf.msg_stime);
fprintf(fp, "last_receive: %ld\n", buf.msg_rtime);
fprintf(fp, "last_change: %ld\n", buf.msg_ctime);
fclose(fp);
printf("Message queue state persisted to %s\n", PERSISTENT_FILE);
}
// 恢复消息队列状态
void restore_msgq_state() {
FILE *fp = fopen(PERSISTENT_FILE, "r");
if (fp == NULL) {
if (errno != ENOENT) {
perror("fopen");
}
return;
}
char line[256];
while (fgets(line, sizeof(line), fp)) {
printf("Persisted: %s", line);
}
fclose(fp);
}
// 优雅关闭处理
volatile sig_atomic_t shutdown_requested = 0;
void graceful_shutdown(int sig) {
shutdown_requested = 1;
printf("\nShutdown requested. Finishing current work...\n");
}
// 持久化消息队列服务器
void persistent_msgq_server() {
int msgid;
struct persistent_message msg;
int sequence = 0;
// 设置信号处理
signal(SIGINT, graceful_shutdown);
signal(SIGTERM, graceful_shutdown);
// 恢复之前的状态
restore_msgq_state();
// 创建消息队列
msgid = msgget(MSG_KEY, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
return;
}
printf("Persistent message queue server started (msgid: %d)\n", msgid);
while (!shutdown_requested) {
ssize_t bytes = msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 0, IPC_NOWAIT);
if (bytes == -1) {
if (errno == ENOMSG) {
// 没有消息,继续等待
sleep(1);
continue;
} else {
perror("msgrcv");
break;
}
}
printf("Received: type=%ld, seq=%d, time=%ld, message=%s\n",
msg.mtype, msg.sequence, msg.timestamp, msg.mtext);
sequence++;
// 定期持久化状态
if (sequence % 5 == 0) {
persist_msgq_state(msgid);
}
}
// 最终持久化
persist_msgq_state(msgid);
printf("Server shutdown complete.\n");
}
// 持久化消息队列客户端
void persistent_msgq_client() {
int msgid = msgget(MSG_KEY, 0666);
if (msgid == -1) {
perror("msgget");
return;
}
struct persistent_message msg;
int sequence = 0;
printf("Persistent message queue client started\n");
for (int i = 0; i < 10; i++) {
msg.mtype = 1;
msg.timestamp = time(NULL);
msg.sequence = sequence++;
snprintf(msg.mtext, MAX_MSG_SIZE, "Persistent message %d", i + 1);
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), IPC_NOWAIT) == -1) {
if (errno == EAGAIN) {
printf("Message queue full, retrying...\n");
sleep(1);
i--; // 重试
continue;
} else {
perror("msgsnd");
break;
}
}
printf("Sent: %s\n", msg.mtext);
sleep(2);
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [server|client]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "server") == 0) {
persistent_msgq_server();
} else if (strcmp(argv[1], "client") == 0) {
persistent_msgq_client();
} else {
printf("Invalid argument. Use 'server' or 'client'\n");
return 1;
}
return 0;
}
11.3 共享内存同步
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <pthread.h>
#include <errno.h>
#define SHM_KEY 0x8888
#define SEM_KEY 0x9999
#define SHM_SIZE 4096
// 高级同步结构
struct synchronized_shm {
int shmid;
int semid;
pthread_mutex_t *mutex;
pthread_cond_t *condition;
char *data;
int data_ready;
};
// 初始化POSIX同步原语在共享内存中
int init_shared_sync(struct synchronized_shm *shm) {
// 创建共享内存
shm->shmid = shmget(SHM_KEY, SHM_SIZE, 0666 | IPC_CREAT);
if (shm->shmid == -1) {
perror("shmget");
return -1;
}
// 附加共享内存
shm->data = shmat(shm->shmid, NULL, 0);
if (shm->data == (char*)-1) {
perror("shmat");
return -1;
}
// 在共享内存中设置互斥锁和条件变量
shm->mutex = (pthread_mutex_t*)shm->data;
shm->condition = (pthread_cond_t*)(shm->data + sizeof(pthread_mutex_t));
char *user_data = shm->data + sizeof(pthread_mutex_t) + sizeof(pthread_cond_t);
int *data_ready_ptr = (int*)user_data;
shm->data_ready = *data_ready_ptr;
// 只在第一次初始化同步原语
static int initialized = 0;
if (!initialized) {
pthread_mutexattr_t mutex_attr;
pthread_condattr_t cond_attr;
// 初始化互斥锁属性(进程间共享)
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(shm->mutex, &mutex_attr);
pthread_mutexattr_destroy(&mutex_attr);
// 初始化条件变量属性(进程间共享)
pthread_condattr_init(&cond_attr);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(shm->condition, &cond_attr);
pthread_condattr_destroy(&cond_attr);
*data_ready_ptr = 0;
initialized = 1;
}
return 0;
}
// 生产者进程
void synchronized_producer() {
struct synchronized_shm shm;
if (init_shared_sync(&shm) == -1) {
return;
}
printf("Synchronized producer started\n");
char *user_data = shm.data + sizeof(pthread_mutex_t) + sizeof(pthread_cond_t) + sizeof(int);
int *data_ready = (int*)(shm.data + sizeof(pthread_mutex_t) + sizeof(pthread_cond_t));
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(shm.mutex);
// 等待消费者读取前一个消息
while (*data_ready == 1) {
pthread_cond_wait(shm.condition, shm.mutex);
}
// 生产数据
snprintf(user_data, SHM_SIZE - (user_data - shm.data),
"Message %d from synchronized producer", i + 1);
*data_ready = 1;
printf("Producer: %s\n", user_data);
// 通知消费者
pthread_cond_signal(shm.condition);
pthread_mutex_unlock(shm.mutex);
sleep(2);
}
// 标记结束
pthread_mutex_lock(shm.mutex);
snprintf(user_data, SHM_SIZE - (user_data - shm.data), "END");
*data_ready = 1;
pthread_cond_signal(shm.condition);
pthread_mutex_unlock(shm.mutex);
shmdt(shm.data);
printf("Producer finished\n");
}
// 消费者进程
void synchronized_consumer() {
struct synchronized_shm shm;
if (init_shared_sync(&shm) == -1) {
return;
}
printf("Synchronized consumer started\n");
char *user_data = shm.data + sizeof(pthread_mutex_t) + sizeof(pthread_cond_t) + sizeof(int);
int *data_ready = (int*)(shm.data + sizeof(pthread_mutex_t) + sizeof(pthread_cond_t));
while (1) {
pthread_mutex_lock(shm.mutex);
// 等待生产者提供数据
while (*data_ready == 0) {
pthread_cond_wait(shm.condition, shm.mutex);
}
// 消费数据
printf("Consumer: %s\n", user_data);
if (strcmp(user_data, "END") == 0) {
*data_ready = 0;
pthread_mutex_unlock(shm.mutex);
break;
}
*data_ready = 0;
// 通知生产者
pthread_cond_signal(shm.condition);
pthread_mutex_unlock(shm.mutex);
sleep(1);
}
shmdt(shm.data);
// 清理共享内存
shmctl(shm.shmid, IPC_RMID, NULL);
printf("Consumer finished\n");
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s [producer|consumer]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "producer") == 0) {
synchronized_producer();
} else if (strcmp(argv[1], "consumer") == 0) {
synchronized_consumer();
} else {
printf("Invalid argument. Use 'producer' or 'consumer'\n");
return 1;
}
return 0;
}
11.4 信号处理的可重入性
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
// 可重入的全局变量(使用sig_atomic_t)
volatile sig_atomic_t signal_count = 0;
volatile sig_atomic_t main_loop_active = 1;
// 线程安全的字符串操作(可重入)
void safe_strcpy(char *dest, const char *src, size_t size) {
if (size > 0) {
while (--size > 0 && (*dest++ = *src++));
*dest = '\0';
}
}
// 可重入的信号处理函数
void reentrant_signal_handler(int sig) {
// 只修改sig_atomic_t变量,这些操作是原子的
signal_count++;
// 使用可重入的字符串函数
char buffer[100];
const char *msg = "Signal received: ";
safe_strcpy(buffer, msg, sizeof(buffer));
// 使用可重入的I/O函数
const char *signal_name;
switch (sig) {
case SIGUSR1: signal_name = "SIGUSR1"; break;
case SIGUSR2: signal_name = "SIGUSR2"; break;
case SIGTERM: signal_name = "SIGTERM"; break;
default: signal_name = "UNKNOWN"; break;
}
// 使用write而不是printf(printf不可重入)
write(STDOUT_FILENO, buffer, strlen(buffer));
write(STDOUT_FILENO, signal_name, strlen(signal_name));
write(STDOUT_FILENO, "\n", 1);
if (sig == SIGTERM) {
main_loop_active = 0;
}
}
// 非可重入的危险示例(不要在实际中使用)
char global_buffer[100]; // 全局变量,在信号处理中访问危险
void dangerous_signal_handler(int sig) {
// 危险:使用非可重入函数
sprintf(global_buffer, "Dangerous: Signal %d received", sig);
printf("%s\n", global_buffer); // printf不可重入!
}
// 设置可重入信号处理
void setup_reentrant_signals() {
struct sigaction sa;
sa.sa_handler = reentrant_signal_handler;
sigemptyset(&sa.sa_mask);
// 在处理信号时阻塞其他信号
sigaddset(&sa.sa_mask, SIGUSR1);
sigaddset(&sa.sa_mask, SIGUSR2);
sigaddset(&sa.sa_mask, SIGTERM);
sa.sa_flags = 0; // 不设置SA_RESTART,让系统调用可中断
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
}
// 信号安全的日志函数
void signal_safe_log(const char *message) {
// 使用可重入的I/O操作
size_t len = 0;
const char *p = message;
while (*p++) len++;
write(STDERR_FILENO, message, len);
write(STDERR_FILENO, "\n", 1);
}
// 演示可重入信号处理的程序
void reentrant_signal_demo() {
setup_reentrant_signals();
printf("Reentrant signal demo started (PID: %d)\n", getpid());
printf("Send signals: kill -USR1 %d, kill -USR2 %d, kill -TERM %d\n",
getpid(), getpid(), getpid());
int counter = 0;
while (main_loop_active) {
// 主工作循环
counter++;
// 模拟工作
for (int i = 0; i < 1000000; i++) {
// 一些计算工作
volatile int x = i * i;
(void)x;
}
if (counter % 100 == 0) {
printf("Main loop iteration %d, signals received: %d\n",
counter, (int)signal_count);
}
// 检查是否应该退出
if (!main_loop_active) {
signal_safe_log("Shutting down due to SIGTERM");
break;
}
}
printf("Demo finished. Total signals received: %d\n", (int)signal_count);
}
int main() {
printf("Signal Reentrancy Demonstration\n\n");
reentrant_signal_demo();
return 0;
}
12. 结语
Linux进程间通信机制为开发者提供了丰富多样的工具集,从简单的管道到复杂的共享内存,每种机制都有其特定的应用场景和优势。通过本文的详细分析和代码示例,我们可以看到:
- 管道和FIFO 适合简单的流式数据传递,特别是在有亲缘关系的进程间
- 消息队列 提供了结构化的消息传递,支持优先级和持久化
- 共享内存 在需要高性能数据共享的场景中无可替代,但需要仔细处理同步问题
- 信号量 是进程同步的核心机制,确保对共享资源的互斥访问
- 信号 提供了异步事件通知机制,但需要注意可重入性问题
- 套接字 不仅支持网络通信,Unix域套接字在本地进程间通信中也很高效
- 内存映射 结合了文件I/O和内存操作的优势,适合大文件处理
在实际项目中,选择哪种IPC机制应该基于具体需求:数据量大小、性能要求、进程关系、持久化需求等。通常,一个复杂的系统会组合使用多种IPC机制,每种机制负责最适合的任务。
掌握这些IPC机制不仅有助于编写高效的Linux应用程序,还能帮助开发者更好地理解操作系统的工作原理。随着容器化和微服务架构的普及,进程间通信的重要性更加凸显,这些基础知识将成为每个系统开发者必备的技能。
希望本文提供的理论分析和实践示例能够帮助读者深入理解Linux IPC机制,并在实际项目中做出明智的技术选择。