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()简单,单向,仅父子进程
FIFOmkfifo(), open()有名,可用于无关进程
消息队列msgget(), msgsnd(), msgrcv()结构化,异步
共享内存shmget(), shmat(), mmap()最快,需同步
信号量semget(), semop()同步互斥
信号signal(), sigaction()异步通知
Unix Socketsocket(), bind(), listen()可靠,高效

最佳实践:

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

参考资料

  • 《UNIX环境高级编程》(APUE)
  • 《Linux程序设计》
  • man pages: pipe(2), shmget(2), msgget(2), semget(2), socket(7)

评论

留言经审核后显示,欢迎留下想法。