字符设备驱动(一)框架


title: 字符设备驱动(一)框架
tags: linux
date: 2018-11-19 22:40:11
toc: true

字符设备驱动(一)框架

命令速记

mknod  file c major cnt #创建字符设备
rmmod  file				#卸载驱动
insmod file				#安装驱动
lsmod					#查看安装的驱动
mount -t nfs -o nolock 192.168.5.222:/home/book/stu /mnt #挂载nfs
mount -o nolock,rsize=1024,wsize=1024  192.168.137.222:/home/book/stu  /mnt


ifconfig eth0 192.168.5.200
showmount -e #查看主机允许的挂载

框架结构

LinuxApp通过调用open/close等库函数去控制硬件设备.

  1. open,write,read这些是系统的接口函数,由C库实现
  2. 调用C库的这些函数时,会触发 swi x指令,引发异常,进入异常处理函数称为 system call interface
  3. system call interface会去调用 system open/write/read----称为virtual Filesystem 虚拟文件系统
  4. system open/write/read根据不同的文件调用不同的驱动程序

mark

数据结构

  1. 驱动程序创建了按照 字符设备属性+主设备号+次设备号操作的接口
  2. 需要有一个设备文件,他的属性是字符设备+驱动对应的主设备号
  3. app操作这个设备文件,获取其属性,操作设备文件时,内核调用相应的驱动程序接口
  4. 注意app操作的设备的属性一定是要等同于驱动要求的设备属性,否则依然是无法找到设备的

接口实现

App使用open等函数调用驱动,open这一类函数是定义在fs.h中的struct file_operations ,这是一个统一的接口.所以一个驱动程序需要按照这个格式提供相应的接口即可

*
 * NOTE:
 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
 * can be called without the big kernel lock held in all filesystems.
 */
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	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 *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	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 (*dir_notify)(struct file *filp, unsigned long arg);
	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);
};
struct module *owner
第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.

loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).

ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.

ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化设备上的一个异步写.

int (*readdir) (struct file *, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.

int (*open) (struct inode *, struct file *);
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.

int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.

int (*release) (struct inode *, struct file *);
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

int (*fsync) (struct file *, struct dentry *, int);
这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.

int (*aio_fsync)(struct kiocb *, int);
这是 fsync 方法的异步版本.

int (*fasync) (int, struct file *, int);
这个操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]

int (*check_flags)(int)
这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.

int (*dir_notify)(struct file *, unsigned long);
这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.

其中用到了 struct file表示打开的文件,具体的点这里,Struct inode表示一个磁盘上的具体文件.

struct file {
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
#define f_dentry	f_path.dentry
#define f_vfsmnt	f_path.mnt
	const struct file_operations	*f_op;
	atomic_t		f_count;
	unsigned int 		f_flags;
	mode_t			f_mode;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	unsigned int		f_uid, f_gid;
	struct file_ra_state	f_ra;

	unsigned long		f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;
	spinlock_t		f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
};

驱动注册

实现接口后需要告知内核,也就是注册接口,注册到内核中的函数原型如下:

/**
 * register_chrdev() - Register a major number for character devices.
 * @major: major device number or 0 for dynamic allocation
 * @name: name of this range of devices
 * @fops: file operations associated with this devices
 *
 * If @major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If @major > 0 this function will attempt to reserve a device with the given
 * major number and will return zero on success.
 *
 * Returns a -ve errno on failure.
 *
 * The name of this device has nothing to do with the name of the device in
 * /dev. It only helps to keep track of the different owners of devices. If
 * your module name has only one type of devices it's ok to use e.g. the name
 * of the module here.
 *
 * This function registers a range of 256 minor numbers. The first minor number
 * is 0.
 */
int register_chrdev(unsigned int major, const char *name,
		    const struct file_operations *fops)

major 为主设备号,name为字符串标识,fops就是包含所有接口函数的结构体file_operations 这里补充下设备号的知识.在之前的busybox 之 最小根文件系统中我们使用mknod来创建字符设备或者块设备(/dev/console).

# ls -l /dev/console
crw-rw----    1 0        0          5,   1 Jan  1 01:05 /dev/console

mark

如何使系统去调用这个注册函数?

