zoukankan      html  css  js  c++  java
  • 第二章 构造和运行模块

    一、Hello World模块

    hello.c

    #include <linux/init.h>
    #include <linux/module.h>
    
    MODULE_LICENSE("Dual BSD/GPL");
    
    static int hello_init(void)
    {
        printk(KERN_ALERT"Hello, world
    ");
        return 0;
    }
    
    static void hello_exit(void)
    {
        printk(KERN_ALERT"Goodbye, cruel world
    ");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    hello.c

    Makefile

    # if KERNELRELEASE is defined, we've benn invoked from the
    # kernel build system and can use its language.
    ifneq ($(KERNELRELEASE),)
    
    obj-m := hello.o
    
    # Otherwise we were called directly from the command
    # line; invoke the kernel build system.
    else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD := $(shell pwd)
    default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    endif
    Makefile

    moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核 

    printk函数和print函数类似,字串KERN_ALERT是消息优先级

    与应用程序的不同

    应用程序:

    • 应用程序从头执行到尾。
    • 可以不管理资源的释放或者其他的清理工作。
    • 可以调用它并未定义的函数,通过解析外部引用从而使用适当的函数库。
    • 开发过程中,段错误是无害的。并且可以使用调试器跟踪到源代码中的问题所在。

    内核:

    • 模块初始化的函数任务就是为以后调用模块函数预先做准备。
    • 模块的退出函数必须小心恢复每个由初始化函数建立的东西, 否则会保留一些东西直到系统重启.。
    • 模块能调用的函数仅仅是由内核导出的哪些函数,
    • 一个内核的段错误即使不影响整个系统,也至少会杀死当前的进程。

    用户空间和内核空间

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

    系统至少有两种级别,内核(超级模式),最低级模式(用户模式),以此控制对硬件的直接存取以及对内存的非法访问。

    内核空间和用户空间,都有自己对应的内存映射(自己的地址空间)

    内核的并发

    即使最简单的内核模块,都需要在编写时铭记:同一时刻,可能会有许多事情正在发生。

    当你查看内核 API 时, 你会遇到以双下划线(__)开始的函数名. 这样标志的函数名通常是一个低层的接口组件, 应当小心使用.

    如果不注意并发问题,可能导致出现很难调试的灾难性错误。

    内核的版本检查

    UTS_RELEASE
    这个宏定义扩展成字符串, 描述了这个内核树的版本. 例如, "2.6.10".

    LINUX_VERSION_CODE

    这个宏定义扩展成内核版本的二进制形式, 版本号发行号的每个部分用一个字节表示. 例如, 2.6.10 的编码是 132618 ( 就是, 0x02060a ). [4]4有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本.
    KERNEL_VERSION(major,minor,release)
    这个宏定义用来建立一个整型版本编码, 从组成一个版本号的单个数字. 例如,KERNEL_VERSION(2.6.10) 扩展成 132618. 这个宏定义非常有用, 当你需要比较当前版本和一个已知的检查点.

    二、模块编译和装载

    首先,读者应该确保具备了正确的版本编译器、模块工具和其他必要的工具。

    只有系统调用的名字前带有sys_前缀,而其他函数都没有这个前缀。这种命名在源码中grep系统调用时非常方便。

    • insmod:可以接收一些命令行选项(参见手册),并且可以在装载时给模块变量。
    • rmmod:如果内核认为模块依然使用,或者内核被配置为禁止移除,则无法移除模块。
    • lsmod:通过读取/proc/modules虚拟文件来获得这些信息。有关已装载的模块信息在/sys/module下找到
    • 查看系统日志文件(/var/log/messages或者系统配置使用的文件),将看到导致模块装载失败的具体原因

    模块的堆叠

    可以使用modeprobe,类似于insmod。

    如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义:

    EXPORT_SYMBOL(name);
    EXPORT_SYMBOL_GPL(name);

    几乎所有模块都有

    #include <linux/module.h>

    #include <linux/init.h>

    • MODULE_LICENSE("GPL")
    • MODULE_AUTHOR(声明谁编写了模块)
    • MODULE_DESCRIPION(一个人说明的关于模块做什么的声明)
    • MODULE_VERSION(一个diamante修订版本号)
    • MODULE_ALIAS(模块为人所知的另一个名字)
    • MODULE_DEVICE_TABLE(来告知用户空间,模块支持哪些设备)

    模块的初始化和关停

    模块的初始化通常是:

    static int __init initialization_function(void)
    
    {
    
      /* Initialization code here */
    
    }
    
    module_init(initializatioon_function);
    module_init

    __init标志是给内核的一个暗示,给定的函数只是在初始化使用,加载后会丢掉这个初始化函数。

    __initdata给只在初始化时用的数据,后面可能还会有__devinit和__devinitdata

    清理函数

    static void __exit cleanup_function(void)
    
    {
    
      /* Cleanup code here */
    
    }
    
    module_exit(cleanup_function);
    module_exit

    清理函数没有返回值,__exit修饰符用于模块卸载

    初始化汇总的错误处理

    在之策内核设施时,注册可能失败,几遍最简单的动作常常需要内存分配,分配的内存可能不可用。

    因此模块代码必须一致检查返回值,并且确认要求的操作实际上已经成功。

    如果注册时发生任何错误,首先第一的事情是决定模块是否能够五路你如何继续初始化它自己。

    任何时候,你的模块应当尽力向前,并提供事情失败后具备的能力。

    如何模块证实失败,不能完全加载,必须取消失败前注册动作。因此初始化某个点失败,模块必须自己退回所有东西。否则内核就处于不稳定状态。

    • 使用goto是处理错误回复的最好情况
    • 使用错误编码是一个好习惯,诸如-ENODEV、-ENOMEM
    int _init my_init_function(void)
    {
      int err;
    
      err = register_this(ptr1, "skull");  /* registration takes a pointer and a name */
        if(err)
            goto fail_this;
        err = register_that(ptr2, "skull");
        if(err)
            goto fail_that;
        err = register_those(ptr3, "skull");
        if(err)
            goto fail_those;
        return 0;            /* success */
    fail_those:
        unregister_that(ptr2, "skull");
    fail_that:
        unregister_this(ptr1, "skull");
    fail_this:
        return err;        /* propagate the error */
    }
    错误处理

     err是错误码,在<linux/errno.h>中。显然在模块清理函数中,是按照注册时相反的顺序注销设施。

    void __exit my_cleanup_function(void)
    {
        unregister_those(ptr3, "skull");
        unregister_that(ptr2, "skull");
        unregister_this(ptr1, "skull");
        return;
    }
    模块清理处理

    如果goto内容太多难以管理,清理函数必须撤销注册前的每一项。

    struct something *item1;
    struct somethingelse *item2;
    int stuff_ok;
    
    void my_cleanup(void)
    {
        if(item1)
            release_thing(item1);
        if(item2)
            release_thing2(item2);
        if(stuff_ok)
            unregister_stuff();
        return;
    }
    
    int __init my_init(void)
    {
        int err = -ENOMEM;
    
        item1 = allocate_thing(arguments);
        item2 = allocate_thing2(arguments2);
        if(!item2 || !item2)
            goto fail;
    
        err = register_stuff(item1, item2);
        if(!err)
            stuff_ok = 1;
        else
            goto fail;
        return 0;
    
    fail:
        my_cleanup();
        return err;
    }
    goto清理

    模块加载竞争

    内核的某些别的部分会在注册完成之后马上使用任何你注册的设施。换句话说,内核将调用进你的模块,在你初始化函数任然在运行时,所以你的代码必须准备好被调用,一旦完成注册。

    模块参数

    insmod hellop howmany=10 whom="Mom"
    模块参数

    在模块中参数用module_param宏定义来声明,它定义在moduleparam.h module_param使用了3个参数:变量名,类型,以及一个权掩码用来做一个辅助的sysfs入口。

    static char *whom = "world";
    static int howmany = 1;
    mdoule_param(howmany, int, S_IRUGO);
    module_param(whom, charp, S_IRUGO);
    模块参数用例

    模块参数支持许多类型:

    bool、invbool  一个布尔型,invbool颠倒了值

    charp      字符指针

    int、long、short、uint、ulong、ushort   整型,u开头是无符号值

    数组参数,用逗号间隔的列表提供的值,模块加载者也支持。

    module_param_array(name, type, num, perm);

    name:数组名

    type:数组元素类型

    num:整型变量

    perm:通常的权限值

    无欲速,无见小利。欲速,则不达;见小利,则大事不成。
  • 相关阅读:
    INode满的处理方法
    分布式转码集群思路
    FreeBSD Set a Default Route / Gateway
    ssh遇到port 22:No route to host问题的解决方法
    debian 开启SSH
    virsh 查看信息
    virsh console配置
    virsh console hangs at the escape character “^]”
    virt-install命令---详解
    kmv 学习笔记 工具
  • 原文地址:https://www.cnblogs.com/ch122633/p/9148027.html
Copyright © 2011-2022 走看看