Linux字符设备驱动模型
文章目录
一、Linux设备驱动基础概念
1.1Linux系统空间划分
Linux系统有两层:
- Linux核心代码—高优先级
- 应用软件代码—低优先级=>shell/文本编辑器/x-window等
每一个用户进程在系统上运行都拥有自己的私有地址空间和数据,因此,用户进程造成的破坏会被局部化,而不会影响到内核或其它进程
用户进程需要完成在特权模式下才能完成的某现工作时,通过Linux系统向上提供的系统调用接口进入特权模式,然后执行调用所提供的有限功能
应用程序正常情况下都是运行在普通模式下,这部分代码运行的空间称为用户空间,当代码通过系统调用接口进入到特权级运行的时候,对应的代码执行空间称为内核空间
1.2Linux系统调用接口
某些情况下,用户空间需要获得某些系统服务—如操作硬件设备,这是操作系统就必须给用户进程提供"特殊接口"来向内核请求服务了,这些系统服务的"特殊接口"就是系统调用接口
Linux系统调用约250个左右,大致分为进程控制、进程间通信、文件系统控制、存储管理、网络管理、套接字控制、用户管理等几类
其实,前面提到的系统调用并不直接与程序员进行交互,它仅仅是一个通过软中断机制向内核提交请求以获取内核服务接口,实际使用中,程序员调用的通常都是用户编程接口(API)
如:创建进程的应用API-fork()函数对应于内核的sys_fork()系统调用,打开文件的API-open()函数对应于内核的sys_open()系统调用,但不是所有的API都对应一个系统调用
1.3如何操作文件?
Linux中一切皆为文件,那么字符设备驱动当然也是个文件,我们要想知道如何控制字符设备,那就得先知道如何操作普通的文件
普通文件的操作实例:
- 打开/关闭文件
- 读/写文件
- 移动文件指针
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int fd;//用于保存文件描述符
char save_buff[1024];//用于保存读取到的数据
int main(int argc,const char *argv[])
{
int ret = 0;
fd = open("1.txt",O_RDWR);//以可读可写的方式打开文件
if(fd < 0)
{
perror("open error\r\n");
return -1;
}
printf("fd = %d\r\n",fd);//打印一下文件描述符
ret = read(fd,save_buff,1024);//从文件fd里读取[最多]1024个字符
if(ret < 0)
{
perror("read error\r\n");
close(fd);
return -1;
}
write(1,save_buff,ret);//将保存到的数据写入到标准输出
close(fd);//关闭文件
return 0;
}
上面操作的是一个普通的文件,对于设备文件来说,操作方法相同
1.4Linux设备分类
字符设备:
顺序的数据流设备,对这种设备的读写是按字符(BYTE)进行的,而且这些字符是连续的形成一个数据流,不具备缓冲,读写实时
块设备:
随机存取设备,对这种设备的读写是按块进行的,使用缓冲区来存放暂时的数据,条件成熟后,从缓冲区一次性写入设备或者设备一次性读入缓冲区
如:硬盘
网络设备:
面向数据包的接受和发送而设计,它并不对应于文件系统的节点(/dev目录下),而是由系统分配一个唯一的名字(如eth0)
1.5Linux内核框架
用户进程通过设备文件(/dev/xxx)来与实际的硬件打交道,每个设备文件都有其文件属性
c—字符设备
b—块设备
每个设备文件都有一个设备号,由两部分组成:主设备号和次设备号
主设备号:用于标识驱动程序,或者说用来表示一种设备
次设备号:用于标识同一个设备驱动程序的不同硬件设备
二、Linux设备驱动模型
在目前的内核版本中,存在三种流行的字符设备编程模型:
- 杂项设备驱动模型
- 早期经典标准字符设备驱动模型
- Linux2.6标准字符设备驱动模型
2.1杂项设备驱动模型
杂项设备核心数据结构
#include <linux/miscdevice.h>
/*杂项字符设备驱动核心结构体*/
struct miscdevice {
int minor; //次设备号,杂项字符设备主设备号固定为10
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;
};
上面结构中核心部分有三个成员:minor,name,fops,这三个成员必须实现
文件操作方法函数集合 fops
fops对应的结构体定义:struct file_operations
它定义了一系列操作设备的函数指针,用户根据自己需要实现所需要功能的指针成员
#include <linux/fs.h>
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 *);
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 (*aio_fsync) (struct kiocb *, 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
};
杂项设备的设备号
主设备号:固定为10
次设备号:0-255(写255时内核会动态分配一个能用的次设备号)
杂项设备的特征
安装杂项设备会自动在/dev目录生成设备文件
调用一次misc_register注册函数,只会占用一个次设备号
杂项设备注册/注销函数
原型 | int misc_register(struct miscdevice* misc) |
---|---|
功能 | 向内核注册一个杂项字符设备 |
参数 | misc已经实现好:minor/name/fops三个成员的misc地址 |
返回 | 注册成功:0 失败:<0 |
原型 | int misc_deregister(struct miscdevice* misc) |
---|---|
功能 | 注销一个已经注册到内核中杂项字符设备 |
参数 | misc已经注册的struct miscdevice结构变量地址 |
返回 | 注销成功:0 失败:<0 |
杂项设备驱动模型代码模板
伪代码
- 编写fops中设备驱动文件操作方法的具体实现
- 将实现定义到fops结构体中
- 定义核心数据结构
- 编写注册杂项字符设备函数
- 编写注销杂项字符设备函数
驱动代码模板 misc.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#define DEVICE_NAME "my_miscdevice"
//定义文件操作函数
loff_t wz_llseek (struct file *fd, loff_t offset, int whence)
{
printk("line : %d function:%s\r\n",__LINE__,__FUNCTION__);
return offset;
}
ssize_t wz_read (struct file *fd, char __user *buf, size_t count, loff_t *off)
{
printk("line : %d function:%s\r\n",__LINE__,__FUNCTION__);
return count;
}
ssize_t wz_write (struct file *fd, const char __user *buf, size_t count, loff_t *off)
{
printk("line : %d function:%s\r\n",__LINE__,__FUNCTION__);
return count;
}
long wz_unlocked_ioctl (struct file *fd, unsigned int t, unsigned long request)
{
printk("line : %d function:%s\r\n",__LINE__,__FUNCTION__);
return request;
}
int wz_open (struct inode *pinode, struct file *pfile)
{
printk("line : %d function:%s\r\n",__LINE__,__FUNCTION__);
return 0;
}
int wz_release (struct inode *pinode, struct file *pfile)
{
printk("line : %d function:%s\r\n",__LINE__,__FUNCTION__);
return 0;
}
//定义文件操作方法集合指针
static struct file_operations my_fops =
{
.open = wz_open,
.release = wz_release,
.read = wz_read,
.write = wz_write,
.unlocked_ioctl = wz_unlocked_ioctl,
.llseek = wz_llseek,
};
//定义核心结构
static struct miscdevice my_misc =
{
.minor = 255,//子设备号,255表示自动分配
.name = DEVICE_NAME,//设备名
.fops = &my_fops,//文件操作方法
};
//模块注册
static int __init misc_init(void)
{
int ret;
ret = misc_register(&my_misc);
if(ret < 0)
{
printk("misc_register error\r\n");
return -1;
}
printk("misc_register ok\r\n");
return 0;;
}
//模块注销
static void __exit misc_exit(void)
{
misc_deregister(&my_misc);
printk("misc_deregister ok\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
应用程序测试驱动 app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
int main(int argc,const char *argv[])
{
char save_buf[1024] = {0};
int ret;
int fd;//文件描述符
if(argc != 2)
{
fprintf(stderr,"Usage:%s /dev/[devicename]",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR);//以可读可写的方式打开设备文件
if(fd < 0)
{
perror("open error\r\n");
return -1;
}
ret = read(fd,save_buf,1024);//读文件
if(ret < 0)
{
perror("read error\r\n");
return -1;
}
ret = write(fd,"console out test\r\n",sizeof("console out test\r\n"));//写操作
if(ret < 0)
{
perror("write error\r\n");
return -1;
}
lseek(fd,0,SEEK_SET);//文件指针移动到文件开头
ret = read(fd,save_buf,1024);//读文件
if(ret < 0)
{
perror("read error\r\n");
return -1;
}
close(fd);
return 0;
}
Makefile
# Makefile 2.6
#模块名,也是对应的c文件名。
obj-m += misc.o
#应用程序源文件列表
APP_SRC :=app.c
#应用程序名
APP_NAME:=app
#编译器
cc:=aarch64-linux-gnu-gcc
# KDIR 内核源码路径,根据自己需要设置
KDIR ?= /home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y
all:
@make ARCH=arm64 -C $(KDIR) M=$(PWD) modules
@$(cc) $(APP_SRC) -o $(APP_NAME)
clean:
@make ARCH=arm64 -C $(KDIR) M=$(PWD) modules clean
@rm -f *.ko $(APP_NAME) *.o *.mod.o *.mod.c *.bak *.symvers *.markers *.unsigned *.order *~
操作方法
使用Makefile后会生成对应应用程序的可执行文件(app)和驱动代码对应的.ko文件
xh@xh-virtual-machine:~/test_wz$ ls
app.c Makefile misc.c
xh@xh-virtual-machine:~/test_wz$ make
make[1]: 进入目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
CC [M] /home/xh/test_wz/misc.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/xh/test_wz/misc.mod.o
LD [M] /home/xh/test_wz/misc.ko
make[1]: 离开目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
xh@xh-virtual-machine:~/test_wz$ ls
app app.c Makefile misc.c misc.ko misc.mod.c misc.mod.o misc.o modules.order Module.symvers
将这两个文件文件加载到开发板上
模块加载命令:
insmod misc.ko
此时会在/dev/下生成对应的设备节点文件
查看设备节点文件:
ls /dev/
运行应用程序(app):
./app /dev/my_miscdevice
注销模块命令:
rmmod misc.ko
完整演示
root@SOM-RK3399v2:~/wz# ls
app misc.ko
root@SOM-RK3399v2:~/wz# insmod misc.ko
[ 140.940475] [5: insmod: 1226] misc_register ok
root@SOM-RK3399v2:~/wz# ll /dev/my_miscdevice
crw------- 1 root root 10, 54 Nov 7 12:50 /dev/my_miscdevice
root@SOM-RK3399v2:~/wz# ./app /dev/my_miscdevice
[ 207.207167] [5: app: 1298] line : 85 function:wz_open
[ 207.207754] [5: app: 1298] line : 67 function:wz_read
[ 207.208421] [5: app: 1298] line : 73 function:wz_write
[ 207.209025] [5: app: 1298] line : 61 function:wz_llseek
[ 207.209598] [5: app: 1298] line : 67 function:wz_read
[ 207.210188] [5: app: 1298] line : 91 function:wz_release
root@SOM-RK3399v2:~/wz# rmmod misc.ko
[ 216.239571] [1: rmmod: 1309] misc_deregister ok
2.2Linux早期经典标准字符设备驱动模型
早期字符设备特征与描述
特征
- 安装后,不会自动创建/dev目录下的设备文件节点,需要手动使用mknod命令创建
- 调用一次register_chrdev注册函数后,一个主设备号下面的256(0-255)个次设备号就被占用完了,也就是说一个主设备号只能使用注册函数注册一次
描述
没有结构体封装,没有整体描述
意味着:整个模型创建非常简单快捷,但不够灵活
主设备号:0-255
次设备号:0-255(一个主设备号会占用全部次设备号,也就是创建主设备号后,下面256个次设备号全部被占用完了)
早期经典字符设备注册/注销函数
原型 | int register_chrdev(unsigned int major,const char *name,const struct file_operations *fops) |
---|---|
功能 | 向内核注册一个早期经典标准字符设备 |
参数 | major:主设备号(0-255 10除外 major=0时表示自动分配) |
参数 | name:设备名,不需要和/dev下对应节点名相同,这个名字注册后会出现在/proc/device中 |
参数 | fops:文件操作方法结构指针 |
返回 | 当major==0,成功:返回所分配的主设备号 当major>0,成功:返回0 失败:返回负数 |
原型 | void unregister_chrdev(unsigned int major,const char *name) |
---|---|
功能 | 注销一个已经注册到内核中早期经典标准字符设备 |
参数 | major:主设备号 |
参数 | name:注册时的设备名 |
返回 | None |
早期经典字符设备驱动代码模板
和杂项设备驱动模型相比,文件操作方法相同,不同的只是注册/注销函数和设备好的分配
要把杂项模型代码修改为早期经典模型代码,主要修改模块的加载函数和模块的卸载函数
驱动代码模板:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
//定义设备名
#define DEVICE_NAME "my_earlydevice"
int major;//主设备号
//文件操作函数具体实现
ssize_t wz_read (struct file *pfile, char __user *buf, size_t count, loff_t *off)
{
printk("line:%d function:%s\r\n",__LINE__,__FUNCTION__);
return count;
}
ssize_t wz_write (struct file *pfile, const char __user *buf, size_t count, loff_t *off)
{
printk("line:%d function:%s\r\n",__LINE__,__FUNCTION__);
return count;
}
loff_t wz_llseek (struct file *pfile, loff_t off, int pl)
{
printk("line:%d function:%s\r\n",__LINE__,__FUNCTION__);
return off;
}
long wz_unlocked_ioctl (struct file *pfile, unsigned int t, unsigned long r)
{
printk("line:%d function:%s\r\n",__LINE__,__FUNCTION__);
return r;
}
int wz_open (struct inode *pinode, struct file *pfile)
{
printk("line:%d function:%s\r\n",__LINE__,__FUNCTION__);
return 0;
}
int wz_release (struct inode *pinode, struct file *pfile)
{
printk("line:%d function:%s\r\n",__LINE__,__FUNCTION__);
return 0;
}
//定义文件操作集结构体
static struct file_operations fops =
{
.read = wz_read,
.write = wz_write,
.llseek = wz_llseek,
.open = wz_open,
.release = wz_release,
};
//早期经典字符设备驱动注册
static int __init eardev_init(void)
{
int ret;
ret = register_chrdev(0,DEVICE_NAME,&fops);
if(ret < 0)
{
printk("register_chrdev error\r\n");
return -1;
}
major = ret;//记录主设备号
printk("major = %d\r\n",major);
printk("register_chrdev ok\r\n");
return 0;
}
//早期经典字符设备驱动注销
static void __exit eardev_exit(void)
{
unregister_chrdev(major,DEVICE_NAME);
printk("unregister_chrdev ok\r\n");
}
module_init(eardev_init);
module_exit(eardev_exit);
MODULE_LICENSE("GPL");
应用程序测试驱动 app.c
[同杂项设备应用程序测试驱动]( ####应用程序测试驱动 app.c)
Makefile
操作方法
使用Makefile后会生成对应应用程序的可执行文件(app)和驱动代码对应的.ko文件
xh@xh-virtual-machine:~/test_wz$ ls
app.c earlydev.c Makefile
xh@xh-virtual-machine:~/test_wz$ echo 7 4 1 7 > /proc/sys/kernel/printk
-bash: /proc/sys/kernel/printk: 权限不够
xh@xh-virtual-machine:~/test_wz$ make
make[1]: 进入目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
CC [M] /home/xh/test_wz/earlydev.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/xh/test_wz/earlydev.mod.o
LD [M] /home/xh/test_wz/earlydev.ko
make[1]: 离开目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
xh@xh-virtual-machine:~/test_wz$ ls
app app.c earlydev.c earlydev.ko earlydev.mod.c earlydev.mod.o earlydev.o Makefile modules.order Module.symvers
然后将这两个文件文件加载到开发板上
完整演示
root@SOM-RK3399v2:~/wz# insmod earlydev.ko
root@SOM-RK3399v2:~/wz# mknod /dev/my_earlydevice c 241 0
root@SOM-RK3399v2:~/wz# ls /dev
autofs i2c-4 media1 null shm tty21 tty4 tty58 v4l-subdev2 video1
block i2c-7 mem port snd tty22 tty40 tty59 v4l-subdev3 video2
bus i2c-9 memory_bandwidth ppp stderr tty23 tty41 tty6 v4l-subdev4 video3
char iio:device0 mma8452_daemon psaux stdin tty24 tty42 tty60 vcs video4
console initctl mmcblk1 ptmx stdout tty25 tty43 tty61 vcs1 video5
cpu_dma_latency input mmcblk1boot0 pts tty tty26 tty44 tty62 vcs2 video6
disk kmsg mmcblk1boot1 ram0 tty0 tty27 tty45 tty63 vcs3 video7
dri log mmcblk1p1 ram1 tty1 tty28 tty46 tty7 vcs4 video8
fb0 loop0 mmcblk1p2 ram2 tty10 tty29 tty47 tty8 vcs5 video9
fd loop1 mmcblk1p3 ram3 tty11 tty3 tty48 tty9 vcs6 vpu_service
full loop2 mmcblk1p4 ram4 tty12 tty30 tty49 ttyFIQ0 vcsa watchdog
fuse loop3 mmcblk1p5 ram5 tty13 tty31 tty5 ttyS0 vcsa1 zero
hdmi_hdcp1x loop4 mmcblk1p6 ram6 tty14 tty32 tty50 ttyS4 vcsa2 zram0
hugepages loop5 mmcblk1p7 ram7 tty15 tty33 tty51 ttyUSB0 vcsa3
hwrng loop6 mmcblk1p8 random tty16 tty34 tty52 uhid vcsa4
i2c-0 loop7 mmcblk1rpmb rfkill tty17 tty35 tty53 uinput vcsa5
i2c-1 loop-control mqueue rkvdec tty18 tty36 tty54 urandom vcsa6
i2c-10 mali0 my_earlydevice rtc tty19 tty37 tty55 v4l vendor_storage
i2c-2 mapper network_latency rtc0 tty2 tty38 tty56 v4l-subdev0 vhci
i2c-3 media0 network_throughput serial tty20 tty39 tty57 v4l-subdev1 video0
root@SOM-RK3399v2:~/wz# ll /dev/my_earlydevice
crw-r--r-- 1 root root 241, 0 Nov 8 11:46 /dev/my_earlydevice
root@SOM-RK3399v2:~/wz# ./app /dev/my_earlydevice
root@SOM-RK3399v2:~/wz# rmmod earlydev.ko
串口数据
[ 7531.129681] [0: insmod: 8912] major = 241
[ 7531.130195] [0: insmod: 8912] register_chrdev ok
[ 7666.369016] [0: app: 9051] line:33 function:wz_open
[ 7666.369732] [0: app: 9051] line:13 function:wz_read
[ 7666.370425] [0: app: 9051] line:18 function:wz_write
[ 7666.370990] [0: app: 9051] line:23 function:wz_llseek
[ 7666.371596] [0: app: 9051] line:13 function:wz_read
[ 7666.372154] [0: app: 9051] line:38 function:wz_release
[ 7672.101269] [0: rmmod: 9059] unregister_chrdev ok
2.3Linux2.6标准字符设备驱动模型
标准字符设备核心结构
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;//设备文件操作方法
struct list_head list;
dev_t dev; //32位设备号:包含主和次
unsigned int count;//占用多少个连续的次设备号
};
必须实现的成员:
ops:文件操作方法指针
dev:起始设备号(包含主设备号和次设备号),dev_t实际上是一个u32类型
count:本设备要占用次设备号的数量(连续的),从dev中的次设备号开始
核心结构体分配函数
原型 | struct cdev *cdev_alloc(void) |
---|---|
功能 | 在堆空间中分配一个核心结构,注意,不使用的时候需要kfree函数释放 |
参数 | None |
返回 | 返回分配到struct cdev结构空间首地址 |
核心结构体初始化函数
原型 | void cdev_init(struct cdev *cdev,const struct file_operations *fops) |
---|---|
功能 | 初始化核心结构,具体做的是清零核心结构,初始化核心结构的list,kobj,ops成员 |
参数 | cdev:需要初始化的核心结构体指针 |
参数 | fops:文件操作方法结构体指针 |
返回 | None |
标准字符设备特征和设备号
特征
- 安装后,不会自动创建/dev设备文件节点,需要手动使用mknod命令创建
- 调用一个cdev_add注册后,指定数量的次设备号被占用,数量可以自己指定,一个主设备可以使用cdev_add函数注册多次
设备号
设备号使用前需要先申请:register_chrdev_region—静态分配 alloc_chrdev_region—动态分配
tip:标准字符设备设备号:
- 主设备号:0~212
- 次设备号:0~220
静态设备号申请函数
原型 | int register_chrdev_region(dev_t from,unsigned count,const char *name) |
---|---|
功能 | 静态申请一个设备号范围 |
参数 | from:起始设备号(主、次) |
参数 | count:连续的次设备号数量 |
参数 | name:设备名,不需要和/dev的设备文件名相同 |
返回 | 0:成功 失败:返回负数 |
动态设备号申请函数
原型 | int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name) |
---|---|
功能 | 动态申请一个设备号范围 |
参数 | dev:存放分配到的第一个设备号(包含主次设备号) |
参数 | baseminor:要分配起始次设备号 |
参数 | count:连续的次设备号数量 |
参数 | name:设备名,不需要和/dev的设备文件名相同 |
返回 | 0:成功 失败:返回负数 |
设备号释放函数
原型 | void unregister_chrdev_region(dev_t from,unsigned int count) |
---|---|
功能 | 释放一个设备号范围 |
参数 | from:起始设备号(主、次) |
参数 | count:连续的次设备号数量 |
返回 | None |
标准字符设备注册/注销函数
设备注册函数
原型 | int cdev_add(struct cdev *p,dev_t dev,unsigned count) |
---|---|
功能 | 注册一个cdev结构 |
参数 | p:已经初始化的核心结构体指针 |
参数 | dev:起始设备号(包含主和次) |
参数 | count:连续次设备号数量 |
返回 | 0:成功 失败:返回负数 |
设备注销函数
原型 | void cdev_del(struct cdev *p) |
---|---|
功能 | 注销一个cdev结构 |
参数 | p:已经注册过的struct cdev结构体指针 |
返回 | None |
Linux2.6字符设备驱动代码模板
伪代码
- 文件操作方法具体实现
- 定义文件操作方法集合结构体
- 分配cdev结构空间
- 分配主次设备号
- 初始化cdev结构体
- 注册初始化好的cdev结构体
- 获取主设备号
- 注销cdev结构
- 释放设备号
- 释放cdev结构体空间
驱动代码模板
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#define DEVICE_NAME "my_linux26dev"
unsigned int major;//主设备号
dev_t dev_no;//用于保存分配到的主次设备号
struct cdev *pcdev;//用于保存分配的cdev结构空间指针
//文件操作方法具体实现
loff_t wz_llseek (struct file *pfile, loff_t off, int pl)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return off;
}
ssize_t wz_read (struct file *pfile, char __user *buf, size_t count, loff_t *off)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return count;
}
ssize_t wz_write (struct file *pfile, const char __user *buf, size_t count, loff_t *off)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return count;
}
long wz_unlocked_ioctl (struct file *pfile, unsigned int i, unsigned long r)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return r;
}
int wz_open (struct inode *pinode, struct file *pfile)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return 0;
}
int wz_release (struct inode *pinode, struct file *pfile)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return 0;
}
static struct file_operations fops =
{
.read = wz_read,
.write = wz_write,
.llseek = wz_llseek,
.open = wz_open,
.release = wz_release,
.unlocked_ioctl = wz_unlocked_ioctl,
};
//linux2.6标准字符设备驱动注册函数
static int __init linux26_init(void)
{
int ret = -1;
//申请cdev结构空间
pcdev = cdev_alloc();
if(pcdev == NULL)
{
printk("cdev_alloc error\r\n");
return -1;
}
//动态申请设备号
ret = alloc_chrdev_region(&dev_no, 0, 1, DEVICE_NAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\r\n");
//释放前一步申请的cdev空间
kfree(pcdev);
return -1;
}
//初始化cdev结构空间
cdev_init(pcdev, &fops);
//注册已经初始化好的cdev结构
ret = cdev_add(pcdev, dev_no, 1);
if(ret < 0)
{
printk("cdev_add error\r\n");
//释放前两步的空间
kfree(pcdev);
unregister_chrdev_region(dev_no, 1);
}
major = MAJOR(dev_no);
printk("cdev_add ok\r\n");
printk("major = %d\r\n",major);
printk("device name : %s\r\n",DEVICE_NAME);
return 0;
}
//linux2.6标准字符设备驱动注销函数
static void __exit linux26_exit(void)
{
kfree(pcdev);
unregister_chrdev_region(dev_no, 1);
cdev_del(pcdev);
printk("cdev_del ok\r\n");
}
module_init(linux26_init);
module_exit(linux26_exit);
MODULE_LICENSE("GPL");
应用程序测试驱动 app.c
[同杂项设备应用程序测试驱动]( ####应用程序测试驱动 app.c)
Makefile
操作方法
使用Makefile后会生成对应应用程序的可执行文件(app)和驱动代码对应的.ko文件
xh@xh-virtual-machine:~/test_wz$ ls
app.c linux26dev.c Makefile
xh@xh-virtual-machine:~/test_wz$ make
make[1]: 进入目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
CC [M] /home/xh/test_wz/linux26dev.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/xh/test_wz/linux26dev.mod.o
LD [M] /home/xh/test_wz/linux26dev.ko
make[1]: 离开目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
xh@xh-virtual-machine:~/test_wz$ ls
app linux26dev.c linux26dev.mod.c linux26dev.o modules.order
app.c linux26dev.ko linux26dev.mod.o Makefile Module.symvers
然后将这两个文件文件加载到开发板上
完整演示
root@SOM-RK3399v2:~/wz# insmod linux26dev.ko
root@SOM-RK3399v2:~/wz# mknod /dev/my_linux26dev c 241 0
root@SOM-RK3399v2:~/wz# ll /dev/my_linux26dev
crw-r--r-- 1 root root 241, 0 Nov 8 12:40 /dev/my_linux26dev
root@SOM-RK3399v2:~/wz# ./app /dev/my_linux26dev
root@SOM-RK3399v2:~/wz# rmmod linux26dev.ko
rmmod: ERROR: ../libkmod/libkmod.c:514 lookup_builtin_file() could not open builtin file '/lib/modules/4.4.179XH/modules.builtin.bin'
root@SOM-RK3399v2:~/wz# rm /dev/my_linux26dev
串口数据输出
[10805.066678] [0: insmod:12280] cdev_add ok
[10805.067150] [0: insmod:12280] major = 241
[10805.067781] [0: insmod:12280] device name : my_linux26dev
[10908.864849] [2: app:12393] line : 36 function : wz_open
[10908.865463] [2: app:12393] line : 21 function : wz_read
[10908.867328] [2: app:12393] line : 26 function : wz_write
[10908.867981] [2: app:12393] line : 16 function : wz_llseek
[10908.868576] [2: app:12393] line : 21 function : wz_read
[10908.870122] [2: app:12393] line : 41 function : wz_release
[10918.168263] [2: rmmod:12402] cdev_del ok
三、Linux标准字符设备自动创建设备文件
杂项设备在安装模块是会自动在/dev目录下创建设备文件,考研分析杂项设备注册函数的源码,仿照他实现早期经典模型和Linux2.6模型实现自动创建设备文件的功能
3.1class函数说明
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
owner:类的拥有者:固定为THIS_MODULE
name:类名:随便,能有含义最好,不是/dev下设备的名字,这个名字决定了/sys/class/name
和这个函数对应还有一个相反的函数:用于销毁一个class
*void class_destory(struct class cls)
3.2device函数说明
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
class:已经创建好的类指针
parent:指向父设备的指针,如果没有可以为NULL
devt:32位设备号
drvdata:驱动数据,也可以是NULL
fmt,…:可变参数,用来生成/dev目录下的设备文件名
成功返回struct device*指针
和这个函数对应还有一个相反的函数:用于销毁一个device
*void device_destory(struct class class,dev_t devt)
3.3自动创建设备文件示例代码
根据前面所讲内容,修改以前写的Linux2.6设备驱动代码.让它具有自动创建设备文件的功能
驱动代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#define DEVICE_NAME "linux26dev_auto"
static unsigned int major;//用于存放主设备号
static dev_t dev_no;//用于存放申请到的主次设备号
static struct cdev *pcdev;//用于存放自动分配的cdev结构空间首地址
static struct class* class;//用于存放创建的类
static struct device* device;//用于存放创建的设备名
//linux26标准字符驱动设备文件操作具体实现
loff_t wz_llseek (struct file *pfile, loff_t off, int pl)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return off;
}
ssize_t wz_read (struct file *pfile, char __user *buf, size_t count, loff_t *off)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return count;
}
ssize_t wz_write (struct file *pfile, const char __user *buf, size_t count, loff_t *off)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return count;
}
long wz_unlocked_ioctl (struct file *pfile, unsigned r, unsigned long q)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return q;
}
int wz_open (struct inode *pinode, struct file *pfile)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return 0;
}
int wz_release (struct inode *pinode, struct file *pfile)
{
printk("line : %d function : %s\r\n",__LINE__,__FUNCTION__);
return 0;
}
//文件操作集定义
static struct file_operations fops=
{
.read = wz_read,
.write = wz_write,
.open = wz_open,
.release = wz_release,
.unlocked_ioctl = wz_unlocked_ioctl,
.llseek = wz_llseek,
};
//linux26标准字符设备驱动初始化
static int __init linux26_init_auto(void)
{
int ret = -1;
//申请一个cdev结构空间
pcdev = cdev_alloc();
if(pcdev == NULL)
{
printk("cdev_alloc error\r\n");
return -1;
}
//申请设备号(包含主次设备号)
ret = alloc_chrdev_region(&dev_no, 0, 2, DEVICE_NAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\r\n");
//释放前面申请的cdev空间
kfree(pcdev);
return -1;
}
//初始化cdev结构空间
cdev_init(pcdev, &fops);
//注册linux26标准字符设备驱动
ret = cdev_add(pcdev, dev_no, 2);
if(ret < 0)
{
printk("cdev_add error\r\n");
//释放前面申请的cdev空间
kfree(pcdev);
//释放申请的设备号
unregister_chrdev_region(dev_no, 2);
return -1;
}
major = MAJOR(dev_no);//存储主设备号
printk("major = %d\r\n",major);
printk("Device Name : %s\r\n",DEVICE_NAME);
printk("cdev_add ok\r\n");
//创建一个类
class = class_create(THIS_MODULE, DEVICE_NAME);
//创建第一个设备
device = device_create(class, NULL, dev_no, NULL,"%s%d", DEVICE_NAME,0);
//创建第二个设备
device = device_create(class, NULL, dev_no+1, NULL,"%s%d", DEVICE_NAME,1);
return 0;
}
//linux26标准字符设备驱动注销
static void __exit linux26_exit_auto(void)
{
//先删除设备文件
device_destroy(class, dev_no);
device_destroy(class, dev_no+1);
//销毁已经创建的类
class_destroy(class);
//释放前面申请的cdev空间
kfree(pcdev);
//释放申请的设备号
unregister_chrdev_region(dev_no, 2);
//注销设备
cdev_del(pcdev);
printk("cdev_del ok\r\n");
}
module_init(linux26_init_auto);
module_exit(linux26_exit_auto);
MODULE_LICENSE("GPL");
应用程序测试驱动 app.c
[同杂项设备应用程序测试驱动]( ####应用程序测试驱动 app.c)
Makefile
操作方法
使用Makefile后会生成对应应用程序的可执行文件(app)和驱动代码对应的.ko文件
xh@xh-virtual-machine:~/test_wz$ ls
app.c linux26dev_auto.c Makefile
xh@xh-virtual-machine:~/test_wz$ make
make[1]: 进入目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
CC [M] /home/xh/test_wz/linux26dev_auto.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/xh/test_wz/linux26dev_auto.mod.o
LD [M] /home/xh/test_wz/linux26dev_auto.ko
make[1]: 离开目录“/home/xh/work/kernel-rockchip-nanopi4-linux-v4.4.y”
xh@xh-virtual-machine:~/test_wz$ ls
app linux26dev_auto.c linux26dev_auto.mod.c linux26dev_auto.o modules.order
app.c linux26dev_auto.ko linux26dev_auto.mod.o Makefile Module.symvers
然后将这两个文件文件加载到开发板上
完整演示
root@SOM-RK3399v2:~/wz# ls
app linux26dev_auto.ko
root@SOM-RK3399v2:~/wz# insmod linux26dev_auto.ko
root@SOM-RK3399v2:~/wz# ll /dev/linux26dev_auto0
root@SOM-RK3399v2:~/wz# ./app /dev/linux26dev_auto0
root@SOM-RK3399v2:~/wz# rmmod linux26dev_auto.ko
串口数据输出
[ 251.841781] [5: insmod: 1320] linux26dev_auto: loading out-of-tree module taints kernel.
[ 251.843713] [5: insmod: 1320] major = 241
[ 251.844209] [5: insmod: 1320] Device Name : linux26dev_auto
[ 251.844792] [5: insmod: 1320] cdev_add ok
crw------- 1 root root 241, 0 Nov 8 13:08 /dev/linux26dev_auto0
[ 281.119895] [4: app: 1355] line : 38 function : wz_open
[ 281.120527] [4: app: 1355] line : 23 function : wz_read
[ 281.121105] [4: app: 1355] line : 28 function : wz_write
[ 281.121788] [4: app: 1355] line : 18 function : wz_llseek
[ 281.122404] [4: app: 1355] line : 23 function : wz_read
[ 281.122984] [4: app: 1355] line : 43 function : wz_release
[ 288.231866] [4: rmmod: 1364] cdev_del ok
Module.symvers
然后将这两个文件文件加载到开发板上
完整演示
```shell
root@SOM-RK3399v2:~/wz# ls
app linux26dev_auto.ko
root@SOM-RK3399v2:~/wz# insmod linux26dev_auto.ko
root@SOM-RK3399v2:~/wz# ll /dev/linux26dev_auto0
root@SOM-RK3399v2:~/wz# ./app /dev/linux26dev_auto0
root@SOM-RK3399v2:~/wz# rmmod linux26dev_auto.ko
串口数据输出
[ 251.841781] [5: insmod: 1320] linux26dev_auto: loading out-of-tree module taints kernel.
[ 251.843713] [5: insmod: 1320] major = 241
[ 251.844209] [5: insmod: 1320] Device Name : linux26dev_auto
[ 251.844792] [5: insmod: 1320] cdev_add ok
crw------- 1 root root 241, 0 Nov 8 13:08 /dev/linux26dev_auto0
[ 281.119895] [4: app: 1355] line : 38 function : wz_open
[ 281.120527] [4: app: 1355] line : 23 function : wz_read
[ 281.121105] [4: app: 1355] line : 28 function : wz_write
[ 281.121788] [4: app: 1355] line : 18 function : wz_llseek
[ 281.122404] [4: app: 1355] line : 23 function : wz_read
[ 281.122984] [4: app: 1355] line : 43 function : wz_release
[ 288.231866] [4: rmmod: 1364] cdev_del ok