2022年字符设备驱动基本编程借鉴 .pdf

上传人:Che****ry 文档编号:34284329 上传时间:2022-08-15 格式:PDF 页数:12 大小:802.90KB
返回 下载 相关 举报
2022年字符设备驱动基本编程借鉴 .pdf_第1页
第1页 / 共12页
2022年字符设备驱动基本编程借鉴 .pdf_第2页
第2页 / 共12页
点击查看更多>>
资源描述

《2022年字符设备驱动基本编程借鉴 .pdf》由会员分享,可在线阅读,更多相关《2022年字符设备驱动基本编程借鉴 .pdf(12页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、4 月 9 日字符设备驱动基本编程本文将使用内存来虚拟4 个同类型字符设备scull ,并以该字符设备为例来进行字符设备驱动基本编程的讲解。本文可素材和源代码 (做了少量修改) 均来源于Linux Device Driver一书的第 3 版,因此本文可视为对该书相关章节的阅读理解。一、让我们先来体验一下scull设备吧1、下载 scull设备的驱动源码( 单击下载 ),解压后 make ,可得到 scull.ko。将其加载进内核 : insmod scull.ko 2、创建设备节点文件dennisdennis-desktop:/work/studydriver/examples/scull$

2、cat /proc/devices|grep scull 252 scull 252 scullp dennisdennis-desktop:/work/studydriver/examples/scull$ sudo mknod scull0 c 252 0 dennisdennis-desktop:/work/studydriver/examples/scull$ sudo mknod scull1 c 252 1 dennisdennis-desktop:/work/studydriver/examples/scull$ sudo mknod scull2 c 252 2 dennisd

3、ennis-desktop:/work/studydriver/examples/scull$ sudo mknod scull3 c 252 3 dennisdennis-desktop:/work/studydriver/examples/scull$ sudo chmod 666 scull0-3 3、体验 scull设备。向该字符设备写入内容后再将内容读出dennisdennis-desktop:/work/studydriver/examples/scull$ cat scull0 dennisdennis-desktop:/work/studydriver/examples/scu

4、ll$ echo yangzhu scull0 dennisdennis-desktop:/work/studydriver/examples/scull$ cat scull0 yangzhu 二、实现字符设备驱动的工作1、确定主设备号和次设备号什么是主设备 / 次设备号名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 12 页 - - - - - - - - - o主设备号是内核识别一个设备属于哪一个驱动的标识。是一个整数,范围从 0 到(4096-1) ,但是一般使用

5、 1 到 255。o次设备号是驱动程序自己用来区别多个设备的。是一个整数,范围从 0 到(1048576-1) ,但是一般使用 0 到 255。o预定义的设备号:详见Documentation/devices.txt o查看设备号: $ ls l /dev设备编号的内部表示o内核用 32bit表示设备号typedef unsigned long dev_t; o其中高 12bit为主设备号,低20bit 为次设备号。要想获得一个dev_t 的主或者次设备号, 使用内核定义的宏: MAJOR(dev_t dev); 和 MINOR(dev_t dev); #define MINORBITS 20

6、 #define MINORMASK (1U MINORBITS) #define MINOR(dev) (unsigned int) (dev) & MINORMASK) o主次设备号转换为一个 dev_t ,使用内核定义的宏 : MKDEV(int major, int minor);#define MKDEV(ma,mi) (ma) MINORBITS) | (mi) 分配主设备号 / 次设备号的方法和内核API o手工分配设备号:找一个主设备号来使用。 first为第 1 个设备号,count 为请求的设备号数量, name为设备名称(出现在/proc/devices中)。失败返回负数

7、。分配的时机应该在驱动程序的初始化函数函数中。int register_chrdev_region(dev_t first, unsigned int count, char *name);o动态申请主设备号。 dev 存放操作系统动态分配的第1 个设备号,firstminor为请求的第 1 个次设备号, count 为请求的设备号数量,name为设备名称(出现在 /proc/devices中)。失败返回负数。申请的时机应该在驱动程序的初始化函数函数中。int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned i

8、nt count, char *name); 以下是 main.c 中获取主设备号的代码名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 12 页 - - - - - - - - - 44 int scull_major = SCULL_MAJOR;/宏 SCULL_MAJOR在 scull.h中被定义为 0 45 int scull_minor = 0; 688 if (scull_major) 689 dev = MKDEV(scull_major, scull_min

9、or); 690 result = register_chrdev_region(dev, scull_nr_devs, scull); 691 else 692 result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, 693 scull); 694 scull_major = MAJOR(dev); 695 696 if (result cdev, &scull_fops); 669 dev-cdev.owner = THIS_MODULE; 670 dev-cdev.ops = &scull_fops; 671 err

10、= cdev_add (&dev-cdev, devno, 1);672 /* Fail gracefully if need be */ 673 if (err) 674 printk(KERN_NOTICE Error %d adding scull%d, err, index); 675 668 行初始化了 sturct cdev结构体(在不引起混淆的情况下,以后将称其为cdev)的各个字段,其中最重要的是把ops 初始化为了 scull_fops。这样 cdev结构体就与 scull_fops建立了关联的关系。 671 行将 scull设备的设备号 devno(252:0)和 cdev

