Bash中stdout输出异常:Linux I/O程序运行卡顿问题
Linux I/O程序卡顿问题排查与修复
问题背景
学习Linux I/O编程时编写了一段从标准输入读取内容并输出到标准输出的代码,在CLion调试模式下运行正常,但在Bash终端中输入几次后程序出现卡顿。使用的rio_前缀函数来自《Computer Systems: A Programmer's Perspective》一书。
主程序代码
int main(int argc,char **argv){ int n; int acc= 0; rio_t rio; char buf[MAXLINE]; char *readp = buf; rio_readinitb(&rio,STDIN_FILENO); while((n= rio_readlineb(&rio,readp,MAXLINE)) != 0){ readp = readp +n; acc += n; write(STDOUT_FILENO,"Outputs\n",8); write(STDOUT_FILENO,"--------\n",9); rio_writen(STDOUT_FILENO,buf,acc); } }
相关rio_函数实现代码
ssize_t rio_writen(int fd,void *usrbuf,size_t n){ size_t nleft = n; ssize_t nwritten; char* bufp = usrbuf; while(nleft >0){ if((nwritten = write(fd,bufp,nleft)) <= 0 ){ if (errno = EINTR){ // 错误:赋值而非比较 nwritten = 0; }else{ return -1; } } nleft -= nwritten; bufp += nwritten; } return n; } // Buffered I/O void rio_readinitb(rio_t *rp,int fd){ rp-> rio_fd = fd; rp-> rio_cnt = 0; rp-> rio_bufptr = rp->rio_buf; } static ssize_t rio_read(rio_t *rp,char *usrbuf,size_t n){ /* Refill if buffer is empty*/ while(rp->rio_cnt <= 0){ rp->rio_cnt = read(rp->rio_fd,rp->rio_buf,sizeof(rp->rio_buf)); if(rp->rio_cnt <0){ if(errno != EINTR){ return -1; } } else if(rp->rio_cnt == 0){ return 0; }else { rp->rio_bufptr = rp->rio_buf; } } size_t cnt = rp->rio_cnt; if(n < rp->rio_cnt){ cnt = n; } memcpy(usrbuf,rp->rio_bufptr,cnt); rp-> rio_bufptr += cnt; rp->rio_cnt -= cnt; return cnt; } ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen){ int n,rc; char* bufp = usrbuf; char c; for(n = 0;n<maxlen;n++){ if((rc= rio_read(rp,&c,1))== 1){ *bufp++ = c; if(c == '\n'){ n++; break; } }else if(rc == 0){ if(n==0){ return 0; } else{ break; } }else{ return -1; } } *bufp = 0; return n; } ssize_t rio_readbnb(rio_t *rp,void* usrbuf,size_t n){ size_t nleft = n; ssize_t nread; char *bufp = usrbuf; while(nleft > 0){ if(nread = rio_read(rp,bufp,n) < 0){ // 错误:运算符优先级问题 if (errno == EINTR){ nread = 0; }else{ return -1; } }else if(nread == 0){ break; } nleft -= nread; bufp += nread; } return (n-nleft); }
问题原因分析
1. 主程序缓冲区溢出
主程序中buf是固定大小的栈数组(MAXLINE),但readp持续向后移动、acc持续累加读取的字节数,多次输入后readp会超出buf的边界,触发栈缓冲区溢出。调试模式下CLion通常会启用栈保护机制(如-fstack-protector),能阻止程序崩溃,但在Bash直接运行时无此保护,导致未定义行为(表现为卡顿、崩溃或异常)。
2. rio_writen函数的逻辑错误
rio_writen中errno = EINTR是赋值操作而非比较操作,导致errno被错误设置为EINTR,后续错误处理逻辑完全失效。
3. rio_readbnb函数的运算符优先级问题
nread = rio_read(rp,bufp,n) < 0中,<的优先级高于=,实际执行顺序是nread = (rio_read(...) < 0),nread会被赋值为0或1(比较结果),而非实际读取的字节数,导致读取逻辑混乱。
修复方案
修复主程序(解决缓冲区溢出)
每次处理完一行后,直接输出当前读取的行内容,避免持续占用缓冲区:
#include "csapp.h" // 假设包含rio_t定义和MAXLINE常量 int main(int argc,char **argv){ int n; rio_t rio; char buf[MAXLINE]; rio_readinitb(&rio,STDIN_FILENO); while((n= rio_readlineb(&rio,buf,MAXLINE)) != 0){ write(STDOUT_FILENO,"Outputs\n",8); write(STDOUT_FILENO,"--------\n",9); rio_writen(STDOUT_FILENO,buf,n); // 直接输出当前读取的n字节 } }
修复rio_writen函数
将赋值操作改为比较操作:
ssize_t rio_writen(int fd,void *usrbuf,size_t n){ size_t nleft = n; ssize_t nwritten; char* bufp = usrbuf; while(nleft >0){ if((nwritten = write(fd,bufp,nleft)) <= 0 ){ if (errno == EINTR){ // 修正为比较操作 nwritten = 0; }else{ return -1; } } nleft -= nwritten; bufp += nwritten; } return n; }
修复rio_readbnb函数
给赋值表达式添加括号,确保正确的执行顺序:
ssize_t rio_readbnb(rio_t *rp,void* usrbuf,size_t n){ size_t nleft = n; ssize_t nread; char *bufp = usrbuf; while(nleft > 0){ if((nread = rio_read(rp,bufp,n)) < 0){ // 添加括号修正优先级 if (errno == EINTR){ nread = 0; }else{ return -1; } }else if(nread == 0){ break; } nleft -= nread; bufp += nread; } return (n-nleft); }
验证
修复后,在Bash终端运行程序,多次输入内容将正常输出,不会出现卡顿或异常。
内容的提问来源于stack exchange,提问作者H.Okan
相关产品推荐
相关产品推荐

