Linux进程间通信的方式汇总
Linux进程间通信的方式主要有以下几种:
- 管道(Pipe):管道是一种最简单的IPC方式,用于实现父子进程之间的通信。一个进程可以向管道中写入数据,另一个进程可以从管道中读取数据。
- 命名管道(Named Pipe)或FIFO(First In First Out):类似于管道,但是命名管道允许无亲缘关系的进程之间进行通信。它在文件系统中有对应的文件名,并通过文件描述符来访问。
- 信号(Signal):信号是在软件层次上对中断机制的一种模拟,用于通知进程有某事件发生。一个进程收到一个信号后,可以根据信号的不同含义来执行不同的操作。
- 消息队列(Message Queue):消息队列是消息的链接表,它允许进程之间通过发送和接收消息来进行通信。消息队列被内核维护,保证了消息的独立性和安全性。
- 共享内存(Shared Memory):共享内存允许多个进程访问同一块内存区域,进程可以直接读写该内存区域来进行通信。这种方式通信速度快,但需要同步机制来避免冲突。
- 信号量(Semaphore):信号量是一种用于进程间同步和互斥的机制,可以控制对共享资源的访问,避免竞争和死锁等问题。
- 套接字(Socket):套接字不仅可以用于本地进程间通信,还可以用于网络通信。它提供了一种跨网络平台的进程间通信机制,使得不同主机上的进程可以进行通信。
这些通信方式各有特点,可以根据具体的应用场景和需求来选择合适的通信方式。
管道
下面是一个简单的 Linux C 示例代码,它演示了如何使用管道(pipe)来实现父进程和子进程之间的通信。在这个例子中,父进程会向管道中写入一个字符串,然后子进程会从管道中读取这个字符串并打印出来。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int fd[2]; // 文件描述符数组,用于管道的读写
pid_t pid; // 子进程的进程ID
char buf[1024]; // 用于存储从管道读取的数据的缓冲区
const char *message = "Hello from parent process!"; // 父进程要发送的消息
// 创建管道
if (pipe(fd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
// 父进程
if (pid > 0) {
close(fd[0]); // 关闭读端,因为父进程要写入
// 写入消息到管道
write(fd[1], message, strlen(message) + 1);
close(fd[1]); // 写入完毕后关闭写端
// 等待子进程结束
waitpid(pid, NULL, 0);
printf("Parent process has finished.\n");
}
// 子进程
else {
close(fd[1]); // 关闭写端,因为子进程要读取
// 从管道读取消息
ssize_t bytesRead = read(fd[0], buf, sizeof(buf) - 1);
if (bytesRead > 0) {
buf[bytesRead] = '\0'; // 在读取到的字符串末尾添加空字符
printf("Child process received: %s\n", buf);
}
close(fd[0]); // 读取完毕后关闭读端
exit(EXIT_SUCCESS);
}
return 0;
}
在这个示例中,首先通过 pipe()
函数创建了一个管道,并返回了两个文件描述符 fd[0]
和 fd[1]
,分别代表管道的读端和写端。
接着,使用 fork()
函数创建了一个子进程。在父进程中,关闭了管道的读端,并使用 write()
函数将消息写入管道的写端。写入完成后,关闭了写端,并等待子进程结束。
在子进程中,关闭了管道的写端,并使用 read()
函数从管道的读端读取消息。读取到的消息存储在 buf
缓冲区中,并打印出来。读取完成后,关闭了读端,并退出子进程。
注意,在实际使用中,通常需要对 read()
和 write()
函数的返回值进行检查,以处理可能出现的错误情况。在这个简单的示例中,为了简洁,我省略了错误检查代码。在实际编程中,请确保添加适当的错误处理逻辑。
命名管道
下面是一个简单的 Linux C 示例代码,它演示了如何使用命名管道(named pipe)来实现进程间的通信。在这个例子中,我们将创建一个命名管道,一个进程写入数据,另一个进程读取数据。
首先是写入数据的进程(writer.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FIFO_PATH "/tmp/my_named_pipe"
#define BUFFER_SIZE 256
int main() {
int fd;
char buffer[BUFFER_SIZE];
const char *message = "Hello from named pipe writer!";
// 创建命名管道
if (mkfifo(FIFO_PATH, 0666) == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
// 打开命名管道以写入数据
fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 写入数据到命名管道
if (write(fd, message, strlen(message) + 1) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
close(fd);
printf("Writer process has finished writing.\n");
// 删除命名管道
unlink(FIFO_PATH);
return 0;
}
然后是读取数据的进程(reader.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_PATH "/tmp/my_named_pipe"
#define BUFFER_SIZE 256
int main() {
int fd;
char buffer[BUFFER_SIZE];
// 打开命名管道以读取数据
fd = open(FIFO_PATH, O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 从命名管道读取数据
ssize_t bytesRead = read(fd, buffer, BUFFER_SIZE - 1);
if (bytesRead == -1) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[bytesRead] = '\0'; // 添加字符串结束符
printf("Reader process received: %s\n", buffer);
close(fd);
return 0;
}
要运行这两个程序,你需要分别编译它们:
gcc -o writer writer.c
gcc -o reader reader.c
然后,你可以先运行 reader 进程来等待命名管道的打开:
./reader
在另一个终端窗口中,运行 writer 进程来写入数据:
./writer
如果一切正常,你应该在第一个终端窗口中看到 reader 进程打印出从 writer 进程接收到的消息。
注意,命名管道(FIFO)在文件系统中有一个路径,所以你需要确保该路径是存在的并且有足够的权限进行读写操作。此外,你还需要处理可能发生的错误情况,比如命名管道不存在或打开失败等。在这个简单的示例中,为了保持代码的简洁性,我省略了这些错误处理逻辑。在实际编程中,请确保添加适当的错误处理代码。
信号
下面是一个简单的 Linux C 示例代码,它演示了如何使用信号(Signal)来在进程之间发送通知。在这个例子中,我们将创建一个进程,它将发送一个 SIGUSR1 信号给自身,并在接收到信号时执行一个特定的处理函数。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void signal_handler(int signo) {