11、 注册进操作系统(在将cdev 的 dev、ops、count 字段正确填写后,链入操作系统的字符设备链表),这样一来操作系统内部就建立了设备号-cdev-scull_fops三者之间的关联关系。642 cdev_del(&scull_devicesi.cdev); 而要从操作系统中注销字符设备, 只需要执行内核API cdev_del 即可。 它将 cdev从操作系统的字符设备链表中移除。3)、应用程序调用open 打开一个设备时,操作系统干了什么?由于操作系统内部已经建立了设备号-cdev-scull_fops三者之间的关联关系,所以当用户程序调用open(fd, /dev/scull0)

12、打开设备文件的时候,操作系统就可以根据设备文件名得到设备号,再根据设备号找到cdev,进而找到fops ,从而为该设备在内核空间中建立3 张表:文件描述符表 (file descriptor table )、文件表( file table)、i 节点表( i-node table),关于 3 张表的关系和作用,请参见“文件描述符表、文件表、i 节点表关系与作用”一文。i 节点表中含有:i_rdev 字段代表实际的设备号;i_cdev 字段指向字符设备cdev。文件表中含有:f_op 字段指向 fops ;f_pos 字段表示设备的当前读写位置;f_flags字段标识文件打开时是否可读或可写;名

13、师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 12 页 - - - - - - - - - private_data字段指向私有数据指针,驱动程序可以将这个成员用于任何目的或者忽略这个成员。应用程序完成 open 调用后,内核的状况图4)、应用程序调用read(fd, buff)读取设备时,操作系统干了什么?操作系统根据 fd 和文件描述符表找到文件表, 再根据文件表中的 f_op 字段找到fops ,而 fops (scull_fops)存放的函数指针就是对应于操作物理

14、设备(例如read 或 write 等)的各类驱动函数的函数名,这些函数就组成了驱动程序源代码的主体。所以操作系统就可以根据read 在 scull_fops中找到.read(scull_read),进而调用它完成对物理设备的读操作。所以我们写驱动程序很大一部分工作就是要实现这些直接操作设备硬件的函数。613 struct file_operations scull_fops = 614 .owner = THIS_MODULE, 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7

15、页,共 12 页 - - - - - - - - - 615 / .llseek = scull_llseek, 616 .llseek = no_llseek, 617 .read = scull_read, 618 .write = scull_write, 619 .ioctl = scull_ioctl, 620 .open = scull_open, 621 .release = scull_release, 622 ; file_operations的主要字段:struct module *owner:指向模块自身。open:打开设备。release :关闭设备。read:从设备上

16、读数据。write::向设备上写数据。ioctl:操作设备函数。llseek :定位读写指针。mmap :映射设备空间到进程的地址空间。4、实现 open 函数257 int scull_open(struct inode *inode, struct file *filp) 258 259 struct scull_dev *dev; /* device information */ 261 dev = container_of(inode-i_cdev, struct scull_dev, cdev); 265 filp-private_data = dev; /* for other m

17、ethods */ 267 /* now trim to 0 the length of the device if open was write-only */ 268 if ( (filp-f_flags & O_ACCMODE) = O_WRONLY) 275 scull_trim(dev); 281 283 return 0; /* success */ 284 用户程序调用 open 时,操作系统会建立并初始化好前述的3 张表后,再调用驱动程序中的 scull_open函数,传入的参数 inode 是 i 节点表指针, filp是文件表指针。261 行使用内核提供的宏containe

18、r_of,根据字符设备结构体cdev 的地址(inode-i_cdev )推算出驱动程序定义的设备结构体地址(scull_devices),并将其赋予文件表中的private_data字段,以便其它驱动函数将来比较容易地找到驱动程序定义的设备结构体。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 12 页 - - - - - - - - - 268 行根据文件表中的读写标志(由操作系统已经根据用户程序open时指定的标志设置好了该标志),决定是否要清空设备中存放的数据。请

19、思考,如何才能让 echo zhu /dev/scull0能得到预定的结果?出于演示的目的, 所以本驱动中的 open函数比较简单。 其实 open函数需要做的事情很多,总结如下:模块使用计数加1。识别次设备号,如有必要更新 f_op 指针 (这主要用于 misc 类型的设备),以支持一个驱动可以驱动拥有相同主设备号但却属于不同类型的设备。分配并填写置于filp-private_data 里的数据结构。硬件操作:o检查设备相关错误(诸如设备未就绪或类似的硬件问题)。o如果设备是首次打开,则对其初始化。o如果有中断操作,申请中断处理程序5、实现 release 函数用户程序调用 close 时,

