您现在的位置是:首页 > 正文

Linux内核模块开发 第 6 章

2024-02-29 16:50:27阅读 1

The Linux Kernel Module Programming Guide

6 字符设备驱动

include/linux/fs.h 中定义了结构体 file_operations ,这个结构体包含指向再设备上执行各种操作的系列函数。结构体的每一字段都对应着驱动中定义的处理请求的函数的地址。

所谓“每一字段对应驱动中…的函数的地址”,即是说 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 (*iopoll)(struct kiocb *kiocb, bool spin); 
    int (*iterate) (struct file *, struct dir_context *); 
    int (*iterate_shared) (struct file *, struct dir_context *); 
    __poll_t (*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 *); 
    unsigned long mmap_supported_flags; 
    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 (*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); 
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, 
        loff_t, size_t, unsigned int); 
    loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, 
             struct file *file_out, loff_t pos_out, 
             loff_t len, unsigned int remap_flags); 
    int (*fadvise)(struct file *, loff_t, loff_t, int); 
} __randomize_layout;

某些操作不会在驱动中实现(implemeted by a driver)。例如声卡驱动不需要实现从目录结构中读取的接口,那么这个驱动提供的 file_operations 结构体中的相关指针就可以设为 NULL

GCC 扩展(gcc extension)支持便捷的结构体初始化方式(即内核中常用的乱序初始化),用法形如:

struct file_operations fops = { 
    read: device_read, 
    write: device_write, 
    open: device_open, 
    release: device_release 
};

或者使用 C99 风格的 designed initilizers 初始化结构体。

file_operations 中包含的用于实现read、write等系统调用的函数,通常被称为 fops

从 3.14 版内核开始, read、write、seek等操作fops 就已经有线程安全的(thread-safe)特定锁(specific lock)保护了,这使得文件位置更新(file position update)是互斥的(mutual exclusion)。所以我们在实现这些操作的时候不需要类似目的的锁(unnecessary locking)。

在计算机中,文件位置更新是指将文件指针移动到文件中的特定位置。

此外,从 5.6 版开始,开发者向内核引入了 proc_ops 结构体,在注册 proc handlers 时不在使用 file_operations 结构体。

在计算机操作系统中,进程处理程序(proc handlers)是一种用于处理进程中断和异常的机制。主要作用是保证进程的正常运行和安全性。当进程发生中断或异常时,进程处理程序可以采取适当的措施来处理这些事件,例如重新启动进程、恢复进程状态、记录日志等。

6.2 File 结构体

每个设备在内核中都由一个 struct file 结构体表示,这个结构体定义在文件 include/linux/fs.h.中。

这个结构体不是用户程序常用的 glibc 中定义的 FILE。另外这个结构体的命名有些误导作用,它指的是抽象的打开的文件,而非用 inode 指代的磁盘文件。

struct file 的指针(instance)通常被称为 filp

驱动基本不会使用include/linux/fs.h. 中定义的各类接口直接覆写(fill) struct file ,只会调用 struct file 中包含的各结构体。

6.3 注册设备

如前所述,用户一般是通过 /dev 目录下的设备文件(device files)访问字符设备的。

主设备号标明驱动处理哪个设备文件,次设备号只用于有多个设备时,驱动分辨正在使用的设备(which device is operating on)。

向系统中添加一个设备意味着将这个设备注册到内核中。在模块初始化的时候会通过调用定义在 include/linux/fs.h 中的 register_chrdev() 函数为设备分配一个主设备号,其原型如下:

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

major 是主设备号,name 是可以在文件 /proc/devices 中看到的设备名,*fops 指向驱动中 file_operations 表的指针。函数返回负数表明设备注册失败。值得一提的是,这个函数不涉及次设备号,因为只有驱动才使用这个属性,内核并不关心次设备号。

现在问题来了,如何才能获取一个没被使用的主设备号呢?最简单的方式是查看 Documentation/admin-guide/devices.txt 然后选一个没有被占用的设备号。但这不是一个好办法,因为这个方法无法操作的互斥性,不能保证后续不会有其他设备使用同样的设备号。正确的答案是向内核请求一个动态的主设备号(ask the kernel to assign you a dynamic major number)。

