close-on-exec 相关的一个 bug
测试一个用 V4L2 拍照的程序时,发现程序单独运行很正常,但在多进程环境下运行时就会出现问题,具体表现为执行 open
系统调用打开 /dev/video
设备时返回 EBUSY
错误,查询 V4L2 的文档可以看到该错误的含义
EBUSY
The driver does not support multiple opens and the device is already in use.
也就是说该设备不支持多次重复打开或者已经在被使用了
在程序运行时执行 lsof
,发现是一些和拍照相对无关的进程(比如 wpa_supplicant)持有着 video 设备,这些进程是肯定不会自己的去打开 video 设备的,肯定是由于某种原因从主进程中拿到了 video 的句柄
回忆一下 Linux 中执行其他程序的方式,是先执行 fork
再执行 exec
-
fork
的时候会拷贝父进程的文件描述符表(Linux 由于有写时拷贝,所以不会立即拷贝) - 通过
exec
打开新程序后,会替换掉fork
子进程的正文段、数据段、堆和栈,但并不会关掉已经打开的文件描述符
这种特性被 shell 用来实现 IO 重定向,比如说我们要将标准输出重定向到日志文件 log.txt
中
- shell 先
fork
出一个子 shell - 在子 shell 中关掉标准输出(即文件描述符 1),然后立即打开
log.txt
,那么log.txt
的 fd 就会是 1,因为open
是从所有可用的文件描述符中选一个最小的 - 然后调用
exec
执行 shell 命令,这样 shell 命令写到标准输出里的内容就被写入了log.txt
然而在套接字编程或使用设备时,这种特性会引来一些意想不到的 BUG
- 比如继承给新程序的套接字占了父进程的 ip 端口对,导致父进程没法使用
- 在我们这个场景中,也是因为新程序从父进程拿走了
/dev/video
的描述符,而/dev/video
本身是不允许共享的(跟驱动程序相关),导致设备使用冲突
为了解决该问题,Linux 中有一个文件描述符标志 FD_CLOEXEC
,如果某个文件描述符设定了该标志,执行 exec
期间就会自动关闭该文件描述符
- 可以通过
fcntl
的F_GETFD
和F_SETFD
参数获取和设置该标志 - 也可以在
open
时指定O_CLOEXEC
标志,它会给打开的文件描述符设置FD_CLOEXEC