这里使用宏 module_init来实现,module_init 就是定义一个结构体,这个结构体中有一个函数指针,指向“入口函数”。 当安装一个驱动程序时,内核会自动找到这样一个结构体,调用里面的函数指针,入口函数.也就是当我们执行命令去加载驱动的时候,会去遍历这些指针应该.

先了解自上而下的调用过程

  1. app自主去open("dev/xxx"),这里的xxx设备文件为字符设备,它有标识为字符设备,以及主设备号和次设备号
  2. VFS 系统根据xxx的属性,即字符设备类型+主设备号找到注册到的file_operations结构,调用函数

函数原理

综上, register_chrdev函数最简单的实现方式也就是存在一个chardev的数组,按照主设备号为索引,内容为file_operations指针即可.所谓注册也就是填充这个数组即可

小结

register_chrdev是实现注册的方式, module_init是系统在装载驱动的时候去寻找到这个注册的方式.为什么不直接在 register_chrdev中直接实现 module_init的通知功能.可以这么理解,装载驱动的时候可能还需要其他一些动作,系统提供的 register_chrdev只是在这个chardev数组增加,并没有办法做其他事情,所以这里一般是以下这种形式

void my_regist_fun()
{
	dosomething();
    register_chrdev(major,name,&my_file_operations);
}
module_init(my_regist_fun);

驱动卸载

同驱动加载注册一样,使用unregister_chrdev实现,使用module_exit来使得内核能够主动调用

int unregister_chrdev(unsigned int major, const char *name)

同样的一般实现形式如下

void my_unregist_fun()
{
	dosomething();
    unregister_chrdev(major,name);
}
module_init(my_regist_fun);

程序设计

(一)手动创建主设备号

  1. 头文件包含

    #include <linux/module.h>
    #include <linux/kernel.h>		//内核相关
    #include <linux/fs.h>			//文件操作相关结构提
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>		//内核与用户数据交互 
    #include <asm/irq.h>
    #include <asm/io.h>				//ioremap等 io访问
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
    
  2. Makefile 中需要提前编译好内核,并且加入其中.-C是指使用KERN_DIR中的Makefile来编译,M表示当前目录,moudle也就是目标了

    KERN_DIR = /work/system/linux-2.6.22.6
    all:
    	make -C $(KERN_DIR) M=`pwd` modules 
    clean:
    	make -C $(KERN_DIR) M=`pwd` modules clean
    	rm -rf modules.order
    obj-m	+= first_drv.o
    
  3. 添加 GPL 的licence,否则在安装的时候会有警告

    MODULE_LICENSE("GPL");
    
    # insmod ./dri.ko
    dri: module license 'unspecified' taints kernel.
    
  4. 使用cat /proc/devices查看挂载的驱动设备

    # cat /proc/devices
    Character devices:
      1 mem
      ...
    

代码一览

// 驱动程序 dri.c
#include <linux/module.h>
#include <linux/kernel.h>		//内核相关
#include <linux/fs.h>			//文件操作相关结构提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>		//内核与用户数据交互 
#include <asm/irq.h>
#include <asm/io.h>				//ioremap等 io访问
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open
");
	return 0;
}
ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	printk("first_drv_write
");
	return 0;
}
struct file_operations first_drv_fops = {
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};
int first_drv_init(void)
{
	register_chrdev(111, "first_drv", &first_drv_fops); 
	return 0;
}
void first_drv_exit(void)
{
	unregister_chrdev(111, "first_drv");  
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

测试程序test.c,编译命令是arm-linux-gcc -o test test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("xxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!
");
	}
	write(fd, &val, 4);
	return 0;
}

测试

  1. 安装驱动insmod dri.ko,如果之前有安装过,使用rmmod dri.ko卸载驱动

  2. 查看驱动是否安装 cat /proc/devices ,这里能够看到主设备号是111,设备名是first_drv

    Character devices:
    ...
    111 first_drv
    ...
    
  3. 创建测试所需的设备文件xxx,这里的xxx的主设备号必须与驱动的一致,否则打开错误,内核是根据主设备号查找的,而非名字

    mknod xyz c 111 5 #111 是主设备号,5是次设备号
    
  4. 执行测试程序./test,如果没有步骤3创建设备文件,会提示没有设备文件的

    # ./test
    first_drv_open
    first_drv_write
    

