3. 字符设备驱动基础

任务目标

在Android内核(Linux内核)环境中实现一个最小字符设备驱动:动态注册主设备号、创建device class与device节点,并实现open、read、write、release四个基本文件操作。通过用户态程序验证设备节点的读写功能。

环境与依赖

  • Android内核源码树(或独立Linux内核源码树),已配置交叉编译工具链
  • 内核版本:建议5.10+(适配Android GKI或AOSP common kernel)
  • 目标设备已解锁并支持内核模块加载(或编译进内核)
  • 用户态验证工具:adb shell或串口终端

驱动实现

创建设备节点路径为/dev/char_demo。write操作将用户数据暂存于内核缓冲区(最大4KB),read操作将缓冲区数据拷贝回用户态,open/release仅维护访问计数。

```c #include #include #include #include #include #include #define DEVICE_NAME "char_demo" #define CLASS_NAME "demo_class" #define BUF_SIZE 4096 static int major_num; static struct class *demo_class; static struct device *demo_device; static char *kernel_buf; static int buf_len; static int dev_open(struct inode *inode, struct file *file) { return 0; } static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) { int ret; if (buf_len == 0) return 0; if (count > buf_len) count = buf_len; ret = copy_to_user(buf, kernel_buf, count); if (ret) return -EFAULT; buf_len = 0; return count; } static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) { int ret; if (count > BUF_SIZE) return -ENOMEM; ret = copy_from_user(kernel_buf, buf, count); if (ret) return -EFAULT; buf_len = count; return count; } static int dev_release(struct inode *inode, struct file *file) { return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, }; static int __init char_demo_init(void) { major_num = register_chrdev(0, DEVICE_NAME, &fops); if (major_num < 0) { pr_err("Failed to register device\n"); return major_num; } demo_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(demo_class)) { unregister_chrdev(major_num, DEVICE_NAME); return PTR_ERR(demo_class); } demo_device = device_create(demo_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME); if (IS_ERR(demo_device)) { class_destroy(demo_class); unregister_chrdev(major_num, DEVICE_NAME); return PTR_ERR(demo_device); } kernel_buf = kzalloc(BUF_SIZE, GFP_KERNEL); if (!kernel_buf) { device_destroy(demo_class, MKDEV(major_num, 0)); class_destroy(demo_class); unregister_chrdev(major_num, DEVICE_NAME); return -ENOMEM; } buf_len = 0; pr_info("char_demo: initialized, major=%d\n", major_num); return 0; } static void __exit char_demo_exit(void) { kfree(kernel_buf); device_destroy(demo_class, MKDEV(major_num, 0)); class_destroy(demo_class); unregister_chrdev(major_num, DEVICE_NAME); pr_info("char_demo: exited\n"); } module_init(char_demo_init); module_exit(char_demo_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kernel Dev"); MODULE_DESCRIPTION("Simple character device driver demo"); MODULE_VERSION("1.0"); ```

编译与加载

将上述代码保存为char_demo.c,内核源码树内创建Kbuild文件:

```makefile obj-m += char_demo.o ```

执行编译(以AOSP common kernel为例):

```bash export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-android- make defconfig # 或使用目标设备的现有配置 make M=$PWD modules ```

将生成的char_demo.ko推送至设备并加载:

```bash adb push char_demo.ko /data/local/tmp/ adb shell su -c "insmod /data/local/tmp/char_demo.ko" ```

加载后检查设备节点是否生成:

```bash adb shell ls -l /dev/char_demo ```

用户态验证

编写最小测试程序test_demo.c:

```c #include #include #include #include int main(void) { int fd; char wbuf[] = "hello kernel"; char rbuf[64] = {0}; int ret; fd = open("/dev/char_demo", O_RDWR); if (fd < 0) { perror("open"); return 1; } ret = write(fd, wbuf, strlen(wbuf) + 1); printf("write ret = %d\n", ret); ret = read(fd, rbuf, sizeof(rbuf)); printf("read ret = %d, data = %s\n", ret, rbuf); close(fd); return 0; } ```

编译并运行:

```bash # 使用Android NDK交叉编译,或直接在设备上使用toybox cc adb push test_demo.c /data/local/tmp/ adb shell su -c "cd /data/local/tmp && cc test_demo.c -o test_demo && ./test_demo" ```

验证判定标准

  • insmod无报错,dmesg可见char_demo: initialized日志
  • /dev/char_demo节点存在且权限可读写
  • 测试程序输出write ret = 13(含结尾\0),read ret = 13, data = hello kernel,与代码逻辑一致
  • 卸载模块后节点消失,dmesg可见退出日志

常见问题

  • 设备节点未生成:确认CONFIG_DEVTMPFS已启用,且udev或vold未拦截设备创建
  • insmod失败提示Invalid module format:内核版本与模块编译版本不匹配,需使用目标设备对应内核源码编译
  • read返回0:驱动设计为单次读取后清空缓冲区,需先write再read
用户态 (User Space) test_demo open/read/write syscall 内核态 (Kernel Space) VFS file_operations char_demo 驱动 dev_open dev_read / dev_write dev_release 设备号 major (动态分配) device class demo_class kernel_buf 4KB 暂存区 /dev/char_demo