Linux进程间通信(IPC)详解

次浏览

概述

进程间通信(Inter-Process Communication, IPC)是指不同进程之间交换数据的机制。由于每个进程拥有独立的地址空间,进程间无法直接访问彼此的内存,必须通过内核提供的 IPC 机制进行通信。

Linux 提供了多种 IPC 机制:

机制 特点 适用场景
管道 单向、父子进程 简单数据流
FIFO 有名管道、无亲缘关系 无关进程通信
消息队列 消息格式化、异步 结构化数据
共享内存 最快、需同步 大量数据交换
信号量 同步互斥 资源管理
信号 异步通知 事件通知
Socket 跨机器通信 网络通信

一、管道(Pipe)

1.1 匿名管道

匿名管道是最古老的 IPC 机制,只能用于有亲缘关系的进程(父子进程)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];  // pipefd[0] 读端, pipefd[1] 写端
    pid_t pid;
    char buf[100];
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }
    
    pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    }
    
    if (pid == 0) {
        // 子进程:写入数据
        close(pipefd[0]);  // 关闭读端
        
        const char* msg = "Hello from child process!";
        write(pipefd[1], msg, strlen(msg) + 1);
        
        close(pipefd[1]);
        printf("Child: sent message\n");
        
    } else {
        // 父进程:读取数据
        close(pipefd[1]);  // 关闭写端
        
        read(pipefd[0], buf, sizeof(buf));
        printf("Parent: received '%s'\n", buf);
        
        close(pipefd[0]);
        wait(NULL);  // 等待子进程结束
    }
    
    return 0;
}

管道特点:

  • 单向数据流(半双工)
  • 数据先进先出(FIFO)
  • 管道缓冲区有限(通常 64KB)
  • 读端关闭时,写操作会产生 SIGPIPE 信号

1.2 管道的工作原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
父进程                          子进程
┌──────────┐                   ┌──────────┐
│ 写端关闭  │                   │ 读端关闭  │
│          │    pipefd[1]──►───┤ 写端     │
│ 读端     │◄───pipefd[0]      │          │
└──────────┘                   └──────────┘
                    内核缓冲区
              ┌─────────────────────┐
              │  data flow ────────►│
              └─────────────────────┘

1.3 命名管道(FIFO)

FIFO 可以用于无亲缘关系的进程通信。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// fifo_writer.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>

#define FIFO_PATH "/tmp/my_fifo"

int main() {
    // 创建 FIFO(如果不存在)
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo");
    }
    
    printf("Writer: opening FIFO...\n");
    int fd = open(FIFO_PATH, O_WRONLY);  // 阻塞直到有读者
    
    const char* msg = "Message through FIFO!";
    write(fd, msg, strlen(msg) + 1);
    
    printf("Writer: sent '%s'\n", msg);
    close(fd);
    
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// fifo_reader.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#define FIFO_PATH "/tmp/my_fifo"

int main() {
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo");
    }
    
    printf("Reader: opening FIFO...\n");
    int fd = open(FIFO_PATH, O_RDONLY);
    
    char buf[100];
    read(fd, buf, sizeof(buf));
    
    printf("Reader: received '%s'\n", buf);
    close(fd);
    
    unlink(FIFO_PATH);  // 删除 FIFO
    
    return 0;
}

二、消息队列

消息队列是保存在内核中的消息链表,允许进程以消息为单位发送和接收数据。

2.1 System V 消息队列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#define MSG_KEY 1234
#define MSG_SIZE 256

// 消息结构
struct message {
    long mtype;          // 消息类型(必须 > 0)
    char mtext[MSG_SIZE]; // 消息内容
};

