Board logo

标题: 面对不断升级的内核,我们该该如何学习LINUX设备驱动 [打印本页]

作者: zyp2018    时间: 2018-9-29 10:25     标题: 面对不断升级的内核,我们该该如何学习LINUX设备驱动

面对不断升级的linux内核、GNU开发工具、linux环境下的各种图形库,很多linux应用程序开发人员和linux设备驱动开发人员即兴奋,又烦躁。兴奋的是新的软件软件、工具给我提供了更强大的功能,烦躁的是适应新软件的特性、搭建新环境是一项非常繁琐的事情。本文想从以下3个方面探讨一下“面对不断升级的内核,如何学习linux设备驱动”。
内核发展的现状及其对技术人员的影响
    内核的升级对应从事linux应用程序开发的人员来说影响较小,因为系统调用基本保持兼容。而影响比较大的是驱动开发人员。每次内核的更新都可以导致很多内核函数使用上的变化。其中有内核本身提供的函数,也有硬件平台代码提供的函数,后者变化的更加频繁。这一点让初学内核驱动的人很迷茫,因为当他们按照手里的经典著作,如:Alessandro的《linux设备驱动程序》,编写驱动时,发现并不能够成功的在你的linux平台上编译通过、或不能正常执行。你的朋友会告诉你,你用的内核和书里的不一致。那该怎么办呢?
    我想从两个方面去解释这个问题,一方面是如何写好linux设备驱动,另一方面是如何应对不断升级的内核。
如何写好Linux设备驱动
    Linux设备驱动是linux内核的一部分,是用来封装硬件细节,为上层提供标准接口的一种方法。为了能够编写出质量比较高的驱动,要求工程师必须具备以下几个方面的知识:
熟悉处理器的性能

掌握驱动目标的硬件工作原理及通讯协议
掌握硬件的控制方法
   制器会有多种控制方法,你需要根据系统性能的需要合理的选择操作方法。
    此项对于初学者来说:重要程度:****。初学阶段以实现功能为目的。掌握的顺序应该是,轮询->  中断->  DMA。随着学习的深入,需要综合考虑系统的性能需求,采取合适的方法。
良好的GNU C语言编程基础
这些编程语法是编写设备驱动的基础。  此项无论对于初学者还是熟手重要程度:*****。
良好的linux操作系统概念
    此项对于初学者来说:重要程度:***。开始可以以实现功能为目的,逐步完善自己的驱动。
掌握linux内核中设备驱动的编写接口
     Linux内核为设备驱动编写者留下了标准的接口。驱动编写者无需精通内核的各个部分,只需要明确内核留给我们的接口,并实现此接口就可以了。内核流出的接口采用的是面向对象的思路,即把目标设备看成一个对象,通常利用一个结构体来描述这个对象。驱动工程师的任务就是实现这个对象。这个结构体中会包含设备的属性(用变量表示)和操作方法(用函数指针表示)。如:字符设备的cdev
    struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;  //操作方法结合,其它项都是属性
    struct list_head list;
    dev_t dev;
    unsigned int count;
};
    此项对于初学者来说:重要程度:****。开始阶段可以以模仿为主,即套用一些固定的模板。
如何应对不断升级的内核
    内核升级对驱动的影响主要体现在,(1)驱动接口定义的变化(2)内核的一些功能函数的名称、参数、头文件、宏定义的变化(3)平台代码关于硬件操作方面封装的一些函数的变化(4)设备模型的影响。下面探讨一下,如何应对这几个方面的问题:
驱动接口定义的变化
   如:2.4内核中字符设备驱动的注册接口是
        int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)
         而2.6内核中已经不建议使用这种方法了,改为:
        int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    又如:2.6.27内核中网卡接口的net_device结构成员和低版本的net_device结构成员也发生了一些变化。
   这种接口定义及注册方法带来的变化,发生的并不频繁。解决方案是:参考内核中的代码。这种接口定义及注册方法在内核中非常容易找到,如:字符设备驱动的注册方法及接口定义可以参照内核driver/char/目录下的很多实例。
   内核的一些功能函数的名称、参数、头文件、宏定义的变化
    在2.6.8中,中断注册函数的定义为:
        int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),unsigned long irq_flags, const char * devname, void *dev_id)
         irq_flags的取值主要为下面的某一种或组合:
        SA_INTERRUPT、SA_SAMPLE_RANDOM、SA_SHIRQ
        在2.6.26中,中断注册函数的定义为:
        int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
typedef irqreturn_t (*irq_handler_t)(int, void *);
        irq_flags的取值主要为下面的某一种或组合:(功能和2.6.8的对应)
        IRQF_DISABLED、IRQF_SAMPLE_RANDOM、IRQF_SHARED
    当出现这些问题时,编译过程中,编译器会给我们比较明确的错误提示,根据这些提示你可以判断出是否是缺少头问题、是否是函数参数定义有误等。解决问题的最好办法还是到你的目标内核中找信息。此时找问题的方法可以借助于搜索,如:你可以在新的内核中搜索request_irq,看新内核中的驱动是如何使用它的。这种方法非常有效。
平台代码关于硬件操作方面封装的一些函数的变化
    内核中,硬件平台相关的代码在内核更新过程中变化比较频繁。和我们的设备驱动也是息息相关。所以在针对一个新内核编写设备驱动前,一定要熟悉你的平台代码的结构。有时平台虽然提供了内核要求的接口函数,但使用起来功能却并不完善。下面还是先举个例子说明平台代码更新对设备驱动的影响。
        原来并没有实现。而在较高版本的内核,如2.6.26内核中,这个函数是实现了的。所以你一定要小心。当平台函数不好用时,一定要查查原因,或者直接操作硬件寄存器来达到目的。
2.6内核设备模型对驱动的影响
    在2.6内核中写设备驱动和在2.4内核中有着很大的不同,就是在设备驱动中融入了比设备驱动本身结构还复杂,难以理解的设备模型。初学驱动时你可以不理会设备模型,但你会发现内核里的驱动代码基本上都是融入了设备模型的了。所以很多时候你不得不面对现实,还是要弄懂它,并且它也的注册方法也会随着内核的升级而发生变化。解决此类问题的最好方法还是参考目标内核驱动代码。
总结:
    开始学习设备驱动时,选择一个当前比较流行的内核版本和硬件平台。不着急追赶最新潮流。这样你可以找到的网络资源会比较多,不至于有孤军奋战的感觉。我想这个过程应该不低于1年。当过了这个过程后,尝试将你编写过的驱动移植到各个目标平台上。上面的一些建议、和应对方法是本人的一些经验总结,仅供参考。
作者: zyp2018    时间: 2018-9-29 10:26

更多单片机,嵌入式ARM,linux,Java,PCB,FPGA,汇编C,Android课程的学习请咨询周老师QQ1848147256,电话18126759309.




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0