Linux字符设备驱动程序问题

bem22

我正在编写LKM来创建字符设备驱动程序。

Linux内核:VirtualBox中为4.4.0-93-generic,2GB内存,SWAP为300Kb

问题1

如果我编写了一个在dev_write中处理fd的C程序,那么一切都很好,它会按应有的方式进行读取,但是如果我尝试使用head -n 1 </ dev / opsysmem,它将不会输出任何内容。

从设备读取的代码:

int main()
{
    int ret, fd;
    char stringToSend[BUFFER_LENGTH];
    printf("Starting device test code example...\n");
    fd = open("/dev/opsysmem", O_RDWR); // Open the device with read/write access
    if (fd < 0)
    {
        perror("Failed to open the device...");
        return errno;
    }

    printf("Press ENTER to read back from the device...\n");
    getchar();

    printf("Reading from the device...\n");
    ret = read(fd, receive, BUFFER_LENGTH); // Read the response from the LKM
    if (ret < 0)
    {
        perror("Failed to read the message from the device.");
        return errno;
    }
    printf("The received message is: [%s]\n", receive);

    return 0;
}

问题2

如果我反复发送足够大的消息,则一切正常,我的2MiB缓冲区已满,然后丢弃以下消息。但是,如果消息较小(即每个消息为1个字符),则消息将在大约10000个节点后停止。这是我的链表实现问题,还是已知的linux问题,或者仅仅是我没有观察到代码中的某些内容?

当我遇到问题2时,vCPU以正弦方式加速

这是我的读写功能:

static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) {
    Node *msg;
    int ret_val;
    char* message;
    unsigned int message_length;

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

    msg = pop(&l, 0);

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

    if(len < msg->length) {
        up(&sem);
        return -EINVAL;
    }

    // Since we have a message, let's send it!
    current_size -= message_length;

    // copy_to_user has the format ( * to, *from, size) and returns 0 on success
    ret_val = copy_to_user(buffer, msg->string, message_length);

    if (!ret_val) {
        remove_element(&l, 0);
        up(&sem);
        return ret_val;
    } else {
        up(&sem);
        return -EFAULT; // Failed
    }
}


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
    if(SLEEP) msleep(100);

    // 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;
}

这是我的链表及其功能

typedef struct Node {
    unsigned int length;
    char* string;
    struct Node *next;
} Node;

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

static void init(list * l){
    l->node = NULL;
}

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);
    }
}

static Node* pop(list *l, unsigned int index) {
    struct Node *_current = l->node;

    // Cut down index until reaching the desired position
    while(_current) {
        if(index) {
            _current = _current->next;
            index--;
        } else { return _current; }
    }

    // If you are here, the node does not exist
    return NULL;
}

static int push(list * l, Node *n) {
    if(!n) { return -1; }

    // Initialize the string

    // Do we have a node in the list?
    if (l->node) { 
        // Virtually add it as a head
        n->next = l->node;
    } else {
        n->next = NULL;
    } // Otherwise prepare the list to have no tail

    // Now make the list point to the head
    l->node = n;

    return 0;
}

static int remove_element(list * l, unsigned int index){
    // Get the reference for head
    struct Node *previous;
    struct Node *_current;

    previous = NULL;
    _current = (Node*) l->node;

    // Swap _current until index
    while(_current) {
        // Is the index !0 and we have more nodes?
        if(index) {
            previous = _current;
            _current = _current->next;
            index--;
        } else {
            if(previous) {
                previous->next = _current->next;
            } else {
                l->node = _current->next;
            }
            // Free memory, assign NULL pointer
            kfree(_current->string);
            _current-> string = NULL;
            kfree(_current);
            _current = NULL;

            // Return success
            return 0;
        }
    }

    // No _current? No problem!
    return -1;
}

关于__问题2的更新__我为输入字符串尝试了不同的大小,然后发现:在对设备驱动程序进行了大约650次调用之后,大小为3.3k,消息列表大小变为4MiB(最大值)。对该设备再进行几次调用,然后内核冻结。

编辑1:我按照注释更新了te dev_write并删除了调试代码编辑2:添加了更多功能:push / pop / destroy编辑3:我检查了缓冲区长度与消息长度

默里·詹森(Murray Jensen)

我认为问题1可能是因为head没有看到行尾字符(例如,换行符'\n'),或者它使用了搜索系统调用,而您忽略了and函数中offset参数(这意味着,如果我无法执行搜索,正确地理解它)...看看这个了-头并尝试优化使用的东西追求,但不知道这是否适用于你的情况。dev_read()dev_write()

也不确定您因时间不同步而导致的问题2的答案是否正确(除非与无关msleep())...我的猜测可能是内存分配问题或竞争状况,但您没有向我们显示源的代码push()pop()所以我们不知道。

看来您只是存储了bufferandlen参数dev_write(),然后使用它们dev_read()传递给copy_to_user()...该缓冲区中的数据仍将位于用户空间中,因此您可能正在尝试从用户空间复制到用户空间。阅读此内容可能会有所帮助。

您应该使用push()pop()...的代码更新您的问题,但至少push()需要为要插入列表中的链表元素和用于保存写入数据的缓冲区分配内存,然后用于copy_from_user()获取数据用完用户空间并放入内核缓冲区...,然后,在完成msgin之后dev_read(),您将需要释放其中包含的内核缓冲区msg,然后释放msg自身。

我知道在这里进行了大量复制,但是要避免这种情况,您必须非常努力地处理虚拟内存系统和代码设计(即零复制实现)。

一个更小,但很重要的事情,在dev_read()你不检查message_length<= len即有在缓冲区中的消息足够的空间。例如,如您的代码所示,您的驱动程序可能会尝试复制大于可用空间的消息。copy_to_user()应该抓住这一点,但是也许这又是问题2的根源

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章