int main() {
    key_t key = MSG_KEY;
    int msgid;
    struct message msg;
    
    // 创建或获取消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);
    if (msgid == -1) {
        perror("msgget");
        return 1;
    }
    
    // 发送消息
    msg.mtype = 1;
    strcpy(msg.mtext, "Hello from sender!");
    if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
        perror("msgsnd");
        return 1;
    }
    printf("Sent: %s\n", msg.mtext);
    
    // 接收消息
    if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
        perror("msgrcv");
        return 1;
    }
    printf("Received: %s\n", msg.mtext);
    
    // 删除消息队列
    if (msgctl(msgid, IPC_RMID, NULL) == -1) {
        perror("msgctl");
        return 1;
    }
    
    return 0;
}

2.2 POSIX 消息队列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

#define QUEUE_NAME "/test_queue"
#define MAX_SIZE 256
#define MSG_PRIO 0

int main() {
    mqd_t mq;
    struct mq_attr attr;
    char buffer[MAX_SIZE + 1];
    
    // 设置队列属性
    attr.mq_flags = 0;
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = MAX_SIZE;
    attr.mq_curmsgs = 0;
    
    // 创建消息队列
    mq = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0644, &attr);
    if (mq == (mqd_t)-1) {
        perror("mq_open");
        return 1;
    }
    
    // 发送消息
    strcpy(buffer, "Hello POSIX MQ!");
    if (mq_send(mq, buffer, strlen(buffer) + 1, MSG_PRIO) == -1) {
        perror("mq_send");
        return 1;
    }
    printf("Sent: %s\n", buffer);
    
    // 接收消息
    unsigned int prio;
    ssize_t bytes_read = mq_receive(mq, buffer, MAX_SIZE, &prio);
    if (bytes_read == -1) {
        perror("mq_receive");
        return 1;
    }
    printf("Received (prio=%u): %s\n", prio, buffer);
    
    // 关闭并删除队列
    mq_close(mq);
    mq_unlink(QUEUE_NAME);
    
    return 0;
}

2.3 消息队列的优缺点

优点:

  • 支持消息类型,可实现优先级
  • 异步通信,发送者无需等待接收者
  • 消息持久化,接收者可以稍后读取

缺点:

  • 消息大小有限制
  • 拷贝开销(用户态 ↔ 内核态)
  • 需要显式删除

三、共享内存

共享内存是最快的 IPC 方式,多个进程映射同一块物理内存,直接读写,无需数据拷贝。

3.1 System V 共享内存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>

#define SHM_KEY 5678
#define SHM_SIZE 1024

int main() {
    int shmid;
    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 == (void*)-1) {
        perror("shmat");
        return 1;
    }
    
    // 写入数据
    strcpy(shm_ptr, "Hello from shared memory!");
    printf("Written: %s\n", shm_ptr);
    
    // 读取数据
    printf("Read: %s\n", shm_ptr);
    
    // 分离
    shmdt(shm_ptr);
    
    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    
    return 0;
}

3.2 POSIX 共享内存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

#define SHM_NAME "/my_shm"
#define SHM_SIZE 1024

int main() {
    int fd;
    void *ptr;
    
    // 创建共享内存对象
    fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (fd == -1) {
        perror("shm_open");
        return 1;
    }
    
    // 设置大小
    if (ftruncate(fd, SHM_SIZE) == -1) {
        perror("ftruncate");
        return 1;
    }
    
    // 映射
    ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    
    // 写入数据
    strcpy((char*)ptr, "Hello POSIX shared memory!");
    printf("Written: %s\n", (char*)ptr);
    
    // 清理
    munmap(ptr, SHM_SIZE);
    close(fd);
    shm_unlink(SHM_NAME);
    
    return 0;
}

3.3 共享内存 + 信号量同步

共享内存本身没有同步机制,需要配合信号量使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/wait.h>

#define SHM_KEY 9999
#define SEM_KEY 8888
#define SHM_SIZE 256

// 信号量操作
void sem_wait(int semid) {
    struct sembuf op = {0, -1, 0};  // P 操作
    semop(semid, &op, 1);
}

void sem_signal(int semid) {
    struct sembuf op = {0, 1, 0};   // V 操作
    semop(semid, &op, 1);
}

