C - Linux 内核:关闭两个 kthreads 冻结机器

C - Linux Kernel: Closing two kthreads freezes machine

编辑:认为我解决了这个问题。明天一上班,我会尽快添加我自己的answer/solution。


我正在为 Linux 内核 3.16.x 开发内核模块。我的模块应该接收和发送以太网帧,所以我创建了两个线程。一个用于接收部分,一个用于发送部分。它工作正常。

当我尝试关闭模块的设备文件并卸载它时出现我的问题。准确地说,它发生在试图杀死我的线程时。机器(我正在虚拟机上测试,Ubuntu 14.04)在没有任何输出之前就冻结了。但是,当我从代码中删除 "thread killing part" 时,它不再冻结。这就是为什么我很确定,问题源于我处理 kthreads 被杀死的方式。

这是我的两个线程:

TX THREAD:信号量 tx_sem 在代码的另一部分中向上。

static int tx_task(void *par)
{
    device->tx_task_running = 1;

    allow_signal(SIGTERM);
    while (!signal_pending(current) && device->tx_task_running) {
        /* wait until there is something to send */
        down_interruptible(&device->tx_sem);

        if (signal_pending(current)) {
            PRINTD("device_TX_task(): Received kill signal\n");
            break;
        }

        /* check if device is still initialized before continuing*/
        if (!device->init_flag) {
            break;
        }
    }
    device->tx_task_running = 0;
    return DEVICE_RET_OK;
}

接收线程

static int device_rx_task(void *par)
{
    device->rx_task_running = 1;

    /* task loop */
    allow_signal(SIGTERM);
    while (!signal_pending(current) && device->rx_task_running) {
        rxlen = kernel_recvmsg(device->sock, &msg, 
            (struct kvec *)&iov, 1, DEVICE_PAY_SIZE, 0);

        if (signal_pending(current)) {
            PRINTD("device_rx_task(): Received kill signal\n");
            break;
        }

        if(rxlen < 0) {
            PRINTD("device_RX_task(): Got error when receiving\n");
            break;
        }

        /* check if device is still initialized before continuing*/
        if (!device->init_flag) {
            break;
        }
    }
    device->rx_task_running = 0;
    return DEVICE_RET_OK;
}

这些家伙 运行 直到我尝试关闭我的设备。当关闭内核模块时,这个函数被调用,这就是它崩溃的时候。例如,如果我注释掉 "send_sig" 函数,它不会崩溃。当我尝试手动终止线程时它也会崩溃:

int Device_DevTerm(int dev)
{
    device->init_flag = 0;

    send_sig(SIGTERM, device->rx_thread, 0);
    send_sig(SIGTERM, device->tx_thread, 0);
    device->rx_task_running = 0;
    device->tx_task_running = 0;

    return DEVICE_RET_OK;
}

我google了很久,为了找到问题所在,但到目前为止我没有成功。由于我已经在这个问题上花了很多时间,所以我决定问问你们。

我做错了什么?

p.s。我不认为我曾经在这里发布过,我只是阅读了很多。我希望我的问题足够清楚和简洁。应该不是。

看来我找到问题了。

在为 kernel_recvmsg() 函数设置 msghdr 时,我曾经这样做过:

struct sockaddr_in client;

MEM_SET(&client, 0x00, sizeof(struct sockaddr_in));

/* setup receive parameter */
msg.msg_name = &client;
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

现在我把套路改成:

struct sockaddr_in client[3];

MEM_SET(&client[0], 0x00, 3 *sizeof(struct sockaddr_in));

/* setup receive parameter */
msg.msg_name = &client[1];
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

有了这个它就不会再崩溃了。然后我在我们的代码库中发现了以下注释:

/*
 * NOTE:
 * use three times the size of struct sockaddr_in as workaround
 * for a possible kernel bug which has been seen on a Ubuntu 64bit
 * system
 * after the call of kernel_recvmsg 2 bytes behind the structure
 * client had been corrupted on the stack
 */

我不确定这是否属实,也不确定这是否涵盖了我的全部问题,但它似乎可以解决问题。