zoukankan      html  css  js  c++  java
  • 如何编写一个简单的Linux驱动(二)——设备操作集file_operations

    前期知识

      如何编写一个简单的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
    ");
     5     return 0;
     6 }
     7 
     8 /*释放(关闭)设备*/
     9 static int shanwuyan_release(struct inode *inode, struct file *filp)
    10 {
    11     printk(KERN_EMERG "shanwuyan_close
    ");
    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
    ");
    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
    ");
    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
    ");    //注册失败
    16     else
    17         printk(KERN_EMERG "shanwuyan_init
    ");//注册成功
    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
    ");
    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!
    ");
    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
    ", 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!
    ");
    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函数,但是并没有真正的读写数据。读写数据操作将在下一章中出现。

      本章的全部代码在这里

      

  • 相关阅读:
    Git标签使用技巧
    Git入门基本概述
    过滤器+缓存在.NET5WebApi项目中的简单使用
    在.NET5中 使用JWT鉴权授权
    Git常用开发命令
    时间戳的实际使用
    两个日期字段相减,进行计算
    MQ的理论理解
    第一周学习C语言的总结!
    问题(the question)
  • 原文地址:https://www.cnblogs.com/UnfriendlyARM/p/13659337.html
Copyright © 2011-2022 走看看