int main() {
    int shmid, semid;
    char *shm_ptr;
    
    // 创建共享内存
    shmid = shmget(SHM_KEY, SHM_SIZE, 0666 | IPC_CREAT);
    shm_ptr = shmat(shmid, NULL, 0);
    
    // 创建信号量(初始值为 1)
    semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
    semctl(semid, 0, SETVAL, 1);
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程:写入
        sem_wait(semid);  // 获取锁
        
        strcpy(shm_ptr, "Message from child");
        printf("Child: wrote '%s'\n", shm_ptr);
        
        sem_signal(semid);  // 释放锁
        exit(0);
        
    } else {
        // 父进程:读取
        wait(NULL);  // 等待子进程写入
        
        sem_wait(semid);
        printf("Parent: read '%s'\n", shm_ptr);
        sem_signal(semid);
        
        // 清理
        shmdt(shm_ptr);
        shmctl(shmid, IPC_RMID, NULL);
        semctl(semid, 0, IPC_RMID);
    }
    
    return 0;
}

四、信号量

信号量用于进程间的同步与互斥,本质是一个计数器。

4.1 System V 信号量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/wait.h>

#define SEM_KEY 1234

// 联合体,用于 semctl
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P 操作(等待/获取资源)
int sem_p(int semid) {
    struct sembuf op = {0, -1, 0};
    return semop(semid, &op, 1);
}

// V 操作(释放资源)
int sem_v(int semid) {
    struct sembuf op = {0, 1, 0};
    return semop(semid, &op, 1);
}

int main() {
    int semid;
    union semun arg;
    
    // 创建信号量集(1个信号量)
    semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
    if (semid == -1) {
        perror("semget");
        return 1;
    }
    
    // 初始化信号量值为 1(二值信号量/互斥锁)
    arg.val = 1;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("semctl SETVAL");
        return 1;
    }
    
    printf("Semaphore initialized to 1\n");
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程
        printf("Child: trying to acquire semaphore...\n");
        sem_p(semid);
        printf("Child: acquired semaphore, working...\n");
        sleep(2);
        printf("Child: releasing semaphore\n");
        sem_v(semid);
        exit(0);
        
    } else {
        // 父进程
        sleep(1);  // 让子进程先获取
        printf("Parent: trying to acquire semaphore...\n");
        sem_p(semid);
        printf("Parent: acquired semaphore\n");
        sem_v(semid);
        
        wait(NULL);
        
        // 删除信号量
        semctl(semid, 0, IPC_RMID);
    }
    
    return 0;
}

4.2 POSIX 信号量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/wait.h>

#define SEM_NAME "/my_semaphore"

int main() {
    sem_t *sem;
    
    // 创建命名信号量(初始值为 1)
    sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        return 1;
    }
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程
        printf("Child: waiting for semaphore...\n");
        sem_wait(sem);
        printf("Child: in critical section\n");
        sleep(2);
        printf("Child: leaving critical section\n");
        sem_post(sem);
        exit(0);
        
    } else {
        // 父进程
        printf("Parent: waiting for semaphore...\n");
        sem_wait(sem);
        printf("Parent: in critical section\n");
        sleep(1);
        printf("Parent: leaving critical section\n");
        sem_post(sem);
        
        wait(NULL);
        
        // 清理
        sem_close(sem);
        sem_unlink(SEM_NAME);
    }
    
    return 0;
}

4.3 生产者-消费者模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 5

int buffer[BUFFER_SIZE];
int in = 0, out = 0;

sem_t empty;    // 空槽位数
sem_t full;     // 已填充数
pthread_mutex_t mutex;

