Linux驱动编程 step-by-step (六) 用户地址检测 简单模块调试 以及一些杂项
用户地址检测 简单模块调试 以及一些杂项
检测用户空间地址的有效性
上一节中提到在read write时候要检测用户空间传递的参数地址是否是有效地址,有的内核函数会自行检测,但是在调用轻量级的内核函数时候,就可能不去检测用户空间的地址是否有效,如果此时用户无传递一个无效地址,而内核函数去操作了它,这时棘手的问题出现了,轻则内核oops 关机重启就OK了,在特别严重的情况下,可能你的系统就崩溃了(我又遇到过),所以,我们在驱动程序中操作用户空间地址时候要小心加小心。如果电脑配置可以就在虚拟机中玩, 或者在开发板上试,当然这边的测试代码我都有试过,不至于让你系统崩溃的。
如何检测呢?
调用一个access_ok函数去检测
- #define access_ok(type,addr,size)
type 标识读写操作VERIFY_READ表示地址可读,VERIFY_WRITE表示地址可写
addr 用户传入的地址
size 读写的长度
此代码在有内存管理的芯片与无内存管理之间有区别
我们 看一段内核代码 (path : arch/arm/include/asm/uaccess.h)
- #define access_ok(type,addr,size) (__range_ok(addr,size) == 0)
- #ifdef CONFIG_MMU
- ...
- #define __range_ok(addr,size) ({ \
- unsigned longflag, roksum; \
- __chk_user_ptr(addr); \
- __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0"\
- : "=&r"(flag),"=&r"(roksum) \
- : "r"(addr),"Ir"(size),"0"(current_thread_info()->addr_limit) \
- : "cc"); \
- flag; })
- #else
- ...
- #define __range_ok(addr,size)<span style="white-space:pre"> </span>(0)
即在有内存管理并配置了内存管理的芯片内调用次函数会执行检测操作,而在没有配置内存管理的芯片中此函数总是返回真,而做驱动的不应该做这些假设,所以传入的参数在有必要的情况下还是要自行检测再看看copy_to_user函数
- staticinlineunsignedlong__must_check copy_to_user(void__user *to,constvoid*from, unsignedlongn)
- {
- if(access_ok(VERIFY_WRITE, to, n))
- n = __copy_to_user(to, from, n);
- returnn;
- }
可以看到他在函数内部做了这种检测
而当我们调用
- __copy_to_user
- __copy_from_user
- get_user
- __get_user
- put_user
- __put_user
时都需要检测用户地址是否可用
简单模块调试技术
为什么要加简单呢? 因为这边只介绍了用打印来调试程序。
看了LDD3上边介绍的很多调试技术 查询调试 观察调试之类
我觉得 打印调试来的最简单最直接 虽然他有一些限制
1、大量的使用printk会使系统变慢
2、没次打印一行都会引起磁盘操作
...
在printk中有7中 消息的选项 表示着不同的消息等级
KERN_GMERG<0> | 用于紧急消息, 常常是那些崩溃前的消息. |
KERN_ALERT<1> | 需要立刻动作的情形. |
KERN_CRIT<2> | 严重情况, 常常与严重的硬件或者软件失效有关. |
KERN_ERR<3> | 用来报告错误情况; 设备驱动常常使用 来报告硬件故障. |
KERN_WARNING<4> | 有问题的情况的警告, 这些情况自己不会引起系统的严重问题 |
KERN_NOTICE<5> | 正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告. |
KERN_INFO<6> | 信息型消息. 比如 :打印它们发现的硬件的信息. |
KERN_DEBUG<7> | 用作调试消息. |
内核中定义了 DEFAULT_MESSAGE_LOGLEVEL (在printk.c中)默认数值小于它的消息类型才会被答应到终端,我们可以把他设置为8则所有的信息都会被终端打印出来。
在系统中我们 可以使用 echo 8 > /proc/sys/kernel/printk 来调整这个数值(要root权限) 使信息全部被打印出来。
当然我们 也可以通过dmesg来查看所有的打印信息 (有一点不适用,就是当系统出现oops的时候 就不行了 因为你已经死机了 也就输不了这个命令 就看不到打印信息了) 。
- #if SIMPLE_DEBUG
- #define D(...) printk(KERN_DEBUG __VA_ARGS__)
- #define WAR(...) printk(KERN_WARNING __VA_ARGS__)
- #else
- #define D(...) ((void)0)
- #define WAR(...) ((void)0)
- #endif
在需要调试的时候,我们去定义SIMPLE_DEBUG这个宏,在驱动代码测试都OK可以发行时候,去掉这个定义。
在需要打印的地方我们就使用
- D(“print the logintfunc:%s line:%d”, __func__ ,__LINE__);
当然要修改成有意义的debug信息
打印当前进程信息
内核模块不像应用程序一样顺序执行,只用应用进程调用到想关联的函数才会到内核模块中call这个接口,那可不可以 打印调用进程的信息呢?
答案是肯定的,linux中定义了 current 这个变量(<linux/sched.h>)current指向了当前的进程,他是一个 task_struct 类型
其中有两个重要的成员
comm 表示了 当前的命令名名
pid 表示了当前进程号
- D("[process: %s] [pid: %d] xxx\n", current->comm, current->pid);
内核的container_of函数
在写字符设备驱动时候我们都会去自定义一个结构,其中包含了cdev结构
- structsimple_dev{
- char*data;
- loff_t count;
- structcdev cdev;
- structsemaphore semp;
- };
在open方法中除了上一节看到的那样用一个次设备好来获取结构(有时候会出错 如果我们次设备号不是从0开始分配)
所以需要寻求一种安全的方法
container_of为我们提供了这么一个很好的接口
- /**
- * container_of - cast a member of a structure out to the containing structure
- * @ptr: the pointer to the member.
- * @type: the type of the container struct this is embedded in.
- * @member: the name of the member within the struct.
- *
- */
- #define container_of(ptr, type, member)
再解释一下
ptr是指结构体一个成员的地址
type 指要获得的结构体
member ptr指向的成员在结构体中的名字
- /*container_of(pointer, container_type, container_fild)
- we can get the container_type with one number in the container
- by this function. container_fild is the one of the number in the
- container_type , pointer point to confain_field type
- */
- temp_dev = container_of(inodp->i_cdev, structsimple_dev, cdev);
上边的E文 是我写的 poor English
container_of 的实现基本原理是这样的:
知道了结构体中某个成员的地址, 又可以求的该成员在改结构体中得偏移,拿成员的地址减去这个偏移,就得到了整个结构的地址,太佩服写内核的人了
- #define container_of(ptr, type, member) ({ \
- consttypeof(((type *)0)->member) * __mptr = (ptr); \
- (type *)((char*)__mptr - offsetof(type, member)); })
Linux驱动编程 step-by-step (六) 用户地址检测 简单模块调试 以及一些杂项相关推荐
- Linux驱动编程 step-by-step
第三次看了LDD3了(虽然现在已经是kernel3.0但从这本书商还是能学到很多) 每次都有一些收获 现在终于能够写一写代码了 驱动程序的作用: 简单来说 驱动程序就是使计算机与设备通信的特殊的代码, ...
- Linux驱动编程 step-by-step (二) 简单字符设备驱动
简单字符设备驱动 1.主次设备号 主设备号标识设备连接的的驱动,此设备好由内核使用,标识在相应驱动下得对应的设备 在linux中设备号是一个32位的dev_t类型 typedef __u32 _ ...
- Linux驱动编程 step-by-step (二)
简单字符设备驱动 1.主次设备号 主设备号标识设备连接的的驱动,此设备好由内核使用,标识在相应驱动下得对应的设备 在linux中设备号是一个32位的dev_t类型 typedef __u32 _ ...
- linux驱动编程——ch340x驱动移植
Linux驱动编程--ch340x驱动移植 主要概念: ch340x驱动移植 作为通用器件,厂商都有提供适配各种平台的驱动.linux一般会提供驱动源码. 一般所谓驱动移植,就是将厂商的驱 ...
- Linux驱动编程视频教程
本视频教程主要介绍字符驱动.杂项设备.中断.调试驱动的基本方法以及驱动的移植等. Linux驱动编程视频教程: 链接:https://pan.baidu.com/s/1Yn5d4w9uudb4tGDT ...
- Linux驱动编程(驱动程序基石)(上)
一.休眠与唤醒 要休眠的线程,放在 wq 队列里,中断处理函数从 wq 队列里把它取出来唤醒.所以,我们要做这几件事: ① 初始化 wq 队列 ② 在驱动的 read 函数中,调用 wait_even ...
- Linux驱动编程 step-by-step (五)主要的文件操作方法实现
主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作 [cpp] view plaincopy struct file_opera ...
- linux驱动read函数 copytouser,Linux驱动编程 step-by-step (五)主要的文件操作方法实现...
主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作 structfile_operations { ... loff_t (*l ...
- Linux驱动编程(驱动程序基石)(下)
一.中断的线程化处理 复杂.耗时的事情,尽量使用内核线程来处理.上节视频介绍的工作队列用起来挺简单,但是它有一个缺点:工作队列中有多个 work,前一个 work 没处理完会影响后面的 work.解决 ...
最新文章
- webstorm的使用技巧——1
- 玲珑杯 ACM Round #10
- How does Spring @Transactional Really Work?--转
- 标题栏外区域拖动窗体
- 在一起 10 年了,这封信你一定要收下
- SAP SD信用控制管理
- vmware, failed to lock the file 的解决
- HDU 2504 又见GCD
- UVA 12501 Bulky process of bulk reduction ——(线段树成段更新)
- ajax上传文件报404_FTP上传文件需要注意哪些事项
- ajax请求携带tooken_Spring Boot+Vue 文件上传,如何携带令牌信息?
- PyTorch 1.2 中文文档校对活动 | ApacheCN
- MySQL回放_mysql回顾
- c语言有2维结构体没,c语言结构体说明
- springmvc源码阅读3--dispatcherServlet reqeust的执行流程
- Android开发笔记(四十五)手势事件
- 图片复印如何去除黑底_如何处理打印出来的图片很黑的情况
- 服务器怎么架设为虚拟主机,架设服务器虚拟主机教程
- 第四章 原子结构和波粒二象性
- word文档通配符换行_Word怎么批量删除分隔符
热门文章
- boost::pfr::tuple_size相关的测试程序
- boost::mpi::wait_any相关用法的测试程序
- boost::log模块实现从设置文件初始化库的示例,具有自定义过滤器和格式化程序工厂的属性
- boost::hana::fold_right用法的测试程序
- boost::graph模块演示 GGCL Vertex 接口
- boost::fusion::transform_view用法的测试程序
- boost::filesystem模块实现为错误报告测试用例提供了一个模板
- GDCM:gdcm::String的测试程序
- ITK:打印顶点邻居
- VTK:PolyData之WarpVector