Linux驱动开发:字符设备驱动开发

Posted Top嵌入式

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发:字符设备驱动开发相关的知识,希望对你有一定的参考价值。

文章目录

Linux驱动开发:字符设备驱动开发

一、字符设备简介

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI、LCD 等都是字符设备,这些设备的驱动就叫做字符设备驱动,字符设备的调用按照驱动的调用方式进行

二、驱动调用原理

驱动调用框架如下:

上层应用距离底层硬件间隔了库调用、内核调用、驱动调用三层,因为 Linux 一切皆为文件,对字符串驱动的调用就是以操作文件的形式完成,驱动加载成功以后会在 “/dev” 目录下生成一个相应的文件,对外设的操作最终会调用相关的驱动文件操作操作该文件,以打开文件函数 open 为例子:

应用程序调用 C 库后,会调用系统调用进入内核态,调用底层驱动,底层驱动有一个专门的结构体服务:

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 (*mremap)(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
;

此结构体就是 Linux 内核驱动操作函数集合,几个常见的参数:

  • owner 是指向拥有该结构体的模块的指针
  • read 函数用于读取设备文件
  • write 函数用于向设备文件写入(发送)数据
  • open 函数用于打开设备文件
  • release 函数用于释放(关闭)设备文件

其他函数使用时再具体查看

三、驱动加载与卸载

Linux 驱动两种运行方式:

  • 驱动编译进 Linux 内核中运行
  • 驱动编译成模块,驱动编译成模块

一般调试使用模块化的运行方式,模块的加载和卸载需要下面的函数:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

module_init 函数用来向 Linux 内核注册一个模块加载函数,module_exit 则是卸载,当我们在命令行输入加载和卸载指令(insmod 和 rmmod)时底层调用的就是这两个函数

驱动编译完成以后扩展名为 .ko,insmod 命令用于加载指定的 .ko 模块,但 insmod 命令不能解决模块的依赖关系,而 modprobe 指令会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,卸载则使用 rmmod,对 modprobe 来说,卸载使用 modprobe -r

四、字符设备注册与注销

字符设备的注册和注销函数如下:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)

参数:

  • major:要注册或者注销的主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分
  • name:要注册或者注销的设备名字,指向一串字符串。
  • fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量

五、设备驱动编写流程

上面提到的 file_operations 结构体就是设备的具体操作函数,我们要定义一个结构体用于字符串操作:

static struct file_operations test_fops = 
    .owner = THIS_MODULE,
    .open = chrtest_open,
    .read = chrtest_read,
    .write = chrtest_write,
    .release = chrtest_release,
;

将结构体的操作函数指针指向对应的底层驱动函数(由我们自己实现),以打开函数为例:

/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)

	/* 用户实现具体功能 */
	return 0;

设置好底层驱动函数后,编写函数驱动进入和退出入口,注册入口函数,以入口函数为例子:

static int __init xxx_init(void)

    /* 入口函数具体内容 */
    int retvalue = 0;
    /* 注册字符设备驱动 */
    retvalue = register_chrdev(200, "chrtest", &test_fops);
    if(retvalue < 0)
    	/* 字符设备注册失败,自行处理 */
    
    return 0;


//注册入口函数
module_init(xxx_init);

驱动代码流程大致如上

六、添加驱动信息

最后需要在驱动中加入 LICENSE 信息和作者信息,函数如下:

MODULE_LICENSE()	//添加模块 LICENSE 信息
MODULE_AUTHOR()		//添加模块作者信息

例如:

MODULE_LICENSE("MIT")	//MIT协议
MODULE_AUTHOR("Top嵌入式")		//作者信息

其中 LICENSE 是必须添加的,Linux 内核编译时会检测,没有则会报错

以上是关于Linux驱动开发:字符设备驱动开发的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动开发:字符设备驱动开发

linux驱动开发 - 01_字符设备驱动开发

嵌入式开发(七):linux字符型设备驱动初步

从Linux内核LED驱动来理解字符设备驱动开发流程

linux驱动学习——字符设备驱动开发

Linux驱动开发:字符设备驱动开发实战