如何编写一个简单的Linux驱动(二)——设备操作集file_operations

时间:2023-02-18 14:47:31

前期知识

  如何编写一个简单的Linux驱动(一)——驱动的基本框架

前言

  在上一篇文章中,我们学习了驱动的基本框架。这一章,我们会在上一章代码的基础上,继续对驱动的框架进行完善。要下载上一篇文章的全部代码,请点击这里

1.字符设备的四个基本操作

  驱动让用户程序具备操作硬件设备的能力,那么对硬件设备有哪些操作呢?在学习编程语言时,我们都学过对文件的操作,包括打开文件、关闭文件、读文件、写文件这四个基本操作。对于Linux来说,一切设备皆文件,所以对设备的基本操作也可以分为打开、关闭、读、写这四个。而对于设备(已字符设备为例),Linux提供了一个操作集合——file_operarions。file_operations是一个结构体,其原型如下。

 1 struct file_operations {
2 struct module *owner;
3 loff_t (*llseek) (struct file *, loff_t, int);
4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
6 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
7 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
8 int (*iterate) (struct file *, struct dir_context *);
9 int (*iterate_shared) (struct file *, struct dir_context *);
10 unsigned int (*poll) (struct file *, struct poll_table_struct *);
11 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
12 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
13 int (*mmap) (struct file *, struct vm_area_struct *);
14 int (*open) (struct inode *, struct file *);
15 int (*flush) (struct file *, fl_owner_t id);
16 int (*release) (struct inode *, struct file *);
17 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
18 int (*fasync) (int, struct file *, int);
19 int (*lock) (struct file *, int, struct file_lock *);
20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
22 int (*check_flags)(int);
23 int (*flock) (struct file *, int, struct file_lock *);
24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
26 int (*setlease)(struct file *, long, struct file_lock **, void **);
27 long (*fallocate)(struct file *file, int mode, loff_t offset,
28 loff_t len);
29 void (*show_fdinfo)(struct seq_file *m, struct file *f);
30 #ifndef CONFIG_MMU
31 unsigned (*mmap_capabilities)(struct file *);
32 #endif
33 ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
34 loff_t, size_t, unsigned int);
35 int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
36 u64);
37 ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
38 u64);
39 }

  要使用该结构体,需要包含头文件"linux/fs.h"。该结构体中的成员变量很多,但在本章中,我们只用到打开(open)、关闭(release)、读(read)、写(write)这四个成员变量,以及一个默认需要的所有者(owner)成员变量。    

1 struct file_operations {
2 ...
3 struct module *owner;
4 int (*open) (struct inode *, struct file *);
5 int (*release) (struct inode *, struct file *);
6 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
7 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
8 ...
9 }

  file_operations结构体的成员变量向应用程序提供一个对设备操作的接口,但是接口的具体操作需要我们自己来实现。打开上一章所写的驱动源代码"shanwuyan.c",定义一个"file_operations"类型的结构体,再定义四个函数"shanwuyan_open"、"shanwuyan_release"、"shanwuyan_read"、"shanwuyan_write",让file_operations结构体变量的成员变量初始化为这四个函数。  

 1 /*打开设备*/
2 static int shanwuyan_open(struct inode *inode, struct file *filp)
3 {
4 return 0;
5 }
6
7 /*释放(关闭)设备*/
8 static int shanwuyan_release(struct inode *inode, struct file *filp)
9 {
10 return 0;
11 }
12
13 /*读设备*/
14 static ssize_t shanwuyan_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
15 {
16 return 0;
17 }
18
19 /*写设备*/
20 static ssize_t shanwuyan_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
21 {
22 return 0;
23 }
24
25 static struct file_operations shanwuyan_fops =
26 {
27 .owner = THIS_MODULE, //默认
28 .open = shanwuyan_open, //打开设备
29 .release = shanwuyan_release, //关闭设备
30 .read = shanwuyan_read, //读设备
31 .write = shanwuyan_write, //写设备
32 };

  这样,用户在使用库函数"open"打开设备时,就会调用函数"shanwuyan_open";用"close"函数关闭设备时,就会调用函数"shanwuyan_release";用"read"函数读设备时,就会调用函数"shanwuyan_read";用"write"函数写设备时,就会调用函数"shanwuyan_write"。为了让这四个函数的调用更直观地为程序员所观察,我们可以在这四个函数中添加打印语句,这样每次对设备进行操作的时候,程序员都能在终端观察到相应的信息,如下方代码。  

 1 /*打开设备*/
