本文是对视频个人的学习记录。
如何编写驱动程序
- 确定主设备号,也可以让内核分配
- 定义自己的file_operations 结构体
- 实现对应的drv_open/drv_read/drv_write 等函数,填入file_operations 结构体
- 把file_operations 结构体告诉内核:register_chrdev
- 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
- 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
- 其他完善:提供设备信息,自动创建设备节点:class_create, device_create
设备号
一个设备号由以下两个模块组成:1、主设备号;2、次设备号;
主设备号用以表示一个驱动程序的种类。如:USB、显示器、鼠标等。
次设备号用以表示此种类下具体的设备。如:第n个USB接口、第n个显示屏、第n个鼠标等。
主设备号是抽象的事物种类,次设备号则是具象的实体设备。
在 /proc/devices 文件中记载着系统中所有已注册的主设备号及其注册名称。
设备号占32位的宽度。其中主设备号占高12位,次设备号占低20位。不过虽然主设备号的宽度长达12位,实际上在Linux系统中有效值范围仅为 1 ~ 255。
(以上来源)
设备号是应用程序寻找驱动程序使用的。
相关函数和结构
register_chrdev()/unregister_chrdev()
register_chrdev() 函数是 Linux 内核提供的一种向系统注册字符设备驱动程序的方法。字符设备是指与字符流相关的硬件设备,如打印机、串口、终端等。在 Linux 系统中,每个字符设备都需要有一个驱动程序来将其与操作系统连接起来,而 register_chrdev() 函数就是用于实现这个功能的。
函数原型如下:
#include <linux/fs.h>
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
该函数接受三个参数:
major:需要注册的设备的主设备号,如果指定为 0 则表示让操作系统自动分配主设备号。
name:需要注册的设备的名称,用于在 /proc/devices 文件中显示该设备的名称。
fops:一个指向字符设备的操作函数集的指针,包含了与相应字符设备相关的读写、控制和访问等操作。
用法举例:
major = register_chrdev(0, "hello", &hello_drv);
unregister_chrdev(major, "hello");
class_create()/class_destroy()
#include <linux/device.h>
struct class *class_create(struct module *owner, const char *name);
-
class_create是Linux内核中的一个函数,用于创建一个新的设备类。在Linux中,设备驱动程序通常使用设备类来抽象并表示设备。
-
该函数的第一个参数是一个指向struct module的指针,用于指示该设备类所属的内核模块。通常情况下,这个参数可以设为THIS_MODULE来表示设备类是由当前模块创建的。该函数的第二个参数是一个指向设备类名字的字符串。设备类名字是一个用户可读的字符串,用于标识该设备类。该函数返回一个指向新创建的设备类的指针。如果创建失败,则返回一个NULL指针。
#include <linux/device.h>
void class_destroy(struct class *cls);
-
在设备驱动程序结束运行时,需要调用
class_destroy()
函数释放已经分配的所有资源。这有利于避免潜在的内存泄漏和其他资源耗尽问题,并且可以保证系统的稳定性和安全性。 -
需要注意的是,在调用
class_destroy()
函数之前,必须确保该设备类中的所有设备对象都已被删除并释放了相应的资源。如果设备类中的任何一个设备对象没有正确处理,可能会导致系统崩溃或产生其他严重问题。
用法举例:
以下代码将会在“/sys/class”目录下创建一个子目录“hello_class”
hello_class = class_create(THIS_MODULE, "hello_class");
err = PTR_ERR(hello_class); //从错误返回值中获取实际的错误码
if (IS_ERR(hello_class)) { // 用于测试是否为错误返回值
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hello"); //注销该字符设备
return -1;
}
device_create/device_destroy()
device_create()
是一个Linux设备驱动编程中用于创建设备节点(device node)的函数,该函数将创建 /dev 文件系统中的一个设备节点,该设备节点将与给定的设备对象(device object)相关联。一旦 /dev 中的设备节点被创建,用户空间程序就可以基于打开这些节点来执行设备驱动程序定义的I/O操作。 函数原型如下:
#include <linux/device.h>
struct device *device_create(struct class *class, struct device *parent, dev_t dev, void *drvdata, const char *fmt, ...);
参数:
-
class
:设备对象的class指针,用于将创建的设备节点注入到系统中的相应设备容器中,该参数值一般为调用class_create()函数创建的设备类对象。 -
parent
:父级设备的设备对象指针,通常为NULL指针,表示该设备无父设备。 -
dev
:设备号,可以使用MKDEV(maj,min)宏合成,其中maj为设备的主设备号,min为设备的次设备号。 -
drvdata
:此参数可以为空,但如果设备的驱动程序需要传入一些私有数据,则可以使用该参数将设备的驱动程序指针传递给该函数。 -
fmt
:节点名称的格式字符串,类似于常规的C控制台输出格式。例如:“i2c-%d”,其中%d是一个整数占位符,在生成节点名称时将替换为相应的整数值。
返回值:
该函数成功完成时将返回新创建的设备对象的指针。 创建设备节点失败时将返回指向错误代码的ERR_PTR()类型的指针。
具体来说,device_create()函数将创建一个新的设备节点,并将与之关联的设备对象放入到class指向的现有设备类中。该函数还在/dev/目录下创建一个设备节点,并将其与设备对象相关联。此后,用户应用程序可以使用open()系统调用来打开设备节点,从而实现对设备对象的访问。
#include <linux/device.h>
void device_destroy(struct class *class, dev_t devt);
-
调用
device_destroy()
之后,与设备号对应的字符设备节点及其关联的设备对象将被销毁,其占用的资源将被释放。如果该字符设备节点未被打开,则该函数会立即删除该节点,否则等待所有打开该设备节点的对象关闭后再删除。 -
需要注意的是,在调用
device_destroy()
时,必须确保字符设备已经释放了所有相关资源,并且在调用device_destroy()
之后,不能再使用销毁的设备节点进行读写操作。
举例:
以 下 代 码 将 会 在 “/sys/class/hello_class ” 目 录 下 创 建 一 个 文 件“hello”
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); //创建为“/dev/hello”设备文件
注销
device_destroy(hello_class, MKDEV(major, 0));
注册函数声明
#include <linux/module.h>
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
file_operations
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 (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*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 *);
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);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
};
static struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.read = hello_drv_read,
.write = hello_drv_write,
.release = hello_drv_close,
};
copy_from_user()/copy_to_user
#include <linux/uaccess.h>
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
这两个函数负责内核与用户空间数据的传输。
ioremap/iounmap()
#include <linux/io.h>
static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
void iounmap(const volatile void __iomem *addr)
ioremap是用于将设备寄存器的物理地址映射到虚拟地址,以便内核能够访问它们。
hallo驱动代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#define BUF_SIZE 1024
#define MIN(x,y) ((x)<(y)?(x):(y))
char ker_buf[BUF_SIZE];
static int major=0;
static struct class *hallo_class;
ssize_t hallo_read (struct file *fp, char __user *usr_buf, size_t size, loff_t * off_set){
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
copy_to_user(usr_buf,ker_buf,MIN(size,BUF_SIZE));
return MIN(size,BUF_SIZE);
}
ssize_t hallo_write (struct file *fp, const char __user *usr_buf, size_t size, loff_t *off_set){
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
copy_from_user(ker_buf,usr_buf,MIN(size,BUF_SIZE));
return MIN(size,BUF_SIZE);
}
int hallo_open (struct inode *inode, struct file *fp){
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
int hallo_close(struct inode *inode, struct file *fp){
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
static struct file_operations hallo_drv={
.owner=THIS_MODULE,
.open=hallo_open,
.release=hallo_close,
.read=hallo_read,
.write=hallo_write,
};
static int __init hallo_init(void){
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
major=register_chrdev(0,"hallo",&hallo_drv);
hallo_class=class_create(THIS_MODULE,"hallo_class");
device_create(hallo_class,NULL,MKDEV(major,0),NULL,"hallo");
return 0;
}
static void __exit hallo_exit(void){
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
device_destroy(hallo_class,MKDEV(major,0));
class_destroy(hallo_class);
unregister_chrdev(major,"hallo");
}
module_init(hallo_init);
module_exit(hallo_exit);
MODULE_LICENSE("GPL");