尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

字符设备驱动相关知识

字符设备驱动相关知识 一、字符设备驱动框架1.设备号注册- alloc_chrdev_region/unregister_chrdev_regionalloc_chrdev_region(dev-dev_no, 1, 1, MANE_DEV); // 动态分配主设备号unregister_chrdev_region(dev-dev_no, 1); // 释放设备号dev_t是32位高12位是主设备号major低20是次设备号minor- MAJOR(dev_t) / MINOR(dev_t) / MKDEV(major, minor) 这三个宏- 静态分配用 register_chrdev_region动态用 alloc_chrdev_region- 查看已分配设备号cat /proc/devices2. cdev 结构体 — cdev_init / cdev_add / cdev_delcdev_init(dev-gpio4c3_cdev, chardevice_fops); // 绑定 file_operationsdev-gpio4c3_cdev.owner THIS_MODULE; // 防止模块卸载时空指针cdev_add(dev-gpio4c3_cdev, dev-dev_no, 1); // 注册到内核cdev_del(dev-gpio4c3_cdev); // 注销3. 自动创建设备节点 — class_create device_createclass_create(THIS_MODULE, MANE_CLASS); // 在 /sys/class/ 下创建类device_create(class, NULL, dev_no, NULL, MANE_DEV); // 自动在 /dev/ 下创建设备节点- 老驱动需要手动 mknod这两个 API 让内核自动创建 /dev/xxx 节点- class_create 返回 struct class *用 IS_ERR 判断失败- device_destroy class_destroy 做反向销毁- 依赖于 CONFIG_SYSFS 和 udev/mdev- cdev_init 做了什么事初始化 cdev 的 ops 指针设置 kobj- 为什么必须设置 .owner THIS_MODULE模块引用计数防止在使用时被 rmmod- cdev_add 和 cdev_init 能不能合并老版本用 register_chrdev 可以现在分开更灵活- struct cdev 和 struct inode 的关系inode-i_cdev 指向对应的 cdev二、container_of 宏高频考点手写概率极高dev container_of(inode-i_cdev, struct gpio_led_dev, gpio4c3_cdev);- 作用通过结构体成员的指针反推出包含它的结构体的指针- 原理(type *)((char *)ptr - offsetof(type, member))#define container_of(ptr, type, member) \((type *)((char *)(ptr) - offsetof(type, member)))- 这里 inode-i_cdev 指向 struct cdev但我们把它嵌在 struct gpio_led_dev 里所以需要反推出外层结构体static struct file_operations chardevice_fops {.owner THIS_MODULE,.open gpio4c3_open,.write gpio4c3_write,};面试考点- 指定初始化器designated initializer语法不依赖成员声明顺序- 常见成员.open, .release, .read, .write, .unlocked_ioctl, .mmap, .llseek- 你的代码缺少 .release 和 .read面试可能问如果只写不读有什么问题- .owner THIS_MODULE 的作用---四、内核态与用户态数据交互必问copy_from_user(value, buf, 1); // 用户空间 → 内核空间面试考点- 为什么不能直接用 memcpy用户空间指针可能无效/恶意内核空间和用户空间地址隔离- copy_from_user 返回值0 成功非 0 表示未拷贝的字节数- 对应的 copy_to_user、get_user、put_user- __user 标记的作用Sparse 静态检查- -EFAULT 错误码的含义---五、内存映射 I/OMMIOgpio_led_device-gpio_base ioremap(GPIO_BASE_ADDR, SZ_4K); // 物理地址 → 虚拟地址iowrite32(reg_value, dev-gpio_base offset); // 写寄存器reg_value ioread32(dev-gpio_base offset); // 读寄存器iounmap(dev-gpio_base); // 释放映射- 为什么需要 ioremapCPU 访问的是虚拟地址外设寄存器是物理地址不能直接操作- void __iomem * 类型的含义标记为 I/O 内存编译器会做类型检查- ioremap vs phys_to_virtioremap 用于外设phys_to_virt 用于物理内存- iowrite32 vs writeliowrite 系列是推荐使用的 API有小端序保证- SZ_4K 的含义4KB 一页大小---六、内核内存管理gpio_led_device kzalloc(sizeof(struct gpio_led_dev), GFP_KERNEL);kfree(gpio_led_device);面试考点- kzalloc vs kmallockzalloc 多做了清零- GFP_KERNEL vs GFP_ATOMICGFP_KERNEL 可能睡眠GFP_ATOMIC 不会用于中断/自旋锁上下文- kfree(NULL) 是安全的- vmalloc vs kmallocvmalloc 虚拟连续物理不一定连续kmalloc 都连续---七、错误处理与 goto 模式内核风格你的代码用了级联 goto 的 unwind 模式但存在 bug见下方代码问题。正确的内核风格ret step1();if (ret) goto err1;ret step2();if (ret) goto err2;return 0;err2: undo_step1();err1: return ret;int register_chrdev_region(dev_t *dev, unsigned count ,const char *name);参数说明dev:设备号的起始值。类型是dev_t类型。count:要申请的设备号的个数。name设备名字返回值成功返回0失败返回负数int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)参数说明dev这个函数的第一个参数是输出型参数获得一个分配到的设备号可以用MAJOR宏和minor宏将主设备号和次设备号提取打印出来看是自动分配的是多少方便我们在mknod创建设备文件时用到主设备号和次设备号
返回列表