2 static int shanwuyan_open(struct inode *inode, struct file *filp)
3 {
4 printk(KERN_EMERG "shanwuyan_open\r\n");
5 return 0;
6 }
7
8 /*释放(关闭)设备*/
9 static int shanwuyan_release(struct inode *inode, struct file *filp)
10 {
11 printk(KERN_EMERG "shanwuyan_close\r\n");
12 return 0;
13 }
14
15 /*读设备*/
16 static ssize_t shanwuyan_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
17 {
18 printk(KERN_EMERG "shanwuyan_read\r\n");
19 return 0;
20 }
21
22 /*写设备*/
23 static ssize_t shanwuyan_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
24 {
25 printk(KERN_EMERG "shanwuyan_write\r\n");
26 return 0;
27 }

2.注册与注销字符设备

  字符设备的注册是在入口函数"shanwuyan_init"中完成的,字符设备的注销是在出口函数"shanwuyan_exit"中完成的。在上一篇文章中,这两个函数的作用只是打印一行字符串,并没有注册和注销字符设备的功能。在本章,我们将完善这两个函数。

  首先介绍一个函数"register_chrdev",函数原型如下。

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);    //major是主设备号,name是设备名,fops是字符设备操作集的地址

  该函数的作用是注册字符设备,设备号为程序员给定的一个主设备号major,设备名为用户给定的一个字符串,字符操作集为上文中定义的结构体地址。如果函数该函数返回值为负数,说明设备注册失败,否则说明设备注册成功。

  接下来介绍注销字符设备的函数"unregister_chrdev",该函数的原型如下。

static inline void unregister_chrdev(unsigned int major, const char *name);    //major是主设备号,name是设备名

  该函数的作用是注销字符设备。

  打开开发板的系统终端,输入命令"cat /proc/devices"可以查看有哪些设备号已经被占用。经过查看,本系统的设备号"200"处于空闲状态,可以用来注册字符设备。

  完善入口函数和出口函数,代码如下。  

 1 ...
2 #define SHANWUYAN_MAJOR 200 //程序员给定的主设备号
3 #define SHANWUYAN_NAME "shanwuyan" //程序员给定的设备名字符串
4 ...
5 static struct file_operations shanwuyan_fops =
6 {
7 ...
8 } //定义的字符设备操作集
9 static int __init shanwuyan_init(void) //驱动入口函数
10 {
11 int ret = 0;
12
13 ret = register_chrdev(SHANWUYAN_MAJOR, SHANWUYAN_NAME, &shanwuyan_fops);
14 if(ret < 0)
15 printk(KERN_EMERG "init failed\r\n"); //注册失败
16 else
17 printk(KERN_EMERG "shanwuyan_init\r\n");//注册成功
18 return 0;
19 }
20 static void __exit shanwuyan_exit(void) //驱动出口函数
21 {
22 unregister_chrdev(SHANWUYAN_MAJOR, SHANWUYAN_NAME); //注销字符设备
23 printk(KERN_EMERG "shanwuyan_exit\r\n");
24 }
25 ...

  这样,一个字符设备驱动的雏形就完成了。

3.编写应用程序

  编写一个应用程序,包含对设备的打开、关闭、读和写的操作。源代码如下

 1 //文件名为"shanwuyan_APP.c"
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <stdio.h>
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 /*
11 *argc:应用程序参数个数,包括应用程序本身
12 *argv[]:具体的参数内容,字符串形式
13 *./shanwuyan_APP <filename> <r:w> r表示读,w表示写
14 */
15 int main(int argc, char *argv[])
16 {
17 int ret = 0;
18 int fd = 0;
19 char *filename;
20
21 if(argc != 3) //共有三个参数
22 {
23 printf("Error usage!\r\n");
24 return -1;
25 }
26
27 filename = argv[1]; //获取文件名称
28
29 fd = open(filename, O_RDWR);
30 if(fd < 0)
31 {
32 printf("cannot open file %s\r\n", filename);
33 return -1;
34 }
35
36 if(!strcmp(argv[2], "r")) //读设备
37 {
38
39 read(fd, NULL, 0); //只是使用读函数,但不读出数据
40 }
41 else if(!strcmp(argv[2], "w")) //写设备
42 {
43 write(fd, NULL, 0); //只是使用写函数,但并不向设备写数据
44
45 }
46 else
47 {
48 printf("ERROR usage!\r\n");
49 }
50
51 /*关闭设备*/
52 close(fd);
53
54 return 0;
55 }

4.应用

  编译驱动文件,交叉编译应用程序,拷贝到开发板中,并加载驱动。

  驱动加载完成后,使用命令"mknod /dev/shanwuyan c 200 0",在"/dev"目录下创建"shanwuyan"设备节点。其中参数"c"是指创建一个字符设备节点,200表示主设备号,0表示次设备号。然后使用ls命令查看是否创建成功。

如何编写一个简单的Linux驱动(二)——设备操作集file_operations

  分别输入命令"./shanwuyan_APP /dev/shanwuyan r"和命令"./shanwuyan_APP /dev/shanwuyan w",可以看到终端打印了如下信息。可以看到,应用程序打开设备、关闭设备、读设备、写设备的操作都有所体现。如何编写一个简单的Linux驱动(二)——设备操作集file_operations

  在本章中,我们只是单纯得调用了read和write函数,但是并没有真正的读写数据。读写数据操作将在下一章中出现。

  本章的全部代码在这里

  

