进程间通信(Inter-Process Communication,IPC)是指在操作系统中,两个或多个独立的进程之间进行数据交换和信息共享的机制。在多任务和多进程的操作系统中,进程通常是相互独立的,IPC 提供了一种机制,使它们能够协同工作、交换数据和实现同步。
本质
代码流程总结:
匿名管道具体实例:
#include
#include
#include
int main() {
int pipefd[2]; // 用于存放管道的文件描述符
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t child_pid = fork(); // 创建子进程
if (child_pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (child_pid == 0) { // 子进程
close(pipefd[1]); // 关闭写入端
char buffer[100];
read(pipefd[0], buffer, sizeof(buffer));
printf("Child process received: %s\n", buffer);
close(pipefd[0]); // 关闭读取端
_exit(EXIT_SUCCESS);
} else { // 父进程
close(pipefd[0]); // 关闭读取端
const char *data = "Hello,Pipe!";
//write(pipefd[1], data, strlen(data));
close(pipefd[1]); // 关闭写入端
wait(NULL); // 等待子进程结束
exit(EXIT_SUCCESS);
}
}
匿名管道是单向的,所以如果需要双向通信,通常需要创建两个管道。匿名管道是在相关进程间创建的,因此通常用于具有亲缘关系的进程之间的通信。
说明:
为什么上面的例子直接退出了呢?
close(pipefd[1]);
// 关闭写入端命名管道具体实例(用mknod宏控,分别完成了mkfifo和mknod的方式通信)
#include
#include
#include
#include
#include
#include
#include
#define mknod
int main() {
char *fifo_path = "/tmp/my_fifo"; // 指定命名管道的路径
#ifdef mknod
if (mkfifo(fifo_path, 0666) == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
#else
mode_t mode = S_IFIFO | 0666; // 设置文件类型为FIFO(命名管道)以及权限
if (mknod(fifo_path, mode, 0) == -1) {
perror("mknod");
exit(EXIT_FAILURE);
}
#endif
pid_t child_pid = fork();
if (child_pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
#ifdef mknod
if (child_pid == 0) { // 子进程
int fd = open(fifo_path, O_RDONLY); // 以只读方式打开命名管道
char buffer[100];
read(fd, buffer, sizeof(buffer));
printf("Child process received: %s\n", buffer);
close(fd);
exit(EXIT_SUCCESS);
} else { // 父进程
int fd = open(fifo_path, O_WRONLY); // 以只写方式打开命名管道
char *data = "Hello, FIFO!";
write(fd, data, strlen(data));
close(fd);
wait(NULL);
unlink(fifo_path); // 删除命名管道
exit(EXIT_SUCCESS);
}
#else
if (child_pid == 0) { // 子进程
int fd = open(fifo_path, O_RDONLY);
char buffer[100];
read(fd, buffer, sizeof(buffer));
printf("Child process received: %s\n", buffer);
close(fd);
exit(EXIT_SUCCESS);
} else
{ // 父进程
int fd = open(fifo_path, O_WRONLY);
const char *data = "Hello, FIFO!";
size_t len = strlen(data);
// 复制字符串内容到缓冲区
char buffer[len + 1];
strcpy(buffer, data);
// 使用缓冲区的地址进行写入
write(fd, buffer, len);
close(fd);
wait(NULL);
exit(EXIT_SUCCESS);
}
#endif
return 0;
}
管道这种进程通信方式虽然使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。
本质
多重理解
#include
#include
#include
#include
#include
#include
#define MAX_MESSAGE_SIZE 1024
// 定义消息结构
struct message {
long mtype; // 消息类型
char mtext[MAX_MESSAGE_SIZE]; // 消息内容
};
int main()
{
key_t key = ftok("/tmp", 'A'); // 通过路径名和项目标识符生成唯一的 key
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
// 创建或获取消息队列
int msgid = msgget(key, IPC_CREAT | 0666);//IPC_CREAT 表示如果消息队列不存在则创建它,而 0666 是消息队列的权限位。
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
// 发送消息
struct message msg_to_send;
msg_to_send.mtype = 1; // 设置消息类型
strcpy(msg_to_send.mtext, "Hello, Message Queue!");
if (msgsnd(msgid, &msg_to_send, sizeof(msg_to_send.mtext), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
// 接收消息
struct message msg_to_receive;
if (msgrcv(msgid, &msg_to_receive, sizeof(msg_to_receive.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
// 打印接收到的消息
printf("Received message: %s\n", msg_to_receive.mtext);
// 删除消息队列
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(EXIT_FAILURE);
}
return 0;
}
注意:
如果消息队列已经存在,并且已经读了一遍,再去读还会有已经读到过的信息吗?
不会,消息队列的内容在读过一次后会被取出,不会继续保存在消息队列中
说明
我们只能自己创建链表,将数据从kernel中读出来保存在自己创建的链表里吗?链表不是在kernel中吗?
一会儿消息队列,一会儿链表,他们有什么区别?
内存共享是另一种进程间通信的方式,它允许多个进程共享同一块内存区域,从而实现数据的共享。与消息队列不同,内存共享更加灵活,允许进程直接读写共享的内存,而不需要通过中介来传递消息。
创建共享内存并写入信息
#include
#include
#include
#include
#include
#include
#define SHM_SIZE 1024
int main() {
int times = 0;
key_t key = ftok("/tmmmm", 'A');//ftok 函数是用于生成一个键值的函数,通常用于为 IPC(Inter-Process Communication)机制创建唯一的标识符
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
// 创建或获取共享内存
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 将共享内存连接到当前进程的地址空间
char *shared_memory = shmat(shmid, NULL, 0);
if (shared_memory == (char*)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 在共享内存中写入数据
strcpy(shared_memory, "Hello, Shared Memory!");
while(1)
{
printf("Message in shared memory: %s\n",shared_memory);
sleep(5);
}
//分离共享内存
if (shmdt(shared_memory) == -1) {
perror("shmdt");
exit(EXIT_FAILURE);
}
return 0;
}
连接到已存在的共享内存并进行读写操作
#include
#include
#include
#include
#include
#include
#define SHM_SIZE 1024
int main() {
key_t key = ftok("/tmmmm", 'A');
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
// 获取已存在的共享内存标识符
int shmid = shmget(key, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 连接到共享内存
char *shared_memory = shmat(shmid, NULL, 0);
if (shared_memory == (char*)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 在共享内存中进行读写操作
strcpy(shared_memory, "Hello, Shared Memory8888!");
// 打印共享内存中的内容
while(1)
{
printf("Message in shared memory: %s\n",shared_memory);
sleep(5);
}
// 分离共享内存
if (shmdt(shared_memory) == -1) {
perror("shmdt");
exit(EXIT_FAILURE);
}
return 0;
}
process_communication$ ./shared_memory_touch
Message in shared memory: Hello, Shared Memory!
Message in shared memory: Hello, Shared Memory!
Message in shared memory: Hello, Shared Memory8888!
成功修改内容为Hello, Shared Memory8888!
使用信号是一种在进程之间进行简单通信的方法。信号是一种异步通知机制,允许一个进程向另一个进程发送信号,而另一个进程可以安装信号处理函数来响应这些信号。
非父子进程间发送信号
进程1,等待进程2的信号
#include
#include
#include
#include
// 信号处理函数
void signal_handler(int signum) {
if (signum == SIGUSR1) {
printf("Received SIGUSR1 signal.\n");
}
}
int main() {
// 注册信号处理函数
if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
perror("signal");
exit(EXIT_FAILURE);
}
printf("PID of Process 1: %d\n", getpid());
printf("Waiting for signal...\n");
// 等待信号的到来
pause();
printf("Signal received. Exiting.\n");
return 0;
}
进程2发送中断信号给进程1
#include
#include
#include
#include
int main() {
// 获取进程1的PID,这里假设进程1已经在运行
pid_t pid =3025; /* 进程1的PID */;
// 向进程1发送SIGUSR1信号
if (kill(pid, SIGUSR1) == -1) {
perror("kill");
exit(EXIT_FAILURE);
}
return 0;
}
验证结果
process_communication$ ./signals
PID of Process 1: 3025
Waiting for signal...
Received SIGUSR1 signal.
Signal received. Exiting.
通过套接字,进程可以在网络上进行通信,也可以用于本地进程间通信。套接字提供了一种通用的通信机制,适用于不同主机间的进程通信。
前面讲的基于套接字的通信实在太多了,在这里就不举例了
信号量是一种同步工具,用于协调多个进程对共享资源的访问。
两个进程,互斥:
#include
#include
#include
#include
#include
#include
#define SEM_NAME "/my_semaphore"
int main() {
sem_t *sem;
// 创建或打开信号量
sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
if (sem == SEM_FAILED) {
perror("sem_open");
exit(EXIT_FAILURE);
}
// 生产者等待信号量
printf("Producer waiting...\n");
sem_wait(sem);
// 执行生产者的工作
printf("Producing...\n");
sleep(10);
// 释放信号量
sem_post(sem);
printf("释放信号量\n");
// 关闭并删除信号量
sem_close(sem);
sem_unlink(SEM_NAME);
return 0;
}
#include
#include
#include
#include
#include
#include
#define SEM_NAME "/my_semaphore"
int main() {
sem_t *sem;
// 打开已存在的信号量
sem = sem_open(SEM_NAME, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
exit(EXIT_FAILURE);
}
// 消费者等待信号量
printf("Consumer waiting...\n");
sem_wait(sem);
// 执行消费者的工作
printf("Consuming...\n");
sleep(2);
// 释放信号量
sem_post(sem);
printf("释放信号量\n");
// 关闭信号量
sem_close(sem);
return 0;
}
分别编译:
gcc semaphores_consumer.c -o semaphores_consumer -lpthread
gcc semaphores_producer.c -o semaphores_producer -lpthread
注意:一定要在参数后加-lpthread,否则会报错
结果
进程1:
process_communication$ ./semaphores_producer
Producer waiting...
Producing...
释放信号量
进程2
process_communication$ ./semaphores_consumer
Consumer waiting...
Consuming...
释放信号量
验证成功
必须进程1释放了信号量,进程2才能使用吗?