void* producer(void* arg) {
    for (int i = 0; i < 10; i++) {
        sem_wait(&empty);           // 等待空槽位
        pthread_mutex_lock(&mutex); // 互斥访问
        
        buffer[in] = i;
        printf("Produced: %d\n", i);
        in = (in + 1) % BUFFER_SIZE;
        
        pthread_mutex_unlock(&mutex);
        sem_post(&full);            // 增加已填充数
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 10; i++) {
        sem_wait(&full);            // 等待数据
        pthread_mutex_lock(&mutex);
        
        int item = buffer[out];
        printf("Consumed: %d\n", item);
        out = (out + 1) % BUFFER_SIZE;
        
        pthread_mutex_unlock(&mutex);
        sem_post(&empty);           // 增加空槽位
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;
    
    // 初始化信号量
    sem_init(&empty, 0, BUFFER_SIZE);
    sem_init(&full, 0, 0);
    pthread_mutex_init(&mutex, NULL);
    
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    
    sem_destroy(&empty);
    sem_destroy(&full);
    pthread_mutex_destroy(&mutex);
    
    return 0;
}

五、信号

信号是一种异步通信机制,用于通知进程某个事件发生。

5.1 常见信号

信号 默认行为 含义
SIGINT 终止 Ctrl+C
SIGTERM 终止 正常终止请求
SIGKILL 终止 强制终止(不可捕获)
SIGSTOP 暂停 暂停进程(不可捕获)
SIGCONT 继续 恢复暂停的进程
SIGCHLD 忽略 子进程状态改变
SIGUSR1 终止 用户自定义信号1
SIGUSR2 终止 用户自定义信号2

5.2 信号处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号处理函数
void sigint_handler(int signo) {
    printf("\nReceived SIGINT (Ctrl+C), signo=%d\n", signo);
    printf("Press Ctrl+\\ to quit\n");
}

void sigterm_handler(int signo) {
    printf("Received SIGTERM, cleaning up...\n");
    exit(0);
}

int main() {
    // 注册信号处理函数
    signal(SIGINT, sigint_handler);
    signal(SIGTERM, sigterm_handler);
    
    printf("PID: %d\n", getpid());
    printf("Try: kill -SIGTERM %d\n", getpid());
    printf("Press Ctrl+C to test SIGINT\n");
    
    while (1) {
        sleep(1);
        printf("Working...\n");
    }
    
    return 0;
}

5.3 使用 sigaction(更推荐)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void handler(int signo, siginfo_t *info, void *context) {
    printf("Received signal %d\n", signo);
    printf("From PID: %d\n", info->si_pid);
    printf("Value: %d\n", info->si_value.sival_int);
}

int main() {
    struct sigaction sa;
    
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = handler;
    sa.sa_flags = SA_SIGINFO;  // 使用 sa_sigaction
    
    sigaction(SIGUSR1, &sa, NULL);
    
    printf("PID: %d\n", getpid());
    printf("Try: kill -SIGUSR1 %d\n", getpid());
    
    // 发送信号给自己(带数据)
    union sigval value;
    value.sival_int = 42;
    sigqueue(getpid(), SIGUSR1, value);
    
    pause();  // 等待信号
    
    return 0;
}

5.4 进程间信号通信

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

volatile sig_atomic_t child_ready = 0;

void child_handler(int signo) {
    child_ready = 1;
}

int main() {
    signal(SIGCHLD, child_handler);
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程
        printf("Child (PID=%d) working...\n", getpid());
        sleep(2);
        printf("Child: work done, exiting\n");
        exit(0);
        
    } else {
        // 父进程
        printf("Parent: waiting for child\n");
        
        while (!child_ready) {
            pause();  // 等待信号
        }
        
        printf("Parent: child is ready\n");
        wait(NULL);
    }
    
    return 0;
}

六、Unix Domain Socket

Unix Domain Socket 用于同一主机上的进程通信,比网络 Socket 更高效。

