• Linux驱动设计—— 内核模块(一)


    Linux内核理论基础


    组成Linux内核的5个子系统:进程调度(SCHED)/内存管理(MM)/虚拟文件系统(VFS)/网络接口(NET)/进程间通信(IPC)。

    进程调度(SCHED)

    在设备驱动编程中,当请求的资源不能得到满足时,驱动一般会调度其他进程执行,并使本进程进入睡眠状态,直到它请求的资源被释
    放,才会被唤醒而进入就绪态。睡眠分成可被打断的睡眠和不可被打断的睡眠,两者的区别在于可被打断的睡眠在收到信号的时候会醒。

    内存管理(MM)

    内存管理的主要作用是控制多个进程安全地共享主内存区域。当CPU 提供内存管理单元(MMU)时,Linux 内存管理完成为每个进程进行虚拟内存到物理内存的转换。

    虚拟文件系统(VFS)

    Linux 虚拟文件系统(VFS)隐藏各种了硬件的具体细节,为所有的设备提供了统一的接口。它独立于各个具体的文件系统,是对各种文件系统的一个抽象,它使用超级块super block 存放文件系统相关信息,使用索引节点inode 存放文件的物理信息,使用目录项dentry 存放文件的逻辑信息。

    网络接口(NET)

    网络接口提供了对各种网络标准的存取和各种网络硬件的支持。

    在Linux 中网络接口可分为网络协议和网络驱动程序,网络协议部分负责实现每一种可能的网络传输协议,网络设备驱动程序负责与硬件设备通信,每一种可能的硬件设备都有相应的设备驱动程序。

    进程间通信(IPC)

    进程通信支持提供进程之间的通信,Linux 支持进程间的多种通信机制,包含信号量、共享内存、管道等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。

    Linux 内核的5 个组成部分之间的依赖关系如下:
    1、进程调度与内存管理之间的关系:这两个子系统互相依赖。在多道程序环境下,程序要运行必须为之创建进程,而创建进程的第一件事情,就是将程序和数据装入内存。
    2、进程间通信与内存管理的关系:进程间通信子系统要依赖内存管理支持共享内存通信机制,这种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。
    3、虚拟文件系统与网络接口之间的关系:虚拟文件系统利用网络接口支持网络文件系统(NFS),也利用内存管理支持RAMDISK 设备。
    4、内存管理与虚拟文件系统之间的关系:内存管理利用虚拟文件系统支持交换,交换进程(swapd)定期由调度程序调度,这也是内存管理依赖于进程调度的惟一原因。当一个进程存取的内存映射被换出时,内存管理向文件系统发出请求,同时,挂起当前正在运行的进程。


    Linux内核空间与用户空间

    现代CPU 内部往往实现了不同的操作模式(级别),不同的模式有不同的功能,高层程序往往不能访问低级功能,而必须以某种方式切换到低级模式。

    ARM 处理器分为7 种工作模式:

    ! 用户模式(usr):大多数的应用程序运行在用户模式下,当处理器运行在用户模式下时,某些被保护的系统资源是不能被访问的。
    ! 快速中断模式(fiq):用于高速数据传输或通道处理。
    ! 外部中断模式(irq):用于通用的中断处理。
    ! 管理模式(svc):操作系统使用的保护模式。
    ! 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
    ! 系统模式(sys):运行具有特权的操作系统任务。
    ! 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。

    ARM Linux 的系统调用实现原理是采用swi 软中断从用户态usr 模式陷入内核态svc 模式

    X86 处理器包含4 个不同的特权级,称为Ring 0~Ring 3。Ring0 下,可以执行特权级指令,对任何I/O 设备都有访问权等,而Ring3 则被限制很多操作。

    Linux 系统充分利用CPU 的这一硬件特性,但它只使用了两级。在Linux 系统中,内核可进行任何操作,而应用程序则被禁止对硬件的直接访问和对内存的未授权访问。例如,若使用X86处理器,则用户代码运行在特权级3,而系统内核代码则运行在特权级0内核空间用户空间这两个名词被用来区分程序执行的这两种不同状态,它们使用不同的地址空间。Linux 只能通过系统调用硬件中断完成从用户空间到内核空间的控制转移。


    Linux下编码风格:(与window做对比)

    Windows 程序中:

    #define PI 3.141 592 6 /*用大写字母代表宏*/
    int minValue, maxValue; /*变量:第一个单词全写,其后的单词第一个字母小写*/
    void SendData(void); /*函数:所有单词第一个字母都大写定义*/
    

    相对应的Linux程序:

    #define PI 3.141 592 6
    int min_value, max_value;
    void send_data(void);
    

    Linux 的代码缩进使用“TAB”(8 个字符)。
    Linux 的代码括号“{”和“}”的使用原则如下。
    (1)对于结构体、if/for/while/switch 语句,“{”不另起一行。

    (2)如果 if、for 循环后只有 1 行,不要加“{”和“}”。

    (3)if 和 else 混用的情况下,else 语句不另起一行。

    (4)对于函数,“{”另起一行。在switch/case 语句方面,Linux 建议switch 和case 对齐。

    另外,请注意代码中空格的应用,譬如“for!(i!=!0;! i!<!10;! i++)!{”语句中“!”都是空格。

    GNU C对ANSI C的拓展 (详见《Linux设备驱动开发详解 第2版》P73)

    1.零长度和变量长度数组

    struct var_data {
    int len;
    char data[0];  //char data[0]仅仅意味着程序中通过var_data 结构体实例的data[index]成员可以访问len 之后的第index 个地址,它并没有为data[]数组分配内存,因此sizeof(struct var_data)=sizeof(int)。
    };
    
    struct var_data s;
    ...
    for (i = 0; i < s.len; i++)
    printf("%02x", s.data[i]);
    

     变量长度数组例子:

    int main (int argc, char *argv[])
    {
        int i, n = argc;
        double x[n]; 
        for (i = 0; i < n; i++)
        x[i] = i;
        return 0;
    }
    

    2.case 范围

    GNU C 支持case x…y 这样的语法,区间[x,y]的数都会满足这个case 的条件。看到这个想起了曾经遇到的一段代码就是这样子,当时还觉得很奇怪。

    switch (ch) {
    case '0'... '9': c -= '0';
    break;
    case 'a'... 'f': c -= 'a' - 10;
    break;
    case 'A'... 'F': c -= 'A' - 10;
    break;
    }
    

    3.语句表达式
    GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环、局部变量等。

    //GNU C可以这样
    #define min_t(type,x,y) 
    ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
    int ia, ib, mini;
    float fa, fb, minf;
    mini = min_t(int, ia, ib);
    minf = min_t(float, fa, fb);
    //因为重新定义了_ _xx 和_ _y 这两个局部变量,所以以上述方式定义的宏将不会有副作用。在标准C 中,对应的如下宏则会产生副作用:
    
    //ANSI C只能这样
    #define min(x,y) ((x) < (y) ? (x) : (y))
    //代码min(++ia,++ib)会被展开为((++ia) < (++ib) ? (++ia): (++ib)),传入宏的“参数”被增加2 次。

    4.typeof 关键字
    typeof(x)语句可以获得x 的类型。

    #define min(x,y) ({ 
    const typeof(x) _x = (x); 
    const typeof(y) _y = (y); 
    (void) (&_x == &_y); 
    _x < _y ? _x : _y; })

    //我们不需要像min_t(type,x,y)这个宏那样把type 传入,因为通过typeof(x)、typeof(y)可以获得type。代码行(void) (&_x == &_y)的作用是检查_x 和_y 的类型是否一致。

    5.可变参数宏

    标准C 就支持可变参数函数,意味着函数的参数是不固定的,例如printf()函数的原型为:
    int printf( const char *format [, argument]... );

    而在 GNU C 中,宏也可以接受可变数目的参数,例如:
    #define pr_debug(fmt,arg...)
        printk(fmt,##arg)
    这里arg 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成arg 的值,在宏扩展时替换arg,例如下列代码:
      pr_debug("%s:%d",filename,line)
      会被扩展为:
      printk("%s:%d", filename, line)
      使用“##”的原因是处理arg 不代表任何参数的情况,这时候,前面的逗号就变得多余了。

    使用“##”之后,GNU C 预处理器会丢弃前面的逗号,这样,代码:
      pr_debug("success! ")
      会被正确地扩展为:
      printk("success! ")
      而不是:
      printk("success! ",)
      这正是我们希望看到的。

    6.标号元素 

    标准C 要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C 中,通过指定索引或结构体成员名,允许初始化值以任意顺序出现。

    指定数组索引的方法是在初始化值前添加“[INDEX] =”,当然也可以用“[FIRST ... LAST] =”的形式指定一个范围。例如,下面的代码定义一个数组,并把其中的所有元素赋值为0:
    unsigned char data[MAX] = { [0 ... MAX-1] = 0 };

    下面的代码借助结构体成员名初始化结构体:
    struct file_operations ext2_file_operations = {
      llseek: generic_file_llseek,
      read: generic_file_read,
      write: generic_file_write,
      ioctl: ext2_ioctl,
      mmap: generic_file_mmap,
      open: generic_file_open,
      release: ext2_release_file,
      fsync: ext2_sync_file,
    };

    Linux 2.6 推荐类似的代码应该尽量采用标准C 的方式:
    struct file_operations ext2_file_operations = {
      .llseek = generic_file_llseek,
      .read = generic_file_read,
      .write = generic_file_write,
      .aio_read = generic_file_aio_read,
      .aio_write = generic_file_aio_write,
      .ioctl = ext2_ioctl,
      .mmap = generic_file_mmap,
      .open = generic_file_open,
      .release = ext2_release_file,
      .fsync = ext2_sync_file,
      .readv = generic_file_readv,
      .writev = generic_file_writev,
      .sendfile = generic_file_sendfile,
    };

    7.当前函数名

    GNU C 预定义了两个标志符保存当前函数的名字,_ _FUNCTION_ _保存函数在源码中的名字,__PRETTY_FUNCTION_ _保存带语言特色的名字。在C 函数中,这两个名字是相同的。

      void example()
      {
        printf("This is function:%s", _ _FUNCTION_ _);
      }
    代码中的_ _FUNCTION_ _意味着字符串“example”。C99 已经支持_ _func_ _宏,因此建议在
    Linux 编程中不再使用_ _FUNCTION_ _,而转而使用_ _func_ _:
      void example()
      {
        printf("This is function:%s", _ _func_ _);
      }

    8.特殊属性声明

    http://www.cnblogs.com/astwish/p/3460618.html

    http://blog.chinaunix.net/uid-178707-id-2849461.html 每个知识点都带有实例

    GNU C 允许声明函数、变量和类型的特殊属性,以便进行手工的代码优化和定制代码检查的方法。要指定一个声明的属性,只需要在声明后添加_ _attribute_ _ (( ATTRIBUTE ))。ATTRIBUTE 为属性说明,如果存在多个属性,则以逗号分隔。GNU C 支持noreturn、format、section、aligned、packed 等十多个属性。   

    noreturn 属性作用于函数,表示该函数从不返回。这会让编译器优化代码,并消除不必要的
    警告信息。例如:
    # define ATTRIB_NORET _ _attribute_ _((noreturn)) ....
    asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
    format 属性也用于函数,表示该函数使用printf、scanf 或strftime 风格的参数,指定format
    属性可以让编译器根据格式串检查参数类型。例如:
    asmlinkage int printk(const char * fmt, ...) _ _attribute_ _ ((format (printf, 1, 2)));
    上述代码中的第1 个参数是格式串,从第2 个参数开始都会根据printf()函数的格式串规则检
    查参数。
    unused 属性作用于函数和变量,表示该函数或变量可能不会被用到,这个属性可以避免编译
    器产生警告信息。
    aligned 属性用于变量、结构体或联合体,指定变量、结构体或联合体的对界方式,以字节为
    单位,例如:
    struct example_struct {
    char a;
    int b;
    long c;
    } _ _attribute_ _((aligned(4)));
    表示该结构类型的变量以4 字节对界。
    packed 属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对界,用于枚
    举、结构体或联合体类型时表示该类型使用最小的内存。例如:

    struct example_struct {
      char a;
      int b;
      long c _ _attribute_ _((packed));
    };

    9.内建函数


    Linux内核模块

    Linux内核模块的特点

    1、模块本身不被编译入内核映像,从而控制了内核的大小。

    2、模块一旦被加载,它就和内核中的其他部分完全一样。

    一个最简单的Linux 内核模块 “Hello World”

    1 #include <linux/init.h>
    2 #include <linux/module.h>
    3
    4 static int hello_init(void)
    5 {
    6   printk(KERN_INFO " Hello World enter
    ");
    7   return 0;
    8 }
    9
    10 static void hello_exit(void)
    11 {
    12   printk(KERN_INFO " Hello World exit
     ");
    13 }
    14
    15 module_init(hello_init);
    16 module_exit(hello_exit);
    17
    18 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
    19 MODULE_LICENSE("Dual BSD/GPL");
    20 MODULE_DESCRIPTION("A simple Hello World Module");
    21 MODULE_ALIAS("a simplest module");
    

    在Linux 中,使用lsmod 命令可以获得系统中加载了的所有模块以及模块间的依赖关系,lsmod 命令实际上读取并分析“/proc/modules”文件。/proc 文件系统是动态的,所以驱动程序模块可以在任何时候添加或删除其中的文件项。

    lihacker@lihacker-laptop:~/$ cat /proc/modules
    hello 9472 0 - Live 0xf953b000  # 模块名 占用空间bytes 模块加载计数 Live表示模块可用 0xf953b000是模块的起始地址
    nls_iso8859_1 12032 1 - Live 0xf950c000
    nls_cp437 13696 1 - Live 0xf9561000
    vfat 18816 1 - Live 0xf94f3000
    

    内核中已加载模块的信息也存在于/sys/module 目录下,加载hello.ko 后,内核中将包含/sys/module/hello 目录,该目录下又包含一个refcnt 文件和一个sections 目录,在/sys/module/hello目录下运行“tree –a”得到如下目录树:

    (不一定包含所有的目录)

    holders  持有人,是写本模块的人。但是目录为空。

    initstate  记录模块活动

    notes   暂且没有查到,好像是日记,有个隐藏文件,可能就是记录本模块的信息 

    parameters  使用的变量

    所有内联模块的参数也可以由 "<module_name>.<param_name>=<value>" 的形式写在内核启动参数上,如启动内核时加上参数 "printk.time=1" 与 向 "/sys/module/printk/parameters/time" 写入1的效果相同。

    refcnt   模块的加载计数

    section 段信息

    srcversion  模块版本号 像模块的ID一样

    lihacker@lihacker-laptop:/sys/module/hello$ tree -a
    .
    |-- holders
    |-- initstate
    |-- notes
    | '-- .note.gnu.build-id
    |-- refcnt
    |-- sections
    | |-- .bss
    | |-- .data
    | |-- .gnu.linkonce.this_module
    | |-- .note.gnu.build-id
    | |-- .rodata.str1.1
    | |-- .strtab
    | |-- .symtab
    | '-- .text
    '-- srcversion
    3 directories, 12 files
    

    modprobe 命令比insmod 命令要强大,它在加载某模块时,会同时加载该模块所依赖的其他模块。使用modprobe 命令加载的模块若以“modprobe -r filename”的方式卸载将同时卸载其依赖的模块
    使用modinfo <模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic:

    模块信息在代码中指定参考http://www.cnblogs.com/kwseeker-bolgs/p/4343392.html

    lihacker@lihacker-laptop: ~ /develop/svn/ldd6410-read-only/training/kernel/drivers/hello$ modinfo hello.ko
    filename: hello.ko
    alias: a simplest module      
    description: A simple Hello World Module
    license: Dual BSD/GPL
    author: Barry Song <21cnbao@gmail.com>
    srcversion: 3FE9B0FBAFDD565399B9C05
    depends:
    vermagic: 2.6.28-11-generic SMP mod_unload modversions 586
    

      

    Linux内核模块程序结构

    (1)模块加载函数(一般需要)

    Linux 内核模块加载函数一般以_ _init 标识声明

    static int _ _init initialization_function(void) //它返回整型值,若初始化成功,应返回0。而在初始化失败时,应该返回错误编码。在Linux 内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM 之类的符号值。用户程序可以利用perror 等方法把它们转换成有意义的错误信息字符串。
    {
        /* 初始化代码 */
    }
    module_init(initialization_function);
    

    在Linux 中,所有标识为_ _init 的函数在连接的时候都放在.init.text 这个区段内,此外,所有的_ _init 函数在区段.initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些_ _init 函数,并在初始化完成后,释放init 区段(包括.init.text、.initcall.init 等)。

    (2)模块卸载函数(一般需要)

    Linux 内核模块加载函数一般以_ _exit 标识声明。

    static void _ _exit cleanup_function(void)
    {
         /* 释放代码 */
    }
    module_exit(cleanup_function);
    

    模块卸载函数要完成与模块加载函数相反的功能

    ! 若模块加载函数注册了XXX,则模块卸载函数应该注销XXX。
    ! 若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。
    ! 若模块加载函数申请了硬件资源(中断、DMA 通道、I/O 端口和I/O 内存等)的占用,
    则模块卸载函数应释放这些硬件资源。
    ! 若模块加载函数开启了硬件,则卸载函数中一般要关闭之。

    和_ _init 一样,_ _exit 也可以使对应函数在运行完成后自动回收内存。实际上,_ _init 和_ _exit
    都是宏,其定义分别为:

    #define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text"))) 
    
    #ifdef MODULE
    #define _ _exit _ _attribute_ _ ((_ _section_ _(".exit.text")))
    #else
    #define _ _exit _ _attribute_used_ _attribute_ _ ((_ _section_ _(".exit.text")))
    #endif
    

    数据也可以被定义为_ _initdata 和_ _exitdata,这两个宏分别为:

    #define _ _initdata _ _attribute_ _ ((_ _section_ _ (".init.data")))
    
    #define _ _exitdata _ _attribute_ _ ((_ _section_ _(".exit.data")))
    

      

    (3)模块许可证声明(必须)

    在Linux 2.6 内核中,可接受的LICENSE 包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。大多数情况下,内核模块应遵循GPL 兼容许可权。Linux 2.6 内核模块最常见的是以MODULE_LICENSE( "Dual BSD/GPL" )语句声明模块采用BSD/GPL 双LICENSE。

    (4)模块参数(可选)

    模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量

    可以用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了1 个整型参数和1 个字符指针参数:

    static char *book_name = " dissecting Linux Device Driver ";
    static int num = 4 000;
    module_param(num, int, S_IRUGO);
    module_param(book_name, charp, S_IRUGO);
    

    在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或modprobe)模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的缺省值。参数类型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或invbool(布尔的反),在模块被编译时会将module_param 中声明的类型与变量定义的类型进行比较,判断是否一致。

    perm参数(参数权限)的作用是什么?
    最后的 module_param 字段是一个权限值; 你应当使用 中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.如果 perm 被设为 0, 就根本没有 sysfs 项. 否则, 它出现在 /sys/module下面, 带有给定的权限. 使用 S_IRUGO 作为参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.
    perm参数的作用是什么?
    最后的 module_param 字段是一个权限值,表示此参数在sysfs文件系统中所对应的文件节点的属性。你应当使用 中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.当perm为0时,表示此参数不存在 sysfs文件系统下对应的文件节点。 否则, 模块被加载后,在/sys/module/ 目录下将出现以此模块名命名的目录, 带有给定的权限.。
    权限在include/linux/stat.h中有定义
    比如:
    #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
    使用 S_IRUGO 作为参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.

    通过宏MODULE_PARM_DESC()对参数进行说明:
    static unsigned short size = 1;
    module_param(size, ushort, 0644);
    MODULE_PARM_DESC(size, “The size in inches of the fishing pole”
    “connected to this computer.” );

    带参数的内核模块

    #include <linux/init.h>
    #include <linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL");
    
    static char *book_name = "dissecting Linux Device Driver";
    static int num = 4 000;
    
    static int book_init(void)
    {
    printk(KERN_INFO " book name:%s
    ",book_name);
    printk(KERN_INFO " book num:%d
    ",num);
    return 0;
    }
    static void book_exit(void)
    {
    printk(KERN_INFO " Book module exit
     ");
    }
    module_init(book_init);
    module_exit(book_exit);
    module_param(num, int, S_IRUGO);
    module_param(book_name, charp, S_IRUGO);
    
    MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
    MODULE_DESCRIPTION("A simple Module for testing module params");
    MODULE_VERSION("V1.0");
    

    对上述模块运行“insmod book.ko”命令加载,相应输出都为模块内的默认值,通过查看
    /var/log/messages”日志文件可以看到内核的输出:
    [root@localhost driver_study]# tail -n 2 /var/log/messages
    Jul 2 01:03:10 localhost kernel: <6> book name:dissecting Linux Device Driver
    Jul 2 01:03:10 localhost kernel: book num:4 000
    当用户运行“insmod book.ko book_name='GoodBook' num=5 000”命令时,输出的是用户传递
    的参数:
    [root@localhost driver_study]# tail -n 2 /var/log/messages
    Jul 2 01:06:21 localhost kernel: <6> book name:GoodBook
    Jul 2 01:06:21 localhost kernel: book num:5 000


    (5)模块导出符号(可选)
    内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。

    Linux 2.6 的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。

    模块可以使用如下宏导出符号到内核符号表:

    EXPORT_SYMBOL(符号名);
    EXPORT_SYMBOL_GPL(符号名);
    

    导出的符号将可以被其他模块使用,使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含GPL 许可权的模块。


    (6)模块作者等信息声明(可选)

    在Linux 内核模块中,我们可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS 分别声明模块的作者、描述、版本、设备表和别名。

    MODULE_AUTHOR(author);
    MODULE_DESCRIPTION(description);
    MODULE_VERSION(version_string);
    MODULE_DEVICE_TABLE(table_info);
    MODULE_ALIAS(alternate_name);
    

    对于USB、PCI 等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,表明该驱动模块所支持的设备。

    模块的使用计数

    Linux 2.6 内核提供了模块计数管理接口try_module_get(&module)和module_put (&module),从而取代Linux 2.4 内核中的模块使用计数管理宏。模块的使用计数一般不必由模块自身管理,而且模块 计数管理还考虑了 SMP 与 PREEMPT 机制的影响。

    int try_module_get(struct module *module);
    该函数用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在 被卸载中。
    void module_put(struct module *module);
    该函数用于减少模块使用计数。

    模块的编译模板

    KVERS = $(shell uname -r)
    # Kernel modules
    obj-m += hello.o
    # Specify flags for the module compilation.
    #EXTRA_CFLAGS=-g -O0
    build: kernel_modules
    kernel_modules:
    make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
    clean:
    make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
    

    如果一个模块包括多个.c 文件(如file1.c、file2.c),则应该以如下方式编写Makefile:

    obj-m := modulename.o
    modulename-objs := file1.o file2.o
    

    Linux文件系统与设备文件系统

    Linux文件操作

    参考:http://www.cnblogs.com/kwseeker-bolgs/p/4373131.html

    Linux文件系统

    1./bin 包含基本命令,这个目录中的文件都是可执行的。
    2./sbin 包含系统命令,如modprobe、hwclock、ifconfig 等,大多是涉及系统管理的命令,这个目录中的文件都是可执行的。
    3./dev 设备文件存储目录,应用程序通过对这些文件的读写和控制就可以访问实际的设备。
    4./etc 系统配置文件的所在地,一些服务器的配置文件也在这里,如用户账号及密码配置文件。busybox 的启动脚本也存放在该目录。
    5./lib 系统库文件存放目录。
    6./mnt 一般是用于存放挂载储存设备的挂载目录的,比如有cdrom 等目录。可以参看/etc/fstab 的定义。
    7./opt opt 是“可选”的意思,有些软件包会被安装在这里,例如,在LDD6410 的文件系统中,Qt/Embedded 就存放在该目录。

    8./proc 操作系统运行时,进程及内核信息(比如CPU、硬盘分区、内存信息等)存放在这里。/proc目录为伪文件系统proc 的挂载目录,proc 并不是真正的文件系统,它存在于内存之中。
    9./tmp 有时用户运行程序的时候,会产生临时文件,/tmp 就用来存放临时文件的。
    10./usr 这个是系统存放程序的目录,比如用户命令、用户库等。
    11./var var 表示的是变化的意思,这个目录的内容经常变动,如/var 的/var/log 目录被用来存放系统日志。
    12./sys Linux 2.6 内核所支持的sysfs 文件系统被映射在此目录。Linux 设备驱动模型中的总线、驱动和设备都可以在sysfs 文件系统中找到对应的节点。当内核检测到在系统中出现了新设备后,内核会在sysfs 文件系统中为该新设备生成一项新的记录。

    Linux文件系统与设备驱动

    磁盘文件(存放于Ramdisk、Flash、ROM、SD 卡、U盘等文件系统中的文件)

    应用程序和VFS 之间的接口是系统调用

    VFS 与磁盘文件系统以及普通设备之间的接口是file_operations 结构体成员函数,这个结构体包含对文件进行打开、关闭、读写、控制的一系列成员函数。

    在设备驱动程序的设计中,一般而言,会关心file 和inode 这两个结构体。

    参考:http://www.cnblogs.com/kwseeker-bolgs/p/4363358.html

  • 相关阅读:
    Java中字符串indexof() 的使用方法
    .Net Core WebApi(3)—NLog
    .Net Core WebApi(2)—Swagger
    left join 左边有数据,右边无数据
    Angular—入门环境,项目创建,导入项目
    SQLite介绍和使用
    .Net Core-类库中创建CodeFirst
    .Net Core WebApi(1)— 入门
    .Net Jpush极光推送
    Webform中的前后端分离
  • 原文地址:https://www.cnblogs.com/kwseeker-bolgs/p/4392979.html
Copyright © 2020-2023  润新知