查看原文
其他

多进程中之文件描述符继承的消除

CPP开发者 2021-07-20

推荐关注↓

什么是文件描述符的继承

当父进程创建子进程时,无论 fork 函数或者是 vfork 函数,子进程通常都会继承父进程的文件描述符。所谓的继承,就是子进程可以使用相同的文件描述符,和父进程操作同一个文件对象。如图所示

父子进程共享文件描述符

这种可能会造成权限安全隐患。怎么办呢?

最简单的做法当然就是什么也不做。告诉开发人员,父子进程之间这种共享文件对象的方式很危险,你自己开着办,除了事情自己负责,当然这种处理方式,对于执行体程序库而言代价最小,因为不用添加任何代码,顶多在文档上写两句说明的话。很显然,这种方法是一种走投无路的方法。

第二种方法,就是在刚创建完子进程后,立马把它继承而来的文件描述符给关闭了。关闭文件描述符一般使用的是 close 函数。唯一麻烦的是怎么知道进程打开了那些文件描述符?

读取目录的基本操作

通常在 Linux 系统中,/proc 目录记录了各个进程运行时的信息,在/proc 中每个进程都有一个目录,该目录以进程 ID 命名。而在每个进程的所属目录中,又有一个名为“fd”的目录。进程打开的文件描述,在该目录中对应了一个符号链接文件,而该文件的名称,即文件描述符的数值。好了,现在看来问题就好办多了。只要我们能遍历“/proc/PID/fd”目录,就能获得当前进程打开的文件描述符,然后我们再为其调用 close 函数即可。

那么,如何遍历一个目录呢?

要遍历一个目录,首先得调用 opendir 函数打开目录,然后再调用 readdir 函数获取目录中子目录名称和文件名称,最后调用 closedir 函数关闭目录。好了,下面首先看看这几个函数的原型。

#include <dirent.h>
  DIR* opendir(const char* name)    

在读取目录之处,需要首先调用 opendir。该函数接收一个参数,即需要打开的目录名称,当然这个名称需要带路径信息。当 opendir 函数调用成功时,会返回一个 DIR 结构体指针,出错则会返回 0。这个 DIR 结构体的指针,将会被用于后续的 readdir 函数和 closedir 函数的调用。好了,下面来看看 readdir 函数的原型:

#include <dirent.h>
struct dirent* readdir(DIR *dirp);

readdir 函数只接收一个参数,即 opendir 函数的返回值,表示要从在调用 opendir 函数时指定的目录中读取信息。调用一次 readdir 函数就会返回目录中的一个文件或子目录相关的结构体实例指针,即 dirent 结构体指针。下一次再调用 readdir 函数时,又会返回目录中下一个文件或子目录对应的结构体实例指针。

当所有文件或者子目录都返回过了之后,再调用 readdir 函数将返回 0。如此不断循环,就可以获得指定目录下的所有文件或子目录的 dirent 结构体实例了。那么现在,我们需要的文件名在哪里呢?实际上 dirent 结构体有一个 d_name 字段,该字段就是指向文件名或子目录名的字符串指针。当我们不再需要读目录时,为了释放资源,需要调用 closedir 函数关闭目录,该函数的原型如下:

#include <dirent.h>
int closedir(DIR* dirp);

文件描述符的消除实现

