- 主题:求助一个glibc相关的问题
Linux下的C语言编程,glibc版本是glibc_2.31-13+deb11u5.debian
程序逻辑大概是这样的,从一个文件A里用getline()逐行读取,然后对每行进行字符串解析并且做相应的业务逻辑。问题是每次读到一个特定的行,getline返回的数据就会乱掉。用GDB调试之后,发现是getline把当前行的一部分,和之前已经读过的某一行拼在一起返回了。getline()是buffered io,它自己维护一个用户态buffer,先把数据从内核读到自己的用户态buffer,再把buffer里的数据copy到一块malloc出来的内存里返回给调用者。这个用户态buffer满了之后,getline()函数是要再重新读数据填buffer,并且更新相应的指针。但是GDB跟踪发现在出问题的这一行,读到buffer末尾还没找到一个\n,getline()执行重新读数据填buffer的操作,但是执行完毕的结果是指针回到了buffer头,但是buffer里的数据还是旧的。于是之前读过的某一行就这样被拼到了这一行的后面。
关掉业务逻辑,发现简单的getline逐行读取没有这个问题,由此可以断定是某业务代码破坏了getline的执行逻辑。经过缩小范围,发现是调用的某个第三方函数引起的问题。这个函数我没有源码,从注释上看这个函数的功能是dump某些数据到另外一个文件B里面。我怀疑这个函数在dump的过程中做了某种多进程/多线程的操作,或者对文件A的内核buffer产生了某种影响。
现在问题是,有没有大神对buffered io比较熟悉的,能指个方向,到底这个第三方函数可能干了什么事情,才会导致getline()出现上面所说的逻辑错误。
多谢多谢!
--
FROM 43.254.66.*
问题定位到这里,要解决或者规避它不是难事
我感兴趣的是,两个函数到底是怎么冲突的,毕竟它俩处理的是两个不同的文件。
【 在 slowaction 的大作中提到: 】
: 你这个排查过程搞复杂了
: 你先在中间加一个数据交换的逻辑,让这两个函数不要直接碰面
: 经验判断,大概率是因为错误使用了某个函数导致的
--
FROM 43.254.66.*
如果是这么简单的问题,不值得我用GDB苦哈哈的单步跟libc
【 在 slowaction 的大作中提到: 】
: 我看你或者把文件指针搞错了或者把文件对象搞混了
:
--
FROM 43.254.66.*
第三方函数没有写任何数据过去,getline返回的脏数据不是别人写进去的,是真实的从内核返回的文件数据。
我还是搞到了第三方函数的代码,大致梳理了一下它的逻辑,发现这个函数fork了一个子进程,然后waitpid。
于是问题就清楚了,子进程退出的时候,把getline读取的那个FILE的位置重置了。这是Linux系统的逻辑,比如说getline从内核读了4K的数据到自己的用户态buffer里面,然后给调用者返回了1K,那么在进程退出的时候,剩下的3K要还回给内核,于是内核把FILE的已读取位置回退了3K。然后第三方函数执行完毕返回主循环,主循环继续调用getline,那么在getline把4K的用户态buffer用完了之后,再读下一个4K的时候,其实读的是回退的3K加新的1K。于是getline就从用户态copy了旧的数据返回给调用者了。
我自己用类似的方法写了一个函数复现了这个问题。
我真是没办法了才去GDB标准库的。。。
总之,getline确实是thread safe的,因为有锁。但是这个锁保护的只是getline的并发调用,无法保护子进程退出时对buffer的操作。
【 在 slowaction 的大作中提到: 】
: 我理解,你需要先知道哪里错了,写了个什么数据过去
: 然后才能去分析这个错误为什么会导致库异常行为
: 你上去就gdb标准库,这是选了一条最难的路
--
修改:flingfish FROM 43.254.66.*
FROM 43.254.66.*
大哥,你们难道都没听说过屎山能跑就不要动的箴言吗
我不过调用了一个屎山里的API,屎山里的API又辗转十万八千里调用到了这个第三方函数,中间还有一堆鬼知道在干什么的的ctx维护,信号量同步,然后这坨屎山安安稳稳运行了十几年了,你觉得为了这点小问题去大动干戈搞什么多一层的fork,share memory,别的不说,你问问代码review有人肯帮你+2不。。。
大家都现实一点,让自己的生活更加轻松一点不香嘛。。。
【 在 StephenLee 的大作中提到: 】
: 我看下来,重点不是怎么处理读文件的问题,而是调用第三方函数,怎么做好隔离。不应该共享给它的资源,比如文件句柄,就不要给。
: 如果可以的话,调用第三方库也用fork出的进程,把文件句柄都关掉,再调用;传递数据用share mem。
--
FROM 43.254.66.*
每有读一个文件的时候都这么来一下吗。。
话说你们是用C做什么的,我工作十来年,产品代码里面需要我自己写读写文件功能的不超过3次。。。
【 在 moudy 的大作中提到: 】
: 这种多进程读文件踩坑很有可能。我会弄一个进程读文件,pipe给其它的进程。跟系统打交道的api和handle能不share就不要share
--
FROM 43.254.66.*
不明觉厉
【 在 moudy 的大作中提到: 】
: 我们给嵌入式系统提供类似于libc这样的基础io库,各种坑见太多了
:
--
FROM 43.254.66.*
你说的对,我已经打算这么干了。把这坨都隔离了。
搞两个process,一个只管读文件,然后pipe给另外一个process去跑业务。
【 在 StephenLee 的大作中提到: 】
: 你这么说,那不是调用第三方函数的问题呀,整个问题都很大。这一坨都别改,全隔离算了。
: 要不是不知道你调用的频率和依赖,都想劝你包装一个独立程序去跑了,调什么api,debug不够烦的。
--
FROM 203.18.50.*
好问题,这个坑我也是Google了之后才搞清楚。
* The child inherits copies of the parent’s set of open file descrip-
tors. Each file descriptor in the child refers to the same open
file description (see open(2)) as the corresponding file descriptor
in the parent. This means that the two descriptors share open file
status flags, current file offset, and signal-driven I/O attributes
(see the description of F_SETOWN and F_SETSIG in fcntl(2)).
具体来说,先open()后fork()的情况下,父子进程不共享用户态的IO buffer,但是共享内核态的文件状态,其中包括offset。
【 在 paramita555 的大作中提到: 】
:
: 大佬,麻烦再解释一下,为啥子进程的exit修改了它自己的offset,我理解父进程中FILE的offset应当不变啊,它又没有重新打开这个文件,为什么还需要手动ftell/fseek呢?
--
FROM 203.18.50.*