前期知识

  如何编写一个简单的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命令查看是否创建成功。

  分别输入命令"./shanwuyan_APP /dev/shanwuyan r"和命令"./shanwuyan_APP /dev/shanwuyan w",可以看到终端打印了如下信息。可以看到,应用程序打开设备、关闭设备、读设备、写设备的操作都有所体现。

  在本章中,我们只是单纯得调用了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_构造一个简单的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. Hyperledger Fabric介绍

    转载地址 https://blog.csdn.net/xiaonu123/article/details/81006936 简介 Hyperledger介绍 超级账本(Hyperledger)项目是首 ...

  2. LeetCode746 Min Cost Climbing Stairs(爬上楼梯的最小损失)

    题目 On a staircase, the i-th step has some non-negative cost cost[i] assigned (0 indexed). Once you p ...

  3. python设计模式之模板模式

    python设计模式之模板模式 编写优秀代码的一个要素是避免冗余.在面向对象编程中,方法和函数是我们用来避免编写冗余代码的重要工具. 现实中,我们没法始终写出100%通用的代码.许多算法都有一些(但并 ...

  4. Object.prototype.__proto__, [[prototype]] 和 prototype

    Object.prototype.__proto__ , [[prototype]] 和 prototype Object.prototype.__proto__ 是什么? __proto__ 是一个 ...

  5. python 常用函数集合

    1.常用函数     round() :  四舍五入         参数1:要处理的小数         参数2:可选,如果不加,就是不要小数,如果加,就是保留几位小数     abs() :绝对值 ...

  6. docker入门1-docker container

    image和container介绍 一个image是一个可被docker执行的包,它包括程序运行的所有东西,包括代码,运行时,库,环境变量和配置文件. 一个container是image在内存中的运行 ...

  7. VM获取子网掩码和网关

  8. Vue Axios 的封装使用

    目录 Axios 说明 安装 Axios 请求配置 响应结构 常用请求方法 默认值配置 全局的 请求配置项 自定义实例默认值 配置的优先顺序 拦截器 个人完整 axios 配置 Axios 说明 Ax ...

  9. 安装centos7显示器分辨率不适配的解决方法

    1,系统读取安装信息后,选择Install Centos7 然后Tab调出参数行 2,在quiet后空格输入nomodeset回车即可

  10. Jmeter 常用函数(20)- 详解 __counter

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.htm 作用 计数器,跟配置元件里面的计数器作用类似哦 ...