Linux进程间通信


Linux进程间通信(IPC)

简介

进程间通信(Inter-Process Communication,IPC)是指在不同进程之间传递数据或信号的机制。Linux 提供了多种 IPC 方式,每种方式都有其特点和适用场景。

IPC方式分类

Linux 中常见的 IPC 方式包括:

  1. 管道(Pipe):匿名管道,用于有亲缘关系的进程间通信
  2. 命名管道(FIFO):命名管道,可用于无亲缘关系的进程间通信
  3. 消息队列(Message Queue):消息队列,进程间传递结构化数据
  4. 信号量(Semaphore):用于进程同步和互斥
  5. 共享内存(Shared Memory):最快的 IPC 方式,多个进程共享同一块内存
  6. 信号(Signal):用于进程间异步通知
  7. 套接字(Socket):可用于本地和网络通信
  8. 内存映射(mmap):将文件映射到内存,实现进程间通信

1. 管道(Pipe)

特点

  • 半双工通信(数据只能单向流动)
  • 只能用于有亲缘关系的进程(父子进程)
  • 基于文件描述符
  • 数据是流式的,无格式

创建和使用

1
2
3
4
#include <unistd.h>

int pipe(int pipefd[2]);
// pipefd[0] 用于读,pipefd[1] 用于写

示例

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
int fd[2];
char buf[100];
pid_t pid;

// 创建管道
if (pipe(fd) < 0) {
perror("pipe");
return 1;
}

pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}

if (pid == 0) {
// 子进程:关闭读端,写入数据
close(fd[0]);
const char *msg = "Hello from child!";
write(fd[1], msg, strlen(msg));
close(fd[1]);
} else {
// 父进程:关闭写端,读取数据
close(fd[1]);
read(fd[0], buf, sizeof(buf));
printf("Parent received: %s\n", buf);
close(fd[0]);
wait(NULL);
}

return 0;
}

使用场景

  • 父子进程间通信
  • Shell 命令管道(如 ls | grep

2. 命名管道(FIFO)

特点

  • 有名字的管道,存在于文件系统中
  • 可用于无亲缘关系的进程间通信
  • 半双工通信
  • 通过文件路径访问

创建和使用

1
2
3
4
5
6
# 使用 mkfifo 命令创建
mkfifo /tmp/myfifo

# 使用 mkfifo 函数创建
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

示例

创建 FIFO:

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

int main() {
const char *fifo = "/tmp/myfifo";

// 创建 FIFO
if (mkfifo(fifo, 0666) < 0) {
perror("mkfifo");
return 1;
}

// 写入数据
int fd = open(fifo, O_WRONLY);
const char *msg = "Hello from FIFO!";
write(fd, msg, strlen(msg));
close(fd);

return 0;
}

读取 FIFO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
const char *fifo = "/tmp/myfifo";
char buf[100];

// 读取数据
int fd = open(fifo, O_RDONLY);
read(fd, buf, sizeof(buf));
printf("Received: %s\n", buf);
close(fd);

return 0;
}

使用场景

  • 无亲缘关系的进程间通信
  • 客户端-服务器模型

3. 消息队列

特点

  • 消息队列是消息的链表,存储在内核中
  • 每个消息队列用一个标识符(队列ID)来标识
  • 消息是有格式的,可以按类型读取
  • 支持多个进程读写
  • 消息队列独立于进程存在

系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/msg.h>

// 创建或打开消息队列
int msgget(key_t key, int msgflg);

// 发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

// 接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

// 控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

消息结构

1
2
3
4
struct msgbuf {
long mtype; // 消息类型,必须 > 0
char mtext[1]; // 消息数据
};

示例

发送消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct msgbuf {
long mtype;
char mtext[100];
};

int main() {
key_t key = ftok("/tmp", 'a');
int msqid = msgget(key, IPC_CREAT | 0666);

struct msgbuf msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello from message queue!");

msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0);
printf("Message sent\n");

return 0;
}

接收消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>

struct msgbuf {
long mtype;
char mtext[100];
};

int main() {
key_t key = ftok("/tmp", 'a');
int msqid = msgget(key, 0);

struct msgbuf msg;
msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
printf("Received: %s\n", msg.mtext);

// 删除消息队列
msgctl(msqid, IPC_RMID, NULL);

return 0;
}

使用场景

  • 需要按类型处理消息的场景
  • 进程间传递结构化数据
  • 解耦生产者和消费者

4. 信号量

特点

  • 用于进程同步和互斥
  • 是一个计数器,用于控制多个进程对共享资源的访问
  • 支持多个信号量(信号量集)
  • 原子操作,保证操作的原子性

