Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊!
喜欢我的博客的话,记得点个红心❤️和小关小注哦!您的支持是我创作的动力!数据源
存放在我的资源下载区啦!
Linux程序开发(八):操作系统进程通信编程
目录
- Linux程序开发(八):操作系统进程通信编程
-
- [1. 问答题](#1. 问答题)
-
- [1.1. 操作系统中进程通信的作用?](#1.1. 操作系统中进程通信的作用?)
- [1.2. Linux进程间通信有哪几种方式?这几种方式之间的特点是什么?](#1.2. Linux进程间通信有哪几种方式?这几种方式之间的特点是什么?)
- [1.3. 查看以下代码:](#1.3. 查看以下代码:)
- [2. 编程题](#2. 编程题)
-
- [2.1. 利用dup/dup2实现往文件中写入数据。要求:在代码中执行两次以下语句:](#2.1. 利用dup/dup2实现往文件中写入数据。要求:在代码中执行两次以下语句:)
- [2.2. 编写程序实现如下功能:创建父子进程,父子进程之间通过管道进行通信,父程向子进程发送英文字符串,子进程接收到该字符串后,将该字符串倒序,并附加上自己的进程pid传回给父进程。](#2.2. 编写程序实现如下功能:创建父子进程,父子进程之间通过管道进行通信,父程向子进程发送英文字符串,子进程接收到该字符串后,将该字符串倒序,并附加上自己的进程pid传回给父进程。)
- [2.3. 利用无名管道pipe()函数、创建进程fork()函数,实现ps -uax | grep root |wc -l命令。](#2.3. 利用无名管道pipe()函数、创建进程fork()函数,实现ps -uax | grep root |wc -l命令。)
- [2.4. 用popen实现无名管道,完成ps -uax|grep root。](#2.4. 用popen实现无名管道,完成ps -uax|grep root。)
1. 问答题
1.1. 操作系统中进程通信的作用?
properties
操作系统中进程通信的作用是允许不同的进程之间进行数据交换和协同工作,以实现共享资源、并发执行和协作完成任务。以下是进程通信的几个主要作用:
1.数据共享:进程通信允许不同的进程之间共享数据。通过共享内存、文件、管道、套接字等通信机制,进程可以将数据传递给其他进程,从而实现信息共享和协同处理。
2.资源共享:多个进程可以通过进程通信来共享系统资源,如文件、设备、数据库等。例如,一个进程可以将其打开的文件描述符传递给另一个进程,使其可以读取或写入同一个文件。
3.进程协作:进程通信使得不同的进程能够协同工作,共同完成复杂的任务。例如,一个进程可以将任务分解为多个子任务,并将这些子任务分配给其他进程进行处理,最后再将结果合并。
4.进程同步:当多个进程同时访问共享资源时,需要进行进程同步以避免竞态条件和数据不一致问题。进程通信提供了各种同步机制,如信号量、互斥锁、条件变量等,用于控制进程的访问顺序和临界区的互斥访问。
5.进程间通信:进程通信还可以用于不同计算机之间的通信,实现分布式系统和网络应用。通过网络协议和套接字等通信方式,进程可以在不同主机上进行远程通信和数据交换。
总而言之,进程通信在操作系统中起着重要的作用,它使得不同的进程能够相互协作、共享资源和信息,并实现并发执行和分布式计算。
1.2. Linux进程间通信有哪几种方式?这几种方式之间的特点是什么?
properties
在Linux中,常见的进程间通信方式包括以下几种:
1.管道(Pipe):管道是一种半双工的通信方式,用于具有亲缘关系的父子进程之间或者兄弟进程之间的通信。管道通过创建一个字节流来实现进程间的数据传输,数据在管道中按先进先出(FIFO)的顺序传递。
特点:
(1)只能用于具有亲缘关系的进程间通信。
(2)只能实现单向通信,需要建立两个管道才能实现双向通信。
(3)数据传输效率高,但容量有限。
2.命名管道(Named Pipe):命名管道也是一种半双工的通信方式,但不同于管道,它允许无亲缘关系的进程之间进行通信。命名管道在文件系统中有对应的文件节点,可以通过文件路径进行访问。
特点:
(1)适用于无亲缘关系的进程间通信。
(2)可以实现单向或双向通信。
(3)数据传输效率高,但容量有限。
3.共享内存(Shared Memory):共享内存是一种高效的进程间通信方式,它允许多个进程直接访问同一块物理内存区域,从而实现数据共享。进程通过将共享内存映射到各自的虚拟地址空间中,来进行读写操作。
特点:
(1)数据传输效率高,因为进程直接访问内存而无需复制数据。
(2)需要进程间进行同步和互斥操作,以避免竞态条件和数据一致性问题。
(3)适用于大量数据交换和频繁访问的场景。
4.消息队列(Message Queue):消息队列是一种按消息进行通信的方式,消息被放入队列中并按照一定的优先级被取出。进程可以通过消息队列发送和接收消息,实现进程间的异步通信。
特点:
(1)适用于多个进程之间的数据传输和同步。
(2)支持点对点和发布/订阅模式。
(3)可以设置消息的优先级,确保高优先级消息先被处理。
(4)适用于多种类型和大小的数据传输。
5.信号量(Semaphore):信号量是一种计数器,用于控制多个进程对共享资源的访问。进程可以通过对信号量进行P(等待)和V(释放)操作来实现进程间的同步和互斥。
特点:
(1)主要用于进程间的同步和互斥。
(2)可以用于限制对共享资源的访问数量。
(3)适用于多进程并发控制。
6.套接字(Socket):套接字是一种网络编程接口,可以用于不同主机之间的进程通信。通过套接字,进程可以在网络上发送和接收数据,实现远程通信。
特点:
(1)适用于不同主机之间的进程通信。
(2)支持多种传输协议和通信方式,如TCP、UDP等。
(3)能够实现分布式计算和网络应用。
1.3. 查看以下代码:
int fd1, fd2, fd3, fd4;
fd1 = open("a.txt", O_RDONLY);
fd2 = open("b.txt", O_WRONLY);
fd3 = dup(fd1);
fd4 = dup2(fd2, 0);
![image-6.png](attachment:image-6.png)
假设当前终端没有打开任何正常文件,请问:最后fd1、fd2、fd3和fd4的值为多少?并解释原因。
提示:
- 系统调用dup()和dup2()都能够复制文件描述符。
- dup返回新的文件描述符,成功时返回最小的尚未被使用的文件描述符。
- dup2可以让用户指定返回的文件描述符的值,它通常用来重新打开或者重定向一个文件描述符。
properties
# 根据给定的代码片段和提示,可以得出以下结论:
fd1:调用open("a.txt", O_RDONLY)打开文件"a.txt",以只读方式打开,返回一个新的文件描述符。因为当前终端没有打开任何正常文件,所以open()函数返回的文件描述符应该是最小的尚未被使用的文件描述符。因此,fd1的值应为一个大于或等于3的整数。
fd2:调用open("b.txt", O_WRONLY)打开文件"b.txt",以只写方式打开,返回一个新的文件描述符。同样地,因为当前终端没有打开任何正常文件,所以fd2的值应该是一个大于或等于3的整数,且与fd1不同。
fd3:调用dup(fd1)复制文件描述符fd1,返回一个新的文件描述符。由于dup函数复制的是最小的尚未被使用的文件描述符,所以fd3的值应该等于fd1的值。
fd4:调用dup2(fd2, 0)将文件描述符fd2复制到标准输入文件描述符0,并返回0。因为dup2函数可以让用户指定返回的文件描述符的值,这里将fd2复制到了标准输入文件描述符0,所以fd4的值应为0。
综上所述:
fd1的值应为大于或等于3的整数。
fd2的值应为大于或等于3的整数,且与fd1不同。
fd3的值应与fd1相同。
fd4的值应为0。
2. 编程题
2.1. 利用dup/dup2实现往文件中写入数据。要求:在代码中执行两次以下语句:
printf("Hello Linux\n");
前一次输出到文件c.txt中,后一次输出到屏幕上。
提示:printf语句后增加fflush(NULL);语句,刷新打开的流。
(1)编写C语言程序dup_test.c
python
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("c.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("Failed to open file");
return 1;
}
int stdout_copy = dup(STDOUT_FILENO); // 备份stdout文件描述符
// 将标准输出重定向到文件
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("Failed to redirect stdout to file");
close(fd);
return 1;
}
printf("Hello Linux\n");
fflush(NULL); // 刷新输出流
// 恢复标准输出到屏幕
if (dup2(stdout_copy, STDOUT_FILENO) == -1) {
perror("Failed to restore stdout");
close(fd);
return 1;
}
printf("Hello Linux\n"); // 输出到屏幕
close(fd);
return 0;
}
(2)用gcc编译器进行程序编译
properties
gcc -o dup_test dup_test.c
(3)运行程序
properties
./dup_test
2.2. 编写程序实现如下功能:创建父子进程,父子进程之间通过管道进行通信,父程向子进程发送英文字符串,子进程接收到该字符串后,将该字符串倒序,并附加上自己的进程pid传回给父进程。
例如:父进程发出Hello World!;子进程处理返回!dlroW olleH2709,2709是子进程号。
(1)在linux上编写c语言程序pipe_communication.c
python
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 100
int main() {
int fd[2]; // 管道文件描述符数组
pid_t pid;
char input_string[] = "Hello World!";
char read_buffer[BUFFER_SIZE];
if (pipe(fd) < 0) {
perror("Pipe creation failed");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid < 0) {
perror("Fork failed");
exit(EXIT_FAILURE);
}
if (pid > 0) { // 父进程
close(fd[0]); // 关闭读端
// 向子进程发送字符串
write(fd[1], input_string, strlen(input_string)+1);
close(fd[1]); // 关闭写端
// 从子进程读取处理后的字符串
read(fd[0], read_buffer, BUFFER_SIZE);
printf("Received from child process: %s\n", read_buffer);
close(fd[0]); // 关闭读端
} else { // 子进程
close(fd[1]); // 关闭写端
// 从父进程接收字符串
read(fd[0], read_buffer, BUFFER_SIZE);
printf("Received from parent process: %s\n", read_buffer);
// 处理字符串,倒序并附加进程id
int pid = getpid();
int length = strlen(read_buffer);
for (int i = 0; i < length / 2; i++) {
char temp = read_buffer[i];
read_buffer[i] = read_buffer[length - i - 1];
read_buffer[length - i - 1] = temp;
}
char pid_str[20];
sprintf(pid_str, "%d", pid);
strcat(read_buffer, pid_str);
// 将处理后的字符串发送给父进程
write(fd[1], read_buffer, strlen(read_buffer)+1);
close(fd[0]); // 关闭读端
close(fd[1]); // 关闭写端
}
return 0;
}
(2)用gcc编译器进行程序编译
properties
gcc -std=gnu99 -o pipe_communication pipe_communication.c
(3)运行程序
properties
./pipe_communication
2.3. 利用无名管道pipe()函数、创建进程fork()函数,实现ps -uax | grep root |wc -l命令。
(1)在linux上编写c语言程序pipe_test.c
python
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define READ_END 0
#define WRITE_END 1
int main() {
int pipe1[2], pipe2[2];
pid_t pid1, pid2;
if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
perror("Pipe creation failed");
exit(EXIT_FAILURE);
}
pid1 = fork();
if (pid1 < 0) {
perror("Fork failed");
exit(EXIT_FAILURE);
} else if (pid1 == 0) { // 子进程1
close(pipe1[READ_END]);
dup2(pipe1[WRITE_END], STDOUT_FILENO);
execlp("ps", "ps", "-uax", NULL);
perror("Exec failed");
exit(EXIT_FAILURE);
} else { // 父进程
pid2 = fork();
if (pid2 < 0) {
perror("Fork failed");
exit(EXIT_FAILURE);
} else if (pid2 == 0) { // 子进程2
close(pipe1[WRITE_END]);
close(pipe2[READ_END]);
dup2(pipe1[READ_END], STDIN_FILENO);
dup2(pipe2[WRITE_END], STDOUT_FILENO);
execlp("grep", "grep", "root", NULL);
perror("Exec failed");
exit(EXIT_FAILURE);
} else { // 父进程
close(pipe1[READ_END]);
close(pipe1[WRITE_END]);
close(pipe2[WRITE_END]);
dup2(pipe2[READ_END], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
perror("Exec failed");
exit(EXIT_FAILURE);
}
}
return 0;
}
(2)用gcc编译器进行程序编译
properties
gcc -o pipe_test pipe_test.c
(3)运行程序
properties
./pipe_test
ps -uax | grep root |wc -l
2.4. 用popen实现无名管道,完成ps -uax|grep root。
(1)在linux上编写c语言程序popen.c
python
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE* fp1, *fp2;
char cmd1[] = "ps -uax";
char cmd2[] = "grep root";
fp1 = popen(cmd1, "r");
if (fp1 == NULL) {
perror("Popen failed");
exit(EXIT_FAILURE);
}
fp2 = popen(cmd2, "w");
if (fp2 == NULL) {
perror("Popen failed");
exit(EXIT_FAILURE);
}
int c;
while ((c = fgetc(fp1)) != EOF) {
fputc(c, fp2);
}
pclose(fp1);
pclose(fp2);
return 0;
}
(2)用gcc编译器进行程序编译
properties
gcc -o popen popen.c
(3)运行程序
properties
./popen
ps -uax|grep root