6.1 流式 Socket(类似 TCP)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCKET_PATH "/tmp/unix_socket"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un addr;
    char buf[256];
    
    // 创建 socket
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        return 1;
    }
    
    // 绑定地址
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCKET_PATH);
    
    unlink(SOCKET_PATH);  // 删除已存在的 socket 文件
    
    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 1;
    }
    
    // 监听
    if (listen(server_fd, 5) == -1) {
        perror("listen");
        return 1;
    }
    
    printf("Server listening on %s\n", SOCKET_PATH);
    
    // 接受连接
    client_fd = accept(server_fd, NULL, NULL);
    if (client_fd == -1) {
        perror("accept");
        return 1;
    }
    
    // 接收数据
    ssize_t n = read(client_fd, buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("Server received: %s\n", buf);
    
    // 发送响应
    write(client_fd, "Hello from server!", 18);
    
    close(client_fd);
    close(server_fd);
    unlink(SOCKET_PATH);
    
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCKET_PATH "/tmp/unix_socket"

int main() {
    int fd;
    struct sockaddr_un addr;
    char buf[256];
    
    // 创建 socket
    fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("socket");
        return 1;
    }
    
    // 连接服务器
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCKET_PATH);
    
    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        return 1;
    }
    
    // 发送数据
    write(fd, "Hello from client!", 18);
    
    // 接收响应
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("Client received: %s\n", buf);
    
    close(fd);
    
    return 0;
}

6.2 数据报 Socket(类似 UDP)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SERVER_PATH "/tmp/dgram_server"
#define CLIENT_PATH "/tmp/dgram_client"

int main() {
    int fd;
    struct sockaddr_un server_addr, client_addr;
    char buf[256];
    
    fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    
    // 绑定客户端地址(可选,用于接收响应)
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sun_family = AF_UNIX;
    strcpy(client_addr.sun_path, CLIENT_PATH);
    unlink(CLIENT_PATH);
    bind(fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SERVER_PATH);
    
    // 发送数据
    sendto(fd, "Hello DGRAM!", 12, 0, 
           (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    printf("Sent message\n");
    
    close(fd);
    unlink(CLIENT_PATH);
    
    return 0;
}

七、IPC 机制对比与选择

7.1 性能对比

1
2
3
4
5
            数据拷贝次数    速度    复杂度
管道          2次          慢      低
消息队列      2次          中      中
共享内存      0次          最快    高(需同步)
Socket        2次          中      中

7.2 选择指南

场景 推荐 IPC
简单父子进程通信 管道
无亲缘关系进程简单通信 FIFO
结构化消息传递 消息队列
大量数据交换 共享内存 + 信号量
需要同步互斥 信号量
事件通知 信号
跨机器通信 TCP/UDP Socket
同机器高效通信 Unix Domain Socket

7.3 IPC 对象管理命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 查看 IPC 对象
ipcs -a          # 所有 IPC
ipcs -m          # 共享内存
ipcs -s          # 信号量
ipcs -q          # 消息队列

# 删除 IPC 对象
ipcrm -m <shmid>    # 删除共享内存
ipcrm -s <semid>    # 删除信号量
ipcrm -q <msqid>    # 删除消息队列

# 查看 POSIX 消息队列
ls /dev/mqueue/

# 查看共享内存
ls /dev/shm/

八、总结

IPC 类型 关键 API 特点
管道 pipe(), fork() 简单,单向,仅父子进程
FIFO mkfifo(), open() 有名,可用于无关进程
消息队列 msgget(), msgsnd(), msgrcv() 结构化,异步
共享内存 shmget(), shmat(), mmap() 最快,需同步
信号量 semget(), semop() 同步互斥
信号 signal(), sigaction() 异步通知
Unix Socket socket(), bind(), listen() 可靠,高效

最佳实践:

  1. 优先选择适合场景的 IPC,不要过度设计
  2. 共享内存必须配合同步机制
  3. 注意清理 IPC 资源,避免泄漏
  4. 处理好错误和边界情况
  5. 考虑进程异常退出时的资源释放

参考资料

  • 《UNIX环境高级编程》(APUE)
  • 《Linux程序设计》
  • man pages: pipe(2), shmget(2), msgget(2), semget(2), socket(7)
使用 Hugo 构建
主题 StackJimmy 设计