Linux驱动小结

驱动中常用到的等待输入方法:

(1)查询方式
占用cpu资源庞大

(2)等待队列(wait queue):
驱动中的read主动通过wait_event_interruptible()让当前进程状态变为STATE_INTERRUPTIBLE后使用schedule()重新进行任务调度达到阻塞等待的目的;中断事件中通过wakeup_interruptible()重新让休眠的进程变为STATE_RUNNING,达到唤醒的目的。

(3) polling:
应用程序调用poll后将调用内核中的sys_poll,sys_poll的操作包括两方面:1.先调用驱动程序的poll方法,里面的poll_wait将当前进程注册到等待队列中;2.随后调用schedule_timeout()让当前进程放弃占用CPU;
polling可以被以下条件唤醒:1.wakeup_interruptible强行将队列中的进程置位STATE_RUNNING还原运行,但是被唤醒之后条件不成立将因为for(;;)的关系重新进入休眠;2.超时退出;3.系统信号待处理

(4)异步IO:
通过异步读写接口aio_write和aio_read,当读写操作完成,会自动调用异步控制块struct aiocb中的完成回调,通知应用程序读写结果。

(5)异步通知:
1.应用程序通过signal注册一个接收到信号之后读取有效数据的函数;
2.用fcntl()调用驱动的fasync中的fasync_helper完成异步信号初始化,告诉驱动可往该进程发送约定信号;
3.驱动中断通过kill_fasync发出信号,收到异步信号的应用程序被中断后开始signal处理,这种方法在异步信号到达之前,应用程序保持非阻塞状态。



用的比较多的驱动设计模式:

面向对象四个基本特征:抽象、封装、继承、多态。
总线型:

(充分体现了面向对象的封装性)
(1)虚拟总线驱动
将设备驱动分层分成两部分:
①platform_driver部分,负责管理驱动具体操作;
②platform_device部分,负责管理IO、MEM、IRQ等资源
调用注册函数注册device或者driver时,将遍历另一方的链表,如果双方name属性匹配,驱动的probe函数会被执行,驱动中可以通过get_resource获取平台设备硬件资源.

(2)USB
USB总线下面可以根据接口个数,通过usb_add_hcd创建多个主控器,各主控器完成SetConfiguration之前枚举工作,包括:
①获取8B设备描述符
②SetAddress
③获取18B完整设备描述符
④获取9B配置描述符
⑤获取32B配置描述符(包括配置描述符,接口描述符,端点描述符)
⑥SetConfiguration

当USB设备插入后,主控器完成设备枚举过程,随后调用usb_new_device创建一个设备
用户的设备驱动通过usb_register注册一个驱动实例,当其中的id_table匹配(可以是归属一类设备或者是PID/VID相同),驱动的probe函数将被执行
驱动中,数据交互的对象,是除控制端点之外的端点,通讯操作就是填充一个urb(usb request block),并且通过usb_submit_urb提交请求,这个请求控制块包括数据源、目的、长度,另外还有传输完成后的回调服务函数。

(3)I2C
I2C总线下面根据接口个数可以有若干个适配器(adapter),每个适配器都有一个算法成员,里面有xfer方法,完成数据传输,一般的smbus协议数据过程:
起始信号S(时钟高时拉低数据);
应答ACK(发送方释放SDA,应答方拉低数据,表现为一个窄脉冲) ;
非应答nACK(发送方释放SDA,总线保持高) ;
数据Data;
停止信号T(时钟高时拉高数据)。

读:(S) 设备地址 (ACK) 设备从地址 (ACK) | (S) 设备地址 (Data) (nACK) (T)
写:(S) 设备地址 (ACK) 设备从地址 (ACK) (Data) (ACK) (T)
Linux提供了一套可以直接在用户空间操作adapter的方法,可以直接使用这一套函数与I2C设备通讯。

可以通过i2c_new_probe_device尝试添加一个I2C设备(client),系统先调用默认监探函数,指定适配器发出addr_list中的地址,如果有响应,才调用i2c_new_device创建一个I2C设备
可以通过i2c_add_driver添加驱动,当在设备链表中找到i2c_board_info.type字段与驱动中的id_table字段匹配的设备,probe函数将被调用,随后就是一般字符设备的文件节点read、write操作

一般板级支持文件中可能会有i2c_register_board_info()添加I2C设备(client),作用是往_i2c_board_list这条链表添加一条i2c设备信息,在i2c adapter注册的时候,会扫描__i2c_board_list链表,然后调用i2c_new_device()函数来注册i2c设备。注意,要先于i2c adapter注册之前就添加好i2c设备信息,否则会出现调用了i2c_register_board_info()函数,而设备不能注册的情况。

module_i2c_driver()是对i2c_add_driver的封装。

(4)SPI
(/* TODO */)

驱动封装型

(内核里面已经为该类设备分配了固定主设备号,并创建了设备类,且为这一类设备提供了一套比较通用的class_fops,用户只需要通过系统提供的xxx_device_register就可以完成驱动设备实例的创建,如果这个实例有它私有的fops,将通过class_fops->open完成传递,最后私有的fops被封装在class_fops中,充分体现了面向对象中的抽象和继承性):
(1)输入子系统:
将输入设备的公共部分交给内核处理,硬件设备通过申请中断和注册输入设备,通过input_evetn通知内核搬运数据
input.c创建一个主设备号为13的设备类,这是输入系统的基本框架,由于输入设备还可以细分为鼠标、键盘、游戏摇杆、触摸屏等,他们的操作不尽相同,因此内核中通过input_register_handler注册了若干种操作方法,他们分别占有一段次设备号

