linux应用与驱动的交互方式之file_operations结构体

Linux通过提供统一的API如open(),write(),read()等使得用户态代码无需直接操作硬件。驱动程序实现了这些函数,以文件操作的形式控制硬件。当用户需要控制硬件时,通过打开设备文件,调用相应的API,驱动内部处理硬件交互。文章介绍了如何在Linux下编写驱动,包括定义file_operations结构体,注册驱动以及设备文件的创建。
摘要由CSDN通过智能技术生成

linux用户态为何需要驱动控制硬件

1、linux需要对用户提供统一的操作函数,如read()、 write()等其他API函数,使写用户态代码的人只不需要深入了解硬件。
2、linux需要保证系统的稳定性和安全性,如果用户可以随意操作寄存器很有可能会导致系统崩溃,专业的人应该交由专业的人坐。

linux用户态怎么控制硬件

可以简单地理解为用户使用open()、 close()、 read()、 write()等其他API函数来控制硬件,驱动则是提供这些函数的实现。
如有一个需求:用户想控制一个灯的亮灭,并查看灯的状态。
1、linux说:我一切都是文件,要想我干什么,操作文件即可。
2、灯的驱动说:你只需打开我提供的文件;写入"1"我就亮,写"0"我就灭;你也可以读我,读出是"1"我出于亮的状态,读出是"0"我出于灭的状态;使用完文件记得关闭我。
3、用户的功能实现:open-> write->read->close实现功能。

linux提供的标准接口

上面说到,linux应用想要控制硬件,就必须要通过驱动程序来控制硬件;那么提供了那些方法呢?常用的方法就这些open()、 close()、 read()、 write()…,当然每种方法在驱动中都对应一个函数,那么这些函数的集合就被今天讲的主角——file_operations结构体封装在一起,如图:

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下怎么写一个驱动

由上述可知,Linux驱动其实就是实现了file_operations中的方法,应用就通过标准接口调用该方法,就能实现控制硬件了;不知道大家有没有发现一个问题,就算我实现了file_operations中的方法,我应该怎么告诉linux自己实现了这些方法呢?还有,linux下肯定不止我一个驱动实现了这些方法,那么linux又怎么找到我实现的这个方法呢?
1、通过register_chrdev()函数告诉内核,我要把这些方法注册到内核中,该函数原型如下:

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

可得知第三个参数就是我们心心恋恋file_operations结构体,把我们实现的file_operations结构体地址传给该函数即可,是不是非常简单。
2、上面还说了linux是如何在众多的file_operations中找到我们所需要的方法的,应用使用驱动是需要打开一个设备文件来操作的,那么打开的文件是什么呢?这里就需要我们手动创建(linux提供了驱动自动创建设备文件的方法,在以后的文章中会讲到,本篇重点介绍file_operations结构体,希望大家点个关注不迷路啦)“mknod /dev/xxx c 200 0”,此时会/dev目录下会创建一个名字为“xxx”的设备文件,其中200表示主设备号,也就是我们register_chrdev时传入的major设备号,0表示次设备号,可以理解为内核中有一个file_operations结构体数组,而这个主次设备号就是用来找到我们要的file_operations的数组标号,打开这个文件,就能找到我们实现的file_operations里的方法。

具体的示例代码

注:本代码未实现具体硬件相关的功能,只是一个框架,包含了之前文章中讲的驱动入口和出口宏,以及释放一个字符设备的操作方式,请看:


#include <linux/ide.h>
#include <linux/module.h>

#define XXX_MAJOR 	200 		/* 主设备号 */
#define XXX_NAME 	"xxx_test" 	/* 设备名 */


static int xxx_open(struct inode *inode, struct file *filp)
{
	//通常返回:0 成功;其他 失败
	return 0;
}

static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	//通常返回:读取的字节数;小于0,表示读取失败
	return 0;
}

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	//通常返回:写入的字节数;小于0,表示写入失败
	return 0;
}

static int xxx_release(struct inode *inode, struct file *filp)
{
	//通常返回:0 成功;其他 失败
	return 0;
}

//本xxx_fops只列出了部分常用的操作
static const struct file_operations xxx_fops = {
	//owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
	.owner 		= THIS_MODULE,
	//open 函数用于打开设备文件,用户程序打开设备文件会得到一个设备描述符,并执行驱动指定的open函数(不管驱动有无open函数,用户使用都需要打开)
	.open 		= xxx_open,	
	//read 函数用于读取设备文件,应用执行read时会调用该函数
	.read 		= xxx_read,
	//write 函数用于向设备文件写入(发送)数据,应用执行write时会调用该函数
	.write 		= xxx_write,
	//release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
	.release 	= xxx_release,
};

//模块初始化函数,在模块装载的时候调用
static int __init xxx_init(void)
{
	int ret = 0;
	
	//主设备号为0,自动分配设备号,返回值是分配的设备号
	ret = register_chrdev(XXX_MAJOR, XXX_NAME, &xxx_fops);
	//返回值为负数,注册失败
	if(ret < 0)
	{
		return -1;
	}
	
	return 0;
}

//模块退出函数,在模块卸载的时候调用
static void __exit xxx_exit(void)
{
	//注销字符设备驱动
	unregister_chrdev(XXX_MAJOR, XXX_NAME);
}

//向内核表明初始化函数,在模块装载的时候调用
module_init(xxx_init);
//向内核表明退出函数,在模块卸载的时候调用
module_exit(xxx_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaoShuATao");

注:作者水平有限,如有错误,请大家及时指出,我会第一时间修改,谢谢大家了。
版权说明:可自由转载使用,转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值