系统调用

1
2
3
4
5
6
7
8
9
10
#include <sys/sem.h>

// 创建或打开信号量集
int semget(key_t key, int nsems, int semflg);

// 信号量操作
int semop(int semid, struct sembuf *sops, size_t nsops);

// 控制信号量
int semctl(int semid, int semnum, int cmd, ...);

操作结构

1
2
3
4
5
struct sembuf {
unsigned short sem_num; // 信号量编号
short sem_op; // 操作值(正数:V操作,负数:P操作)
short sem_flg; // 操作标志
};

示例

使用信号量实现互斥:

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
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

// 创建信号量
int create_sem(key_t key) {
int semid = semget(key, 1, IPC_CREAT | 0666);
if (semid < 0) {
perror("semget");
return -1;
}

// 初始化信号量为1(互斥锁)
semctl(semid, 0, SETVAL, 1);
return semid;
}

// P操作(等待)
void P(int semid) {
struct sembuf op = {0, -1, 0};
semop(semid, &op, 1);
}

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

int main() {
key_t key = ftok("/tmp", 'b');
int semid = create_sem(key);

pid_t pid = fork();
if (pid == 0) {
// 子进程
P(semid);
printf("Child process in critical section\n");
sleep(2);
printf("Child process leaving critical section\n");
V(semid);
} else {
// 父进程
P(semid);
printf("Parent process in critical section\n");
sleep(2);
printf("Parent process leaving critical section\n");
V(semid);
wait(NULL);

// 删除信号量
semctl(semid, 0, IPC_RMID);
}

return 0;
}

使用场景

  • 进程同步
  • 互斥访问共享资源
  • 生产者-消费者问题
  • 读者-写者问题

5. 共享内存

特点

  • 最快的 IPC 方式,因为数据不需要在内核和用户空间之间复制
  • 多个进程可以访问同一块内存区域
  • 需要配合信号量或互斥锁使用,保证同步
  • 数据在进程间直接共享

系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/shm.h>

// 创建或打开共享内存
int shmget(key_t key, size_t size, int shmflg);

// 将共享内存映射到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);

// 解除映射
int shmdt(const void *shmaddr);

// 控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

示例

写入共享内存:

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
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
key_t key = ftok("/tmp", 'c');

// 创建共享内存(1KB)
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
if (shmid < 0) {
perror("shmget");
return 1;
}

// 映射共享内存
char *shm = (char *)shmat(shmid, NULL, 0);
if (shm == (char *)-1) {
perror("shmat");
return 1;
}

// 写入数据
strcpy(shm, "Hello from shared memory!");
printf("Data written to shared memory\n");

// 等待读取
sleep(5);

// 解除映射
shmdt(shm);

// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);

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
#include <sys/shm.h>
#include <stdio.h>
#include <unistd.h>

int main() {
key_t key = ftok("/tmp", 'c');

// 打开共享内存
int shmid = shmget(key, 1024, 0);
if (shmid < 0) {
perror("shmget");
return 1;
}

// 映射共享内存
char *shm = (char *)shmat(shmid, NULL, 0);
if (shm == (char *)-1) {
perror("shmat");
return 1;
}

// 读取数据
printf("Data from shared memory: %s\n", shm);

// 解除映射
shmdt(shm);

return 0;
}

使用场景

  • 需要高速数据传输的场景
  • 大数据量共享
  • 实时数据共享

6. 信号(Signal)

特点

  • 异步通知机制
  • 用于进程间通信和进程控制
  • 信号是软件中断
  • 有默认处理方式,可以自定义处理函数

常用信号

信号 说明
SIGHUP 1 挂起信号
SIGINT 2 中断信号(Ctrl+C)
SIGQUIT 3 退出信号(Ctrl+\)
SIGKILL 9 强制终止(不可捕获)
SIGTERM 15 终止信号
SIGUSR1 10 用户自定义信号1
SIGUSR2 12 用户自定义信号2

系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <signal.h>

// 发送信号
int kill(pid_t pid, int sig);

// 注册信号处理函数
void (*signal(int sig, void (*handler)(int)))(int);

// 更强大的信号处理函数
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);

// 发送信号给自己
int raise(int sig);

// 暂停进程,等待信号
int pause(void);

示例

信号处理:

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
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void signal_handler(int sig) {
printf("Received signal %d\n", sig);
if (sig == SIGINT) {
printf("Handling SIGINT...\n");
exit(0);
}
}

int main() {
// 注册信号处理函数
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);