当用户需要创建一个输入设备驱动实例的时候,通过调用input_device_register,此时系统根据这个输入设备类型,匹配系统中已经注册好的各个handler,匹配成功就通过connect在handler与device之间创建一个handle,用以方便相互的访问

(2)framebuffer
内核里面做好了fbmem这个设备驱动类,主设备号为29;用户只需要在init函数中初始化好硬件(时钟、LCD控制器硬件初始化),提供一个包含控制器

基本信息的fb_info结构体(屏幕长宽,显存位置),通过调用register_framebuffer既可以注册一个驱动实例。

(3)MiscDevice
比较通用的一种杂项字符设备,主设备号为10,通过misc_register即可以简单完成一个杂项设备的注册,注册入参miscdevice设备结构体里面提供fops与name和minor,由于混杂设备基本没有通用操作,所以都是直接使用miscdevice中的fops的。

(4)RTC:
对rtc抽象出一种设备类(/rtc/class.c),用户使用rtc_device_register,提供时间读写操作函数fops
内核中实现的rtc类主要就是对用户提供的私有读写操作函数进行封装(添加进程读写互斥等)

块设备MTD:

RAM_Block_Device
一般块设备驱动编写流程:通过register_blkdev分配一个主设备号,随后分配、设置、注册一个gendisk,设置操作包括为gendisk提供块设备读写请求队列request_queue,而且这个队列有一个默认的请求执行函数request_fn_proc,同时这个结构体还有磁盘的容量,块操作函数fops,最后通过add_disk即可以完成设备注册。
NandFlash
内核里面已经对nand设备封装的比较好,主需要创建一个nand_chip结构体,设置好其中的片选、判忙、寄存器地址,随后调用scan_nand完成初始化,最后调用add_mtd_partition添加分区。

网络设备NetDevice:

Virtual_Net
通过alloc_netdev分配一个网络设备,设置网络设备的MAC地址还有fops,其中必须有xmit发送函数(可以在网卡中断中实现提交数据包),在最后调用register_netdev注册



其他:

(1)定时器(精度为10ms):入口函数中初始化并且添加定时器,定时器有一个服务函数,需要进行延时的地方mod_timer,时间到达,服务函数被执行

(2)tty1的输入设备是键盘,输出设备是显示器,做lcd驱动的时候,需要
vi drivers/video/samsung/Makefile
注释掉obj-$(CONFIG_FB_S3C_EXT) += s3cfb.o(针对友善的源码,我自行移植的2.6.38内核貌似不用)

(3)ts驱动基本上就是有两个中断函数(adc、pend_updown)的输入字符设备



驱动调试方法:

(1)printk打印到串口,或者自己做一个/proc/mdmesg文件节点记录打印信息
(2)编写一个读写寄存器数值的驱动小工具
(3)使用系统心脏时钟中断,找到僵死程序的pc值与sp值,进行回溯分析
(4)栈回溯,对.ko或者vmlinux进行反汇编分析
最后两个调试方法,需要先定位pc值大概在模块驱动哪个位置,需要系统没有死掉,并且可以查看/proc/kallsyms



应用调试方法:

(1)通过strace跟踪应用程序的系统调用过程
(2)通过应用程序打印的栈回溯分析错误
(3)使用gdb通过网口在线单步调试应用程序,或者使用gdb分析coredump信息
(4)使用自己制作的swi指令替换源程序中的代码,达到软断点的目的
(5)通过proc文件记录输入信息,随后通过读取这个记录还原输入信息进行测试

模块驱动加载

module_init分为静态加载和动态加载
①静态加载:
#define module_init(fn) device_initcall(fn)
静态编译进内核的init函数根据被加载的段域,有不同的执行先后顺序:

#define pure_initcall(fn)              __define_initcall("0",fn,0)

#define core_initcall(fn)              __define_initcall("1",fn,1)

#define core_initcall_sync(fn)    __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)      __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)

#define arch_initcall(fn)              __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)    __define_initcall("3s",fn,3s)

#define subsys_initcall(fn)          __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

#define fs_initcall(fn)                  __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)           __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)         __define_initcall("6",fn,6)

#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)

#define late_initcall(fn)               __define_initcall("7",fn,7)

#define late_initcall_sync(fn)      __define_initcall("7s",fn,7s)

越靠后优先级越低

②动态加载:
以上全部late_initcall和fs_initcall等宏,全部会重定义成module_init

#define module_init(initfn)     \
             static inline initcall_t __inittest(void)  \  /*定义此函数用来检测传入函数的类型,并在编译时提供警告信息*/
              { return initfn; }     \
int init_module(void) __attribute__((alias(#initfn)));  /*声明init_modlue为 initfn的别名,insmod只查找名字为init_module函数并调用*/

在以模块方式编译一个模块的时候,会自动生成一个xxx.mod.c文件, 在该文件里面定义一个struct module变量,并把init函数设置为上面的init_module() ,而上面的这个init_module,被alias成模块的初始化函数。也就是说,模块装载的时候(insmod,modprobe),sys_init_module()系统调用会调用module_init指定的函数。

驱动中的延时

mdelay()、udelay()、ndelay()为阻塞性延时,精度比较高,可以低于10ms;
msleep()由于是任务调度性的延时,所以精度最大只能到系统的心脏节拍频率。

中断

共享中断的嵌套使能和禁能规则:使用desc->depth记录__disable_irq从0开始的调用次数,
后面多次调用__enable_irq对desc->depth逐渐,为1时正式使能,因此在未调用disable的
情况下调用enable会有"Unbalanced enable for IRQ %d\n"警告。

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值