dlclose 不关闭库打开的文件句柄

dlclose does not close library open file handles

我正在使用 dlopen 动态加载库,然后使用 dlclose 关闭它。我希望在 dlclose 完成后释放所有库资源,但在 dlclose 调用后库中仍有打开的文件描述符。我想知道如何确保在程序执行过程中卸载库,以便清理所有资源。

我的代码如下:

#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX_PATH_LENGTH         80

int deviceQ()
{
    cl_int ret;
    void * libHandle = dlopen("/usr/lib64/libOpenCL.so", RTLD_LAZY);
    cl_int (* clGetPlatformIDs)(cl_uint, cl_platform_id*, cl_uint*) = dlsym(
            libHandle, "clGetPlatformIDs"
    );
    cl_int (* clGetDeviceIDs)(cl_platform_id, cl_device_type, cl_uint, cl_device_id*, cl_uint*) =
        dlsym(libHandle, "clGetDeviceIDs");
    /********************** PREAMBLE **************************************/
    cl_device_id device_id = NULL;
    cl_platform_id platform_id = NULL;
    cl_uint ret_num_devices;
    cl_uint ret_num_platforms;

    ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
    if (ret != CL_SUCCESS) {
        perror("Failed to get platform IDs");
    } else if (ret_num_platforms != 1) {
        fprintf(stderr, "Number of platforms returned is %d\n", ret_num_platforms);
        exit(1);
    }

    printf("Read platform IDs\n");

    ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_GPU, 1, &device_id,
        &ret_num_devices);
    if (ret != CL_SUCCESS) {
        perror("Failed to get device IDs");
    } else if (ret_num_devices != 1) {
        fprintf(stderr, "Number of returned devices is %d\n", ret_num_devices);
        exit(1);
    }
    printf("Read device IDs\n");
    /********************** PREAMBLE **************************************/

    /***************** RELEASE AND FREE ****************************/
    dlclose(libHandle);
    /***************** RELEASE AND FREE ****************************/

    return 0;
}

size_t closeFileDescriptors(void ** arr) {
    // step 1 - get PID
    pid_t pid = getpid();
    //printf("PID is %d\n", pid);

    char path[MAX_PATH_LENGTH];
    memset(path, '[=10=]', MAX_PATH_LENGTH);
    sprintf(path, "/proc/%d/fd", pid);

    int fd;
    DIR * d = opendir(path);
    struct dirent *dir;
    struct stat s;
    char dirPath[MAX_PATH_LENGTH];
    char realPath[MAX_PATH_LENGTH];

    size_t index = 0;

    if (d) {
        while ((dir = readdir(d)) != NULL) {
            if (strcmp(dir->d_name, ".") != 0 && 
                strcmp(dir->d_name, "..") != 0) {
                fd = atoi(dir->d_name);

                if (fstat(fd, &s) != 0) {
                    perror("fstat failed");
                }
                memset(dirPath, '[=10=]', MAX_PATH_LENGTH);
                strcpy(dirPath, path);
                strcat(dirPath, "/");
                strcat(dirPath, dir->d_name);

                #ifdef S_IFLNK
                if (s.st_mode & S_IFLNK) {
                #else
                if (S_ISLNK(s.st_mode)) {
                #endif
                    memset(realPath, '[=10=]', MAX_PATH_LENGTH);
                    #ifdef readlink
                    readlink(dirPath, realPath, MAX_PATH_LENGTH);
                    printf("%s -> %s\n", dirPath, realPath);
                    #else
                    printf("[readlink not defined] %s\n", dirPath);
                    #endif
                } else {
                    printf("Not link: %s (proceeding anyway)\n", dirPath);
                    //printf("Not link: %s (ignoring)\n", dirPath);
                    //continue;
                }

                if (fd > 2) {
                    //int fdFlags = fcntl(fd, F_GETFD);
                    int fdFlags = fcntl(fd, F_GETFL);
                    if (fdFlags == -1) {
                        perror("fcntl failed");
                    }
                    //off_t offset = lseek(fd, 0, SEEK_CUR);
                    off_t offset = 0;
                    if (offset == -1) {
                        perror("lseek failed");
                    }
                    if (arr != NULL) {
                        /*
                        arr[index] = (fileData *) malloc(sizeof (fileData));
                        arr[index]->flags = fdFlags;
                        arr[index]->offset = offset;
                        arr[index]->fd = fd;
                        strcpy(arr[index]->fdPath, realPath);*/
                    }
                    index++;

                    // ignore stdin, stdout, stderr
                    printf("Closing FD %d (flags %d, offset %zd)\n", 
                            fd, fdFlags, offset);
                    close(fd);
                }
            }
        }
        closedir(d);
    } else {
        fprintf(stderr, "Could not open directory %s\n", path);
    }
    return index;
}

int main () {
    deviceQ();

    printf("=> Closing open file descriptors\n");
    closeFileDescriptors (NULL);

    deviceQ();
    return 0;
}

你的期望是错误的。当您调用 dlclose(3) 时,只有 "plugin"(实际上是共享对象)是 "closed"(实际上, 可能是 munmap-ed) ,但不是它使用的资源(特别是文件描述符,可能还有堆分配的内存)。

此外,在 Linux 上,dlclose 正在调用插件的所谓析构函数(那些用 [=12= 声明的),请阅读 function attributes海湾合作委员会)。

如果您正在编写一个共享库,您可以将其设计为 一些 资源在 dlclose 时间被释放(通过适当的完成 运行通过 析构函数 函数)。一般来说,这不是很容易实现的(而且它应该是一个记录在案的约定)。

虚拟内存中的地址 space 等资源(通过 mmap(2) etc...) and file descriptors (obtained by open(2), socket(2), pipe(2) etc etc...) are global (and common) to the entire process 获得)。 因此,在一个共享库中获取一些资源(例如打开一些文件描述符)并在另一个共享库中(或在主程序中)释放它是可能的(并且是合法的,如果有记录的话)。

既然一个资源"belongs"到整个过程,那么说释放一个库获取的资源就没有意义了。

所以你的 closeFileDescriptors 很可能是一个巨大的错误(并且它可能泄漏了一些其他资源)。

(IIRC,OpenCL API 有一些方法可以释放它的资源,例如设备、上下文、内核等....但我忘记了 丑陋的 详细信息;参见 clReleaseContextclReleaseMemObject 等等,包括一些特定于实现的内容。)

阅读更多关于 garbage collection 的内容可能会开阔您的视野。

另请阅读 Drepper 的论文:How To Write a Shared Library & credentials(7)

如果您确实需要尽早释放 OpenCL 相关资源,更明智的方法可能是启动一个专用于 OpenCL 事物的不同子进程,并首先使用聪明的 IPC mechanisms (e.g. pipe(7), shm_overview(7), sem_overview(7), etc...) then terminate (properly) that child process once your OpenCL stuff is done. You take advantage of the fact that the kernel is cleaning all the resources used by a defunct process (don't forget to wait... it -e.g. using waitpid(2)- to avoid having zombie processes). If you are not familiar with all that, read Advanced Linux Programming