printf("Process ID: %d\n", getpid());
printf("Waiting for signal...\n");

// 等待信号
while(1) {
pause();
}

return 0;
}

发送信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s <pid> <signal>\n", argv[0]);
return 1;
}

pid_t pid = atoi(argv[1]);
int sig = atoi(argv[2]);

if (kill(pid, sig) < 0) {
perror("kill");
return 1;
}

printf("Signal %d sent to process %d\n", sig, pid);
return 0;
}

使用场景

  • 进程控制(终止、暂停、继续)
  • 异步事件通知
  • 进程间简单通信

7. 套接字(Socket)

特点

  • 可用于本地进程间通信(Unix Domain Socket)
  • 也可用于网络通信(Internet Socket)
  • 全双工通信
  • 支持多种协议(TCP、UDP、Unix Domain)

Unix Domain Socket

服务器端:

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
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
int server_fd, client_fd;
struct sockaddr_un server_addr, client_addr;
socklen_t client_len;
char buf[100];

// 创建套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);

// 设置地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, "/tmp/unix_socket");

// 绑定地址
bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

// 监听
listen(server_fd, 5);

// 接受连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);

// 接收数据
read(client_fd, buf, sizeof(buf));
printf("Received: %s\n", buf);

// 发送数据
const char *msg = "Hello from server!";
write(client_fd, msg, strlen(msg));

close(client_fd);
close(server_fd);
unlink("/tmp/unix_socket");

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
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
int sock_fd;
struct sockaddr_un server_addr;
char buf[100];

// 创建套接字
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, "/tmp/unix_socket");

// 连接服务器
connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

// 发送数据
const char *msg = "Hello from client!";
write(sock_fd, msg, strlen(msg));

// 接收数据
read(sock_fd, buf, sizeof(buf));
printf("Received: %s\n", buf);

close(sock_fd);

return 0;
}

使用场景

  • 客户端-服务器模型
  • 网络通信
  • 本地进程间通信(Unix Domain Socket)

8. 内存映射(mmap)

特点

  • 将文件映射到进程地址空间
  • 多个进程可以映射同一个文件,实现共享
  • 访问映射内存就像访问普通内存一样
  • 可以用于大文件处理

系统调用

1
2
3
4
5
6
7
8
9
10
#include <sys/mman.h>

// 创建内存映射
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

// 解除映射
int munmap(void *addr, size_t length);

// 同步映射
int msync(void *addr, size_t length, int flags);

示例

写入进程:

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
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
const char *file = "/tmp/shared_file";
int fd = open(file, O_CREAT | O_RDWR, 0666);

// 设置文件大小
ftruncate(fd, 1024);

// 映射文件到内存
char *ptr = (char *)mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

// 写入数据
strcpy(ptr, "Hello from mmap!");

// 同步到文件
msync(ptr, 1024, MS_SYNC);

// 解除映射
munmap(ptr, 1024);
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
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
const char *file = "/tmp/shared_file";
int fd = open(file, O_RDONLY);

// 映射文件到内存
char *ptr = (char *)mmap(NULL, 1024, PROT_READ, MAP_SHARED, fd, 0);

// 读取数据
printf("Data from mmap: %s\n", ptr);

// 解除映射
munmap(ptr, 1024);
close(fd);

return 0;
}

使用场景

  • 大文件处理
  • 进程间共享数据
  • 零拷贝文件I/O

IPC方式对比

IPC方式 速度 容量 持久性 同步机制 适用场景
管道 有限 自动 父子进程
FIFO 有限 自动 无亲缘关系进程
消息队列 有限 自动 结构化消息
信号量 - 手动 同步互斥
共享内存 最快 手动 大数据共享
信号 异步 进程控制
套接字 手动 网络/本地通信
mmap 手动 文件共享

选择建议

  1. 父子进程通信:使用管道
  2. 无亲缘关系进程:使用 FIFO、消息队列或套接字
  3. 需要同步:使用信号量
  4. 大数据传输:使用共享内存或 mmap
  5. 进程控制:使用信号
  6. 网络通信:使用套接字
  7. 文件共享:使用 mmap

注意事项

  1. 资源清理:使用完 IPC 资源后要及时清理,避免资源泄漏
  2. 同步问题:共享内存和 mmap 需要配合信号量等同步机制使用
  3. 权限控制:注意 IPC 资源的权限设置,保证安全性
  4. 错误处理:所有 IPC 操作都要检查返回值,进行错误处理
  5. key值生成:使用 ftok() 生成 key 时,要确保文件存在且可访问

文章作者: djaigo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 djaigo !
评论
  目录