瞎看 -- 文件描述符标志

提出问题

今天在阅读tornado源码时,开篇就遇见了这段代码

1
2
3
4
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)

他究竟在做什么?这是什么玩意?硬生生的给我的脑袋上加了很多问号。

不知道怎么办, 查呗!

fcntl.F_GETFD # 获取文件描述符标记 fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags) # 重新设置文件描述符

什么是文件描述符

内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

其实就是系统为文件设置的一个别名,用一个小整数来对应相应的文件,而且内核会为每一个进程都维护一个对应的描述符表

1
self._socket.fileno()

这行代码就是获取socket对应的文件描述符

在Linux的哲学中一切皆文件,键盘鼠标也是文件,当然socket也是文件

描述符标志

会到代码,以上代码设置的语句是

1
2
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)

fcntl.FD_CLOEXEC close_on_exec

close_on_exec: fork子进程时,子进程获得父进程的数据、堆和栈副本,这其中也包括文件描述符,创建之初时,父子进程中相同的文件描述符指向系统文件表中的同一项,但是通常子进程需要使用exec执行自己的逻辑,使用新的程序替换原有的数据,堆和栈,故而父进程的文件描述符在子进程中不再使用的部分就不存在,关闭这部分描述符就变得十分困难。

当然理想状态下在应该需要在fork之初就将无用的描述符关闭,但如果工程复杂,fork后描述符的数量很大,一个一个清理显的有些不切实际。

所以这个标志的意思就是约定好 这个描述符在执行exec后自动关闭

注意关闭的是子进程中的fd,父进程中仍然可用

一个比较常见的问题

父进程监听一个端口然后fork一个子进程,然后kill掉父进程。 重启父进程会出现端口占用的原因。

子进程继承了父进程的描述符,但是没有close,会一直被子线程占用。

“世界就像是个巨大的马戏团,它让你兴奋,却让我惶恐,因为我知道散场后永远是有限温存,无限心酸。”