为什么我的D2XX应用在被fork后无法工作?

4
我正在用C语言编写一个简单的应用程序,在树莓派上运行,利用D2XX驱动程序与串口设备通信。我已经按照很多在线教程和参考指南进行了操作,例如设置自定义udev规则以确保驱动程序可以正确加载,按照FTDI的构建说明安装共享库,使用gcc的-l参数链接库进行编译,以及使用sudo运行我的C程序以确保驱动程序具有适当的访问权限。这些都取得了成功!程序按预期工作。
现在,我正在尝试将我的简单程序转换为守护进程,可以通过init.d脚本(如service start)进行控制,并遇到了麻烦。
为简单起见,这里是一个简化版本的C程序,可以正常工作myprog.c:
#include <stdlib.h>
#include "ftd2xx.h"

int main(int argc, char *argv[])
{
    DWORD i, iNumDevs = 0;
    char *serialNumber = malloc(64);
    FT_STATUS ftStatus = FT_CreateDeviceInfoList(&iNumDevs);
    for (i = 0; i < iNumDevs; i++) {
        ftStatus = FT_ListDevices((PVOID)i, serialNumber, FT_LIST_BY_INDEX|FT_OPEN_BY_SERIAL_NUMBER);
        if (FT_OK == ftStatus) {
            break;
        }
    }

    // more code here...

    return EXIT_SUCCESS;
}

我使用gcc -lftd2xx -o myprog myprog.c编译它,然后使用sudo ./myprog运行它,相信我它确实完成了应该完成的一切。但是现在我正试图将这个相同的代码改为一个守护进程,我一直在遵循其他一些在线教程,并且上面的代码已经转换成更像这样子的东西。目前,这个不起作用:

mydaemon.c:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "ftd2xx.h"

int main(int argc, char *argv[])
{
    pid_t pid, sid;
    pid = fork();
    if (pid < 0) {
        return EXIT_FAILURE;
    }

    if (pid > 0) {
        return EXIT_SUCCESS;
    }

    umask(0);
    openlog("mydaemon", LOG_PID|LOG_CONS, LOG_USER);

    sid = setsid();
    if (sid < 0) {
        syslog(LOG_ERR, "Failed to set session ID on child process");
        return EXIT_FAILURE;
    }

    if ((chdir("/")) < 0) {
        syslog(LOG_ERR, "Failed to change working directory");
        return EXIT_FAILURE;
    }

    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    while (1) {

        DWORD i, iNumDevs = 0;
        char *serialNumber = malloc(64);

        syslog(LOG_INFO, "I get to this line");
        FT_STATUS ftStatus = FT_CreateDeviceInfoList(&iNumDevs);
        syslog(LOG_INFO, "I do not get to this line :( ");

        // more code here...

        sleep(10);
    }

    return EXIT_SUCCESS;
}

我以完全相同的方式编译该程序:gcc -lftd2xx -o mydaemon mydaemon.c;我以相同的方式运行它:sudo ./mydaemon,但不幸的是它无法工作。在另一个控制台窗口中,我正在追踪/var/log/messages文件,并且我可以清楚地看到它到达了我的第一条日志消息(即“我可以到达这一行”),但紧接着它就停滞不前了。我从未看到第二条日志消息,而且在此时,程序变得完全无响应。我必须找到它的进程ID并杀死它。
换句话说,一旦它尝试在分叉的进程中调用D2XX驱动程序,它就会失败。我做错了什么?我已经通过第一个示例证明代码是有效的,那么作为守护进程运行时导致它完全崩溃的原因是什么?据我所知,它甚至没有机会执行相关的D2XX方法;似乎它根本找不到该方法,而在分叉的进程中运行时出现了这种情况。

