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 └──────────────────────────┘ ───────────────────────────────────────────────────── 硬件层: 鼠标设备
应用层控制硬件需要完成的事情:
- 驱动如何注册
- 设备文件如何创建
- 用户空间的系统调用如何回调到系统的操作方法
- 用户空间如何将指令传递到内核,内核如何将数据传递到用户空间
- 将硬件寄存器的物理内存映射为虚拟内存控制硬件
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