本篇基于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.   设备布局

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

数据结构如下,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

linux字符设备开发相关推荐

  1. Linux字符驱动设备开发

    一.基础知识 参考博客:18 linux字符设备驱动之设备号_jklinux的博客-CSDN博客 创建设备驱动的目的,通常是让用户程序来调用.一般我们使用字符设备文件来提供接口,使用户进程可以访问操作 ...

  2. ()shi linux字符设备,Linux字符设备驱动基础(三)

    Linux字符设备驱动基础(三) 6 创建设备节点 6.1 手动创建设备节点 查看申请的设备名及主设备号: cat /proc/devices # cat /proc/devices Characte ...

  3. linux设备模型 字符设备,Linux 字符设备驱动模型之框架解说

    一.软件操作硬件设备模型 在进行嵌入式开发的过程中,在常做的事情就是驱动配置硬件设 备,然后根据功能需求使用硬件设备,实现功能的逻辑.如下图为其 相互之间的关系. 如上图所示: 驱动程序:主要作为操作 ...

  4. Linux字符界面操作进阶

    字符界面最重要的内容当然还是要记住各种命令了!下面我们来介绍一个字符界面下各种常用的操作命令. 常用的文本目录操作命令: ls显示文件和目录 touch创建一个空文件或改变文件的创建时间 cp复制文件 ...

  5. Linux字符驱动开发学习总结

    linux驱动编写(虚拟字符设备编写) 昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备的编写内容,今天我们就可以了解一下相关方面的内容,并且用一个实例来说明在linux上面设备是如何编写的. ...

  6. linux内核定义注册设备,linux字符型设备驱动 一.注册设备并创建设备文件

    1.字符设备 字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系 Linux内核中: a -- 使用cdev结构体来描述字符设备; b -- 通过其成员dev_t来定义设备号(分为主.次设 ...

  7. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

  8. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

  9. linux字符设备驱动的 ioctl 幻数

    在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...

最新文章

  1. Docker系列 一. CentOS上安装Docker
  2. 终于!这个强大的「开源图像识别系统」上线了!
  3. Node.Js 学习随笔2
  4. JAVA知识总结目录
  5. 挑战微信小程序?支付宝小程序可以申请公测啦
  6. django源码简析——后台程序入口
  7. 改善深层神经网络:超参数调整、正则化以及优化——2.3指数加权平均
  8. jQuery源码分析研究学习笔记-jQuery.clean()(七)
  9. 谁能主宰服务器市场?Linux 之父和 Redis 之父有分歧了
  10. [转]Go语言(golang)开源项目大全
  11. Linux ab压力测试工具安装教程
  12. ARM体系结构与编程(一)
  13. UEFI 启动 win7
  14. pcb板led正负极图形_晶体二极管正负极判断_晶体二极管图形符号
  15. 【Java】P1957 口算练习题—(洛谷OJ)
  16. NVIDIA显卡型号有哪些?怎么知道自己电脑的型号?
  17. java读取word2010_Java 添加、读取、删除Word脚注/尾注
  18. Java学生成绩处理
  19. 优课联盟 实境英语 Test for Unit 9
  20. 抖音3d照片怎么制作html,抖音3D卡通小人是什么app软件 怎么制作方法介绍

热门文章

  1. linux【报错】userdel: user xiaoming is currently used by process 4713解决
  2. curl和wget的区别和使用
  3. 挑战JavaScript正则表达式每日两题(1)
  4. 详测 Generics Collections TQueue (3): OnNotify、Extract
  5. RDIFramework.NET 中多表关联查询分页实例
  6. 设置Linux网络的方法
  7. linux之find命令,Linux基础知识之find命令详解
  8. mysql修改密码后无法启动_mysql服务无法启动与强制修改密码指南-Go语言中文社区...
  9. ConcurrentHashMap的源码分析-put方法第四个阶段
  10. Semaphore源码分析