linux字符设备开发

简介:


本篇基于ldd3中第三章,原书自带的源码随着内核版本更新已经不能运行,代码需要进行升级,文章参考代码能在内核版本4.17.2运行。

1.   分配设备编号

建立一个字符驱动时,需要做的第一件事是获取一个或多个设备编号来使用.此目的必要的函数是 register_chrdev_region.

注册字符设备函数执行后会出现在/proc/devices和sysfs中:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

  first是要分配的起始设备编号. first 的次编号部分常常是 0, 但是没有要求是那个效果. count 是请求的连续设备编号的总数. 注意, 如果 count 太大,要求的范围可能溢出到下一个次编号; 但是只要要求的编号范围可用, 一切都仍然会正确工作. name 是应当连接到这个编号范围 的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.

  一些主设备编号是静态分派给最普通的设备的. 一个这些设备的列表在内核源码树的 Documentation/devices.txt 中.

  对于新驱动,建议使用动态分配来获取你的主设备编号, 而不是随机选取一个当前空闲的编号.使用 alloc_chrdev_region, 不是 register_chrdev_region.

这个alloc_chrdev_region是动态分配主设备号的,因为你可能不知道系统中哪些主设备号可以给你的驱动程序使用,动态分配的一个缺点就是不能提前分配设备节点(注:通过MKNOD来创建节点的):

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

            两个函数从名字上也可以区分,一个是注册,表示我有了主设备号只是想内核注册,而alloc_则是分配,意味着让内核来帮着allocate一下。

            注册对应的是注销,这个是驱动程序卸载时候必须做的,从哪里来还那里去,对应的函数是:

void unregister_chrdev_region(dev_t from, unsigned count)

另外,获取设备主设备号和次设备号,使用如下宏:

MAJOR(dev_t dev);

MINOR(dev_t dev);

将主、次设备号转换成一个设备号,如下:

MKDEV(int major, int minor);

2.   基础性的驱动操作

基础性的驱动操作包括 3 个重要的内核数 据结构, 称为 file_operations, file, 和 inode.

2.1     file_operation

  传统上, 一个 file_operation 结构或者其一个指针称为 fops( 或者它的一些变体). 结构中的每个成员 必须指向驱动中的函数, 这些函数实现一个特别的操作

            其中定义的操作函数并不是需要全部实现,根据具体驱动实现针对的函数功能即可。字符设备主要有一下函数需要实现:

owner,llseek,read,write,ioctl,open,release.

2.2     file

file结构表示一个打开的文件。在内核中指向file的指针经常叫做filp,就是file pointer,以免是file搞混。

2.3     inode

inode结构由内核在内部用来表示文件,代表打开文件描述符的文件结构是不同的。多个打开的描述符可能指向一个单个inode结构。

相对于字符设备驱动程序,我们先使用i_rdev和i_cdev。

dev_t i_rdev;//实际设备的节点

Struct cdev *i_cdev; //指向字符设备驱动程序指针

            现在可以通过宏如下,来获取节点的主、次设备号:

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

3.   字符设备注册

内核中使用cdev结构体来表示字符设备。可以通过cdev_alloc来分配。

然后使用cdev_init来初始化。

       我们可以将cdev结构体嵌入到我们自己的设备结构体中,这也正是例子所使用的方法。

            最后告诉内核添加进去,如果不告诉内核就是空有一身资源而无施展之处,通过函数cdev_add。一旦添加,那么内核就可能来骚扰设备,所以要确保所有都准备好的时候调用cdev_add函数。

            去除设备调用函数cdev_del.

4.   设备布局

设备由内存来模拟,其设备中的布局如下图。

916656980f6eb58162ed555062a5ab990640a66a

数据结构如下,scull_qset结构体非常简单,其实现一个链表的同时,每个元素同时指向块内存:

struct scull_qset {

        void **data;

        struct scull_qset *next;

};

设备的结构体如下:

struct scull_dev {

        struct scull_qset *data;  /* Pointer to first quantum set */

        int quantum;              /* the current quantum size */

        int qset;                 /* the current array size */

        unsigned long size;       /* amount of data stored here */

