关于fork后exec子进程继承父进程文件描述符保持开放的问题

在多进程编程中,根据业务需要会通过fork+exec执行shell脚本或其它程序,在fork后父、子进程对于每一个打开的文件描述符共享同一个文件表项,此时可能有多个文件描述符项指向同一文件表项。有时子进程不需要继承父进程的文件描述符,并且在exec后子进程继承下来的文件描述符成了耗费系统资源的一个累赘,此时应该怎么处理呢?接下来我将分享下我在工作中遇到的问题以及解决方案。

1、首先介绍一下fcntl函数,以下摘自Unix环境高级编程:

   #include <unistd.h>
   #include <fcntl.h>
   int fcntl(int fd, int cmd, ... /* arg */ );

fcntl函数有五种功能:
• 复制一个现存的描述符(cmd=F_DUPFD)。
• 获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD)。
• 获得/设置文件状态标志(cmd = F_GETFL或F_SETFL)。
• 获得/设置异步I/O有权(cmd = F_GETOW N或F_SETOWN)。
• 获得/设置记录锁(cmd = F_GETLK , F_SETL K或F_ SETLKW)
• F_DUPFD 复制文件描述符f i l e d e s,新文件描述符作为函数值返回。它是尚未打开的各
描述符中大于或等于第三个参数值(取为整型值)中各值的最小值。新描述符与 filedes共享同
一文件表项(见图3-3)。但是,新描述符有它自己的一套文件描述符标志,其 FD_CLOEXEC
文件描述符标志则被清除(这表示该描述符在 exec 时仍保持开放,我们将在第 8章对此进行
讨论)。
• F_GETFD 对应于filedes 的文件描述符标志作为函数值返回。当前只定义了一个文件描
述符标志FD_CLOEXEC。
• F_SETFD 对于filedes 设置文件描述符标志。新标志值按第三个参数(取为整型值)设置。
应当了解很多现存的涉及文件描述符标志的程序并不使用常数FD_CLOEXEC,而是将此
标志设置为0 (系统默认,在exec时不关闭)或1 (在exec时关闭)。
• F_GETFL 对应于filedes 的文件状态标志作为函数值返回。在说明open函数时,已说明
了文件状态标志。它们列于表3 - 2中。

不幸的是,三个存取方式标志( O_RDONLY, O_WRONLY,以及O_RDWR )并不各占1位。(正
如前述,这三种标志的值各是 0、1和2,由于历史原因。这三种值互斥 — 一个文件只能有这
三种值之一。)因此首先必须用屏蔽字O_ACCMODE取得存取方式位,然后将结果与这三种值
相比较。
• F_SETFL 将文件状态标志设置为第三个参数的值(取为整型值)。可以更改的几个标志是:
O_APPEND,O_NONBLOCK,O_SYNC和O_ASYNC。
• F_GETOWN 取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。12.6.2节将论述这
两种4.3 + BSD异步I/O信号。
• F_SETOWN 设置接收SIGIO和SIGURG信号的进程ID或进程组ID。正的arg指定一个进
程ID,负的a rg表示等于arg绝对值的一个进程组ID。
fcntl的返回值与命令有关。如果出错,所有命令都返回- 1,如果成功则返回某个其他值。
下列三个命令有特定返回值:F_DUPFD,F_GETFD, F_GETFL以及F_GETOWN。第一个返回新
的文件描述符,第二个返回相应标志,最后一个返回一个正的进程 ID或负的进程组ID。

2、对一个文件描述符设置\清除一个或多个文件状态标志

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    void set_fl(int fd, int flags)
    {
        int val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
            printf("fcntl F_GETFL error!\n");
        val |= flags;
        if (fcntl(fd, F_SETFL, val) < 0)
            printf("fcnt F_SETFL error!\n");
    }
    
    void clr_fl(int fd, int flags)
    {
        int val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
            printf("fcntl F_GETFL error!\n");
        val &= ~flags;
        if (fcntl(fd, F_SETFL, val) < 0)
            printf("fcnt F_SETFL error!\n");
    }       

3、fork后exec子进程继承父进程文件描述符保持开放的问题

由于工作原因,需要通过fork后exec执行ffmpeg推流,于是发现fork后子进程继承了父进程文件描述符,导致大量系统资源被占用。查看进程打开的文件描述符可以通过以下命令查看:
    ps -ef | grep [pid];
    lsof -p [pid];

也可以通过

     ll proc/[pid]/fd

来查看进程打开的文件描述符选项。

4、解决方案

1、用getrlimit函数获取最大文件描述符,系统资源的最大使用量,此处参考APUE的做法

    #include <sys/time.h>
    #include <sys/resource.h>
    #include <unistd.h>
    int close_all_fd(void)
    {
        struct rlimit lim;
        unsigned int i;
        if (getrlimit(RLIMIT_NOFILE, &lim) < 0)
            return -1;
        if (lim.rlim_cur == RLIM_INFINITY)
            lim.rlim_cur = 1024;
        for (i = 0; i < lim.rlim_cur; i++) {
    #ifdef MYPERF
            if (i == 1)
                continue;
    #endif
            if (close(i) < 0 && errno != EBADF)
                return -1;
        }
        return 0;
    }  

由于ffmpeg子进程需要读取我的fifofd 还需要将fifofd排除在外,并且个人觉得这种方式不优雅,在开启守护进程的时候可以用这套方案

2、通过设置文件描述符标志FD_CLOEXEC,则子进程不会继承父进程文件描述符

    int flags = fcntl(iSockFd, F_GETFD);
    flags |= FD_CLOEXEC;
    fcntl(iSockFd, F_SETFD, flags);

在多进程中个人比较推荐这种方式操作文件描述符,个人水平有限,如果有错误还请指正

由于之前个人比较懒散,并且写作水平很差,于是没有将一些问题记录下来,最近重读Unix环境高级编程一书,发现了很多之前没有注意的问题,下定决心将各种问题再次记录下来,以便以后查阅。
之前读书一直都没有记笔记的习惯,导致读完就忘,这次需要重读的书籍有TCP/IP详解卷一卷二,设计模式,算法第四版,大话数据结构,数据结构C语言描述,Effective C++ ,More Effective C++,Unix网络编程卷一卷二。为了支撑自己读完这些书籍,一定要多些博客记笔记啊。

猜你喜欢

转载自www.cnblogs.com/SebastianHan/p/12469915.html