内核中通过类型dev_t来描述设备号,其实质是unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。设备号也是一种资源,当我们需要时可以调用函数去申请。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
这是Linux内核中注册字符设备驱动的函数之一,它的作用是在内核中申请一段设备号,并将其与设备驱动程序进行绑定。具体来说,它的参数含义如下:
from
:设备号的起始值,通常为0。count
:需要注册的设备号数量。name
:设备名称,用于在 /proc/devices
文件中显示设备的名字。函数执行成功时,会返回0,否则返回一个负数错误码。
#include
struct cdev {
struct kobject kobj; // 对象,用于实现 sysfs 文件系统接口
struct module *owner; // 指向模块对象的指针,用于记录模块的引用计数
const struct file_operations *ops; // 指向字符设备驱动程序提供的操作函数集合的指针
struct list_head list; // 用于链接同一设备号下的所有设备结构体的链表
dev_t dev; // 设备的主、次设备号
unsigned int count; // 要注册的设备号的数量
};
#include
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 *); // 实现写操作函数
int (*open) (struct inode *, struct file *); // 实现设备打开函数
int (*release) (struct inode *, struct file *); // 实现设备关闭函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // 实现设备控制函数
int (*mmap) (struct file *, struct vm_area_struct *); // 实现设备内存映射函数
int (*flush) (struct file *, fl_owner_t id); // 实现设备缓冲刷新函数
int (*fsync) (struct file *, loff_t, loff_t, int datasync); // 实现设备同步函数
};
struct file_operations
中的函数指针成员包括:
llseek
:实现文件偏移量设置函数。read
:实现读操作函数。write
:实现写操作函数。open
:实现设备打开函数。release
:实现设备关闭函数。unlocked_ioctl
:实现设备控制函数。mmap
:实现设备内存映射函数。flush
:实现设备缓冲刷新函数。fsync
:实现设备同步函数。这些函数指针中的大部分都是可选的,根据实际需求进行选择实现。
insmod和rmmod是Linux系统中用于安装和卸载内核模块的命令,而mknod是用于创建设备节点的命令。
使用gcc编译内核模块源代码,生成.ko文件。
在终端中输入insmod **.ko 命令,加载.ko文件。
内核检查模块符号表,如果符号表正确,则会调用模块中的init函数进行初始化。
init函数会进行设备的初始化,包括申请设备号、创建字符设备结构体、初始化设备、注册设备等。
通过调用cdev_add
函数将字符设备添加到系统中。
创建设备节点,以便应用程序可以使用设备。
在终端中输入rmmod **.ko 命令,卸载模块。
内核会调用模块中的exit函数,对设备进行资源的释放,如注销字符设备、释放设备号等。
调用cdev_del
函数将字符设备从系统中删除。
删除设备节点,以便应用程序无法使用设备。
//mknod [选项] <文件名> <文件类型> <主设备号> <次设备号>
mknod /dev/testchar c 232 0
在终端中输入mknod命令,创建设备节点。
使用mknod
函数创建设备节点,需要指定设备的名称、设备类型、主设备号、次设备号等参数。
创建成功后,应用程序可以通过打开设备节点访问设备。
来源:05-字符设备驱动(三):驱动的测试以及驱动的Makefile_哔哩哔哩_bilibili
ifneq ($(KERNELRELEASE),) 如果KERNELRELEASE变量已定义,即当前为内核构建过程,执行以下代码
obj-m := charDev.o 编译模块charDev为目标文件obj文件,即生成charDev.o
else
PWD := $(shell pwd) 定义变量PWD为当前目录的绝对路径
KDIR:= / lib/ modules/ 'uname -r' / build 定义变量KDIR为内核源码目录路径
all:
make -c$(KDIR)][M=$(PWD) 在内核源码目录下执行make,参数为M=$(PWD),表示生成的目标文件存放在当前目录
clean:
rm -rf *.o *.ko *.mod.c *.symvers .c~s~ 删除编译生成的目标文件
endif
ioctl函数用于在用户态和内核态之间传递控制命令,通常用于设备驱动中实现设备的特定操作,也可以用于进程间通信和内核调试等场景。
int ioctl(int fd, unsigned long request, ...);
fd
表示要进行控制命令的文件描述符;request
表示控制命令的编号,需要设备驱动实现者自己定义,通常使用宏定义的方式定义在头文件中;最后一个可选参数则是控制命令需要的参数,如果没有参数则传递NULL
。
可通过访问 cat /proc/ioports 来获取设备当前的IO端口号