首页 > 系统相关 >Structure of Linux Kernel Device Driver(Part I)

Structure of Linux Kernel Device Driver(Part I)

时间:2024-07-15 17:54:00浏览次数:14  
标签:Kernel struct data Driver Part cdev file my 设备

Structure of Linux Kernel Device Driver

ref. https://www.youtube.com/watch?v=XoYkHUnmpQo&list=LL&index=1&t=272s

Device Drivers

Def.: 设备驱动(Device Drivers),实际上就是硬件设备对应的抽象,用户能够通过这样的一个抽象与对应的硬件进行交互

设备驱动与固件的区别:设备驱动是运行在主机内核上(偶尔也有运行在用户空间)的,让操作系统知道如何与对应的硬件设备进行通信
而固件则是运行在设备硬件上的,只有具备一定智能程度的设备才会拥有固件(Only Devices with some level of intelligence)。

操作系统的职责之一就是提供一个基础框架用于编写和运行设备驱动。

在Unix based的操作系统上,最常见的抽象形式就是文件,也就是说用户通常是通过某个文件来与硬件设备进行交互,如下图所示:

\(\mathcal{Intuition}\): The development of hardware driver composed by two parts:

  1. talk to the driver: 用户在内核中注册驱动程序,生成对应的设备文件,然后通过处理文件的方式与驱动交互
  2. talk to the hardware: 由驱动完成对硬件设备的控制

内核会提供一些API,用于将设备硬件导出为用户空间的文件,这些文件通常位于文件系统中的/dev, /sys等目录下。

这些目录下的文件被称为设备节点或者设备文件,设备文件拥有以下基础属性:

  • 类型:block or char,对于char类型的设备文件,用户通过字节流与之通信;而对于block类型的设备文件,用户通过以块为单位的字节与之通信。
  • 主号(设备号):用于标识设备的类型,比如sata设备和音频设备拥有不同的设备号
  • 次号:用于区分同类型的不同设备

主号-次号对是一个设备在系统中的标识符,因此需要保证每一个设备的主号-次号都是唯一的。

上图中,/dev/ttyS0的设备类型是char类型,主次设备号为4/64;/dev/ram0的设备类型是block类型,主次设备号为1/0。

Talk to a char driver

对于Char类型的设备文件,用户可以通过处理文件的方式与运行在内核系统中的驱动进行交互:

用于读写文件的系统调用,比如open(), read(), write(), close(), lseek(), mmap() etc. 将会被操作系统重定向(redirected)硬件设备对应的驱动程序。设备驱动程序则是一个内核组件(通常是一个模块, module)用于与硬件设备交互。

从设备驱动的角度,用户只需要编写回调函数然后在内核中注册该回调函数,那么用户就能够与设备驱动通信。而设备驱动运行在内核,因此也能和硬件设备通信。

开发设备驱动的三个基本步骤:

  1. 为设备驱动分配设备号(主设备号/次设备号),这个步骤通过register_chrdev_region()或者alloc_chrdev_region()函数完成
  2. 实现文件处理操作的回调函数,e.g.,open(), read(), write(), ioctl()
  3. 在内核中注册该驱动,这个步骤通过cdev_init()以及cdev_add()函数完成

开发设备驱动的步骤可以大致理解为:creating a link from the driver to the device node that the user can see.

Implementation driver

在内核中,使用结构体struct cdev来代表一个char设备,这个结构体主要用于在系统中注册一个驱动,其初始化过程主要涉及两个成员:

typedef unsigned int cdev_t /* An integer type used for device IDs */

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 *);
    [...]
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    [...]
    int (*open) (struct inode *, struct file *);
    [...]
}

从上面的代码中可以看到,open(), read(), write()函数与常见的系统调用不同,其参数中不包含文件路径或者文件描述符,而是将另外两个结构体作为参数:struct file & struct inode,这两个结构体都是用于代表一个文件,不过是角度不同,inode结构体是从文件系统的角度表示一个文件,其属性包括大小、权限已经唯一的标识符;file结构体则是从用户的角度表示一个文件,其属性包括inode、文件名、文件打开属性(e.g. O_RDONLY etc.)以及文件内部偏移。

这两个结构体的区别:
To understand the differences between inode and file, we will use an analogy from object-oriented programming: if we consider a class inode, then the files are objects, that is, instances of the inode class. Inode represents the static image of the file (the inode has no state), while the file represents the dynamic image of the file (the file has state).
https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html