(二)自动分配主设备号

注册函数中使用major=0参数传递给注册函数 register_chrdev时,系统就能自动寻找空闲的一个主设备号返回.

int major;
int first_drv_init(void)
{
	major=register_chrdev(0, "first_drv", &first_drv_fops); 
	return 0;
}
void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv");  
}

这个时候可以手动cat /proc/devices去查看设备号然后去创建设备去控制,使用cat /proc/devices查看设备号然后去操作

(三)自动创建设备文件

busybox 完善(四)中提到了mdev机制,也就是系统自动挂载,mdev会根据sys下的信息自动创建设备的,自动在/proc/sys/kernel/hotplug完成加载与卸载

# cat init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug		#这里实现热拔插自动加载信息
mdev -s

我们这里的是会在sys下创建一个class的文件夹的

代码一览

设计流程

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;

static struct file_operations first_drv_fops = {
	.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
	.open=.....
	.write=...
}

//创建一个类
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
//在类下面去创建一个设备 /dev/xyz 
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* 

//卸载设备需要删除这个类
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);

注意 如果没有加入MODULE_LICENSE("GPL");,会提示

# insmod dri.ko
dri: Unknown symbol class_device_create
dri: Unknown symbol class_device_unregister
dri: Unknown symbol class_create
dri: Unknown symbol class_destroy
insmod: cannot insert 'dri.ko': Unknown symbol in module (-1): No such file or directory

完整代码

#include <linux/module.h>
#include <linux/kernel.h>		//内核相关
#include <linux/fs.h>			//文件操作相关结构提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>		//内核与用户数据交互 
#include <asm/irq.h>
#include <asm/io.h>				//ioremap等 io访问
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;

static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open
");
	return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	printk("first_drv_write
");
	return 0;
}
static struct file_operations first_drv_fops = {
	.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};
static int major;
static int first_drv_init(void)
{
	major=register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

	firstdrv_class = class_create(THIS_MODULE, "first_drv");
	firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
	return 0;
}
static void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv"); // 卸载
	class_device_unregister(firstdrv_class_dev);
	class_destroy(firstdrv_class);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

测试

  1. 安装insmod dri.ko

  2. 查看下是否加载,这里被自动分配了252这个主设备号

    # cat /proc/devices
    ...
    252 first_drv
    ...
    # lsmod
    Module                  Size  Used by    Tainted: P
    dri                     2124  0
    
  3. 运行./test

    # ./test
    first_drv_open
    first_drv_write
    

分析

系统会自动在/sys/class下创建class下的文件信息first_drv,可以看到在/sys/class/first_drv/xyz/dev中的文件内容就是其主设备号+次设备号

# cd /sys/
# ls
block     class     firmware  kernel    power
bus       devices   fs        module

# ls class
first_drv     mem           ppdev         scsi_host     usb_hostt
# ls /sys/class/first_drv/
xyz
# ls /sys/class/first_drv/xyz
dev        subsystem  uevent
# cat  /sys/class/first_drv/xyz/dev
252:0

(四)使用次设备号

在(三)自动分配主设备号中,是在class结构下挂接具体的class_device,这里更改为在同一个class下挂接多个class_device.

//定义一个类下面有四个对象,也就是4个子设备了--------------------------------------
static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev[4];
//注册生成次设备号-------------------------------------------------------------
major=register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
firstdrv_class = class_create(THIS_MODULE, "first_drv");
for (minor = 0; minor < 4; minor++)
{
	firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
}
//卸载设备----------------------------------------------------------------------
unregister_chrdev(major, "first_drv"); // 卸载
for ( minor = 0; minor < 4; minor++)
{
    class_device_unregister(firstdrv_class_dev[minor]);
}
class_destroy(firstdrv_class);

那么如何区分出次设备号呢?

//在读写函数中,使用file结构指针中的参数
int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
//在open函数中,使用node节点
int minor = MINOR(inode->i_rdev);

代码一览

完整的驱动函数代码

#include <linux/module.h>
#include <linux/kernel.h>		//内核相关
#include <linux/fs.h>			//文件操作相关结构提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>		//内核与用户数据交互 
#include <asm/irq.h>
#include <asm/io.h>				//ioremap等 io访问
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev[4];