如何编写一个简单的Linux驱动(二)——设备操作集file_operations的更多相关文章

  1. 如何编写一个简单的Linux驱动(二)——完善设备驱动

    前期知识 1.如何编写一个简单的Linux驱动(一)——驱动的基本框架 2.如何编写一个简单的Linux驱动(二)——设备操作集file_operations 前言 在上一篇文章中,我们编写设备驱动遇 ...

  2. 如何编写一个简单的Linux驱动(一)

    前言 最近在学习Linux驱动,记录下自己学习的历程. 驱动的基本框架 Linux驱动的基本框架包含两部分,“模块入口.出口的注册”和“模块入口.出口函数的实现”,如下方代码. static int ...

  3. 使用CEF(二)— 基于VS2019编写一个简单CEF样例

    使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...

  4. Linux内核分析第三周学习总结:构造一个简单的Linux系统MenuOS

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.Linux内 ...

  5. 20135202闫佳歆--week3 构造一个简单的Linux系统MenuOs--学习笔记

    此为个人学习笔记存档 week 3 构造一个简单的Linux系统MenuOs 复习: 计算机有三个法宝:存储程序计算机,函数调用堆栈,中断 操作系统有两把剑: 1.中断上下文的切换,保存现场和恢复现场 ...

  6. Linux内核分析-构造一个简单的Linux系统MenuOS

    构造一个简单的Linux系统MenuOS linux内核目录结构 arch目录包括了所有和体系结构相关的核心代码.它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel C ...

  7. 20135220谈愈敏Blog3&lowbar;构造一个简单的Linux系统MenuOS

    构造一个简单的Linux系统MenuOS 谈愈敏 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1 ...

  8. Linux内核分析第三周——构造一个简单的Linux系统MenuOS

    构造一个简单的Linux系统MenuOS 李雪琦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/UST ...

  9. Linux内核设计第三周——构造一个简单的Linux系统

    Linux内核设计第三周 ——构造一个简单的Linux系统 一.知识点总结 计算机三个法宝: 存储程序计算机 函数调用堆栈 中断 操作系统两把宝剑: 中断上下文的切换 进程上下文的切换 linux内核 ...

随机推荐

  1. DEV winform treelist设置背景图像

    treelist是一个复杂的控件,包括选中行,奇偶行等均可以单独设置显示效果,空白区域上背景图像的代码如下: private void treeList1_CustomDrawEmptyArea(ob ...

  2. php随笔(一)

    之前的开发一直用的都是Thinkphp框架,对原生的php很不了解,近日打算把以前的项目拿一个出来用原生php再重写一次,顺便再把TP框架拆开好好分析分析. 之前的android开发虽说对面向对象的思 ...

  3. source insight 相对路径

    source insight项目 在移动到另外一个地方时,会因为之前是绝对路径而导致,项目中的文件都不可用,需要重新把这些文件添加一遍. 这是个令人讨厌的事情. 解决办法为创建项目时设定为绝对路径.方 ...

  4. LGLDatePickerView

    这个是封装 系统的PickerView 使用也比较简单, gihub地址:https://github.com/liguoliangiOS/LGLDatePickerView.git 效果图 使用方法 ...

  5. Spring多资源文件properties的配置

    Spring简化了加载资源文件的配置,可以通过<context:property-placeholder去加载,这个元素的写法如下: <context:property-placehold ...

  6. java&period;util&period;AbstractStringBuilder源码分析

    AbstractStringBuilder是一个抽象类,是StringBuilder和StringBuffer的父类,分析它的源码对StringBuilder和StringBuffer代码的理解有很大 ...

  7. 分析uboot中 make xxx&lowbar;config过程

    make xxx_config实质上就是调用了 首先看MKCONFIG: [注意]SRCTREE=源文件下的目录 之后的语句: @$(MKCONFIG) $(@:_config=) arm arm92 ...

  8. 【ES6】函数

    函数默认值问题 在ES6之前,不能直接为函数指定默认值,但是ES6允许为函数的参数设置默认值 之前实现方式 function log(x, y) { y = y || 'World'; console ...

  9. Confluence 6 恢复一个站点

    这个页面对如何从一个 XML 导出文件中恢复到一个已经存在的 Confluence 站点进行描述. 如果你希望导入数据倒一个新的站点,请参考 restoring from backup during ...

  10. CSS 颜色术语

    术语和概念在理科学习非常重要.CSS中有些关键的术语的. 对比度 对比度是指前景色和背景色之间的差距.当前景颜色和背景色之间的对比度较低时,文本就难以阅读.这对视觉障碍或色盲来说更严重. 透明度 透明 ...