同 readdir 函数类似,closedir 函数也只接收一个参数,即 opendir 函数返回的 DIR 结构体的指针。若 closedir 函数调用成功则返回 0,否则返回-1。下面我们来看看具体的代码是怎么实现的。

   1 int Process::CloseFileDescriptor()                                                                                                       |
   2 {                                                                                                                                           
   3     string strPath = "/proc/";                                                                                                              
   4                                                                                                                                             |
   5     char id[LENGTH_OF_PROCESSID];                                                                          
   6     snprintf(id, LENGTH_OF_PROCESSID, "%d", m_ProcessID);                                                                                   
   7                                                                                                                                         |    
   8     strPath += id;                                                                                                                          
   9     strPath += "/fd";                                                                                                                       
  10     string strPath1 = strPath;                                                                                                                                                                                                                                                        
  11     strPath += "/";                                                                                                                         |~                             
  12                                                                                                                                            |~                             
  13     DIR *pDir = opendir(strPath.c_str());                                                                                                   |~                             
  14     if(pDir == 0)                                                                                                                           |~                             
  114     {                                                                                                                                       |~                             
  15             Logger::WriteLogMsg("In Process::CloseFileDescriptor(), opendir error", 0);                                                |~                 
  16             return -1;                                                                                                               |~                             
  17     }                                                                                                                                       |~                             
  18                                                                                                                                             |~                             
  19     while(struct dirent *pDirent = readdir(pDir))                                                                                           |~                             
  20     {                                                                                                                                       |~                             
  21         char captial = pDirent->d_name[0];                                                                                                  |~                             
  22         if((captial == '.') || (captial == '0') || (captial == '1') || (captial == '2'))                                                    |~                             
  23             continue;                                                                                                                       |~                             
  24                                                                                                                                             |~                             
  25          int fd = atoi(pDirent->d_name);                                                                                                     |~                             
  26          if(fd != 0)                                                                                                                         |~                             
  27         {                                                                                                                                   |~                             
  28             if(close(fd) == -1)                                                                                                             |~                             
  29             {                                                                                                                               |~                             
  30                 string errormsg = "In Process::CloseFileDescriptor(), close error, file: ";                                                 |~                             
  31                 errormsg += pDirent->d_name;                                                                                                |~                             
  32                 std::cout << errormsg << std:endl;                                                                               |~                             
  33             }                                                                                                                               |~                             
  34         }                                                                                                                                   |~                             
  35     }                      
  36    
  37    if(closedir(pDir) == -1)                                                                                                                |~                             
  38   {                                                                                                                                       |~                             
  39          Logger::WriteLogMsg("In Process::CloseFileDescriptor(), closedir error", errno);                                                |~                             
  40         return -1;                                                                                                               |~                             
  41     }                                                                                                                                       |~                             
  42                                                                                                                                             |~                             
  43     return 0;                                                                                                                    |~                             
  144 }                  

