【Linux 驱动】第二章 构造和运行模块

一,核心模块与应用程序的对比

        应用程序:小规模及中规模程序,从头到尾执行单个任务。

        核心模块:预先注册自己,以便服务于将来的某个请求。然后他的初始化函数就立即结束。

        退出时候,应用程序可以不释放自己申请的资源,而模块在退出之前必须仔细撤销初始化函数所做的一切。

二,用户空间和内核空间

        模块运行在内核空间,应用程序运行在内核空间。

        每当应用程序执行系统调用或者被硬件中断挂起时,Unix将执行模式从用户空间切换到内核空间。

        应用程序在虚拟内存中布局,并具有一块很大的栈空间(保存函数调用历史以及当前活动函数中的自动变量)。内核具有非常小的栈,所以我们自己的函数必须和整个内核空间调用链一同共享这个栈。

       【注意】在内核API中看到有两个下划线_ _的函数名:接口的底层组件

三,初始化和关闭

         static  int  _ _init  initialization_function(void)

          {

                /*初始化代码*/

                 return <int>;


           }

         module_init(initialization_function); //说明内核初始化位置,没有这个函数,则初始化函数无法调用


【注意】_ _init    _ _initdata表明函数只在初始化期间使用,模块装载完成后不再使用。

 


    static  void  _ _init  cleanup_function(void)//没有返回值

 

 

    清除函数 类似 module_exit(cleanup_function)

四,初始化过程中的错误处理

         1)时刻铭记,注册可能会失败,因此模块代码要始终检查返回值。

         2)当注册时,有些模块注册失败,则需要自行撤销已注册的设施。否则内核处于一种不稳定状态。唯一有效的解决办法:重新引导系统

         3)使用goto

                int  _ _my_init_function(void)

               {

                    int err;

                    err=regeister_this(ptr1,"skull");

                    if(err)  goto fail_this;

                    err=regeister_that(ptr2,"skull");

                    if(err)  goto fail_that;

                   err=regeister_those(ptr3,"skull");
                    if(err)  goto fail_those;

 


                     return 0;//成功

                


                   fail_this:return err;

                   fail_those:unregeister_those(ptr3,"skull");


                   fail_that:unregeister_that(ptr3,"skull");

                 }

           4)初始化函数还在运行时,内核就完全可能会调用我们的模块。所以我们应在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施。


五,模块参数支持很多类型;
    1)基本类型:
        bool   :布尔类型
        invbool:颠倒了值的bool类型;
        charp  :字符指针类型,内存为用户提供的字符串分配;
        int    :整型
        long   :长整型
        short  :短整型
        uint   :无符号整型
        ulong  :无符号长整型
        ushort :无符号短整型

     定义模块参数的方法:
           module_param(name, type, perm);
     其中,name:表示参数的名字;
          type:表示参数的类型;
          perm:表示参数的访问权限;

   2)数组类型:用逗号间隔的列表提供的值;
      声明一个数组参数:
           module_param_array(name, type, num, perm);
      其中,name:表示数组的名字;
           type:表示参数的类型;
           num :表示数组中元素数量;
           perm:表示参数的访问权限;
   3)参数的访问权限
       modlue_param和module_param_array中的perm用于设定该参数的访问权限;
       perm表示该参数在sysfs文件系统中所对应的文件节点的属性;你用该使用<linux/stat.h>中定义的权限值;这个值控制谁可以存取这些模块参数在sysfs文件系统中的表示;当perm为0时,表示此参数不存在sysfs文件系统下对应的文件节点;否则,模块被加载后,在/sys/module/目录下将会出现以此模块名命名的目录,带有给定的权限;
比如:
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
注意:如果一个参数被sysfs修改了,那么你的模块看到的参数值也被修改了,但是你的模块不会收到任何通知;你应当不要使模块参数可写,除非你准备好检测这个改变并因而作出反应;

六,在用户空间编写驱动程序

        用户空间驱动程序的优点:
            1)可以和整个C库链接
            2)驱动程序不用借助外部程序(对于复杂的外设,常常需要和驱动一起发行用户提供策略的应用程序)就可以完成许多非常规的任务。
            3)在驱动中可以使用浮点数,在某些特殊的硬件中,可能需要使用浮点数,而linux内核并不提供浮点数的支持。如果能在用户态实现驱动,就可以轻松解决这一问题
           4) 驱动的问题不会导致整个系统挂起,有过驱动开发经验的人一定会对调试深有感触,一些错误常常导致整个系统挂起。而用户态的驱动在调试上就要方便很多。
           5) 用户内存可以换出
           6)设计良好的驱动仍然可以支持对设备的并发访问
           7)可以给出封闭源码的驱动程序,不必采用GPL,更为灵活


      用户空间驱动的最常见例子是X-server,很多USB设备的驱动也可以放到用户空间。目前,很多人尝试在用户态为PCI设备提供驱动

     用户空间驱动的缺点:
         1)中断在用户空间不可用,最新的UIO接口已经解决了这一问题
         2)响应时间较慢
         3)只能支持字符设备,无法支持块设备和网络设备
         4)可靠性较低,很多驱动都是闭源的,我们没法通过阅读代码解决问题
         5)有些硬件厂商只提供和某些linux开发版(常常早就过时了)相匹配的用户空间驱动

相关阅读:

相关推荐