目录
驱动模型" >驱动模型
实现read,write" >实现read,write
实现ioctl" >实现ioctl
实例" >实例
创建设备文件" >创建设备文件
首页 系统教程 操作系统 Linux字符设备驱动框架:原理与方法

Linux字符设备驱动框架:原理与方法

Feb 09, 2024 pm 09:06 PM
linux linux教程 linux系统 linux命令 外壳脚本 overflow 嵌入式linux linux入门 linux学习

字符设备是Linux系统中一种常见的设备类型,它们以字节为单位进行数据传输,如键盘,鼠标,串口等。字符设备的驱动是Linux驱动开发中最基本的一种,它涉及到设备号,设备文件,文件操作结构体,cdev结构体等概念。在本文中,我们将介绍Linux字符设备驱动框架的原理和方法,包括注册和注销设备号,创建和删除设备文件,初始化和释放cdev结构体,实现和注册文件操作函数等,并举例说明它们的使用方法和注意事项。

Linux字符设备驱动框架:原理与方法

驱动模型

Linux一切皆文件,那么作为一个设备文件,它的操作方法接口封装在struct file_operations,当我们写一个驱动的时候,一定要实现相应的接口,这样才能使这个驱动可用,Linux的内核中大量使用”注册+回调”机制进行驱动程序的编写,所谓注册回调,简单的理解,就是当我们open一个设备文件的时候,其实是通过VFS找到相应的inode,并执行此前创建这个设备文件时注册在inode中的open函数,其他函数也是如此,所以,为了让我们写的驱动能够正常的被应用程序操作,首先要做的就是实现相应的方法,然后再创建相应的设备文件。

#include  //for struct cdev
#include   
 //for struct file
#include     //for copy_to_user
#include          
   //for error number

static int ma = 0;
static int mi = 0;
const int count = 3;

/* 准备操作方法集 */
/* 
struct file_operations {
    struct module *owner;   //THIS_MODULE
    
    //读设备
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    //写设备
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    //映射内核空间到用户空间
    int (*mmap) (struct file *, struct vm_area_struct *);

    //读写设备参数、读设备状态、控制设备
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    //打开设备
    int (*open) (struct inode *, struct file *);
    //关闭设备
    int (*release) (struct inode *, struct file *);

    //刷新设备
    int (*flush) (struct file *, fl_owner_t id);

    //文件定位
    loff_t (*llseek) (struct file *, loff_t, int);

    //异步通知
    int (*fasync) (int, struct file *, int);
    //POLL机制
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    。。。
};
*/

ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
    return 0;
}

struct file fops = {
    .owner = THIS_MODULE,
    .read = myread,
    ...
};

/* 字符设备对象类型 
struct cdev {
    struct kobject kobj;     
    struct module *owner;        //模块所有者(THIS_MODULE),用于模块计数
    const struct file_operations *ops;    //操作方法集(分工:打开、关闭、读/写、...)
    struct list_head list;
    dev_t dev;                            //设备号(第一个)
    unsigned int count;            //设备数量
};
*/

static int __init chrdev_init(void)
{
    ...
    /* 构造cdev设备对象 */
    struct cdev *cdev_alloc(void);

    /* 初始化cdev设备对象 */
    void cdev_init(struct cdev*, const struct file_opeartions*);

    /* 申请设备号,静态or动态*/
    /* 为字符设备静态申请第一个设备号 */
    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);

    ma = MAJOR(dev)     //从dev_t数据中得到主设备号
    mi = MINOR(dev)     //从dev_t数据中得到次设备号
    MKDEV(ma,1) //将主设备号和次设备号组合成设备号,多用于批量创建/删除设备文件

    /* 注册字符设备对象cdev到内核 */
    int cdev_add(struct cdev* , dev_t, unsigned);
    ...
}

