设备号 在 Linux 系统中每一个设备都有相应的设备号,设备号有主设备号 与次设备号 之分:
主设备号 用来表示一个特定的驱动
次设备号 用来管理下面的设备。
比如主设备号1表示USB驱动,然后次设备2表示鼠标驱动,次设备3表示键盘驱动
在注册字符设备驱动之前需要先申请设备号 。
include/linux/types.h
1 2 3 4 5 6 7 typedef u32 __kernel_dev_t ;typedef __kernel_fd_set fd_set;typedef __kernel_dev_t dev_t ;typedef __kernel_ino_t ino_t ;typedef __kernel_mode_t mode_t ;typedef unsigned short umode_t ;
设备号dev_t是u32类型,高12位为主设备号 ,低20位为次设备号 。
设备号操作宏 include/linux/kdev_t.h
1 2 3 4 5 6 #define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MINORBITS 表示次设备号的位数,一共20位
MINORMASK 用于计算次设备号
MAJOR 表示从dev_t中获取主设备号,本质是将dev_t右移20位
MINOR 表示从dev_t中获取次设备号,本质是取低20位的值
MKDEV 用于将主设备号和次设备号组成dev_t类型的设备号
设备号申请函数 在 Linux 驱动中可以使用以下两种方法进行设备号的申请。
include/linux/fs.h
1 2 3 extern int register_chrdev_region (dev_t , unsigned , const char *) ;extern int alloc_chrdev_region (dev_t *, unsigned , unsigned , const char *) ;
通过 register_chrdev_region(dev_t from, unsigned count, const char *name) 函数进行静态申请设备号。
1 register_chrdev_region(dev_t from, unsigned count, const char *name)
函数作用 :静态申请设备号,对指定好的设备号进行申请。
参数含义 :
from: 自定义的 dev_t 类型设备号,比如MKDEV(100,0)表示起始主设备号100,起始次设备号为0
count: 次设备的数量,表示在主设备号相同的情况下有几个次设备号。
name: 申请的设备名称
函数返回值 :申请成功返回 0,申请失败返回负数
通过alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char* name)函数进行动态申请设备号。
1 alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
设备号释放函数 include/linux/fs.h
1 extern void unregister_chrdev_region (dev_t , unsigned ) ;
函数功能:设备号释放函数,注销字符设备以后要释放掉设备号
函数参数:
参数1:要释放的设备号
参数2:释放的设备号的数量
示例 my_driver.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); static dev_t dev_id;static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); printk(KERN_INFO "major from module_param: %d" , MAJOR(dev_id)); printk(KERN_INFO "minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { printk(KERN_ERR "register_chrdev_region error\n" ); } else { printk(KERN_INFO "register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { printk(KERN_ERR "alloc_chrdev_region error\n" ); } else { printk(KERN_INFO "alloc_chrdev_region ok\n" ); printk(KERN_INFO "major allocated: %d" , MAJOR(dev_id)); printk(KERN_INFO "minor allocated: %d" , MINOR(dev_id)); } } printk(KERN_INFO "my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { if (dev_id) { unregister_chrdev_region(dev_id, 1 ); printk(KERN_INFO "unregister_chrdev_region ok\n" ); } printk(KERN_INFO "my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ~ [ 18.414274] my_driver: loading out-of-tree module taints kernel. [ 18.421489] alloc_chrdev_region ok [ 18.421652] major allocated: 511 [ 18.421667] minor allocated: 0 [ 18.421910] my_driver: Module loaded ~ 511 my_driver device ~ [ 27.856484] unregister_chrdev_region ok [ 27.856638] my_driver: Module unloaded ~ ~ ~ [ 16.721502] my_driver: loading out-of-tree module taints kernel. [ 16.728530] major from module_param: 512 [ 16.728574] minor from module_param: 0 [ 16.728753] CHRDEV "my_driver device" major requested (512) is greater than the maximum (511) [ 16.729588] register_chrdev_region error [ 16.729760] my_driver: Module loaded ~ [ 36.863583] unregister_chrdev_region ok [ 36.864560] my_driver: Module unloaded ~ [ 40.101231] major from module_param: 500 [ 40.101265] minor from module_param: 0 [ 40.101430] register_chrdev_region ok [ 40.101657] my_driver: Module loaded ~ [ 50.626705] unregister_chrdev_region ok [ 50.626876] my_driver: Module unloaded
注册字符类设备 include/linux/cdev.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include <linux/kobject.h> #include <linux/kdev_t.h> #include <linux/list.h> #include <linux/device.h> struct file_operations ;struct inode ;struct module ;struct cdev { struct kobject kobj ; struct module *owner ; const struct file_operations *ops ; struct list_head list ; dev_t dev; unsigned int count; } __randomize_layout; void cdev_init (struct cdev *, const struct file_operations *) ;struct cdev *cdev_alloc (void ) ;void cdev_put (struct cdev *p) ;int cdev_add (struct cdev *, dev_t , unsigned ) ;void cdev_set_parent (struct cdev *p, struct kobject *kobj) ;int cdev_device_add (struct cdev *cdev, struct device *dev) ;void cdev_device_del (struct cdev *cdev, struct device *dev) ;void cdev_del (struct cdev *) ;void cd_forget (struct inode *) ;#endif
GCC/Clang attribute(__randomize_layout), 用于内核的 结构体布局随机化(struct layout randomization) ,增加攻击者预测结构偏移的难度,提高内核安全性。
cdev结构体
字段
含义
kobj
继承自 struct kobject,使字符设备能挂在 sysfs 下(即 /sys/class/... 等路径)。
owner
指向拥有该设备的模块(THIS_MODULE),防止模块卸载时设备仍在使用。
ops
指向设备操作函数表(struct file_operations),定义 read/write/ioctl 等行为。
list
内部链表,用于内核维护所有注册的字符设备。
dev
设备号,类型是 dev_t(包含主设备号 major 和次设备号 minor)。
count
表示这个 cdev 控制的连续设备编号数量(常为 1)。
cdev_init fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void cdev_init (struct cdev *cdev, const struct file_operations *fops) { memset (cdev, 0 , sizeof *cdev); INIT_LIST_HEAD(&cdev->list ); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
作用 :初始化一个静态分配的 struct cdev。建立cdev和file_operations之间的联系
cdev_add fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int cdev_add (struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; if (WARN_ON(dev == WHITEOUT_DEV)) return -EBUSY; error = kobj_map(cdev_map, dev, count, NULL , exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0 ; }
作用 :向系统添加一个cdev结构体,也就是添加一个字符设备
cdev_del fs/char_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void cdev_del (struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); static dev_t dev_id;static struct cdev cdev_test ;static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, }; static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); pr_info( "major from module_param: %d" , MAJOR(dev_id)); pr_info( "minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err( "register_chrdev_region error\n" ); return ret; } else { pr_info( "register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err( "alloc_chrdev_region error\n" ); return ret; } else { pr_info( "alloc_chrdev_region ok\n" ); pr_info( "major allocated: %d" , MAJOR(dev_id)); pr_info( "minor allocated: %d" , MINOR(dev_id)); } } cdev_init(&cdev_test, &cdev_test_ops); cdev_test.owner = THIS_MODULE; ret = cdev_add(&cdev_test, dev_id, 1 ); if (ret < 0 ) { pr_err( "cdev_add error\n" ); unregister_chrdev_region(dev_id, 1 ); return ret; }else { pr_info( "cdev_add ok\n" ); } pr_info( "my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { if (dev_id) { cdev_del(&cdev_test); unregister_chrdev_region(dev_id, 1 ); pr_info( "unregister_chrdev_region ok\n" ); } pr_info( "my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
注意先删除设备再释放设备号。
file_operations结构体 Linux有一个很重要的概念叫一切皆文件,也就是Linux中的设备就像普通的文件一样。访问一个设备好像是在访问一个文件。在应用程序中我们可以使用open, read, write, close, ioctl这几个系统调用来操作驱动。当我们在应用程序中调用open函数的时候,最终会去执行驱动中的open函数。所以file_operations将系统调用和驱动程序连接起来了。
应用中调用open函数
1 fd = open("/dev/hello" , O_RDWR);
驱动中执行open函数
1 2 3 4 5 6 7 8 9 10 11 12 static int cdev_test_open (struct inode *, struct file*) { printk("This is cdev_test open" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }
linux中file_operations定义:
include/linux/fs.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 struct file_operations { struct module *owner ; loff_t (*llseek) (struct file *, loff_t , int ); ssize_t (*read) (struct file *, char __user *, size_t , loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t , loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iopoll)(struct kiocb *kiocb, bool spin); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int , unsigned long ); long (*compat_ioctl) (struct file *, unsigned int , unsigned long ); int (*mmap) (struct file *, struct vm_area_struct *); unsigned long mmap_supported_flags; int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t , loff_t , int datasync); int (*fasync) (int , struct file *, int ); int (*lock) (struct file *, int , struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int , size_t , loff_t *, int ); unsigned long (*get_unmapped_area) (struct file *, unsigned long , unsigned long , unsigned long , unsigned long ) ; int (*check_flags)(int ); int (*flock) (struct file *, int , struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t , unsigned int ); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t , unsigned int ); int (*setlease)(struct file *, long , struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t , struct file *, loff_t , size_t , unsigned int ); loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int remap_flags); int (*fadvise)(struct file *, loff_t , loff_t , int ); bool may_pollfree; } __randomize_layout;
常见字段解析:
函数指针
用户态调用
作用
是否常用
owner
—
通常写为 THIS_MODULE,防止模块被正在使用时卸载
✅
open
open()
打开设备时调用,一般用于初始化或统计打开次数
✅
release
close()
关闭设备时调用,用于释放资源
✅
read
read()
用户读设备数据
✅
write
write()
用户写数据到设备
✅
unlocked_ioctl
ioctl()
用户发控制命令
✅
poll
poll()/select()
实现非阻塞 I/O(epoll 机制)
⚙️ 常见于网络或串口驱动
mmap
mmap()
将物理内存映射到用户空间
⚙️ GPU / DMA 驱动常用
llseek
lseek()
文件位置偏移
⚙️ 文件类驱动
fasync
fcntl(F_SETFL, O_ASYNC)
支持异步通知
⚙️ 串口驱动常见
返回值约定 :
ssize_t:>=0 成功字节数,<0 errno
int / long:0 成功,<0 errno
指针:成功返回指针,失败返回 ERR_PTR(-errno)
llseek 1 loff_t (*llseek)(struct file *file, loff_t offset, int whence);
作用 :文件指针偏移操作(类似 lseek 系统调用)。
参数 :
file:文件对象。
offset:偏移量。
whence:
SEEK_SET:相对于文件开头
SEEK_CUR:相对于当前文件指针
SEEK_END:相对于文件末尾
返回值 :
新的文件指针位置(loff_t)
出错返回负值(如 -EINVAL)
read 1 ssize_t (*read)(struct file *file, char __user *buf, size_t count, loff_t *pos);
作用 :从文件读数据到用户空间。
参数 :
file:文件对象。
buf:用户空间缓冲区指针。
count:希望读取的字节数。
pos:文件指针位置,读完后需要更新。
返回值 :
write 1 ssize_t (*write)(struct file *file, const char __user *buf, size_t count, loff_t *pos);
作用 :把用户数据写入文件/设备。
参数 :
file:文件对象
buf:用户缓冲区
count:写入字节数
pos:文件指针
返回值 :
mmap 1 int (*mmap)(struct file *, struct vm_area_struct *);
作用 :支持用户空间内存映射到设备。
参数 :
返回值 :
mmap_supported_flags:映射时支持的标志。
open 1 int (*open)(struct inode *inode, struct file *file);
作用 :当用户态调用 open() 打开设备或文件时被调用。
参数 :
inode:指向设备对应的 inode 结构,包含文件系统层信息和设备号。
file:指向文件对象(struct file),保存文件状态、偏移量、private_data 等。
返回值 :
0:成功打开
<0:出错,返回对应 errno(如 -EBUSY 表示设备忙,-ENOMEM 内存不足)
典型用途 :
检查设备是否已经被占用
初始化 file->private_data 指针
增加模块引用计数(THIS_MODULE)
flush 1 int (*flush)(struct file *file, fl_owner_t id);
作用 :刷新文件缓冲区,确保用户态写入的数据在内核缓冲中被提交。
参数 :
file:文件对象
id:文件句柄拥有者标识,一般是文件描述符对应的进程
返回值 :
说明 :
对于大多数字符设备驱动,flush 不需要特殊实现,直接返回 0 即可
在块设备或网络设备中,flush 可能用于提交缓存数据
release 1 int (*release)(struct inode *inode, struct file *file);
作用 :当用户态调用 close() 关闭设备或文件时被调用。
参数 :
inode:设备对应 inode
file:文件对象
返回值 :
典型用途 :
释放在 open() 时分配的资源
恢复设备状态(如标记为未被占用)
减少模块引用计数
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); static dev_t dev_id;static struct cdev cdev_test ;int cdev_test_open (struct inode *inode, struct file *file) { pr_info("cdev_test_open was called" ); return 0 ; } ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { pr_info("cdev_test_read was called" ); return 0 ; } ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { pr_info("cdev_test_write was called" ); return 0 ; } int cdev_test_release (struct inode *inode, struct file *file) { pr_info("cdev_test_release was called" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }; static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); pr_info("major from module_param: %d" , MAJOR(dev_id)); pr_info("minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err("register_chrdev_region error\n" ); return ret; } else { pr_info("register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err("alloc_chrdev_region error\n" ); return ret; } else { pr_info("alloc_chrdev_region ok\n" ); pr_info("major allocated: %d" , MAJOR(dev_id)); pr_info("minor allocated: %d" , MINOR(dev_id)); } } cdev_init(&cdev_test, &cdev_test_ops); cdev_test.owner = THIS_MODULE; ret = cdev_add(&cdev_test, dev_id, 1 ); if (ret < 0 ) { pr_err("cdev_add error\n" ); unregister_chrdev_region(dev_id, 1 ); return ret; } else { pr_info("cdev_add ok\n" ); } pr_info("my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { if (dev_id) { cdev_del(&cdev_test); unregister_chrdev_region(dev_id, 1 ); pr_info("unregister_chrdev_region ok\n" ); } pr_info("my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
设备结点 在 Linux 操作系统中一切皆文件,对于用来进行设备访问的文件称之为设备节点 。程序通过操作这个”设备文件”,便可以操作对应的硬件。
1 fd = open("/dev/hello" , O_RDWR);
这个“设备文件”就是设备结点。所以Linux设备结点是应用程序和驱动程序沟通的一个桥梁。设备节点被创建在/dev 目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [zhaohang@cyberboy linux_driver_learning]$ ll /dev crw------- 4,15 root 11 Nov 10:05 tty15 crw------- 4,16 root 11 Nov 10:05 tty16 crw------- 4,17 root 11 Nov 10:05 tty17 crw------- 4,18 root 11 Nov 10:05 tty18 crw------- 4,19 root 11 Nov 10:05 tty19 crw------- 4,20 root 11 Nov 10:05 tty20 crw------- 4,21 root 11 Nov 10:05 tty21 crw------- 4,22 root 11 Nov 10:05 tty22 crw------- 4,23 root 11 Nov 10:05 tty23 crw------- 4,24 root 11 Nov 10:05 tty24 crw------- 4,25 root 11 Nov 10:05 tty25 crw------- 4,26 root 11 Nov 10:05 tty26 crw------- 4,27 root 11 Nov 10:05 tty27 crw------- 4,28 root 11 Nov 10:05 tty28 crw------- 4,29 root 11 Nov 10:05 tty29
4,15表示主设备号和次设备号。crw中c表示字符设备。
根据设备节点的创建方式不同,分为了手动创建设备节点 和自动创建设备节点 。
Linux下创建结点的方式 手动创建设备结点 使用 mknod 命令手动创建设备节点,mknod 命令格式为:
1 mknod NAME TYPE MAJOR MINOR
参数含义:
NAME: 要创建的节点名称
TYPE: b 表示块设备,c 表示字符设备,p 表示管道
MAJOR:要链接设备的主设备号
MINOR: 要链接设备的从设备号
例如: 使用以下命令创建一个名为 device_test 的字符设备节点,设备的主设备号和从设备号分别为 236 和 0。
1 mknod /dev/device_test c 236 0
自动创建设备结点 自动创建设备节点是利用 udev 机制来实现的。
udev 是一个用户程序 ,通过检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件 。
自动创建设备节点需要在驱动中首先使用 class_create()函数创建一个类,创建的类位于于/sys/class/ 目录下 ,之后使用 device_create()函数在这个类下创建相应的设备 ,在加载驱动模块时,用户空间中的 udev 会自动响应根据并/sys/class/ 下的信息创建设备节点。
在嵌入式Linux中我们使用的是mdev,mdev是udev的简化版本 。在使用busybox构建的跟文件系统的时候,busybox会自动创建mdev。
class_create函数 linux/device/class.h
引用头文件使用linux/device/device.h因为device.h引用了class.h
1 2 3 4 5 6 7 8 #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
函数作用 :用于动态创建设备类。
参数含义 :
owner:struct module 结构体类型的指针。一般赋值为 THIS_MODULE。
name:char 类型的指针,代表即将创建的 struct class 变量的名字。
返回值 :struct class * 类型的结构体
class_destroy函数 linux/device/class.h
1 extern void class_destroy (struct class *cls) ;
函数作用:用于删除设备类。
参数含义:
owner:struct module 结构体类型的指针,一般赋值为 THIS_MODULE。
name:char 类型的指针,代表即将创建的 struct class 变量的名字。
device_create函数 linux/device/device.h
使用class_create创建好类以后,还需要使用device_create函数在类下面创建一个设备。
1 2 3 __printf(5 , 6 ) struct device * device_create (struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) ;
函数作用:用来在 class 类下创建一个设备文件。
参数含义:
cls :指定所要创建的设备所从属的类。
parent :指定该设备的父设备,如果没有就指定为 NULL。
devt :指定创建设备的设备号。
drvdata :被添加到该设备回调的数据,没有则指定为 NULL。
fmt :添加到系统的设备节点名称。
返回值:struct device * 类型结构体
device_destroy函数 linux/device/device.h
1 void device_destroy (struct class *cls, dev_t devt) ;
函数作用 :用来删除 class 类中的设备。
参数含义:
cls :指定所要创建的设备所从属的类。
devt :指定创建设备的设备号。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); struct class *class ;struct device *device ;static dev_t dev_id;static struct cdev cdev_test ;int cdev_test_open (struct inode *inode, struct file *file) { pr_info("cdev_test_open was called" ); return 0 ; } ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { pr_info("cdev_test_read was called" ); return 0 ; } ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { pr_info("cdev_test_write was called" ); return 0 ; } int cdev_test_release (struct inode *inode, struct file *file) { pr_info("cdev_test_release was called" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }; static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); pr_info("major from module_param: %d" , MAJOR(dev_id)); pr_info("minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err("register_chrdev_region error\n" ); return ret; } else { pr_info("register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err("alloc_chrdev_region error\n" ); return ret; } else { pr_info("alloc_chrdev_region ok\n" ); pr_info("major allocated: %d" , MAJOR(dev_id)); pr_info("minor allocated: %d" , MINOR(dev_id)); } } cdev_init(&cdev_test, &cdev_test_ops); cdev_test.owner = THIS_MODULE; ret = cdev_add(&cdev_test, dev_id, 1 ); if (ret < 0 ) { pr_err("cdev_add error\n" ); unregister_chrdev_region(dev_id, 1 ); return ret; } else { pr_info("cdev_add ok\n" ); } class = class_create(THIS_MODULE, "test" ); device = device_create(class, NULL , dev_id, NULL , "my_driver" ); pr_info("my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { device_destroy(class, dev_id); class_destroy(class); cdev_del(&cdev_test); unregister_chrdev_region(dev_id, 1 ); pr_info("unregister_chrdev_region ok\n" ); pr_info("my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
测试:
1 2 3 4 5 6 7 8 9 10 11 ~ [ 8.681454] my_driver: loading out-of-tree module taints kernel. [ 8.688800] alloc_chrdev_region ok [ 8.688997] major allocated: 511 [ 8.689010] minor allocated: 0 [ 8.689322] cdev_add ok [ 8.690848] my_driver: Module loaded ~ ~ ~ my_driver
用户空间和内核空间 用户空间——>内核空间
用户空间和内核空间的数据交换 内核空间和用户空间的内存是不能互相访问的。但是很多应用程序都需要和内核进行数据的交换,例如应用程序使用 read()函数从驱动中读取数据,使用 write()函数向驱动中写数据,上述功能就需要使用 copy_from_user()和 copy_to_user()俩个函数来完成。
copy_from_user()函数是将用户空间的数据拷贝到内核空间。
copy_to_user()函数是将内核空间的数据拷贝到用户空间。
include/linux/uaccess.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 static __always_inline __must_check unsigned long __copy_from_user_inatomic(void *to, const void __user *from, unsigned long n) { instrument_copy_from_user(to, from, n); check_object_size(to, n, false ); return raw_copy_from_user(to, from, n); } static __always_inline __must_check unsigned long __copy_from_user(void *to, const void __user *from, unsigned long n) { might_fault(); if (should_fail_usercopy()) return n; instrument_copy_from_user(to, from, n); check_object_size(to, n, false ); return raw_copy_from_user(to, from, n); } static __always_inline __must_check unsigned long __copy_to_user_inatomic(void __user *to, const void *from, unsigned long n) { if (should_fail_usercopy()) return n; instrument_copy_to_user(to, from, n); check_object_size(from, n, true ); return raw_copy_to_user(to, from, n); } static __always_inline __must_check unsigned long __copy_to_user(void __user *to, const void *from, unsigned long n) { might_fault(); if (should_fail_usercopy()) return n; instrument_copy_to_user(to, from, n); check_object_size(from, n, true ); return raw_copy_to_user(to, from, n); } #ifdef INLINE_COPY_FROM_USER static inline __must_check unsigned long _copy_from_user(void *to, const void __user *from, unsigned long n) { unsigned long res = n; might_fault(); if (!should_fail_usercopy() && likely(access_ok(from, n))) { instrument_copy_from_user(to, from, n); res = raw_copy_from_user(to, from, n); } if (unlikely(res)) memset (to + (n - res), 0 , res); return res; } #else extern __must_check unsigned long _copy_from_user(void *, const void __user *, unsigned long ); #endif #ifdef INLINE_COPY_TO_USER static inline __must_check unsigned long _copy_to_user(void __user *to, const void *from, unsigned long n) { might_fault(); if (should_fail_usercopy()) return n; if (access_ok(to, n)) { instrument_copy_to_user(to, from, n); n = raw_copy_to_user(to, from, n); } return n; } #else extern __must_check unsigned long _copy_to_user(void __user *, const void *, unsigned long ); #endif static __always_inline unsigned long __must_checkcopy_from_user (void *to, const void __user *from, unsigned long n) { if (likely(check_copy_size(to, n, false ))) n = _copy_from_user(to, from, n); return n; } static __always_inline unsigned long __must_checkcopy_to_user (void __user *to, const void *from, unsigned long n) { if (likely(check_copy_size(from, n, true ))) n = _copy_to_user(to, from, n); return n; } #ifdef CONFIG_COMPAT static __always_inline unsigned long __must_checkcopy_in_user (void __user *to, const void __user *from, unsigned long n) { might_fault(); if (access_ok(to, n) && access_ok(from, n)) n = raw_copy_in_user(to, from, n); return n; } #endif #ifndef copy_mc_to_kernel static inline unsigned long __must_checkcopy_mc_to_kernel (void *dst, const void *src, size_t cnt) { memcpy (dst, src, cnt); return 0 ; } #endif
copy_to_user()函数 函数原型:
1 2 3 4 5 6 7 static __always_inline unsigned long __must_checkcopy_to_user (void __user *to, const void *from, unsigned long n) { if (likely(check_copy_size(from, n, true ))) n = _copy_to_user(to, from, n); return n; }
函数作用:把内核空间的数据复制到用户空间。
参数含义:
*to 是用户空间的指针
from 是内核空间的指针
n 是从内核空间向用户空间拷贝的字节数
返回值:等于0表示成功,其他表示失败
copy_from_user()函数 1 2 3 4 5 6 7 static __always_inline unsigned long __must_checkcopy_from_user (void *to, const void __user *from, unsigned long n) { if (likely(check_copy_size(to, n, false ))) n = _copy_from_user(to, from, n); return n; }
函数作用:把用户空间的数据复制到内核空间。
参数含义
*to 是内核空间的指针
from 是用户空间的指针
n 是从用户空间向内核空间拷贝的字节数
返回值:等于0表示成功,其他表示失败
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 #include "linux/printk.h" #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> static int major = 0 ;static int minor = 0 ;module_param(major, int , S_IRUGO); module_param(minor, int , S_IRUGO); struct class *class ;struct device *device ;static dev_t dev_id;static struct cdev cdev_test ;int cdev_test_open (struct inode *inode, struct file *file) { pr_info("cdev_test_open was called" ); return 0 ; } ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { char kbuf[] = "This is my_driver read\n" ; if (copy_to_user(buf, kbuf, strlen (kbuf)) != 0 ) { pr_err("copy kbuf to buf from kernel error\n" ); return -1 ; } pr_info("copy kbuf to buf from kernel ok\n" ); pr_info("cdev_test_read was called" ); return 0 ; } ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { char kbuf[32 ] = { 0 }; if (copy_from_user(kbuf, buf, size) != 0 ) { pr_err("copy buf to kbuf from kernel error\n" ); return -1 ; } printk("read from user: %s" , kbuf); pr_info("cdev_test_write was called" ); return 0 ; } int cdev_test_release (struct inode *inode, struct file *file) { pr_info("cdev_test_release was called" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }; static int __init my_driver_init (void ) { int ret; if (major) { dev_id = MKDEV(major, minor); pr_info("major from module_param: %d" , MAJOR(dev_id)); pr_info("minor from module_param: %d" , MINOR(dev_id)); ret = register_chrdev_region(dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err("register_chrdev_region error\n" ); return ret; } else { pr_info("register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err("alloc_chrdev_region error\n" ); return ret; } else { pr_info("alloc_chrdev_region ok\n" ); pr_info("major allocated: %d" , MAJOR(dev_id)); pr_info("minor allocated: %d" , MINOR(dev_id)); } } cdev_init(&cdev_test, &cdev_test_ops); cdev_test.owner = THIS_MODULE; ret = cdev_add(&cdev_test, dev_id, 1 ); if (ret < 0 ) { pr_err("cdev_add error\n" ); unregister_chrdev_region(dev_id, 1 ); return ret; } else { pr_info("cdev_add ok\n" ); } class = class_create(THIS_MODULE, "test" ); device = device_create(class, NULL , dev_id, NULL , "my_driver" ); pr_info("my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { device_destroy(class, dev_id); class_destroy(class); cdev_del(&cdev_test); unregister_chrdev_region(dev_id, 1 ); pr_info("unregister_chrdev_region ok\n" ); pr_info("my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
测试
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main (int argc, char **argv) { int ret, fd; fd = open("/dev/my_driver" , O_RDWR); if (fd < 0 ) { printf ("open /dev/my_driver error\n" ); return -1 ; } char rbuf[32 ] = { 0 }; ret = read(fd, rbuf, sizeof (rbuf)); if (ret < 0 ) { printf ("read error\n" ); goto err; } printf ("read from driver:%s\n" , rbuf); char wbuf[32 ] = "hello world\n" ; ret = write(fd, wbuf, sizeof (wbuf)); if (ret < 0 ) { printf ("write error\n" ); goto err; } success: close(fd); return 0 ; err: close(fd); return -1 ; }
makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 TARGET_EXEC := test SRC_DIRS := ./ BUILD_DIR := ./build CC = aarch64-linux-gnu-gcc CFLAGS = -g -Wall SRCS := $(shell find $(SRC_DIRS) -name '*.c' -or -name '*.s' -or -name '*.S') OBJS := $(patsubst %.c,$(BUILD_DIR) /%.o,$(SRCS) ) OBJS := $(patsubst %.s,$(BUILD_DIR) /%.o,$(OBJS) ) OBJS := $(patsubst %.S,$(BUILD_DIR) /%.o,$(OBJS) ) INC_DIRS := $(shell find $(SRC_DIRS) -type d) INC_FLAGS := $(addprefix -I,$(INC_DIRS) ) CPPFLAGS := $(INC_FLAGS) -MMD -MP $(BUILD_DIR) /$(TARGET_EXEC) : $(OBJS) @mkdir -p $(dir $@ ) $(CC) $(OBJS) -o $@ $(LDFLAGS) $(BUILD_DIR) /%.o: %.c @mkdir -p $(dir $@ ) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(BUILD_DIR) /%.o: %.s @mkdir -p $(dir $@ ) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(BUILD_DIR) /%.o: %.S @mkdir -p $(dir $@ ) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ .PHONY : clean deployclean: rm -rf $(BUILD_DIR) deploy: cp $(BUILD_DIR) /$(TARGET_EXEC) ~/tftp -include $(OBJS:.o=.d)
测试输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ~ [ 7.250701] my_driver: loading out-of-tree module taints kernel. [ 7.257204] alloc_chrdev_region ok [ 7.257387] major allocated: 511 [ 7.257406] minor allocated: 0 [ 7.257563] cdev_add ok [ 7.258587] my_driver: Module loaded ~ ~ [ 18.006803] cdev_test_open was called [ 18.007016] copy kbuf to buf from kernel ok read from driver:This is my_driver read [ 18.007275] cdev_test_read was called [ 18.010813] read from user: hello world [ 18.011205] cdev_test_write was called
文件私有数据
通常在驱动开发中会为设备自定义设备结构体 并将设备结构体设置为文件私有数据 。设备结构体用来存放硬件相关信息,如设备号、类、设备名称等。
Linux 中并没有明确规定要必须要使用文件私有数据 ,但是在 linux 驱动源码中随处可见文件私有数据,因此可以认为使用文件私有数据是 Linux 驱动遵循的“潜规则”,实际上文件私有数据也体现了 Linux 面向对象的思想。
文件私有数据就是将私有数据private_data指向设备结构体。
1 2 3 4 5 6 struct device_test dev1 ;static int cdev_test_open (struct inode *inode, struct file *file) { file->private_data = &dev1; return 0 ; }
然后在read, write等函数中通过private_data访问设备结构体
1 2 3 4 static ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off_t ) { struct device_test *test_dev = (struct device_test *)file->private_data; return 0 ; }
用全局变量也可以实现这样的功能,但是随着驱动代码复杂度增多,使用private_data更利于管理
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> struct device_test { dev_t dev_id; char kbuf[32 ]; struct class *class ; struct device *device ; struct cdev cdev_test ; }; struct device_test device1 ;static int major_num;static int minor_num;module_param(major_num, int , S_IRUGO); module_param(minor_num, int , S_IRUGO); int cdev_test_open (struct inode *inode, struct file *file) { pr_info("cdev_test_open was called" ); file->private_data = &device1; return 0 ; } ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { struct device_test *dev1 = (struct device_test *)file->private_data; if (copy_to_user(buf, dev1->kbuf, strlen (dev1->kbuf)) != 0 ) { pr_err("copy kbuf to buf from kernel error\n" ); return -1 ; } pr_info("copy kbuf to buf from kernel ok\n" ); pr_info("cdev_test_read was called" ); return 0 ; } ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { struct device_test *dev1 = (struct device_test *)file->private_data; if (copy_from_user(dev1->kbuf, buf, size) != 0 ) { pr_err("copy buf to kbuf from kernel error\n" ); return -1 ; } printk("read from user: %s" , dev1->kbuf); pr_info("cdev_test_write was called" ); return 0 ; } int cdev_test_release (struct inode *inode, struct file *file) { pr_info("cdev_test_release was called" ); return 0 ; } static struct file_operations cdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release }; static int __init my_driver_init (void ) { int ret; if (major_num) { device1.dev_id = MKDEV(major_num, minor_num); pr_info("major from module_param: %d" , MAJOR(device1.dev_id)); pr_info("minor from module_param: %d" , MINOR(device1.dev_id)); ret = register_chrdev_region(device1.dev_id, 1 , "my_driver device" ); if (ret < 0 ) { pr_err("register_chrdev_region error\n" ); return ret; } else { pr_info("register_chrdev_region ok\n" ); } } else { ret = alloc_chrdev_region(&device1.dev_id, 0 , 1 , "my_driver device" ); if (ret < 0 ) { pr_err("alloc_chrdev_region error\n" ); return ret; } else { pr_info("alloc_chrdev_region ok\n" ); pr_info("major allocated: %d" , MAJOR(device1.dev_id)); pr_info("minor allocated: %d" , MINOR(device1.dev_id)); } } cdev_init(&device1.cdev_test, &cdev_test_ops); device1.cdev_test.owner = THIS_MODULE; ret = cdev_add(&device1.cdev_test, device1.dev_id, 1 ); if (ret < 0 ) { pr_err("cdev_add error\n" ); unregister_chrdev_region(device1.dev_id, 1 ); return ret; } else { pr_info("cdev_add ok\n" ); } device1.class = class_create(THIS_MODULE, "test" ); device1.device = device_create(device1.class, NULL , device1.dev_id, NULL , "my_driver" ); pr_info("my_driver: Module loaded\n" ); return 0 ; } static void __exit my_driver_exit (void ) { device_destroy(device1.class, device1.dev_id); class_destroy(device1.class); cdev_del(&device1.cdev_test); unregister_chrdev_region(device1.dev_id, 1 ); pr_info("unregister_chrdev_region ok\n" ); pr_info("my_driver: Module unloaded\n" ); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("my_driver Kernel Module" );
使用文件私有数据的场景 在Linux中,使用主设备号来表示对应的某一类驱动。用次设备号表示这类驱动下的各个设备。
假如现在我们的驱动要支持主设备相同,但是次设备号不同的设备 。我们的驱动就可以利用文件私有数据来写。
container_of 宏 include/linux/kernel.h
1 2 3 4 5 6 #define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \ !__same_type(*(ptr), void), \ "pointer type mismatch in container_of()" ); \ ((type *)(__mptr - offsetof(type, member))); })
ptr 指向结构体中某个成员的指针。
type 目标结构体类型。
member 成员在结构体中的名字。
BUILD_BUG_ON_MSG(…)
编译期类型检查,保证 ptr 和结构体成员类型一致,避免类型错误。
__mptr - offsetof(type, member)
核心公式:用成员指针减去该成员在结构体中的偏移,得到结构体起始地址。
offsetof(type, member) 标准宏,计算成员在结构体内的字节偏移。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 #include <linux/init.h> #include <linux/module.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #define BUF_SIZE 32 struct device_test { dev_t dev_num; int major; int minor; struct cdev cdev_test ; char kbuf[BUF_SIZE]; }; static struct device_test dev1 ;static struct device_test dev2 ;static struct class *test_class ;static int cdev_test_open (struct inode *inode, struct file *file) { struct device_test *dev ; dev = container_of(inode->i_cdev, struct device_test, cdev_test); file->private_data = dev; pr_info("cdev_test_open: minor=%d\n" , dev->minor); return 0 ; } static ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { struct device_test *dev = file->private_data; size_t len = min(strlen (dev->kbuf), size); if (copy_to_user(buf, dev->kbuf, len)) return -EFAULT; pr_info("cdev_test_read: minor=%d, data=%s\n" , dev->minor, dev->kbuf); return len; } static ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { struct device_test *dev = file->private_data; size_t len = min(size, (size_t )(BUF_SIZE - 1 )); if (copy_from_user(dev->kbuf, buf, len)) return -EFAULT; dev->kbuf[len] = '\0' ; pr_info("cdev_test_write: minor=%d, data=%s\n" , dev->minor, dev->kbuf); return len; } static int cdev_test_release (struct inode *inode, struct file *file) { pr_info("cdev_test_release\n" ); return 0 ; } static struct file_operations cdev_test_fops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release, }; static int __init chr_fops_init (void ) { int ret; ret = alloc_chrdev_region(&dev1.dev_num, 0 , 2 , "my_chardev" ); if (ret < 0 ) return ret; dev1.major = MAJOR(dev1.dev_num); dev1.minor = 0 ; dev2.major = dev1.major; dev2.minor = 1 ; cdev_init(&dev1.cdev_test, &cdev_test_fops); dev1.cdev_test.owner = THIS_MODULE; cdev_add(&dev1.cdev_test, MKDEV(dev1.major, dev1.minor), 1 ); cdev_init(&dev2.cdev_test, &cdev_test_fops); dev2.cdev_test.owner = THIS_MODULE; cdev_add(&dev2.cdev_test, MKDEV(dev2.major, dev2.minor), 1 ); test_class = class_create(THIS_MODULE, "test" ); if (IS_ERR(test_class)) { pr_err("class_create failed\n" ); cdev_del(&dev1.cdev_test); cdev_del(&dev2.cdev_test); unregister_chrdev_region(dev1.dev_num, 2 ); return PTR_ERR(test_class); } device_create(test_class, NULL , MKDEV(dev1.major, dev1.minor), NULL , "test1" ); device_create(test_class, NULL , MKDEV(dev2.major, dev2.minor), NULL , "test2" ); pr_info("my_chardev: Module loaded, major=%d\n" , dev1.major); return 0 ; } static void __exit chr_fops_exit (void ) { device_destroy(test_class, MKDEV(dev1.major, dev1.minor)); device_destroy(test_class, MKDEV(dev2.major, dev2.minor)); class_destroy(test_class); cdev_del(&dev1.cdev_test); cdev_del(&dev2.cdev_test); unregister_chrdev_region(dev1.dev_num, 2 ); pr_info("my_chardev: Module unloaded\n" ); } module_init(chr_fops_init); module_exit(chr_fops_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Zhao Hang" ); MODULE_DESCRIPTION("Dual minor char device driver example" );
杂项设备驱动 杂项设备属于特殊的一种字符型设备 ,是对字符设备的一种封装,本质也是字符设备。
在 Linux 中,把无法归类的五花八门的设备定义成杂项设备。相较于字符设备,杂项设备有以下两个优点:
节省主设备号 : 杂项设备的主设备号固定为 10 ,当系统中注册了多个杂项设备驱动时,只需使用子设备号进行区分即可。而字符设备不管是动态分配还是静态分配设备号,都会消耗一个主设备号,进而造成了主设备号浪费。
使用简单 :杂项设备驱动本身包含创建设备节点操作,不需要额外使用 class_create()函数和 device_create()函数 实现自动创建设备节点操作。只需要填充驱动中的 file_operations 结构体中的成员即可。
定义 include/linux/miscdevice.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct device ;struct attribute_group ;struct miscdevice { int minor; const char *name; const struct file_operations *fops ; struct list_head list ; struct device *parent ; struct device *this_device ; const struct attribute_group **groups ; const char *nodename; umode_t mode; }; extern int misc_register (struct miscdevice *misc) ;extern void misc_deregister (struct miscdevice *misc) ;
定义一个杂项设备,一般只需要填充 miscdevice 结构体中 minor、name、fops 这三个成员变量。
minor 杂项指次设备号,可以从内核源码include/linux/miscdevice.h 文件中预定义的次设备号挑选,也可以自行定义子设备号(没有被其他设备使用即可),通常情况下将该参数设置为 MISC_DYNAMIC_MINOR ,表示自动分配子设备号。
name 表示杂项设备的名字。也就是杂项设备驱动注册成功之后,会在 dev 目录下生成名为 name 的设备节点。
fops 指向了file_operations 的结构体,表示文件操作集。
misc_register 1 extern int misc_register (struct miscdevice *misc) ;
misc_deregister 1 extern void misc_deregister (struct miscdevice *misc) ;
函数作用 :从 mist_list 中删除 miscdevice,进而起到杂项设备卸载的作用。
参数含义 :
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> static ssize_t misc_read (struct file *file, char __user *buf, size_t count, loff_t *ppos) { pr_info("misc_read\n" ); return 0 ; } static ssize_t misc_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) { pr_info("misc_write\n" ); return 0 ; } static const struct file_operations misc_fops = { .owner = THIS_MODULE, .read = misc_read, .write = misc_write, }; static struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "test" , .fops = &misc_fops, }; static int __init misc_init (void ) { int ret; ret = misc_register(&misc_dev); if (ret) { pr_err("misc_register failed: %d\n" , ret); return ret; } pr_info("misc device registered successfully, minor=%d\n" , misc_dev.minor); return 0 ; } static void __exit misc_exit (void ) { misc_deregister(&misc_dev); pr_info("misc device deregistered\n" ); } module_init(misc_init); module_exit(misc_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("zhaohang" ); MODULE_DESCRIPTION("Simple misc device example" );
Linux驱动错误处理 goto 逆序释放 逆序释放 :后申请的先释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static int __init my_driver_init (void ) { int ret; ret = alloc_chrdev_region(&dev1.dev_num, 0 , 2 , "my_driver" ); if (ret < 0 ) goto fail_alloc; cdev_init(&dev1.cdev_test, &cdev_test_fops); dev1.cdev_test.owner = THIS_MODULE; ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1 ); if (ret < 0 ) goto fail_cdev_add; dev1.class = class_create(THIS_MODULE, "test1" ); if (IS_ERR(dev1.class)) { ret = PTR_ERR(dev1.class); goto fail_class_create; } dev1.device = device_create(dev1.class, NULL , dev1.dev_num, NULL , "test1" ); if (IS_ERR(dev1.device)) { ret = PTR_ERR(dev1.device); goto fail_device_create; } pr_info("my_driver initialized successfully\n" ); return 0 ; fail_device_create: class_destroy(dev1.class); fail_class_create: cdev_del(&dev1.cdev_test); fail_cdev_add: unregister_chrdev_region(dev1.dev_num, 1 ); fail_alloc: return ret; }
标签顺序是“逆序释放”,即 后申请的先释放 。
PTR_ERR() + IS_ERR() 用于判断 class_create() 和 device_create() 的错误。
PTR_ERR() 将错误指针转换为错误码,并进行错误码的返回
goto 能避免重复写多次 if 检查和清理代码,使代码简洁。
IS_ERR() 如果一个函数返回值是指针类型,在调用出错的情况下会返回 NULL 指针,但是返回 NULL不能知道问题的确切性,一些函数需要返回一个实际的错误码以便于工程师能够基于返回值作出正确的判断。 对于任何一个指针来说,必然存在三种情况:
合法指针
NULL(也就是空指针)
错误指针(也就是无效指针)
在 Linux 内核中,所谓的错误指针指向了内核空间的最后一页 ,例如,对于一个 64 位系统来说,最后一页的地址是0xfffffffffffff000~0xffffffffffffffff, 最后一页地址是被保留的,并这段地址与内核定义的错误码相关联,因此可以使用错误码用来指明对应出错的情况,如果一个指针指向了该页的地址范围就被定义为错误指针。 在 Linux 内核源码中提供了错误指针相关的 API 函数,主要有 IS_ERR()、PTR_ERR()、ERR_PTR()函数等,其函数的源码在 include/linux/err.h 文件中。
include/linux/err.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #ifndef _LINUX_ERR_H #define _LINUX_ERR_H #include <linux/compiler.h> #include <linux/types.h> #include <asm/errno.h> #define MAX_ERRNO 4095 #ifndef __ASSEMBLY__ #define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO) static inline void * __must_check ERR_PTR (long error) { return (void *) error; } static inline long __must_check PTR_ERR (__force const void *ptr) { return (long ) ptr; } static inline bool __must_check IS_ERR (__force const void *ptr) { return IS_ERR_VALUE((unsigned long )ptr); } static inline bool __must_check IS_ERR_OR_NULL (__force const void *ptr) { return unlikely(!ptr) || IS_ERR_VALUE((unsigned long )ptr); } static inline void * __must_check ERR_CAST (__force const void *ptr) { return (void *) ptr; } static inline int __must_check PTR_ERR_OR_ZERO (__force const void *ptr) { if (IS_ERR(ptr)) return PTR_ERR(ptr); else return 0 ; } #endif #endif
PTR_ERR() 把错误指针转成错误码
include/linux/err.h
1 2 3 4 static inline long __must_check PTR_ERR (__force const void *ptr) { return (long ) ptr; }
错误码 include/uapi/asm-generic/errno-base.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #ifndef _ASM_GENERIC_ERRNO_BASE_H #define _ASM_GENERIC_ERRNO_BASE_H #define EPERM 1 #define ENOENT 2 #define ESRCH 3 #define EINTR 4 #define EIO 5 #define ENXIO 6 #define E2BIG 7 #define ENOEXEC 8 #define EBADF 9 #define ECHILD 10 #define EAGAIN 11 #define ENOMEM 12 #define EACCES 13 #define EFAULT 14 #define ENOTBLK 15 #define EBUSY 16 #define EEXIST 17 #define EXDEV 18 #define ENODEV 19 #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 #define ENFILE 23 #define EMFILE 24 #define ENOTTY 25 #define ETXTBSY 26 #define EFBIG 27 #define ENOSPC 28 #define ESPIPE 29 #define EROFS 30 #define EMLINK 31 #define EPIPE 32 #define EDOM 33 #define ERANGE 34 #endif
示例:
1 2 3 4 if (IS_ERR(dev1.class)) { ret = PTR_ERR(dev1.class); goto fail_class_create; }
点亮一个LED灯(RK3568开发板) TODO