file结构体的主要成员:

  • f_mode:用于标识读(FMODE_READ)或者写(FMODE_WRITE)
  • f_flags:用于标识文件打开的属性(O_RDONLY, O_NONBLOCK, etc.)
  • f_op:用于标识与文件相关联的操作(pointer to the file_operations structure)
  • private_data:可以用于存储由设备特定数据的指针,这个指针将会被初始化值是程序员分配的内存位置
  • f_pos:文件内的偏移量

inode结构体的主要成员为i_cdev指针,该指针用于指向char设备对应的结构体

Implementation of file operations

在设备驱动开发的过程中,推荐使用一个结构体用于保存和跟踪设备相关的信息以及模块所使用到的信息。对于char设备,这样的结构体中需要包含指向该设备的结构体,如下:

#include <linux/fs.h>
#include <linux/cdev.h>

struct my_device_data {
    struct cdev cdev;
    /* my data starts here */
    //...
};

static int my_open(struct inode *inode, struct file *file)
{
    struct my_device_data *my_data;

    my_data = container_of(inode->i_cdev, struct my_device_data, cdev);

    file->private_data = my_data;
    //...
}

static int my_read(struct file *file, char __user *user_buffer, size_t size, loff_t *offset)
{
    struct my_device_data *my_data;

    my_data = (struct my_device_data *) file->private_data;

    //...
}

可以使用inode结构的i_cdev字段(使用container_of宏)找到指向cdev成员的指针。在file结构的 private_data 字段中,可以在打开后存储信息,然后使用read(), write(), release()等函数访问这些数据。

在回调函数中,需要读取和写入数据到用户地址空间,这些通常使用下面的这些宏\函数来实现:

#include <asm/uaccess.h>

/* return 0 in case of success and another value in case of error */
put_user(type val, type *address);
get_user(type val, type *address);
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

Registration and unregistration of char devices

驱动设备的注册与注销过程都和设备的主次设备号相关,可以使用MKDEV宏静态生成一个dev_t,用于保存设备的唯一标识符。不过推荐使用alloc_chrdev_region()函数来动态分配主次设备号。随后,使用函数register_chrdev_region()或者unregister_chrdev_region()来注册或者注销驱动,这些函数原型如下:

dev_t MKDEV(unsigned int maj, unsigned int min);

/*
arguments:
- dev: output parameter for first assigned number
- baseminor: first of the requested range of minor numbers
- count: the number of minor numbers required
- name: the name of the associated device or driver
Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name);

int register_chrdev_region(dev_t first, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);

下面这段代码用于注册"my_minor_count"个设备驱动,其标识符以"my_major:my_first_minor"开始连续递增。

#include <linux/fs.h>
...

err = register_chrdev_region(MKDEV(my_major, my_first_minor), my_minor_count,
                             "my_device_driver");
if (err != 0) {
    /* report error */
    return err;
}
...

在实现了文件操作并分配了驱动标识符后,将进行驱动初始化(cdev_init())并且通知内核添加该驱动(cdev_add()),下面这段代码用于添加"MY_MAX_MINORS"个设备驱动:

#include <linux/fs.h>
#include <linux/cdev.h>

#define MY_MAJOR       42
#define MY_MAX_MINORS  5

struct my_device_data {
    struct cdev cdev;
    /* my data starts here */
    //...
};

struct my_device_data devs[MY_MAX_MINORS];

const struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .read = my_read,
    .write = my_write,
    .release = my_release,
    .unlocked_ioctl = my_ioctl
};

int init_module(void)
{
    int i, err;

    err = register_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS,
                                 "my_device_driver");
    if (err != 0) {
        /* report error */
        return err;
    }

    for(i = 0; i < MY_MAX_MINORS; i++) {
        /* initialize devs[i] fields */
        cdev_init(&devs[i].cdev, &my_fops);
        cdev_add(&devs[i].cdev, MKDEV(MY_MAJOR, i), 1);
    }

    return 0;
}

下面这段代码用于删除并注销驱动:

void cleanup_module(void)
{
    int i;

    for(i = 0; i < MY_MAX_MINORS; i++) {
        /* release devs[i] fields */
        cdev_del(&devs[i].cdev);
    }
    unregister_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS);
}

file_operations结构体的初始化中,使用结构体的成员名称进行初始化,这样的初始化操作是在C99中定义的,而未初始化的成员将会保持默认值,比如上面这段代码中my_fops.mmap将会是一个空指针。

在设备终端中加载对应的驱动:modeprobe [driver_name],内核将会执行上面的代码,完成驱动的初始化并加载该驱动

