zoukankan      html  css  js  c++  java
  • linux驱动开发框架

    不管是Windows还是Linux,驱动程序都扮演着重要的角色。应用程序只能通过驱动程序才能同硬件设备或系统内核通讯。Linux内核对不同的系统定义了标准的接口(API),应用程序就是通过这些标准的接口来操作内核和硬件。驱动可以被编译的内核中(build-in),也可以做为内核模块(Module)存在于内核的外面,需要的时候动态插入到内核中运行。

    就像你学习操作系统概念时所了解的那样,Linux内核也分为几个大的部分:进程管理、内存管理、文件系统、设备控制、网络系统等,参考图1-1。

    linux驱动开发框架

    这里就不对Linux系统内核的各个部分做过多的介绍了,在后面的学习中你就会逐渐地对这些概念有个更深入的了解。其实Linux内核的精髓远不止这些,对于一个Linux内核的爱好者或开发者来说,最好详细的浏览内核源代码,订阅Linux内核相关的邮件列表,或是登陆Linux开发社区。更多的信息,请登陆Linux内核官方网站:http://www.kernel.org

    一个简单的驱动

    下面我们来编写第一个驱动程序,它很简单,在运行时会输出‘Hello World’消息。

    // hello.c
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>

    static int __init hello_init(void)
    {
    printk(KERN_ALERT "Hello World!n");
    return 0;
    }

    static void __exit hello_exit(void)
    {
    printk(KERN_ALERT "Goodbye World!n");
    }

    module_init(hello_init);
    module_exit(hello_exit);

    MODULE_LICENSE("GPL");

    这就是一个简单的驱动程序,它什么也没做,仅仅是输出一些信息,不过对于我们来说这已经足够了。保存这个程序,命名为hello.c。在写一个Makefile文件用来编译它,Makefile和hello.c文件保存在同一个目录下。

    ##Makefile
    ifneq ($(KERNELRELEASE),)
    MODULE_NAME = helloworld
    $(MODULE_NAME)-objs := hello.o
    obj-m := $(MODULE_NAME).o
    else
    KERNEL_DIR = /lib/modules/`uname -r`/build
    MODULEDIR := $(shell pwd)

    .PHONY: modules
    default: modules

    modules:
    make -C $(KERNEL_DIR) M=$(MODULEDIR) modules

    clean distclean:
    rm -f *.o *.mod.c .*.*.cmd *.ko
    rm -rf .tmp_versions
    endif

    编译并运行这个模块:

    //需要root权限来运行
    make

    insmod helloworld.ko
    rmmod helloworld.ko

    尽管我们对它的一些细节还不够了解,它确实神奇的工作了,这个Hello World信息输出到了屏幕终端上(不是VT),www.britepic.org或者系统的Kenrel log里(/var/log/messages),你可以通过运行dmesg来看到这些信息。

    驱动基础
    我们通过分析上面的代码来了解一个驱动程序的基本概念。

    头文件
    就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>

    init.h 定义了驱动的初始化和退出相关的函数,
    kernel.h 定义了经常用到的函数原型及宏定义
    module.h 定义了内核模块相关的函数、变量及宏。
    初始化
    任何一个驱动都去需要提供一个初始化函数,当驱动加载到内核中时,这个初始化函数就会被自动执行,初始化的函数原型定义如下:

    typedef int (*initcall_t)(void);

    驱动程序是通过module_init宏来声明初始化函数的:

    static int __init hello_init(void)
    {
    printk(KERN_ALERT "Hello World!n");
    return 0;
    }
    module_init(hello_init);

    __init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,这样当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。
    初始化函数是有返回值的,只有在初始化成功是才返回0,否则返回错误码(errno)。


    卸载
    如果驱动程序编译成模块(动态加载)模式,那么它需要一个清理函数。当移除一个内核模块时这个函数被调用执行清理工作。清理函数的函数原型定义为:

    typedef void (*exitcall_t)(void);

    驱动程序是通过module_exit宏来声明清理函数的:

    static void __exit hello_exit(void)
    {
    printk(KERN_ALERT "Goodbye World!n");
    }
    module_exit(hello_exit);

    同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作。显然,__init和__exit对动态加载的模块是无效的。


    版权信息
    Linux内核是按照GPL发布的,同样Linux的驱动程序也要提供版权信息,否则当加载到内核中是系统会给出警告信息。Hello World例子中的版权信息是GPL。

    MODULE_LICENSE("GPL");

    后续
    这里你了解了一个驱动程序的基本框架,所有的驱动都会包含这些内容。这里我们没有对Linux 驱动程序的编译系统做详细的介绍,因为它相对C应用程序的编译有些复杂。Linux2.6内核采用Kbuild系统做编译,下一章你会了解到Kbuild的详细内容。

  • 相关阅读:
    How does a single thread run on multiple cores?
    一篇所有研究生都该读的好文:阳光温热 科研静好
    Running Slic3r from git on GNU Linux
    3D打印的各种问题及解决方案
    新工科的新视角:面向可持续竞争力的敏捷教学体系
    What is Modularity
    3d打印模型为什么文件格式必须是stl和stp的?
    剑指offer 39.平衡二叉树
    剑指offer 38.二叉树的深度
    剑指offer 37.数字在排序数组中出现的次数
  • 原文地址:https://www.cnblogs.com/spinsoft/p/2489581.html
Copyright © 2011-2022 走看看