You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Linux内核写操作触发Null pointer exception问题求助

分析Linux并行端口驱动写入时出现NULL指针异常的原因

嘿,作为从Windows驱动转Linux内核的新手,踩这种内存指针的坑太正常了!你遇到的终端提示“killed”和dmesg里的NULL指针异常,根源在代码里几个关键错误,咱们一个个说:

1. parallel_buffer的类型与赋值完全错误

这是触发空指针的核心原因:

  • 你定义parallel_bufferint*(指针类型),但inb(par_base)返回的是一个8位的字节值(unsigned char)。你直接把这个数值赋值给指针变量,等于让parallel_buffer指向一个非法的内存地址(比如如果inb返回0,指针就变成NULL)。
  • 更糟的是,你之前用kmalloc(16, GFP_KERNEL)给指针分配了内存,但紧接着就被parallel_buffer = inb(par_base);覆盖了指针值——不仅内存白分配了造成泄漏,还让指针指向了完全无效的地址。后续copy_from_user写入这个指针时,自然触发内核空指针异常,进程被内核杀死。

2. register_chrdev参数错误

你调用register_chrdev(major_no, dev_name, 0);时,第三个参数应该是你的struct file_operations*指针(也就是&fops),但你传了0。虽然你说读操作正常,但这属于未定义行为,内核根本找不到你的操作函数集合,早晚要出问题。

3. 端口申请的错误

  • 你调用request_region(0x378,1,"parallelport");但没有把返回值赋值给port变量,后面的if(port)判断完全无效(port是未初始化的随机值)。正确做法是把返回值存到port,然后检查是否为NULL。
  • 另外,并行端口通常需要占用3个连续端口(数据口0x378、状态口0x379、控制口0x37A),所以申请的长度应该是3而不是1。

4. copy_to_user/copy_from_user的错误使用

  • 这两个函数的返回值是未成功拷贝的字节数,你完全没检查返回值,如果拷贝失败(比如用户空间地址非法),你的代码会继续执行,导致后续错误。
  • 你用int*类型的parallel_buffer拷贝1字节,类型不匹配,会导致内存访问对齐问题或者数据截断。

5. 中断处理函数的未定义行为

parallel_interrupt里定义了irqreturn_t return_value;但没初始化就返回,这会导致内核收到不确定的返回值,可能引发奇怪的问题。


关键修正代码片段

先把parallel_buffer的类型改成普通字节类型:

// 并口是8位数据,用unsigned char足够,不需要指针
unsigned char parallel_buffer;

然后修正parallel_init函数的核心逻辑:

static int parallel_init(void) {
    int reg_result;
    int irq_req_result;
    unsigned int irq_num = 7;
    printk(KERN_INFO "Entered init\n");
    printk(KERN_INFO "Initializing parallelport dev with major %d\n", major_no);

    // 注册字符设备时传入正确的fops指针
    reg_result = register_chrdev(major_no, dev_name, &fops);
    if (reg_result < 0 ) {
        printk(KERN_ERR "Error registering char dev: %s\n", dev_name);
        return reg_result;
    }

    // 申请并口的3个端口,保存返回值并检查
    port = request_region(par_base, 3, "parallelport");
    if (!port) {
        printk(KERN_ERR "Error allocating parallel port region\n");
        unregister_chrdev(major_no, dev_name);
        return -EBUSY;
    }

    // 初始化buffer为并口当前数据
    parallel_buffer = inb(par_base);
    printk(KERN_INFO "Initialized buffer with port data: %c\n", parallel_buffer);

    // 请求中断,dev_id用&parallel_buffer保证唯一(共享中断需要唯一标识)
    irq_req_result = request_irq(irq_num, parallel_interrupt, IRQF_SHARED, dev_name, &parallel_buffer);
    if (irq_req_result < 0) {
        printk(KERN_ERR "Error requesting IRQ %u\n", irq_num);
        release_region(par_base, 3);
        unregister_chrdev(major_no, dev_name);
        return irq_req_result;
    }

    return 0;
}

修正readwrite函数,加上错误检查并正确操作:

static ssize_t parallel_read(struct file* p_file, char* buf, size_t count, loff_t* f_pos) {
    // 检查拷贝是否成功,返回错误码
    if (copy_to_user(buf, &parallel_buffer, 1)) {
        return -EFAULT;
    }
    printk(KERN_INFO "Copied data: %c\n", parallel_buffer);
    printk(KERN_INFO "Parallel driver read from device: %s\n", dev_name);
    // 返回实际读取的字节数,而不是0
    return 1;
}

static ssize_t parallel_write(struct file* p_file, const char* buf, size_t count, loff_t* f_pos) {
    unsigned char temp_data;
    // 先把用户数据读到临时变量,检查拷贝结果
    if (copy_from_user(&temp_data, buf, 1)) {
        return -EFAULT;
    }
    parallel_buffer = temp_data;
    // 把数据写入并口数据端口
    outb(parallel_buffer, par_base);
    printk(KERN_INFO "Copied data: %c\n", parallel_buffer);
    printk(KERN_INFO "Parallel driver written to device: %s\n", dev_name);
    // 返回实际写入的字节数
    return 1;
}

最后修正中断函数:

static irqreturn_t parallel_interrupt(int irq, void* dev) {
    printk(KERN_INFO "Parallel interrupt detected!!!!\n");
    // 根据实际中断处理情况返回IRQ_HANDLED或IRQ_NONE
    return IRQ_HANDLED;
}

总结一下:最直接导致你写入时崩溃的就是parallel_buffer的指针误用——把普通数值赋值给指针,让它指向了非法内存,copy_from_user操作这个指针时触发了内核空指针异常。修正这些问题后,你的驱动应该就能正常写入了。

内容的提问来源于stack exchange,提问作者Dhyan Deep A.K

火山引擎 最新活动