向函数 register_chrdev() 传参数 0 ,它的返回值就是 dynamic major number。这个办法的弊端在于,因为不确定设备注册时会获得哪个动态设备号,也就不能提前创建设备文件。又三种解决方案:

  1. 驱动打印输出主设备号,然后手动创建设备文件
  2. 新注册的设别会显示在 /proc/devices 文件中,可以通过读这个文件获取主设备号,然后手动/脚本创建设备文件
  3. 驱动在成功注册设备后,通过 device_create() 函数创建设备文件,并在 cleanup_module() 函数中调用函数 device_destroy()

不过,register_chrdev() 函数会占用一些和主设备号相关的次设备号,推荐使用 cdev interface 注册字符设备以减少对次设备号的浪费。

使用 cdev interface 注册字符设备分两步走。

第一步,调用 register_chrdev_region() 或者 alloc_chrdev_region() 注册一系列设备号(register a range of device numbers)。

int register_chrdev_region(dev_t from, unsigned count, const char *name); 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

如果指定设备号,使用 register_chrdev_region(),否则使用 alloc_chrdev_region()

第二步,使用下面的方法为字符设备初始化结构体 struct cdev,并将它和第一步注册的 device number 关联起来(associate it with the device numbers)。

struct cdev *my_dev = cdev_alloc(); 
my_cdev->ops = &my_fops;

上面是 cdev 单独存在的情况,更常规的情况是,设备驱动的 fops 中包含这个结构体,那就要用到 cdev_init() 函数了,原型如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

完成初始化后,可用 cdev_add() 函数将字符设备添加到系统中,函数原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

上述各种用法,可以在第 9 章中找到使用范例。

网站文章

  • .gitlab-ci.yml自动编译

    1..gitlab-ci.yml关键词解析(一)2..gitlab-ci.yml关键词解析(二)3..gitlab-ci.yml关键词解析(三)

    2024-02-29 16:49:52
  • EBS启用configurator模块

    启用configurator模块需要在ebs系统中打开以下配置文件: 1、BOM:Configurator URL of UI Manager : http://ip地址:端口/OA_HTML/configurator/UiServlet 2、BOM: Configuration Validation Engine: Product Configurator Engine 3、CZ: Gene

    2024-02-29 16:49:45
  • c语言数据文件dat和索引文件idx,INFORMIX-4GL实用程序的使用.doc

    INFORMIX-4GL实用程序的使用INFORMIX-4GL实用程序的使用工商银行池州中心支行 (247100) 刘东明---------------------------------------...

    2024-02-29 16:49:38
  • 利用Redisson的Zset 实现历史搜索记录

    ScoreSortedSet 是redisson提供的实现了Zset的数据类型, 相较于set, 在添加元素进集的时候, 需要给定一个score, score决定了该元素的在集里的顺序.首先介绍下,r...

    2024-02-29 16:49:09
  • 使用复合数据类型

    使用复合数据类型(一)PL/SQL 记录标量变量只能处理单行单列的数据,而PL/SQL记录用于处理单行多列的数据。①自定义记录类型和记录变量自定义记录类型语法:TYPE type_name IS RECORD(field datatype [[not null] ][,......] );定义记录变量语法:identifier

    2024-02-29 16:49:04
  • Linux进程控制编程

    一 进程控制理论基础 进程的状态: 进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元; 程序是放到磁盘的可执行文件; 进程是指程序执行的实例;进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。通常进程不可在计算机之间迁移;而程序通常对应着文件、静态和可以复制 进程是暂时的,程序使长久的:进程是一个状态变化的过程,程序可长久保存 进程

    2024-02-29 16:48:57
  • Unity 新的输入系统Input System(二)

    Unity 新的输入系统Input System(二)

    前面一篇大致简单的介绍了下Input System,并且通过官方的 SimpleDemo_UsingPlayerInput 来了解了PlayerInput组件的使用。接下来让我们通过其他几个Demo来...

    2024-02-29 16:48:50
  • NFS搭建、配置及故障排除详解

    1.什么是NFS?NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源。在NFS的应用中,本地N...

    2024-02-29 16:48:24
  • 【并查集 | Python】1631. 最小体力消耗路径

    【并查集 | Python】1631. 最小体力消耗路径

    1631. 最小体力消耗路径题目来源:力扣(LeetCode)https://leetcode-cn.com/problems/path-with-minimum-effort/题目你准备参加一场远足...

    2024-02-29 16:48:17
  • Android的资源引用

    Android的资源引用

    Android中字符串、颜色和尺寸应用1.资源列表2.xml文件二、Android项目布局文件

    2024-02-29 16:48:09