20、操作系统一般都会调用驱动程序中的scull_release函数,传入的参数inode 是 i 节点表指针, filp是文件表指针。出于演示的目的,所以本驱动中的release 函数简单到不能再简单了。其实热release 函数需要做很多与open函数逆向的事情,总结如下:模块使用计数减1 释放放由 open 分配的,保存在filp-private_data中的所有内容硬件操作o如果申请了中断,则释放中断处理程序。o在最后一次关闭操作时关闭设备特别说明, release 函数被调用的时机:当文件表被释放时, release 函数被调用当用户程序调用 close ,但文件表并不被释放时(由于用户程

21、序曾调用fork 、dup,从而导致 FILE 结构体引用计数 1),release 函数不会被调用。由此可见, release 函数与 open函数的被调用次数,应该是相等的,等于设备被 open 的次数,但小于或等于被close 的次数6、实现 read 函数323 ssize_t scull_read(struct file *filp, char _user *buf, size_t count, 324 loff_t *f_pos) 325 326 struct scull_dev *dev = filp-private_data; 327 struct scull_qset *dp

22、tr; /* the first listitem */ 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 12 页 - - - - - - - - - 328 int quantum = dev-quantum, qset = dev-qset; 329 int itemsize = quantum * qset; /* how many bytes in the listitem */ 330 int item, s_pos, q_pos, rest; 331 ssiz

23、e_t retval = 0; 349 if (*f_pos = dev-size) 350 goto out; 351 if (*f_pos + count dev-size) 352 count = dev-size - *f_pos; 354 /* find listitem, qset index, and offset in the quantum */ 355 item = (long)*f_pos / itemsize; 356 rest = (long)*f_pos % itemsize; 357 s_pos = rest / quantum; q_pos = rest % q

24、uantum; 359 /* follow the list up to the right position (defined elsewhere) */ 360 dptr = scull_follow(dev, item); 362 if (dptr = NULL | !dptr-data | ! dptr-datas_pos) 363 goto out; /* dont fill holes */ 365 /* read only up to the end of this quantum */ 366 if (count quantum - q_pos) 367 count = qua

25、ntum - q_pos; 369 if (copy_to_user(buf, dptr-datas_pos + q_pos, count) 370 retval = -EFAULT; 371 goto out; 372 373 *f_pos += count; 374 retval = count; 376 out: 383 return retval; 384 当用户程序调用 read 时,操作系统会调用驱动中的scull_read函数。传入的参数 flip指向文件表,buf 、count 是用户程序调用 read 时传入的第 2、3 个参数,内核原封不动地把它们传给了驱动程序, f_po

26、s 指向了文件表中的f_pos 字段 (表示设备的当前读写位置)。326 行从文件表私有数据字段获得scull_device结构体地址以供稍后使用。355 行计算出需要读取的数据位于第几个quantum集(item );357 行计算出了需要读取的数据位于quantum集合中的第几个 quantum(s_pos)以及 quantum中的第几个字节( q_pos)最重要的是 369行调用内核 API copy_to_user 将内核地址 dptr-datas_pos + q_pos 处开始的 count 个字节拷贝到用户空间buf 名师资料总结 - - -精品资料欢迎下载 - - - - - -

27、 - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 12 页 - - - - - - - - - 373 行将文件表中记录设备当前读写位置的字段增加已经读写的字节数,其目的是使下次读取设备时从本次读取的数据后接着读取374、383行驱动程序将已成功读取的数据的总字节数返回给操作系统。操作系统将会把该值原封不动地返回给用户程序,作为read 函数的返回值。可见用户程序调用 read 时得到的返回值是多少, 最终取决于驱动程序实际读取的字节数。关于 copy_to_user 的特别说明:不能用for (i=0; idatas_pos + q

28、_pos)i; 替代 kernel API copy_to_user的原因是内核空间地址与用户空间地址, 在不同的体系结构下的映射关系是不一样的。 也就是说内核的地址buf 与用户空间的地址buf 未必是指同一个物理位置即使在某些体系结构下,其映射关系是一样的,也不能进行替换。因为,恶意用户会通过调用read 时,指定恶意的 buf 地址,来达到破坏内核核心数据结构的目的内核 API copy_to_user会在拷贝前检查目的地址是否是用户程序可写的位置名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - -

29、 - - - 第 11 页,共 12 页 - - - - - - - - - 7、实现 write函数scull_write函数代码在 386-447 行,它的参数和实现都与scull_read函数非常类似。最主要的区别是用copy_from_user 替代了 copy_to_user ,以完成从用户空间向内核地址拷贝数据的工作。8、实现 llseek函数583 loff_t scull_llseek(struct file *filp, loff_t off, int whence) 584 585 struct scull_dev *dev = filp-private_data; 586

30、 loff_t newpos; 588 switch(whence) 589 case 0: /* SEEK_SET */ 590 newpos = off; 591 break; 593 case 1: /* SEEK_CUR */ 594 newpos = filp-f_pos + off; 595 break; 597 case 2: /* SEEK_END */ 598 newpos = dev-size + off; 599 break; 601 default: /* cant happen */ 602 return -EINVAL; 604 605 if (newpos f_pos = newpos; 607 return newpos; 609 用户程序调用 lseek 时,操作系统会最终调用驱动的llseek函数。传入的参数flip指向文件表,off 和 whence是用户程序调用 lseek 时传入的第 2、 3 个参数,内核原封不动地把它们传给了驱动程序。594、606行完成设备读写指针的移动。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 12 页,共 12 页 - - - - - - - - -

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 高考资料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