我对这个问题进行了一些研究,并决定尝试链接静态库而不是动态库。为此,我必须将编译命令更改为gcc -o mydaemon mydaemon.c /usr/local/lib/libftd2xx.a -lpthread -Wl,--no-as-needed -ldl。不幸的是,这也没有起作用。它编译了,但最终仍然遇到完全相同的问题。 - soapergem
更新:显然,如果我使用vfork而不是fork,那么它就可以工作了。唯一的问题是当你使用vfork时,父进程不会自动终止。我可以手动杀死它,子进程继续运行,并继续成功访问D2XX函数。但是,我真的希望能够使用常规的fork来使其工作...我不想编写脚本每次都杀死父进程,因为这似乎是一个廉价的解决方法。我更愿意找出根本问题并在代码中解决它。 - soapergem
如果您使用-f选项跟踪forks,可能会有所帮助,通过strace程序来查看它在哪里卡住了。 - Dolda2000
1
此外,虽然这里显然不是问题所在,但仅仅关闭 std{in,out,err} 而不替换它们并不总是一个好主意。即使您的程序对它们没有任何操作,某些库可能会假定它们存在于各种目的(即使它们不应该存在)。当然,如果这个 libftd2xx 是那些在 stderr 上输出虚假消息的糟糕编写的库之一(我自己没有经验),那么这可能是许多麻烦的根源。 - Dolda2000
此外,虽然我也想知道程序失败的原因,但我想提醒的是,在分叉和守护进程之前进行此类初始化通常是更好的做法。如果守护进程在所有的初始化都成功完成并准备好实际执行任务时仅使用 EXIT_SUCCESS 退出,则处理这些进程会更加方便。 - Dolda2000
1
这是libftd2xx的已知问题。 - user149341
1个回答

0

可能是因为它使用了libusb... 而且他们似乎做得很糟糕。

请参见我的问题:libusb-1.0 hotplug events stop working in parent after fork(), when child calls libusb_exit()

以及这里的讨论:https://github.com/libusb/libusb/issues/268

我的具体问题与热插拔事件有关,但我预计你也会遇到其他问题。

之所以在您的情况下这不太明显,是因为他们可能在库加载时进行一些设置/初始化(多么善良),而不是在您开始使用它时。

正如@duskwuff指出的那样,在这里还有另一个答案:https://dev59.com/hpPfa4cB1Zd3GeqPGa1j#35186414


我刚刚做了一些尝试,下面跟着看:

cd $(mktemp -d)
curl http://www.ftdichip.com/Drivers/D2XX/Linux/libftd2xx-x86_64-1.3.6.tgz | tar -xvz

把这个放在test.c文件中:
#include <stdio.h>
#include "ftd2xx.h"

int main(void) {
    int num_devs;

    fprintf(stderr, "in to main()\n");

    FT_STATUS ft_status = FT_CreateDeviceInfoList(&num_devs);
    fprintf(stderr, "FT_CreateDeviceInfoList() returned: %d\n", ft_status);

    fprintf(stderr, "out of main()\n");

    return 0;
}

编译:

gcc test.c -o test -g -I release -L release/build -lftd2xx -ldl -lpthread

现在在 gdb 中:

[...]
Reading symbols from test...done.
(gdb) b libusb_init
Breakpoint 1 at 0x40cd20
(gdb) start
Temporary breakpoint 2 at 0x401d75: file test.c, line 7.
Starting program: /tmp/tmp.jJpBNywVzB/test 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000000000040cd20 in libusb_init ()
(gdb) bt
#0  0x000000000040cd20 in libusb_init ()
#1  0x0000000000401f36 in my_init ()
#2  0x000000000041a05d in __libc_csu_init ()
#3  0x00007ffff7614ed5 in __libc_start_main (main=0x401d6d <main>, argc=1, argv=0x7fffffffe228, init=0x41a010 <__libc_csu_init>, fini=<optimised out>, rtld_fini=<optimised out>, stack_end=0x7fffffffe218)
    at libc-start.c:246
#4  0x0000000000401ca9 in _start ()
(gdb)

在调用他们的my_init()时,在进入main()之前就命中了libusb_init()上的断点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接