自定义shell hsh读取管道输入触发SIGPIPE信号求助
问题:自定义Shell(hsh)读取管道输入时触发SIGPIPE信号终止
我在开发名为hsh的自定义Shell时,尝试将用户传入的全部数据读取到缓冲区处理,但进程因signal 13(SIGPIPE,管道破裂信号)终止。
执行命令
valgrind echo " /bin/ls " | ./hsh
执行结果
==203856== Process terminating with default action of signal 13 (SIGPIPE) ==203856== at 0x4960077: write (write.c:26) ==203856== by 0x48E0E8C: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1181) ==203856== by 0x48E17A7: new_do_write (fileops.c:449) ==203856== by 0x48E17A7: _IO_new_file_xsputn (fileops.c:1255) ==203856== by 0x48E17A7: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1197) ==203856== by 0x48E037F: fputs_unlocked (iofputs_u.c:37) ==203856== by 0x10A7AE: ??? (in /usr/bin/echo) ==203856== by 0x4876082: (below main) (libc-start.c:308) ==203856== ==203856== HEAP SUMMARY: ==203856== in use at exit: 0 bytes in 0 blocks ==203856== total heap usage: 228 allocs, 228 frees, 25,267 bytes allocated ==203856== ==203856== All heap blocks were freed -- no leaks are possible ==203856== ==203856== For lists of detected and suppressed errors, rerun with: -s ==203856== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
数据读取步骤
- 使用
lseek定位到文件末尾,统计写入的字节数 - 再次使用
lseek回到文件起始位置 - 分配对应字节数+1(用于空字符)的缓冲区
- 分块读取数据到临时缓冲区
- 将临时缓冲区的数据复制到实际缓冲区
但该方法触发了SIGPIPE信号,不清楚原因。
代码实现
注:所有名称带下划线(_)的函数已在别处实现,其原型声明在"main.h"头文件中。
#include "main.h" #define CHUNK_SIZE 1000 /** * _getlines - Read the command lines * @commands: The list where commands will be stored * Return: On success - number of commands * On error - "-1" */ int _getlines(char **commands) { char *line, *page, *chunk, ch, qoute; ssize_t bytes_read, total_bytes_read = 0; int j = 0, cmd_count = 0, i = 0, file_size; line = malloc(CHUNK_SIZE * sizeof(char)); file_size = lseek(STDIN_FILENO, 0, SEEK_END); lseek(STDIN_FILENO, 0, SEEK_SET); page = malloc(file_size); if (page == NULL) return (-1); while ((bytes_read = read(STDIN_FILENO, line, CHUNK_SIZE) > 0)) { _strcpy(page + total_bytes_read, line); total_bytes_read += bytes_read; } if (bytes_read == -1) { free(page); return (-1); } printf("%s\n", page); while (i < total_bytes_read) { ch = page[i++]; if (ch == '\"' || ch == '\'') { line[j++] = ch; qoute = ch; do { ch = page[i++]; line[j++] = ch; } while (ch != qoute); } else if (ch == '\n') { line[j++] = '\0'; commands[cmd_count] = _strdup(line); j = 0; (cmd_count)++; } else line[j++] = ch; } if (cmd_count == 0) { commands[cmd_count] = _strdup(line); cmd_count++; } free(line); return (cmd_count); }
问题分析与解决
核心原因
从Valgrind输出可以看到,SIGPIPE信号是由echo进程触发的,而非你的hsh进程。当管道的读端(hsh)提前关闭,而写端(echo)还在尝试写入数据时,就会触发SIGPIPE信号。
你的代码存在几个关键问题:
lseek不适用于管道:管道是流式设备,不支持随机访问,lseek(STDIN_FILENO, 0, SEEK_END)会直接返回-1,但你未检查返回值,导致file_size为负数,后续malloc(file_size)会分配超大内存块(malloc接受无符号参数,负数会被转为大正数),引发一系列异常。- 读取循环逻辑错误:
while ((bytes_read = read(STDIN_FILENO, line, CHUNK_SIZE) > 0))的括号位置错误,read的结果先和0比较,再赋值给bytes_read,导致bytes_read只能是0或1,而非实际读取的字节数。这会让你错误处理数据,甚至提前结束读取,使hsh关闭管道读端,此时echo仍在写入,触发SIGPIPE。 - 缓冲区越界风险:
line仅分配了CHUNK_SIZE大小的内存,但处理输入时未检查j是否超过该大小,容易导致缓冲区溢出,破坏进程内存,引发不可预测的终止行为。
修复步骤
- 移除管道上的
lseek操作:对于管道、套接字等流式输入,只能通过循环读取直到read返回0(输入结束)或-1(错误),无法提前获取总长度。 - 修正读取循环的括号:正确写法为
while ((bytes_read = read(STDIN_FILENO, line, CHUNK_SIZE)) > 0),确保bytes_read获取实际读取的字节数。 - 动态扩展缓冲区:因为无法提前知道输入长度,需动态扩展
page缓冲区,比如每次读取后用realloc重新分配更大的内存。 - 添加缓冲区边界检查:向
line写入数据时,检查j是否接近CHUNK_SIZE,避免溢出。 - 检查系统调用返回值:
lseek、malloc、read等系统调用都可能失败,必须检查返回值并处理错误。
修复后的读取逻辑示例
// 替换原有的读取逻辑部分 ssize_t bytes_read; char *page = NULL; size_t total_bytes_read = 0; char line[CHUNK_SIZE]; // 用栈数组代替malloc,减少内存管理负担 while ((bytes_read = read(STDIN_FILENO, line, CHUNK_SIZE)) > 0) { // 动态扩展page缓冲区 char *new_page = realloc(page, total_bytes_read + bytes_read + 1); if (!new_page) { free(page); return -1; } page = new_page; memcpy(page + total_bytes_read, line, bytes_read); total_bytes_read += bytes_read; } if (bytes_read == -1) { free(page); return -1; } // 确保缓冲区以空字符结尾 if (page) { page[total_bytes_read] = '\0'; }
内容的提问来源于stack exchange,提问作者Mohammed Ahmed
相关产品推荐
相关产品推荐

