使用lsof侦测程序句柄泄漏

Unix/Linux 中一切皆文件,socket、设备、目录等都是用文件来表示,_进程_操作这些对象首先要打开文件,打开时返回一个整数文件句柄,句柄用于唯一标识进程和对象之间的连接关系。

open文件的时候,会调用get_unused_id()来分配一个未被占用的文件描述符。

有一次去SK公司玩,一起分析一个文件句柄泄漏。用 lsof 观察发现/tmp/目录下有大量文件没有被关闭,就删除了,造成句柄泄漏。单个进程句柄使用超过了(ulimit -n, 1024)的限制。 通过ulimit -n 2048扩大文件描述符数量限制并不能从根本解决问题。ulimit 还可以用来设置更多系统资源限制,比如内存core大小、进程数目等。

由于这里的文件都是/tmp/目录下,所以很快想到是python程序当中零时文件创建操作引起的。grep后发现有三处使用了python temfile模块使用了零时文件,通过修改零时文件前缀可以定位具体出错位置。

调试发现文件被open了两次,只有close一次。tempfile.mkstemp(prefix=”tmp1_”) 会返回一个文件句柄和临时文件路径,这个临时文件_已经被分配文件句柄_了!!

[amos@amosk devel]$ python
Python 2.7.5 (default, Feb 19 2014, 13:47:28) 
[GCC 4.8.2 20131212 (Red Hat 4.8.2-7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tempfile
>>> tempfile.mkstemp()
(3, '/tmp/tmptcK5Fn')

[amos@amosk devel]$ lsof /tmp/tmptcK5Fn
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
python  5759 amos    3u   REG   0,32        0 74511 /tmp/tmptcK5Fn
# 占用了文件句柄 3

>>> f = open('/tmp/tmptcK5Fn', 'w')

[amos@amosk devel]$ lsof /tmp/tmptcK5Fn
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
python  5759 amos    3u   REG   0,32        0 74511 /tmp/tmptcK5Fn
python  5759 amos    4w   REG   0,32        0 74511 /tmp/tmptcK5Fn
# 两个文件句柄被占用 3,4
>>> f.close()

[amos@amosk devel]$ lsof /tmp/tmptcK5Fn
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
python  5759 amos    3u   REG   0,32        0 74511 /tmp/tmptcK5Fn
# close后文件句柄 3 还被占用

>>> os.remove('/tmp/tmptcK5Fn')

[amos@amosk devel]$ lsof -p $pid |grep tmpmEt_pM
python  12459 amos    3u   REG               0,32         0  182528 /tmp/tmpmEt_pM (deleted)
# 文件已被删除,但句柄未被释放

* tempfile.mkstemp()
返回一个文件句柄,和文件路径,此时文件句柄已被分配。正确的使用方法是,通过os.fdopen()打开文件。

# 正确使用方法1
fd,path = tempfile.mkstemp()
f = os.fdopen(fd, 'w')
f.close()

# 正确使用方法2
fd,path = tempfile.mkstemp()
....
os.close(fd)

* tempfile.mktemp()
只返回一个唯一的临时文件路径,并不打开文件。只需要正常使用返回的路径打开使用关闭文件就行。

* tempfile.TemporaryFile() 返回一个类文件对象,用于临时数据保存(实际上对应磁盘上的一个临时文件)。当文件对象被close或者被del的时候,临时文件将从磁盘上删除。

tempfile.TemporaryFile([mode='w+b'[, bufsize=-1[, suffix=''[, prefix='tmp'[, dir=None]]]]])

>>> f = tempfile.TemporaryFile()
# 用 lsof 查看,这个临时文件是被删除状态 /tmp/tmpchVMut (deleted)

>>> f.write("hellon")
>>> f.seek(0) # 移动指针读取文件
>>> f.read()
'hellon'
>>> f.close() # 句柄被释放

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.