【问题标题】:C Program daemon uses 100% cpu usageC 程序守护进程使用 100% cpu 使用率
【发布时间】:2015-07-03 23:49:52
【问题描述】:

我正在 Debian 中用 C 语言初始化一个守护进程:

/**
 * Initializes the daemon so that mcu.serial would listen in the background
 */
void init_daemon()
{
    pid_t process_id = 0;
    pid_t sid = 0;

    // Create child process
    process_id = fork();

    // Indication of fork() failure
    if (process_id < 0) {
        printf("Fork failed!\n");
        logger("Fork failed", LOG_LEVEL_ERROR);
        exit(1);
    }

    // PARENT PROCESS. Need to kill it.
    if (process_id > 0) {
        printf("process_id of child process %i\n", process_id);
        exit(0);
    }

    //unmask the file mode
    umask(0);
    //set new session
    sid = setsid();

    if(sid < 0) {
        printf("could not set new session");
        logger("could not set new session", LOG_LEVEL_ERROR);
        exit(1);
    }

    // Close stdin. stdout and stderr
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
}

主守护程序在后台运行并监视串行端口以与微控制器通信 - 它读取外围设备(例如按钮按下)并将信息传递给它。主要功能循环是

int main(int argc, char *argv[])
{
    // We need the port to listen to commands writing
    if (argc < 2) {
        fprintf(stderr,"ERROR, no port provided\n");
        logger("ERROR, no port provided", LOG_LEVEL_ERROR);
        exit(1);
    }
    int portno = atoi(argv[1]);

    // Initialize serial port
    init_serial();

    // Initialize server for listening to socket
    init_server(portno);

    // Initialize daemon and run the process in the background
    init_daemon();

    // Timeout for reading socket
    fd_set setSerial, setSocket;
    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 10000;

    char bufferWrite[BUFFER_WRITE_SIZE];
    char bufferRead[BUFFER_READ_SIZE];
    int n;
    int sleep;
    int newsockfd;
    while (1)
    {
        // Reset parameters
        bzero(bufferWrite, BUFFER_WRITE_SIZE);
        bzero(bufferRead, BUFFER_WRITE_SIZE);
        FD_ZERO(&setSerial);
        FD_SET(fserial, &setSerial);
        FD_ZERO(&setSocket);
        FD_SET(sockfd, &setSocket);

        // Start listening to socket for commands
        listen(sockfd,5);
        clilen = sizeof(cli_addr);
        // Wait for command but timeout
        n = select(sockfd + 1, &setSocket, NULL, NULL, &timeout);
        if (n == -1) {
            // Error. Handled below
        } 

        // This is for READING button 
        else if (n == 0) {
            // This timeout is okay 
            // This allows us to read the button press as well

            // Now read the response, but timeout if nothing returned
            n = select(fserial + 1, &setSerial, NULL, NULL, &timeout);
            if (n == -1) {
                // Error. Handled below
            } else if (n == 0) {
                // timeout
                // This is an okay tiemout; i.e. nothing has happened
            } else {
                n = read(fserial, bufferRead, sizeof bufferRead);
                if (n > 0) {
                    logger(bufferRead, LOG_LEVEL_INFO);
                    if (strcmp(stripNewLine(bufferRead), "ev b2") == 0) {
                        //logger("Shutting down now", LOG_LEVEL_INFO);
                        system("shutdown -h now");
                    }
                } else {
                    logger("Could not read button press", LOG_LEVEL_WARN);
                }
            }
        } 

        // This is for WRITING COMMANDS
        else {
            // Now read the command
            newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

            if (newsockfd < 0 || n < 0) logger("Could not accept socket port", LOG_LEVEL_ERROR);

            // Now read the command
            n = read(newsockfd, bufferWrite, BUFFER_WRITE_SIZE);
            if (n < 0) {
                logger("Could not read command from socket port", LOG_LEVEL_ERROR);
            } else {
                //logger(bufferWrite, LOG_LEVEL_INFO);
            }

            // Write the command to the serial
            write(fserial, bufferWrite, strlen(bufferWrite));
            sleep = 200 * strlen(bufferWrite) - timeout.tv_usec;  // Sleep 200uS/byte
            if (sleep > 0) usleep(sleep);

            // Now read the response, but timeout if nothing returned
            n = select(fserial + 1, &setSerial, NULL, NULL, &timeout);
            if (n == -1) {
                // Error. Handled below
            } else if (n == 0) {
                // timeout
                sprintf(bufferRead, "err\r\n");
                logger("Did not receive response from MCU", LOG_LEVEL_WARN);
            } else {
                n = read(fserial, bufferRead, sizeof bufferRead);
            }
            // Error reading from the socket
            if (n < 0) {
                logger("Could not read response from serial port", LOG_LEVEL_ERROR);
            } else {
                //logger(bufferRead, LOG_LEVEL_INFO);
            }

            // Send MCU response to client
            n = write(newsockfd, bufferRead, strlen(bufferRead));
            if (n < 0) logger("Could not write confirmation to socket port", LOG_LEVEL_ERROR);
        }

        close(newsockfd);
    }

    close(sockfd);
    return 0;
}

