调度程序有时会在启动时忽略分叉进程

Scheduler sometimes ignores forked processes at start

我正在帮助我的一个朋友用 C++ 实现一个软件,它利用了四个应该在单核上 运行 quasi-parallel 的进程。

四个进程中的三个是利用 fork() 函数创建的。这些进程由三个信号量(semaOnesemaTwosemaThree)和运行"a relay race"依次在死循环中同步(proc1 -> proc2 -> proc3 -> proc1 -> ...)。第四个进程(显然是父进程)是 watchdog-process,它执行自己的无限循环并观察三个 child 进程。 考虑 children 按预期运行,信号量和同步也是如此。

看门狗实现非常原始,但足够了。 Children 使用他们的 PID 向看门狗注册自己,现在有义务 tick 看门狗一次,并通过这样做来增加计数器。看门狗本身,当它被调度程序允许时 运行、checks children 的条目并递减每个已注册 child 的计数器。如果任何计数器达到状态 0,看门狗应该采取行动。

问题是,有时当我们启动软件时,看门狗进程会在无限循环中调用它的 check 函数,它会抢占主动权并且 运行 会无休止地似乎被调度程序忽略,所以 children 永远不会 运行。在多次尝试启动软件后,我们很幸运 children 设法启动了他们的 "relay race" 然后一切正常。

据我所知,调度程序在 round-robin 模式下工作,应该将 CPU-resources 分配给所有进程,但这似乎无法正常工作。

如有需要,请询问更多信息!

P.S。 Virtual Box中的环境是UbuntuLinux16.04运行ning

如您所见,我已经尝试降低父进程的优先级,希望这会影响调度程序并敦促它为 children 提供一个时间段,但这永远不会发生了。

// main.cpp

// Shared memory allocation and instantiation of semaphores happen here
// This part of the code relies on a simple framework that was given
// by the lecturer. It works as expected and is of no concern in the scope
// of the actual problem.

CWatchdog myWD;
pthread_t proc1 = 0;
pthread_t proc2 = 0;
pthread_t proc3 = 0;

int main(void) {
    pthread_t PID;

    myWD.init();

    PID = fork();

    if (PID == 0) {
        // proc1
        proc1 = getpid();

        myWD.register(proc1);

        while (true) {
            semaOne->take();

            std::cout << "[PROC 1] here" << std::endl;
            // per1form proc1 task here and TICK the watchdog
            myWD.tick(proc1);
            usleep(2000000);

            semaTwo->give();
        }

    } else if (fork() == 0) {
        // proc2
        proc2 = getpid();

        myWD.register(proc2);

        while (true) {
            semaTwo->take();

            std::cout << "[PROC 2] here" << std::endl;
            // perform proc2 task here and TICK the watchdog
            myWD.tick(proc2);
            usleep(2000000);

            semaThree->give();
        }
    } else if (fork() == 0) {
        // proc3
        proc3 = getpid();

        myWD.register(proc3);

        while (true) {
            semaThree->take();

            std::cout << "[PROC 3] here" << std::endl;
            // perform proc3 task here and TICK the watchdog
            myWD.tick(proc3);
            usleep(2000000);

            semaOne->give();
        }
    } else {
        pthread_t wdProcID = getpid();
        int myPrio = 0;

        myPrio = getpriority(PRIO_PROCESS, 0);
        // 0 for current process!
        std::cout << "[WD] PID, priority (old) " << wdProcID << ", " << myPrio << std::endl;
        setpriority(PRIO_PROCESS, 0, -20);

        while (true) {
            std::cout << "[WD] here" << std::endl;
            // perform watchdog task here: CHECK the children
            myWD.check();
            usleep(50000);
        }
        return 0;
    }
}

我们想要实现的是以下内容:即使 watchdog/father-process 在启动时提前进入循环,调度程序也会将 children 的时隙分配给 运行。

谢谢2785528,查看scheduler类型的思路!

我找到了解决方案,现在一切都 运行 符合预期。

以下说明适用于我的开发系统 Ubuntu 16.04,但也适用于其他发行版。

在LinuxShell中,可以使用chrt工具显示任何进程的调度策略。 -p 参数传递你想显示其调度策略的进程的PID。

以下输出适用于目前在我的系统上 运行nung 的 Firefox:

$ chrt -p 19580
pid 19580's current scheduling policy: SCHED_OTHER
pid 19580's current scheduling priority: 0

"SCHED_NORMAL / SCHED_OTHER 这是默认策略,适用于具有某些交互的普通程序。是否抢占其他进程。" (来自 http://manpages.ubuntu.com/manpages/xenial/man8/schedtool.8.html

默认调度策略不是Rount-Robin!因此,对于 运行 任何具有循环调度程序的软件,都必须明确选择。我的 Ubuntu 16.04 上有一个名为 schedtool 的工具,可让您 select 系统上可用的任何调度程序。

schedtool,如果还没有安装,可以轻松安装:

$ sudo apt-get install schedtool

现在,通过不带任何参数执行schedtool,可以显示该工具的帮助。以下是该工具可以处理的调度策略列表的摘录:

set scheduling policies:
    -N                    for SCHED_NORMAL
    -F -p PRIO            for SCHED_FIFO       only as root
    -R -p PRIO            for SCHED_RR         only as root
    -B                    for SCHED_BATCH
    -I -p PRIO            for SCHED_ISO
    -D                    for SCHED_IDLEPRIO

由于我希望从 Eclipse IDE 启动的软件使用 Round-Robin,因此我使用 schedtool 通过 Round-Robin 调度程序启动 Eclipse 并设置进程的优先级到 20:

$ sudo schedtool -R -p 20 -e eclipse

现在,将从 IDE 启动的所有进程也将使用循环调度程序启动。