Linux字符设备驱动模型详解

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早期经典标准字符设备驱动模型

早期字符设备特征与描述

特征
  1. 安装后,不会自动创建/dev目录下的设备文件节点,需要手动使用mknod命令创建
  2. 调用一次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

操作方法

使用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

标准字符设备特征和设备号

特征
  1. 安装后,不会自动创建/dev设备文件节点,需要手动使用mknod命令创建
  2. 调用一个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

操作方法

使用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

操作方法

使用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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值