这个函数作为进程类 Process 的私有成员函数放在创建新进程的成员函数中,在这里就不对我封装的进程类进行具体介绍了。Process 类的声明代码及创建子进程的 Run() 方法主要逻辑如下:

   #include <unistd.h>                                                                                                                           |
  ......                                                                                                                        |▼ Process : class
 1                                                                                                                                              |    [prototypes]
 2  class Process : public Executive                                                                                                              |   -CloseFileDescriptor()
 3  {                                                                                                                                             |   +Process(ExecutiveFunctionP
 4 public:                                                                                                                               |   -Process(const Process&)
 5    ......
 6     Process(ExecutiveFunctionProvider *pExecutiveFunctionProvider, bool bWaitForDeath);                                                       |   +~Process()
 12                                                                                                                                               |   +Run(void *pstrCmdLine = 0)
 13     virtual ~Process();                                                                                                                       |   +WaitForDeath()
 14                                                                                                                                               |   -operator =(const Process&)
 15     virtual int Run(void *pstrCmdLine = 0);                                                                                                |    [members]
 16     virtual int WaitForDeath();                                                                                                            |   -m_ProcessID
 17                                                                                                                                               |   -
 18 private:                                                                                                                                      |   -
 19     int CloseFileDescriptor();                                                                                                             |   -
 20                                                                                                                                               |~                             
 21 private:                                                                                                                                      |~                             
 22     Process(const Process&);                                                                                                                  |~                             
 23     Process& operator=(const Process&);                                                                                                       |~                             
 24                                                                                                                                               |~                             
 25     pid_t m_ProcessID;                                                                                                                        |~                             
 26                                                                                                                                               |~                             
 28    ......                                                                                                                 |~                             
 29 };                          

接下来继续讨论 CloseFileDescriptor 函数,从上面的代码段我们可以看到,函数的主要逻辑,首先形成要访问的目录地址“/proc/进程 ID/fd”在第 13 行调用 opendir 函数打开指定目录,接着进入一个 while 循环遍历目录中的所有文件。

这些文件都是进程已打开文件描述符的数值。在处理这些文件时,有几个特殊的文件需要区别对待,比如 ...。分别代表当前目录和父目录,因此不用理会它们。而文件名为 0、1、2 的文件,很显然分别对应于标准输入、标准输出和标准出错。这些文件描述符不应当被关闭,否则子进程即使成功执行 execv 函数后也无法正常执行输入输出。

因此,子进程在第 22 行没有关闭上述文件描述符,之后,子进程在第 25 行将字符串形式的文件描述符转换成数值,然后在第 28 行调用 close 函数关闭文件。退出遍历文件的循环后,子进程又在第 37 行调用了 closedir 函数关闭刚才打开的目录。如果关闭失败,则记录日志信息。

主要的实现介绍完了,那我们通过一个测试代码来看看这个函数。测试代码如下:

    1 int main(int argc, char *argv[])
    2 {
    3     cout << "in child main" << endl;
    4 
    5     if(write(3, "nihao", 5) == -1)
    6     cout << "child write error" << endl;
    7     else
    8     cout << "child write success" << endl;
    9 
   10     return 0;
   11 }

这个测试代码很简单,首先在第 3 行向屏幕输出"in child main",然后又在第 5 行调用 write 函数向文件描述符 3 写了一条信息“nihao”,并根据 write 函数的返回值打印出相应的信息。正常情况下,除去 0、1、2 外,其余的都应该已经被关闭了。因此,write 函数应该返回出错。现在我们看下可执行程序的运行结果:

看到这个运行结果,大家会大吃一惊吧。居然输出了“child write success”。换句话说,子进程在执行上面的 main 函数之后,向文件描述符 3 写的信息居然写成功了。

而这个文件描述符 3 对应的是哪个文件呢? 我们打开日志文件看下:


可以看到,日志文件中的内容,正是向文件描述符 3 写入的“nihao”,也就是说,文件描述符 3 对应的就是日志文件。这就奇怪了,CloseFileDescriptor 函数找那个,不是已经把 0、1、2 之外的所有文件描述符关闭了吗,怎么 3 却没有关闭了呢?我们在看看上面 CloseFileDescriptor 函数的代码,在 37 行的 closedir 调用失败了。它一失败后,马上进行了写日志。由于之前无论父进程还是子进程都没有写日志。在这里的 WriteLogMsg 将导致日志对象被创建,而在日志对象构造中将调用 open 函数打开日志文件后,返回的文件描述符应该是 3。

那么,在这里 closedir 函数调用失败又是怎么回事呢?在 Linux 系统,无论是目录也好、普通文件也罢,实际上都是文件的一种类型而已。那么 opendir 函数打开目录时,实际上也会执行 open 函数类似的操作,也会得到代表打开目录的文件描述符;同理,closedir 函数关闭目录时,也会关闭打开目录的文件描述符。但是在目录文件遍历代码中,除了 0、1、2 外,其他文件描述符都被关闭了,因此,在调用 closedir 函数时,对一个已经关闭了的文件描述符再执行关闭操作,理所当然关闭了。好了,closedir 函数调用出错的原因找到了,剩下的就是该怎么解决。

大家可以在 Linux 系统上,自己注意观察“/proc/进程 ID/fd”目录中的文件,就会发现它们实际上都是符号链接文件。这些符号链接文件类似于 Windows 系统中的快捷方式,它会指向另一个文件。如果我们在遍历目录中的文件时,能够获取符号链接文件实际指向的文件名称,那事情就好办多了。

我们可以把这个文件名称,同“/proc/进程 ID/fd”相比较,如果相同,则说明了当前正在处理的文件描述符所对应的目录,正是我们遍历的目录,不要为它调用 close 函数,而应该等待目录遍历完毕后调用 closedir 函数关闭它。现在我们看看如果读取符号链接文件实际指向的文件名称,这个很简单,可以直接调用 readlink 函数,下面给出了该函数的原型:

#include <unistd.h>
ssize_t readlink(const char* path, char* buf, size_t bufsize);

readlink 函数接收 3 个参数,第一个就是要读取的符号链接文件的路径信息;当读出了符号链接文件实际指向的文件名称后,readlink 函数会将其写入第二个参数 buf 所指向的缓存中;而第三个参数 bufsize,即 buf 所指向的缓存大小。readlink 函数调用成功后,会返回写入缓存中的字节数,失败则返回 -1。不过有一点需要提醒,该函数向缓存填入信息时,结尾并不会填 0。因此,缓存的初始化操作需要调用者完成。好了,现在我们对刚才的 CloseFileDescriptor 函数进行修改。

  1   int Process::CloseFileDescriptor()                                                                                                       |▶ macros
     {                                                                                                                                           |
  2      string strPath = "/proc/";                                                                                                              |▼ Process* : class
  3                                                                                                                                             |    [functions]
  4      char id[LENGTH_OF_PROCESSID];                                                                                                           |    CloseFileDescriptor()
  5      snprintf(id, LENGTH_OF_PROCESSID, "%d", m_ProcessID);                                                                                   |    Process(ExecutiveFunctionP
  6                                                                                                                                             |    Process(ExecutiveFunctionP
  7      strPath += id;                                                                                                                          |    ~Process()
  8      strPath += "/fd";                                                                                                                       |    Run(void *pstrCmdLine)
  9                                                                                                                                             |    WaitForDeath()
  10     string strPath1 = strPath;                                                                                                              |~                             
  11                                                                                                                                             |~                             
  12     strPath += "/";                                                                                                                         |~                             
  13                                                                                                                                             |~                             
  14     DIR *pDir = opendir(strPath.c_str());                                                                                                   |~                             
  15     if(pDir == 0)                                                                                                                           |~                             
  16    {                                                                                                                                       |~                             
  17         Logger::WriteLogMsg("In Process::CloseFileDescriptor(), opendir error", 0);                                                         |~                             
  18         return -1;                                                                                                               |~                             
  19     }                                                                                                                                       |~                             
  20                                                                                                                                             |~                             
  21     while(struct dirent *pDirent = readdir(pDir))                                                                                           |~                             
  22     {                                                                                                                                       |~                             
  23         char captial = pDirent->d_name[0];                                                                                                  |~                             
  24         if((captial == '.') || (captial == '0') || (captial == '1') || (captial == '2'))                                                    |~                             
  25             continue;                                                                                                                       |~                             
  26                                                                                                                                             |~                             
  27         int fd = atoi(pDirent->d_name);                                                                                                     |~                             
  28         if(fd != 0)                                                                                                                         |~                             
  29         {                                                                                                                                   |~                             
  30             string strTmpPath = strPath;                                                                                                    |~                             
  31             strTmpPath += pDirent->d_name;                                                                                                  |~                             
  32                                                                                                                                             |~                             
  33             char pathname[LENGTH_OF_PATH] = {0};                                                                                            |~                             
  34             if(readlink(strTmpPath.c_str(), pathname, LENGTH_OF_PATH) == -1)                                                                |~                             
  35             {                                                                                                                               |~                             
  36                 Logger::WriteLogMsg("In Process::CloseFileDescriptor(), readlink error", errno);                                            |~                             
  37                 continue;   
  38                }                                                                                                                               |~                             
  39                                                                                                                                             |~                             
  40             if(strcmp(pathname, strPath1.c_str()) == 0)                                                                                     |~                             
  41                 continue;                                                                                                                   |~                                                                                                                                                                          |~                             
  42             if(close(fd) == -1)                                                                                                             |~                             
  43             {                                                                                                                               |~                             
  44                 string errormsg = "In Process::CloseFileDescriptor(), close error, file: ";                                                 |~                             
  45                 errormsg += pDirent->d_name;                                                                                                |~                             
  46                 Logger::WriteLogMsg(errormsg.c_str(), errno);                                                                               |~                             
  47             }                                                                                                                               |~                             
  48         }                                                                                                                                   |~                             
  49     }                                                                                                                                       |~                             
  50                                                                                                                                             |~                             
  51     if(closedir(pDir) == -1)                                                                                                                |~                             
  52     {                                                                                                                                       |~                             
  53         Logger::WriteLogMsg("In Process::CloseFileDescriptor(), closedir error", errno);                                                    |~                             
  54         return -1;                                                                                                               |~                             
  55     }                                                                                                                                       |~                             
  56                                                                                                                                             |~                             
  57     return 0;                                                                                                                    |~                             
  58 }                            

以上代码是对 CloseFileDescriptor 函数的修改。主要的改动,是在遍历目录文件时,调用了 readlink 函数获取符号链接文件实际指向的文件名称,即第 34 行。然后又在第 40 行将该名称同“/proc/进程 ID/fd”相比较。如果相同则不予理会。等待退出循环后,再调用 closedir 函数关闭目录。那么这样写文件描述符 3 的问题就算是搞定了。

总结

由继承而来的文件描述所引发的一系列问题,在这里我们就算告一段落了。我们讨论了如何遍历目录得到进程已打开的文件描述符,如何读取符号链接文件以规避正使用的目录描述符。

转自:神技圈子()

blog.csdn.net/songguangfan/article/details/113736135

- EOF -

推荐阅读  点击标题可跳转

1、编程思想之多线程与多进程

2、Linux 文件系统详解

3、在 C++ 中子类继承和调用父类的构造函数方法


关注『CPP开发者』

看精选C++技术文章 . 加C++开发者专属圈子

点赞和在看就是最大的支持❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存