进程间通信(Inter-Process Communication,IPC)是指两个或多个进程之间进行数据交换和共享信息的机制。在操作系统中,不同的进程可能运行在不同的地址空间,因此需要一些特殊的机制来使它们能够协同工作。
以下是一些常见的进程间通信的方式:
-
管道(Pipes):
- 单向通信,用于父子进程或者兄弟进程之间的通信。
- 可以是匿名管道(使用
pipe
函数)或命名管道(使用mkfifo
函数)。
-
消息队列(Message Queues):
- 允许进程通过消息进行通信,可以实现多对多的通信。
- 使用
msgget
、msgsnd
、msgrcv
等系统调用。
-
共享内存(Shared Memory):
- 两个或多个进程可以访问相同的内存区域,实现高效的数据交换。
- 使用
shmget
、shmat
、shmdt
等系统调用。
-
信号量(Semaphores):
- 用于控制多个进程对共享资源的访问,防止竞争条件。
- 使用
semget
、semop
等系统调用。
-
套接字(Sockets):
- 在网络编程中使用,也可以用于本地进程通信。
- 使用
socket
、bind
、connect
等系统调用。
-
文件锁(File Locking):
- 使用文件锁机制来实现进程间的同步。
- 使用
fcntl
系统调用。
-
信号(Signals):
- 用于通知进程发生了某个事件。
- 使用
kill
和signal
等系统调用。
-
命名管道(Named Pipes):
- 类似于匿名管道,但允许无关的进程之间进行通信。
- 使用
mkfifo
函数。
下面看几个具体的例子:
eg.1.创建一个子进程,子进程向管道写入一段字符串,而父进程从管道读取该字符串并打印出来
#include <sys/wait.h>
void main()
{
int x, fd[2];//定义整型变量和一个包含两个元素的整型数组,用于存储管道的文件描述符。
char buf[30], s[30];//定义两个字符数组,用于存储要传输的数。
// 创建管道
if (pipe(fd) == -1) {//创建管道,fd[0]用于读取,fd[1]用于写入。
perror("pipe");
return;
}
// 创建子进程
while ((x = fork()) == -1);//使用fork创建子进程,如果创建失败则一直尝试。
if (x == 0)/*在Linux系统中,fork 函数调用成功后,它会在父进程和子进程中分别返回不同的值:在父进程中,fork 返回子进程的进程ID(PID),这是一个正整数。在子进程中,fork 返回0。因此,if (x == 0) 这样的判断语句实际上是在检查当前代码是否正在子进程中执行。如果条件成立(x 等于 0),那么就执行对应于子进程的代码块。*/
{
// 子进程向管道写入数据
sprintf(buf, "This is an example \n");//将字符串格式化并存储到buf数组中。
if (write(fd[1], buf, 30) == -1) {//将数据写入管道。
perror("write");
return;
}
}
else//在父进程中执行以下代码块。
{
// 父进程等待子进程执行完毕
wait(NULL);//等待子进程执行完毕
// 父进程从管道读取数据
if (read(fd[0], s, 30) == -1) {//从管道读取数据到s数组。
perror("read");
return;
}
// 打印从管道读取的数据
printf("%s", s);
}
}
编译运行:
终端输出:
eg.2.一个父进程创建两个子进程的示例,通过无名管道进行通信
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/wait.h>
void main() {
int i, r, p1, p2, fd[2];
char buf[50], s[50];
// 创建管道
pipe(fd);
// 创建第一个子进程 (p1)
while ((p1 = fork()) == -1);
if (p1 == 0) {
// 在子进程 p1 中锁定写入管道的文件描述符
lockf(fd[1], 1, 0);//锁定整个文件,以确保在写入数据到管道的时候,不会被其他进程同时写入。
// 向管道中写入消息
sprintf(buf, "child process p1 is sending messages!\n");
printf("child process p1!\n");
write(fd[1], buf, 50);
// 等待5秒
sleep(5);
// 解锁文件描述符
lockf(fd[1], 0, 0);
// 子进程 p1 退出
exit(0);
} else {
waitpid(p1,NULL,0);
// 创建第二个子进程 (p2)
while ((p2 = fork()) == -1);
if (p2 == 0) {
// 在子进程 p2 中锁定写入管道的文件描述符
lockf(fd[1], 1, 0);
// 向管道中写入消息
sprintf(buf, "child process p2 is sending messages\n");
printf("child process p2!\n");
write(fd[1], buf, 50);
// 等待5秒
sleep(5);
// 解锁文件描述符
lockf(fd[1], 0, 0);
// 子进程 p2 退出
exit(0);
} else {
// 等待第一个子进程 p1 结束
wait(0);
// 从管道中读取数据到缓冲区 s
if ((r = read(fd[0], s, 50)) == -1)
printf("can't read pipe\n");
else
printf("%s\n", s);
// 等待第二个子进程 p2 结束
wait(0);
// 再次从管道中读取数据到缓冲区 s
if ((r = read(fd[0], s, 50)) == -1)
printf("can't read pipe\n");
else
printf("%s\n", s);
// 父进程退出
exit(0);
}
}
}
~ ~ ~
--------------------------------------------------------------------------------------------------------------------------------
sprintf()和printf()的区别:
`sprintf` 和 `printf` 都是 C 语言中用于格式化输出的函数,但它们有一些关键的区别:
1. 输出位置:
- `printf` 将格式化的文本输出到标准输出流(通常是终端或控制台)。
- `sprintf` 将格式化的文本输出到字符串中,而不是标准输出。你需要提供一个目标字符串的缓冲区,`sprintf` 将格式化的内容写入这个缓冲区。
2. 返回值:
- `printf` 的返回值是输出的字符数(成功时),如果发生错误,则返回负值。
- `sprintf` 的返回值是写入字符串的字符数,不包括字符串结尾的空字符 `\0`。
3. 用途:
- `printf` 主要用于在屏幕上输出信息,方便用户查看。
- `sprintf` 主要用于将格式化的字符串保存到内存中,以后可以进一步使用或处理。
简单的一个示例:
#include <stdio.h>
int main() {
char buffer[100];
int value = 42;
// 使用 sprintf 将格式化的内容写入缓冲区
sprintf(buffer, "The value is: %d", value);
// 使用 printf 将格式化的内容输出到屏幕
printf("The value is: %d\n", value);
// 输出 sprintf 的结果
printf("sprintf result: %s\n", buffer);
return 0;
}
```
在这个例子中,`sprintf` 将格式化的字符串写入了 `buffer`,而 `printf` 直接将格式化的内容输出到屏幕。