Linux 字符设备驱动

Last edited
Last updated July 5, 2023
Pages
Tags

1. 概念

能够以字节流的形式访问而且只能顺序访问的设备叫做字符设备,针对字符设备编写的驱动叫做字符设备驱动

2. 字符设备驱动原理框图

用户层: app: open("/dev/input/mouse0", O_RDWR); read(), wirte(), close() ───────────────────────────────────────────────────── 内核层: 鼠标设备驱动 ┌──────────────────────────┐ 鼠标设备驱动注册后会得 │ mouse_open() │ 到一个设备号可以创建 │ mouse_read() │ 鼠标设备文件 /dev/... │ mouse_write() │ 设备号:13,32 │ mouse_close() │ 设备号 = 13 << 20 | 32 └──────────────────────────┘ ───────────────────────────────────────────────────── 硬件层: 鼠标设备
应用层控制硬件需要完成的事情:
  1. 驱动如何注册
  1. 设备文件如何创建
  1. 用户空间的系统调用如何回调到系统的操作方法
  1. 用户空间如何将指令传递到内核,内核如何将数据传递到用户空间
  1. 将硬件寄存器的物理内存映射为虚拟内存控制硬件

3. 字符设备驱动注册注销API

3.1 注册接口函数

#include <linux/fs.h> // 头文件 // 函数原型 int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
功能:实现字符设备驱动的注册,这个函数申请了 256 个设备的资源,次设备号是 0 - 255。
参数:
  • major:注册的驱动对应的主设备号
    • major == 0:系统会自动分配一个主设备号
    • major > 0: 会被当作主设备号,静态指定
  • name:注册时填写的驱动名称
  • fops:操作方法结构体指针
返回值:
  • major == 0 时,成功返回主设备号,失败返回错误码
  • major > 0 时,成功返回 0,失败返回错误码
💡
1. cat /proc/devices 查看已经注册的驱动的名字、类型以及主设备号 2. 尽量使用动态申请,避免设备号重复

3.2 操作方法结构体

用于管理驱动中的各种操作方法(节选):
struct file_operations { int (*open) (struct inode *, struct file *); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); int (*release) (struct inode *, struct file *); };

3.3 注销接口函数

void unregister_chrdev(unsigned int major, const char *name);

4. 字符设备驱动注册实例

4.1 编写驱动代码

#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> int major; // 保存主设备号 // 封装操作方法 int mycdev_open(struct inode *inode, struct file *file) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *iof) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } int mycdev_close(struct inode *inode, struct file *file) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } // 定义操作方法结构体变量并完成初始化 struct file_operations fops = { .open = mycdev_open, .read = mycdev_read, .write = mycdev_write, .release = mycdev_close, }; static int __init mycdev_init(void) { major = register_chrdev(0, "mychrdev", &fops); if (major < 0) { printk("字符设备驱动注册失败\n"); return major; } printk("字符设备驱动注册成功major=%d\n", major); return 0; } static void __exit mycdev_exit(void) { unregister_chrdev(major, "mychrdev"); } module_init(mycdev_init); module_exit(mycdev_exit); MODULE_LICENSE("GPL");

4.2 编译驱动代码

还是使用
Linux 内核模块编程
中的 Makefile 文件
make arch=x86 modname=mychrdev

4.3 安装

$ insmod mychrdev.ko $ dmesg [52123.550397] 字符设备驱动注册成功major=239

4.4 查看是否注册

$ cat /proc/devices | grep mychrdev 239 mychrdev

4.5 手动创建设备文件

设备文件不能简单通过 touch 来创建(删除可以直接使用 rm ),需要使用 mknod ,其格式为:
# mknod 设备路径 设备类型(c/b) 主设备号 次设备号 # 例: $ mknod /dev/mychrdev c 239 0 # 查看文件 $ ls -l /dev/mychrdev

4.6 编写程序测试

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char const *argv[]) { char buf[128] = { 0 }; int fd = open("/dev/mychrdev", O_RDWR); if (fd < 0) { printf("打开设备文件失败\n"); exit(-1); } read(fd, buf, sizeof(buf)); write(fd, buf, sizeof(buf)); close(fd); return 0; }
编译执行:
$ gcc test.c $ ./a.out $ dmesg [52248.199508] /sys_import/mychrdev/mychrdev.c:mychrdev_open:10 [52248.199523] /sys_import/mychrdev/mychrdev.c:mychrdev_read:16 [52248.199526] /sys_import/mychrdev/mychrdev.c:mychrdev_write:23 [52248.199597] /sys_import/mychrdev/mychrdev.c:mychrdev_close:29

参考资料:

  1. Character Device Files (tldp.org)