static void __exit chrdev_exit(void)
{
    ...
    /* cdev_del()、cdev_put()二选一 */
    /* 从内核注销cdev设备对象 */
    void cdev_del(struct cdev* );

    /* 从内核注销cdev设备对象 */
    void cdev_put(stuct cdev *);

    /* 回收设备号 */
    void unregister_chrdev_region(dev_t from, unsigned count);
    ...
}
登录后复制

罗嗦一句,如果使用静态申请设备号,那么最大的问题就是不要与已知的设备号相冲突,内核在文档**”Documentation/devices.txt”**中已经注明了哪些主设备号被使用了,从中可以看出,在2^12个主设备号中,我们能够使用的范围是240-255以及261-2^12-1的部分,这也可以解释为什么我们动态申请的时候,设备号经常是250的原因。此外,通过这个文件,我们也可以看出,”主设备号表征一类设备”,但是字符/块设备本身就可以被分为好多类,所以内核给他们每一类都分配了主设备号。
Linux字符设备驱动框架:原理与方法

实现read,write

Linux下各个进程都有自己独立的进程空间,即使是将内核的数据映射到用户进程,该数据的PID也会自动转变为该用户进程的PID,由于这种机制的存在,我们不能直接将数据从内核空间和用户空间进行拷贝,而需要专门的拷贝数据函数/宏:

long copy_from_user(void *to, const void __user * from, unsigned long n)

long copy_to_user(void __user *to, const void *from, unsigned long n)
登录后复制

这两个函数可以将内核空间的数据拷贝到回调该函数的用户进程的用户进程空间,有了这两个函数,内核中的read,write就可以实现内核空间和用户空间的数据拷贝。

ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
    long ret = 0;
    size = size > MAX_KBUF?MAX_KBUF:size;
    if(copy_to_user(user_buf, kbuf,size)
        return -EAGAIN;
    }
    return 0;
}
登录后复制

实现ioctl

ioctl是Linux专门为用户层控制设备设计的系统调用接口,这个接口具有极大的灵活性,我们的设备打算让用户通过哪些命令实现哪些功能,都可以通过它来实现,ioctl在操作方法集中对应的函数指针是long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);,其中的命令和参数完全由驱动指定,通常命令会写在一个头文件中以供应用层和驱动层遵守同样的通信协议,Linux建议如图所示的方式定义ioctl()命令

设备类型    序列号     方向      数据尺寸
8bit        8bit    2bit    13/14bit
登录后复制

设备类型字段为一个幻数,可以是0~0xff之间的数,内核中的”ioctl-number.txt“给出了一个推荐的和已经被使用的幻数(但是已经好久没人维护了),新设备驱动定义幻数的时候要避免与其冲突。
序列号字段表示当前命令是整个ioctl命令中的第几个,从1开始计数。
方向字段为2bit,表示数据的传输方向,可能的值是:**_IOC_NONE_IOC_READ_IOC_WRITE_IOC_READ|_IOC_WRITE**。
数据尺寸字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13或14位。

内核还定义了**_IO()_IOR()_IOW()_IOWR()**这4个宏来辅助生成这种格式的命令。这几个宏的作用是根据传入的type(设备类型字段),nr(序列号字段)和size(数据长度字段)和方向字段移位组合生成命令码。

内核中还预定义了一些I/O控制命令,如果某设备驱动中包含了与预定义命令一样的命令码,这些命令会被当做预定义命令被内核处理而不是被设备驱动处理,有如下4种:

  • FIOCLEX:即file ioctl close on exec 对文件设置专用的标志,通知内核当exec()系统带哦用发生时自动关闭打开的文件
  • FIONCLEX:即file ioctl not close on exec,清除由FIOCLEX设置的标志
  • FIOQSIZE:获得一个文件或目录的大小,当用于设备文件时,返回一个ENOTTY错误
  • FIONBIO:即file ioctl non-blocking I/O 这个调用修改flip->f_flags中的O_NONBLOCK标志

实例