但 CPU 使用率始终为 100%。这是为什么?我能做什么?

编辑

我把整个while循环都注释掉了,把main函数做的这么简单:

int main(int argc, char *argv[])
{
    init_daemon();
    while(1) {
        // All commented out
    }
    return 0;
}

我仍然获得 100% 的 CPU 使用率

【问题讨论】:

  • Id' 猜测问题出在 while (1) { ... } 循环某处。
  • "while(1)" 结合"n = select(sockfd + 1, &setSocket, NULL, NULL, &timeout);"是 CPU 消耗。
  • 关于你的编辑:你剪得太多了。一个没有任何睡眠等的空循环。当然会使 CPU 表现得像一只仓鼠(又名 100% 负载)。
  • while(1) {} 单独会导致 100% 的 cpu 使用率,所以循环内容是这里最重要的想法。
  • 安全漏洞 - 您的代码未验证您从 read 使用的消息是否以空值终止。黑客可以向您发送一条长度正好为BUFFER_READ_SIZE 的消息。该缓冲区上相应的 strlen 和 strcmp 函数可能会导致不良行为。简单的解决方法是将 sizeof(bufferRead)-1 传递给 read 调用,因为您已经将其归零了。

标签: c linux ipc


【解决方案1】:

您需要在每次迭代时将timeout 设置为所需的值,该结构在Linux 上会被修改,所以我认为您的循环除了第一次外不会暂停,即select() 只是第一次阻塞。

尝试在select() 之后打印tv_sectv_usec,看看,它已被修改以反映在select() 返回之前还剩下多少时间。

移动这部分

timeout.tv_sec = 0;
timeout.tv_usec = 10000;

select() 调用之前的循环内,它应该可以按预期工作,您也可以在循环内移动许多声明,这将使您的代码更易于维护,例如,您可以将循环内容移动到未来的一个功能,这可能会有所帮助。

这是来自linux manual page select(2)

在 Linux 上,select() 修改超时以反映未睡眠的时间量;大多数其他实现不这样做。 (POSIX.1-2001 允许任何一种行为。)当读取超时的 Linux 代码被移植到其他操作系统时,以及当代码被移植到 Linux 对多个 select()s 重用 struct timeval 时,这都会导致问题在循环中而不重新初始化它。考虑在select() 返回后未定义超时。

我认为 qoute 中的粗体部分很重要。

【讨论】:

  • 所以问题不是很大的超时,而是 0 超时,因为超时值在每次迭代中都会下降,最终会在“while(1)”中变为 0
猜你喜欢
  • 1970-01-01
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 2013-10-28
  • 2011-10-02
  • 2012-03-03
  • 2011-09-20
  • 2011-01-30
相关资源
最近更新 更多