面试题答案
一键面试内核机制角度
当匿名管道的缓冲区已满,继续向管道写入数据时:
- 阻塞I/O情况:如果管道是阻塞I/O模式(默认情况),write调用会阻塞,直到管道的读端从管道中读取数据,腾出足够的空间,write操作才会继续执行并成功返回写入的字节数。
- 非阻塞I/O情况:如果管道设置为非阻塞I/O模式(通过fcntl函数设置O_NONBLOCK标志),当缓冲区满时,write调用会立即返回 -1,并且将errno设置为EAGAIN或EWOULDBLOCK,表示当前没有足够的空间写入数据。
应用层编程角度处理方法
- 阻塞I/O处理:在阻塞I/O模式下,应用程序不需要额外的处理逻辑,write调用会自动等待直到有足够空间。但是这种方式可能导致应用程序在管道满时长时间等待,影响程序的响应性。
- 非阻塞I/O处理:
- 轮询方式:通过循环调用write,每次失败后等待一段时间再尝试,直到写入成功。这种方式简单,但会浪费CPU资源。
- 使用多路复用技术(如select、poll、epoll):这些函数可以监听管道的可写事件,当管道有空间写入数据时,相应的文件描述符会被标记为可写,应用程序可以在此时调用write进行写入操作。
示例代码
以下是使用非阻塞I/O和select多路复用处理管道写入的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/select.h>
#define BUFFER_SIZE 1024
int main() {
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 设置管道写端为非阻塞
int flags = fcntl(pipefd[1], F_GETFL, 0);
fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);
pid_t cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else if (cpid == 0) { // 子进程(读端)
close(pipefd[1]); // 关闭写端
char buffer[BUFFER_SIZE];
ssize_t numRead;
while ((numRead = read(pipefd[0], buffer, BUFFER_SIZE)) > 0) {
write(STDOUT_FILENO, buffer, numRead);
}
if (numRead == -1) {
perror("read");
}
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { // 父进程(写端)
close(pipefd[0]); // 关闭读端
char *message = "Hello, pipe!\n";
fd_set write_fds;
FD_ZERO(&write_fds);
FD_SET(pipefd[1], &write_fds);
struct timeval timeout;
timeout.tv_sec = 5; // 等待5秒
timeout.tv_usec = 0;
int select_ret = select(pipefd[1] + 1, NULL, &write_fds, NULL, &timeout);
if (select_ret == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (select_ret) {
ssize_t write_ret = write(pipefd[1], message, strlen(message));
if (write_ret == -1) {
perror("write");
}
} else {
printf("Timeout waiting for pipe to be writable\n");
}
close(pipefd[1]);
wait(NULL); // 等待子进程结束
exit(EXIT_SUCCESS);
}
}
此代码中,父进程设置管道写端为非阻塞,并使用select监听管道的可写事件,在管道可写时进行数据写入;子进程从管道读端读取数据并输出到标准输出。这样可以优雅地处理管道缓冲区满的情况,避免数据丢失或程序异常。