//mycmd.h
...
#include 
#define CMDT 'A'
#define KARG_SIZE 36
struct karg{
    int kval;
    char kbuf[KARG_SIZE];
};
#define CMD_OFF _IO(CMDT,0)
#define CMD_ON  _IO(CMDT,1)
#define CMD_R  
 _IOR(CMDT,2,struct karg)
#define CMD_W   _IOW(CMDT,3,struct karg)
...
//chrdev.c
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    static struct karg karg = {
        .kval = 0,
        .kbuf = {0},
    };
    struct karg *usr_arg;

    switch(cmd){
    case CMD_ON:
        /* 开灯 */
        break;
    case CMD_OFF:
        /* 关灯 */
        break;
    case CMD_R:
        if(_IOC_SIZE(cmd) != sizeof(karg)){
            return -EINVAL;
        }
        usr_arg = (struct karg *)arg;
        if(copy_to_user(usr_arg, &karg, sizeof(karg))){
            return -EAGAIN;
        }
        break;
    case CMD_W:     
        if(_IOC_SIZE(cmd) != sizeof(karg)){
            return -EINVAL;
        }
        usr_arg = (struct karg *)arg;
        if(copy_from_user(&karg, usr_arg, sizeof(karg))){
            return -EAGAIN;
        }
        break;
    default:
        ;
    };
    return 0;
}
登录后复制

创建设备文件

插入的设备模块,我们就可以使用cat /proc/devices命令查看当前系统注册的设备,但是我们还没有创建相应的设备文件,用户也就不能通过文件访问这个设备。设备文件的inode应该是包含了这个设备的设备号,操作方法集指针等信息,这样我们就可以通过设备文件找到相应的inode进而访问设备。创建设备文件的方法有两种,手动创建自动创建手动创建设备文件就是使用mknod /dev/xxx 设备类型 主设备号 次设备号的命令创建,所以首先需要使用cat /proc/devices查看设备的主设备号并通过源码找到设备的次设备号,需要注意的是,理论上设备文件可以放置在任何文件加夹,但是放到**”/dev”才符合Linux的设备管理机制,这里面的devtmpfs是专门设计用来管理设备文件的文件系统。设备文件创建好之后就会和创建时指定的设备绑定,即使设备已经被卸载了,如要删除设备文件,只需要像删除普通文件一样rm**即可。理论上模块名(lsmod),设备名(/proc/devices),设备文件名(/dev)并没有什么关系,完全可以不一样,但是原则上还是建议将三者进行统一,便于管理。

除了使用蹩脚的手动创建设备节点的方式,我们还可以在设备源码中使用相应的措施使设备一旦被加载就自动创建设备文件,自动创建设备文件需要我们在编译内核的时候或制作根文件系统的时候就好相应的配置:

Device Drivers --->
        Generic Driver Options --->
            [*]Maintain a devtmpfs filesystem to mount at /dev
            [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs
登录后复制

OR
制作根文件系统的启动脚本写入

mount -t sysfs none sysfs /sys
mdev -s //udev也行
登录后复制

有了这些准备,只需要导出相应的设备信息到**”/sys”**就可以按照我们的要求自动创建设备文件。内核给我们提供了相关的API

class_create(owner,name);
struct device *device_create_vargs(struct class *cls, struct device 
*parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs);

void class_destroy(struct class *cls);   
void device_destroy(struct class *cls, dev_t devt);
登录后复制

有了这几个函数,我们就可以在设备的**xxx_init()xxx_exit()**中分别填写以下的代码就可以实现自动的创建删除设备文件

    /* 在/sys中导出设备类信息 */
    cls = class_create(THIS_MODULE,DEV_NAME);

    /* 在cls指向的类中创建一组(个)设备文件 */
    for(i= minor;i"%s%d",DEV_NAME,i);
    }  
    /* 在cls指向的类中删除一组(个)设备文件 */
    for(i= minor;i
登录后复制

通过本文,我们了解了Linux字符设备驱动框架的原理和方法,它们可以用来实现对字符设备的驱动和控制。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如使用静态或动态分配的设备号,使用标准或自定义的设备文件名,使用正确或简化的cdev初始化方式等。字符设备驱动框架是Linux驱动开发中最重要的一部分,它可以实现对字符设备的读写操作,也可以提升驱动程序的可移植性和可维护性。希望本文能够对你有所帮助和启发。

以上是Linux字符设备驱动框架:原理与方法的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

vscode需要什么电脑配置 vscode需要什么电脑配置 Apr 15, 2025 pm 09:48 PM

VS Code 系统要求:操作系统:Windows 10 及以上、macOS 10.12 及以上、Linux 发行版处理器:最低 1.6 GHz,推荐 2.0 GHz 及以上内存:最低 512 MB,推荐 4 GB 及以上存储空间:最低 250 MB,推荐 1 GB 及以上其他要求:稳定网络连接,Xorg/Wayland(Linux)

notepad怎么运行java代码 notepad怎么运行java代码 Apr 16, 2025 pm 07:39 PM

虽然 Notepad 无法直接运行 Java 代码,但可以通过借助其他工具实现:使用命令行编译器 (javac) 编译代码,生成字节码文件 (filename.class)。使用 Java 解释器 (java) 解释字节码,执行代码并输出结果。

vscode 无法安装扩展 vscode 无法安装扩展 Apr 15, 2025 pm 07:18 PM

VS Code扩展安装失败的原因可能包括:网络不稳定、权限不足、系统兼容性问题、VS Code版本过旧、杀毒软件或防火墙干扰。通过检查网络连接、权限、日志文件、更新VS Code、禁用安全软件以及重启VS Code或计算机,可以逐步排查和解决问题。

Linux体系结构:揭示5个基本组件 Linux体系结构:揭示5个基本组件 Apr 20, 2025 am 12:04 AM

Linux系统的五个基本组件是:1.内核,2.系统库,3.系统实用程序,4.图形用户界面,5.应用程序。内核管理硬件资源,系统库提供预编译函数,系统实用程序用于系统管理,GUI提供可视化交互,应用程序利用这些组件实现功能。

VSCode怎么用 VSCode怎么用 Apr 15, 2025 pm 11:21 PM

Visual Studio Code (VSCode) 是一款跨平台、开源且免费的代码编辑器,由微软开发。它以轻量、可扩展性和对众多编程语言的支持而著称。要安装 VSCode,请访问官方网站下载并运行安装程序。使用 VSCode 时,可以创建新项目、编辑代码、调试代码、导航项目、扩展 VSCode 和管理设置。VSCode 适用于 Windows、macOS 和 Linux,支持多种编程语言,并通过 Marketplace 提供各种扩展。它的优势包括轻量、可扩展性、广泛的语言支持、丰富的功能和版

vscode 可以用于 mac 吗 vscode 可以用于 mac 吗 Apr 15, 2025 pm 07:36 PM

VS Code 可以在 Mac 上使用。它具有强大的扩展功能、Git 集成、终端和调试器,同时还提供了丰富的设置选项。但是,对于特别大型项目或专业性较强的开发,VS Code 可能会有性能或功能限制。

git怎么查看仓库地址 git怎么查看仓库地址 Apr 17, 2025 pm 01:54 PM

要查看 Git 仓库地址,请执行以下步骤:1. 打开命令行并导航到仓库目录;2. 运行 "git remote -v" 命令;3. 查看输出中的仓库名称及其相应的地址。

vscode是什么 vscode是干什么用的 vscode是什么 vscode是干什么用的 Apr 15, 2025 pm 06:45 PM

VS Code 全称 Visual Studio Code,是一个由微软开发的免费开源跨平台代码编辑器和开发环境。它支持广泛的编程语言,提供语法高亮、代码自动补全、代码片段和智能提示等功能以提高开发效率。通过丰富的扩展生态系统,用户可以针对特定需求和语言添加扩展程序,例如调试器、代码格式化工具和 Git 集成。VS Code 还包含直观的调试器,有助于快速查找和解决代码中的 bug。

See all articles