커널 공간 메모리 릴리스로 인해 커널이 정지됨

커널 공간 메모리 릴리스로 인해 커널이 정지됨

커널 모듈을 작성 중입니다. 사용자 공간에서 바이트를 읽고 다시 씁니다.

static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
    Node *msg;
    int error_count = 0;

    // Entering critical section
    down(&sem); //wait state

    msg = pop(&l, 0);

    // No message? No wait!
    if(!msg) {
        up(&sem);
        return -EAGAIN;
    }

    len = msg->length;
    error_count = copy_to_user(buffer, msg->string, msg->length);

    if (error_count == 0) {
        current_size -= msg->length;
        remove_element(&l, 0);
        up(&sem);
        return 0;
    } else {
        up(&sem);
        printk(KERN_INFO "opsysmem: Failed to send %d characters to the user\n", error_count);
        return -EFAULT; // Failed -- return a bad address message (i.e. -14)
    }
}

static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) {
    Node *n;

    // buffer larger than 2 * 1024 bytes
    if(len > MAX_MESSAGE_SIZE || len == 0) {
        return -EINVAL;
    }

    n = kmalloc(sizeof(Node), GFP_KERNEL);

    if(!n) { 
        return -EAGAIN;
    }

    n->string = (char*) kmalloc(len, GFP_KERNEL);
    n->length = len;

    copy_from_user(n->string, buffer, len);

    // Enter critical section
    down(&sem); //wait state

    // buffer is larger than the total list memory (2MiB)
    if(current_size + len > MAX_LIST_SIZE) {
        up(&sem);
        return -EAGAIN;
    }

    current_size += len;

    push(&l, n);

    up(&sem);
    // Exit critical section

    return len;
}

연결된 목록을 해제해야 하는 함수를 삭제합니다.

static void __exit opsysmem_exit(void) {
    // Deallocate the list of messages
    down(&sem);    
    destroy(&l);
    up(&sem);
    device_destroy(opsysmemClass, MKDEV(majorNumber, 0)); // remove the device

    class_unregister(opsysmemClass);                      // unregister the device class
    class_destroy(opsysmemClass);                         // remove the device class
    unregister_chrdev(majorNumber, DEVICE_NAME);          // unregister the major number
    printk(KERN_INFO "charDeviceDriver: Goodbye from the LKM!\n");
}

내 연결 목록과 파괴 기능은 다음과 같습니다.

static void destroyNode(Node *n) {
    if(n) {
        destroyNode(n->next);
        kfree(n->string);
        n->string = NULL;
        kfree(n);
        n = NULL;
    }
}

static void destroy(list *l){
    if(l) {
        destroyNode(l->node);
    }
}
typedef struct Node {
    unsigned int length;
    char* string;
    struct Node *next;
} Node;

typedef struct list{
    struct Node *node;
} list;

질문은 다음과 같습니다.

rmmod나는 장치 드라이버를 작성하고 드라이버와 opsysmem_exitkfree()가 모든 메모리에서 호출되기를 원합니다 .

이는 노드 수가 적을 때 작동합니다.

많은 수의 노드(1000개 이상)를 실행하고 rmmode를 사용하려고 하면 VM이 정지됩니다.

이 문제를 진단하기 위해 왜 해야 하는지 그리고 또 무엇을 해야 하는지 아십니까?

내 함수가 너무 많은 수준의 재귀를 생성합니까?

2000000개 노드에 썼다가 다시 읽어보면 문제 없을 것 같습니다. rmmod를 수행할 때 목록이 비어 있으면 모든 것이 잘 작동합니다.

편집 1:메모리를 해제하지 않고 rmmod를 실행하면 커널이 충돌하지 않는 것으로 나타났습니다. 그러나 아래와 같이 할당된 메모리가 모두 누수됩니다.케델

답변1

여기에 이미지 설명을 입력하세요.

방금 해결했습니다. 머레이 존슨이 옳습니다. 내 핵심을 죽인 것은 재귀였습니다.

이것을 배우는 데 왜 7시간이 걸렸는지 설명할 수 있는 사람이 있습니까? 실제로 C의 최대 재귀 깊이는 얼마입니까? 오늘 아침에 523756이라는 기사를 읽었습니다.여기에서 읽었습니다. C까지 아래로 스크롤하세요..

이것이 내 할당 해제기입니다. 아시다시피 누출이 전혀 없습니다.

static void destroy2(list *l) {
    Node *_current = l->node;
    Node *_next;
    while(_current) {
        _next = _current->next;
        kfree(_current->string);
        kfree(_current);
        _current = _next;
    }
}

내가 메인 포스트에서 사용한 재귀 접근 방식의 또 다른 나쁜 점은 kfreeing 2~4개 노드를 무작위로 건너뛴다는 것입니다.

내 누출 검사 보고서에 관심이 있는 모든 사람을 위해: 저는 github에서 찾은 오픈 소스 도구를 사용하고 있습니다.https://github.com/euspecter/kedr. 보장할 수는 없지만 매우 도움이 됩니다. 커널을 다시 컴파일할 필요는 없습니다.

관련 정보