Linux 系统中的进程通信:标准输入输出详解
2024-01-08 06:22:39
前言
在计算机系统中,进程是执行中的程序,它们彼此独立运行,但又需要相互协作。为了实现进程间的通信,操作系统提供了多种机制,其中标准输入输出是最基本也是最常用的方法。
标准输入输出
标准输入输出(Standard Input/Output,简称 I/O)是操作系统为每个进程默认打开的三个文件:标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)。这三个文件分别对应键盘、显示器和错误输出设备,为进程提供了与外界进行交互和数据传输的通道。
标准输入
标准输入(stdin)用于从键盘读取数据。当您在终端中输入命令或文本时,这些输入都会被写入标准输入。程序可以通过调用 read()
或 scanf()
等函数从标准输入读取数据。
#include <stdio.h>
int main() {
char input[100];
printf("Enter your name: ");
scanf("%s", input);
printf("Hello, %s!\n", input);
return 0;
}
标准输出
标准输出(stdout)用于向显示器输出数据。当程序调用 printf()
或 puts()
等函数时,输出的内容都会被写入标准输出。
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
标准错误输出
标准错误输出(stderr)用于输出错误信息或警告信息。当程序遇到错误或异常时,错误信息通常会通过标准错误输出输出。
#include <stdio.h>
int main() {
fprintf(stderr, "Error: file not found\n");
return 1;
}
管道
管道(Pipe)是一种简单的进程间通信机制,它允许一个进程将数据写入管道,而另一个进程从管道中读取数据。管道通常用于将一个进程的输出作为另一个进程的输入,例如:
$ ls | grep "main.c"
在这个命令中,ls
命令将当前目录下的文件列表输出到管道,而 grep
命令从管道中读取数据,并过滤出包含 "main.c" 字符串的行。
创建管道
可以使用 pipe()
系统调用创建管道。管道由两个文件符组成:一个用于读,一个用于写。
#include <unistd.h>
int main() {
int fd[2];
pipe(fd);
// 将标准输出重定向到管道
dup2(fd[1], STDOUT_FILENO);
// 执行 ls 命令
execlp("ls", "ls", "-l", NULL);
// 从管道中读取数据并打印
close(fd[1]);
char buf[1024];
while (read(fd[0], buf, sizeof(buf)) > 0) {
printf("%s", buf);
}
close(fd[0]);
return 0;
}
使用管道
创建管道后,就可以使用 read()
和 write()
函数在进程之间传递数据。
#include <unistd.h>
int main() {
int fd[2];
pipe(fd);
int pid = fork();
if (pid == 0) {
// 子进程
// 将标准输出重定向到管道
dup2(fd[1], STDOUT_FILENO);
// 执行 ls 命令
execlp("ls", "ls", "-l", NULL);
} else {
// 父进程
// 从管道中读取数据并打印
close(fd[1]);
char buf[1024];
while (read(fd[0], buf, sizeof(buf)) > 0) {
printf("%s", buf);
}
close(fd[0]);
}
return 0;
}
FIFO
FIFO(First In First Out)是一种特殊类型的管道,它允许多个进程同时读写数据。FIFO通常用于在不同进程之间交换数据,例如:
$ mkfifo myfifo
$ cat > myfifo &
$ echo "Hello, world!" > myfifo
在这个命令中,mkfifo
命令创建了一个名为 myfifo
的 FIFO,cat
命令将标准输入重定向到 myfifo
,然后在后台运行。echo
命令将 "Hello, world!" 字符串写入 myfifo
。
创建 FIFO
可以使用 mkfifo()
系统调用创建 FIFO。
#include <sys/stat.h>
#include <fcntl.h>
int main() {
mkfifo("myfifo", 0666);
return 0;
}
使用 FIFO
创建 FIFO 后,就可以使用 open()
函数打开 FIFO,然后使用 read()
和 write()
函数在进程之间传递数据。
#include <sys/stat.h>
#include <fcntl.h>
int main() {
int fd = open("myfifo", O_RDWR);
if (fd == -1) {
perror("open");
exit(1);
}
char buf[1024];
while (read(fd, buf, sizeof(buf)) > 0) {
printf("%s", buf);
}
close(fd);
return 0;
}
消息队列
消息队列(Message Queue)是一种进程间通信机制,它允许进程在内核中创建和维护一个消息队列,并通过这个队列发送和接收消息。消息队列通常用于在不同进程之间传递复杂的数据结构,例如:
$ ipcs -q
这个命令显示了系统中所有的消息队列。
创建消息队列
可以使用 msgget()
系统调用创建消息队列。
#include <sys/msg.h>
int main() {
int msgid = msgget(IPC_PRIVATE, 0666);
if (msgid == -1) {
perror("msgget");
exit(1);
}
return 0;
}
使用消息队列
创建消息队列后,就可以使用 msgsnd()
和 msgrcv()
函数在进程之间发送和接收消息。
#include <sys/msg.h>
int main() {
int msgid = msgget(IPC_PRIVATE, 0666);
if (msgid == -1) {
perror("msgget");
exit(1);
}
struct msgbuf {
long mtype;
char mtext[1024];
};
struct msgbuf msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello, world!");
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(1);
}
struct msgbuf rcv_msg;
if (msgrcv(msgid, &rcv_msg, sizeof(rcv_msg.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("Received message: %s\n", rcv_msg.mtext);
return 0;
}
共享内存
共享内存(Shared Memory)是一种进程间通信机制,它允许进程在内核中创建和维护一块共享内存区域,并通过这块内存区域交换数据。共享内存通常用于在不同进程之间传递大块数据,例如:
$ ipcs -m
这个命令显示了系统中所有的共享内存段。
创建共享内存
可以使用 shmget()
系统调用创建共享内存段。
#include <sys/shm.h>
int main() {
int shmid = shmget(IPC_PRIVATE, 1024, 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
return 0;
}
使用共享内存
创建共享内存段后,就可以使用 shmat()
和 shmdt()
函数将共享内存段映射到进程的地址空间,然后使用普通的内存访问操作在进程之间交换数据。
#include <sys/shm.h>
int main() {