然后打印已添加的驱动:cat /proc/devices,找到对应驱动的主次设备号

创建用户空间可以访问的设备文件:mknod /dev/[file_name] [c/b] [major num] [minor num]

随后,用户对设备文件的读写操作都会调用设备驱动对应的回调函数。

标签:Kernel,struct,data,Driver,Part,cdev,file,my,设备
From: https://www.cnblogs.com/TheFutureIsNow/p/18303258

相关文章

  • C35 空气污染和暴露其中的人口(Part 7)
    导读在过去的几十年里,我们越来越多地接触到一系列监测地球大气成分的卫星传感器。然而,重要的是要注意,卫星测量的是对流层和平流层的污染物浓度,对流层和平流层在地球表面以上延伸许多公里。因此,卫星测量不一定代表人类在地面上接触到的浓度,因此不建议仅依靠卫星数据进行人类健康应......
  • 代码随想录算法训练营第22天 |二叉树part07:235. 二叉搜索树的最近公共祖先、701.二叉
    代码随想录算法训练营第22天|二叉树part07:235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点235.二叉搜索树的最近公共祖先https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/description/代码随想录:htt......
  • ATTACKS ON THIRD-PARTY APIS OF LARGE LANGUAGE MODELS
    本文是LLM系列文章,针对《ATTACKSONTHIRD-PARTYAPISOFLARGELANGUAGEMODELS》的翻译。对大型语言模型第三方api的攻击摘要1引言2提出的流水线3实验4结论摘要大型语言模型(LLM)服务最近开始提供一个插件生态系统来与第三方API服务交互。这项创新增强了LLM的能......
  • java 连接 oracle数据库时报错 Oracle JDBC驱动未找到! No suitable driver found for
    在用IDEA编写java连接Oracle时,报错:OracleJDBC驱动未找到!可这部分之前测试是好用。想来想去。哦,我把这个项目代码换过路径,问题就出在这。需要重新引用下  ojdbc6.jar架包 下面是java连接oracle的部分代码ClassNotFoundException可以捕获OracleJDBC驱动未找到的异......
  • 【代码随想录|第十一章 图论part01 | 797.所有可能的路径 】
    代码随想录|第十一章图论part01|图论理论基础,797.所有可能的路径,广搜理论基础一、图论理论基础1.图的基本概念2.图的构造1)邻接矩阵2)邻接表3.图的遍历方式4.深度优先搜索理论基础二、797.所有可能的路径1.核心代码2.问题三、广搜理论基础总结python一、图论理......
  • TatukGIS Developer Kernel 11.91 FOR net Crack
    .NET开发人员内核TatukGIS开发人员内核(DK).NET版本是专业级托管代码.NETGIS库,可用于为多种操作系统开发专业GIS应用程序。此GISSDK版本专为以下操作系统设计和编译:.NET(又名Core ),支持:.NETStandard2.1用于开发适用于Windows、Linux和macOS的应用程序.......
  • 高通dump ftrace & kernelshark使用
    简介高通ramdump可以解析出ftrace,方便用于追踪快省稳问题。kernelshark是一个可以查看traceevent的图形化工具,方便梳理和观察内核微观行为。trace-cmd是设置读取ftrace的命令行工具,kernelshark既可以记录数据,也可以图形化分析结果。在/sys/kernel/debug/tracing/......
  • 转:functools.partial函数
    Pythonfunctools.partial函数详解与实战_pythonfuctiontoolpartial-CSDN博客functools.partial:Python中灵活函数部分应用的工具_functools.partial()-CSDN博客在Python编程中,functools.partial是一个极具实用价值的函数,它允许我们“冻结”函数的一些参数或关键字,从而生成一个......
  • ISA95-Part5-安全和权限管理的设计思路
    1、具体要求:在MES/MOM系统中实现ISA-95标准的安全和权限管理,具体要求通常包括以下几个方面:1.--数据保护--:确保敏感数据通过加密和安全存储来保护,防止数据泄露或被未授权访问。2.--访问控制--:实施基于角色的访问控制(RBAC),确保只有授权用户才能访问相应资源和数据。3.--......
  • 「代码随想录算法训练营」第十天 | 栈与队列 part2
    150.逆波兰表达式求值题目链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation/题目难度:中等文章讲解:https://programmercarl.com/0150.逆波兰表达式求值.html视频讲解:https://www.bilibili.com/video/BV1kd4y1o7on题目状态:多次修改bug后通过个人思路:......