        unsigned int access_key;  /* used by sculluid and scullpriv */

        struct semaphore sem;     /* 互斥所*/

        struct cdev cdev;         /* Char device structure              */

};

设备的大小为quantum*qset。

 

5.   代码解析

5.1     初始化

初始化函数为scull_init_module

如果指定了主设备号,调用register_chrdev_region否则调用alloc_chrdev_region,并获取主设备号。

然后分配设备scull_dev结构体数组scull_devices,数量为SCULL_NR_DEVS子设备号数量,并初始化为0。接着根据需要分配的内存空间大小正式初始化scull_devices,其中会调用scull_setup_cdev函数(该函数中会使用cdev_init,cdev_add函数,初始化设备结构中嵌入的cdev,同时绑定scull_fops),scull_fops结构如下。

struct file_operations scull_fops = {

        .owner =    THIS_MODULE,

        .llseek =   scull_llseek,

        .read =     scull_read,

        .write =    scull_write,

        .unlocked_ioctl = scull_ioctl,

        .open =     scull_open,

        .release =  scull_release,

};

            然后调用

初始化时候分配了指定数量的设备数据结构,并初始化后增加到了内核,可以在/proc/devices中看到,此时其实并没有分配设备的内存空间,因为还不需要。

 

5.2     退出

退出函数是scull_cleanup_module,该函数先获取设备号。然后根据设备数量循环调用scull_trim,cdev_del函数来删除cdev设备,最后调用kfree释放在初始化中分配的结构体数据。

            其中scull_trim函数负责释放分配的内存空间。

5.3     操作函数集合

驱动的open函数

通过inode获取设备结构体的指针,这个通过内核中的container_of函数来实现,并将其保存到文件对象的private_data中以备后用。

如果是写模式打开,则将之前该设备分配的空间使用scull_trim函数清空。

 

llseek

返回当前读写位置。

 

release

直接返回0,并不做操作。

read

先从filp->private_data中获取设备地址。获取设备总空间大小。

如果要读的位置大于空间大小则退出。否则计算要读取的正确位置,因为每个scull_qset结构体指向的item空间大小是固定的,其相互之间是链表方式连接的。

然后会调用scull_follow函数,该函数中会通过kmalloc函数动态分配scull_qset结构体(如果没有被分配过),直到包含的item累计理论空间能包含要读取的地址。然后返回最后一个scull_qset结构体,如果返回null,说明系统内存空间不够了。

            最后调用copy_to_user函数,将内容复制到用户的buf中。然后更新文件读取位置,并返回所读取字节大小。

write

获取设备结构体的指针,以及对应的设备空间相关大小。如item,quantum。

计算文件读取位置,调用scull_follow,返回位置所在的那个item的scull_qset结构体,如果该结构体对应的数据指针为NULL,说明之前没有给其分配内存空间,则调用kmalloc分配qset指向quantum的指针数数组。

然后根据读取位置,通过函数kmalloc函数分配quantum的内存空间。

最后调用用copy_from_user函数将数据复制到内存中的quantum片段。

 

unlocked_ioctl

对ioctl的实现。

6.   使用测试

加载驱动后,执行如下,其中247是主设备号,在/proc/devices中可以查看到:

mknod /dev/scull0 c 247 0

mknod /dev/scull1 c 247 1

mknod /dev/scull2 c 247 2

mknod /dev/scull3 c 247 3

然后可以使用dd命令或者cp命令复制内容到设备中。

# echo "hello" > scull0

# cat scull0

hello

7.   代码

https://github.com/kernel-z/ldd3/tree/master/scull

 

 

 

 

 

目录
相关文章
|
4月前
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
248 32
|
2月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
96 0
|
6月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
1118 77
|
4月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
131 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
5月前
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化、简单化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权内容与执行内容,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行细节工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
5月前
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
6月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
11月前
|
Linux 开发工具 Perl
Linux命令替换目录下所有文件里有"\n"的字符为""如何操作?
【10月更文挑战第20天】Linux命令替换目录下所有文件里有"\n"的字符为""如何操作?
183 4
|
11月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
345 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】