Linux字符设备驱动框架

本文详细介绍了Linux系统中字符设备驱动的框架,包括驱动模块需要实现的主要函数如open, read, write, release等,并展示了如何定义file_operations结构体,以及如何注册和注销字符设备。此外,还阐述了驱动模块的入口和出口函数、加载和卸载驱动的命令,以及编译驱动模块的Makefile配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux字符设备驱动框架

1、驱动模块需要实现的函数
在Linux系统中,一切皆文件。应用层通过open,read,write,close实现对驱动的调用。
在这里插入图片描述

每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中
有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,如下所示:

struct file_operations { 
  struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES  
   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);//仅用于读取目录,对于设备文件,该字段为NULL   
   unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入   
  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令   
  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl  
  long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替   
  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); //通知设备FASYNC标志发生变化   
  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 **);   
};

因此我们在驱动程序里面实现file_operations的各个函数,就可以在应用层实现调用。当然也不是每一个驱动都需要实现file_operations的全部接口。

实现字符驱动步骤:

1、实现 open,read,write,release函数

/* 打开设备 */
static int Demo_open(struct inode *inode, struct file *filp)
{
	/* 用户实现具体功能 */
	return 0;
}

/* 从设备读取 */
static ssize_t Demo_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
	/* 用户实现具体功能 */
	return 0;
}

/* 向设备写数据 */
static ssize_t Demo_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
	/* 用户实现具体功能 */
	return 0;
}

 /* 关闭/释放设备 */
static int Demo_release(struct inode *inode, struct file *filp)
{
	/* 用户实现具体功能 */
	return 0;
}

2、定义自己的file_operations结构体

static struct file_operations demo_fops = {
	.owner = THIS_MODULE,
	.open = Demo_open,
	.read = Demo_read,
	.write = Demo_write,
	.release = Demo_release,
};

3、字符设备的注册与注销

我们有自己的file_operations结构体以后,需要注册到内核里面去,才能被应用层调用。
字符设备的注册和注销函数原型如下所示:
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 类型指针,指向设备的操作函数集合变量。

/* 驱动入口函数 */
static int __init demo_init(void)
{
	/* 入口函数具体内容 */
	int retvalue = 0;

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(0, "chrdev_demo", &demo_fops);
	if(retvalue < 0){
		/* 字符设备注册失败,自行处理 */
	}
	return 0;
}

/* 驱动出口函数 */
static void __exit demo_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(0, "chrdev_demo");
}

4、实现驱动指定的入口和出口函数

我们实现了驱动模块的入口函数与出口函数,但是Linux怎么才能知道这个是一个驱动的入口函数与出口函数呢?
使用module_init与module_exit函数告诉编译器。
module_init(demo_init); //注册模块加载函数
module_exit(demo_exit); //注册模块卸载函数

5、添加license信息

GPL 协议的传染性,LICENSE 必须采用 GPL 协议,不然不能使用

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wpgulang");

6、使用Makefile编译成.ko文件

KERNELDIR := /home/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,根据自己的实际情况填写即可。
CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取当前所处路
径。
obj-m 表示将 chrdevdemo.c 这个文件编译为 chrdevdemo.ko 模块。

7、驱动模块的加载和卸载

使用命令 insmod加载驱动,使用rmmod卸载驱动。

insmod chrdevdemo.ko
rm chrdevdemo.ko
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值