Linux驱动开发---1.创建字符驱动模板

引言

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在 Linux内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进 Linux 内核中,当然也可以不编译进 Linux内核中,具体看自己的需求。

模块安装与卸载

在我们调试开发阶段,将模块编译成.ko文件,那么如何将这个文件加载进系统呢?

insmod xxx.ko

从系统中移除,当我们在.ko文件目录的时候可以带上.ko后缀,当我们不在目录中的时候要用第二种方法,第二种方法是通用的,建议任何时候使用第二种,不会出错。

rmmod xxx.ko
/* rmmod xxx */

但这时候问题来了,假设我们2.ko调用了1.ko中的函数,产生了依赖关系,那么我们需要先加载1.ko再加载2.ko,那么有没有一种方法可以一起加载呢?

modprobe 2.ko

使用modprobe 命令就可以把2.ko依赖的模块一起加载进来了,注意modprobe 命令默认会去
/lib/modules/目录中查找模块
,一般自己制作的根文件系统是没有这个目录的,需要自己创建。同样modprobe 也可以用来卸载,但通常不建议这么做,一般卸载使用rmmod,因为modprobe 会递归卸载,假设1.ko正在被别的进程占用,那么就会出错。

modprobe -r 2.ko

内核驱动操作结构体

那么什么是内核操作结构体呢?我们看下图可知,当应用调用了open函数,最终会进入驱动程序的open函数,那么这个函数在哪里指定呢?就是指定内核操作结构体中成员。
在这里插入图片描述
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 *);
	int (*iterate_shared) (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 (*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
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
			u64);
};
  • owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。
  • llseek函数用于修改文件当前的读写位置。
  • read 函数用于读取设备文件。
  • write 函数用于向设备文件写入(发送)数据。
  • poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
  • unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
  • compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32
    位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
  • mmap 函数用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD
    显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
  • open 函数用于打开设备文件。
  • release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
  • fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
  • aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。

注册驱动模块加载与卸载函数

通过如下这两个函数就可以完成

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

那么模块加载函数和模块卸载函数的原型是什么呢?如下所示

/* insomod xxx 会进入这个函数 */
static int __init temp_drv_init(void){
	return 0;
}

/* rmomod xxx 会进入这个函数 */
static void __exit temp_drv_exit(void){
	
}

驱动加入许可证信息

由于Linux基于GPL协议开源,根据GPL协议的传染性,要求你的驱动必须遵守GPL协议,所以这也就是很多厂家把驱动写的很简单,核心放在应用层的原因,如果不加这个,编译是不通过的。
注意,没有分号!
**

MODULE_LICENSE(“GPL”) //添加模块 LICENSE 信息
MODULE_AUTHOR(“xxx”) //添加模块作者信息

**

定义内核驱动操作结构体,添加一个操作函数

先定义这么一个结构体,填入操作函数名字

static struct file_operations temp_drv_opr = {
	.owner = THIS_MODULE,
	.open = temp_drv_open,
};

那么这个操作函数格式是什么呢,看一下原型
在这里插入图片描述
对应添加我们的open函数

static int temp_drv_open (struct inode *node, struct file *filep){
	return 0;
}

字符设备注册与注销

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)

register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:

  • major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,写0的话会自动分配一个未占用的主设备号。
  • name:设备名字,指向一串字符串。
  • fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。

unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:

  • major:要注销的设备对应的主设备号。
  • name:要注销的设备对应的设备名。

编写模板

#define DRV_NAME	"temp_drv"

static int major;


static int temp_drv_open (struct inode *node, struct file *filep){
	return 0;
}

static ssize_t temp_drv_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
	return 0;							
}

static ssize_t temp_drv_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt){
	return 0;
}

static int temp_drv_close(struct inode *inode, struct file *filp){
	return 0;
}

static struct file_operations temp_drv_opr = {
	.owner = THIS_MODULE,
	.open = temp_drv_open,
	.release = temp_drv_close,
	.read = temp_drv_read,
	.write = temp_drv_write,
};



/* insomod xxx 会进入这个函数 */
static int __init temp_drv_init(void){
	/* 参数1写0的话	     系统自动分配主设备号 */
	major = register_chrdev(0,DRV_NAME,&temp_drv_opr);
	if(major < 0){
		printk("%s:error init\n",DRV_NAME);
	}
	return 0;
}

/* rmomod xxx 会进入这个函数 */
static void __exit temp_drv_exit(void){
	unregister_chrdev(major, DRV_NAME);
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(temp_drv_init);
module_exit(temp_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jianglin");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值