static int first_drv_open(struct inode *inode, struct file *file)
{
	int minor = MINOR(inode->i_rdev);
	printk("first_drv_open=%d
",minor);

	return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
	printk("first_drv_write=%d
",minor);
	return 0;
}
static struct file_operations first_drv_fops = {
	.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};
static int major;
static int first_drv_init(void)
{
	int minor=0;
	major=register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

	firstdrv_class = class_create(THIS_MODULE, "first_drv");

	for (minor = 0; minor < 4; minor++)
	{
		firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
	}


	return 0;
}
static void first_drv_exit(void)
{
	int minor = 0;
	unregister_chrdev(major, "first_drv"); // 卸载
	for ( minor = 0; minor < 4; minor++)
	{
		class_device_unregister(firstdrv_class_dev[minor]);
	}
	class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

更改下测试代码,使用参数传递所需打开的文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	if (argc <= 1)  {printf("nofile select !
"); return ;}
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can't open!
");
	}
	write(fd, &val, 4);
	return 0;
}

测试

  1. 首先安装驱动insmod dri.ko

  2. 查询驱动

    # lsmod
    Module                  Size  Used by    Tainted: P
    dri                     2232  0
    # cat /proc/devices
    Character devices:
    252 first_drv
    
  3. 查询挂载设备

    # ls /dev/xyz*
    /dev/xyz0  /dev/xyz1  /dev/xyz2  /dev/xyz3
    
  4. 测试

    # ./test /dev/xyz0
    first_drv_open=0
    first_drv_write=0
    # ./test /dev/xyz1
    first_drv_open=1
    first_drv_write=1
    # ./test /dev/xyz2
    first_drv_open=2
    first_drv_write=2
    

分析

查看下sys/class下是否自动创建了xyz的相关信息

# ls /sys/class/
first_drv 

# ls /sys/class/first_drv/xyz*
/sys/class/first_drv/xyz0:
dev        subsystem  uevent
/sys/class/first_drv/xyz1:
dev        subsystem  uevent
/sys/class/first_drv/xyz2:
dev        subsystem  uevent
/sys/class/first_drv/xyz3:
dev        subsystem  uevent

# cat  /sys/class/first_drv/xyz*/dev
252:0
252:1
252:2
252:3

(五)异常捕获处理

注册时的异常捕获

ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
if (ret < 0) {
    printk(DEVICE_NAME " can't register major number
");
    return ret;
}

使用类来调用mdev机制创建类

leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
    return PTR_ERR(leds_class);

类下挂载设备

leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
if (unlikely(IS_ERR(leds_class_devs[minor])))
{
	return PTR_ERR(leds_class_devs[minor]);
}			

(六)驱动信息

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");

小结

  • 应用程序通过open/write/close调用,open等通过传递的文件的主设备号去寻找驱动,执行驱动函数.
  • 驱动程序中使用register_chrdev来注册驱动,使用class_deviceclass相关函数调用mdev机制达到自动创建设备文件的目的
  • 主设备号是提供给系统用的,次设备号是提供给用户用的,可以当作一个参数使用
  1. 定义file_operations结构体,填充打开,读写函数

  2. 定义注册函数与卸载函数

  3. 修饰注册函数与卸载函数module_init,module_exit

  4. 使用mdev需要创建class类,并在class类下挂载对应的class_device设备节点

  5. 创建驱动信息MODULE_LICENSE("GPL");

  6. 头文件包含

    #include <linux/module.h>
    #include <linux/kernel.h>		//内核相关
    #include <linux/fs.h>			//文件操作相关结构提
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>		//内核与用户数据交互 
    #include <asm/irq.h>
    #include <asm/io.h>				//ioremap等 io访问
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
    
  7. Makefile

    KERN_DIR = /work/system/linux-2.6.22.6
    all:
    	make -C $(KERN_DIR) M=`pwd` modules 
    clean:
    	make -C $(KERN_DIR) M=`pwd` modules clean
    	rm -rf modules.order
    obj-m	+= first_drv.o
    

注意:

视频顺序是LED驱动程序_操作LED 然后是LED驱动程序_测试改进

原文地址:https://www.cnblogs.com/zongzi10010/p/9999209.html