ldd3-2.1

                        第二章 建立和运行模块
part1
-------------------------------------------------------------------------
2.2 hello world模块

    //完整代码如下:
    #include <linux/init.h>
    #include <linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL");

    static int hello_init(void)
    {
        printk(KERN_ALERT "Hello, world\n");
        return 0;
    }

    static void hello_exit(void)
    {
        printk(KERN_ALERT "Goodbye, cruel world\n");
    }

    module_init(hello_init);
    module_exit(hello_exit);

    module_init内核宏 来指出hello_init()的角色是:模块加载到内核时被调用。
    module_exit内核宏 来指出hello_exit()的角色是:模块从内核卸载时被调用。
    MODULE_LICENSE)是用来告知内核:该模块带有一个自由的许可证,否则 在模块加载时内核会抱怨.

    printk函数在Linux内核中定义并且对模块可用。 是因为在insmod加载了它之后,  模块被连接到内核并且可存取内核的公用符号。
    KERN_ALERT  是消息的优先级

    对上述模块的操作,需要root权限。
        %make       要有Makefile文件
          ...
        %insmod hello.ko
            Hello, world
        %rmmod  hello.ko
            Goodbye cruel world     ;若消息没有显示,消息进一个系统日志文件中,例如/var/log/messages。


2.3内核模块和应用程序之间不同:
    1 内核可以被加载/卸载模块

    2 模块的退出函数 --必须小心恢复每个由初始化函数建立的东西,否则会保留一些东西直到系统重启.
      应用程序的终止 可以在释放资源方面懒惰

    3 模块     --只连接到内核. 是内核的一部分的函数才可以在内核模块里使用。
      应用程序 可以连接到库函数。

    4 内核模块 --只注册自己以便来服务将来的请求,并且它的初始化函数会立刻终止.
      应用程序 从头至尾处理一个单个任务

    5 模块缺乏浮点函数支持。

    6 出现在内核的错误至少会杀掉当前进程。
      在应用程序开发中段错误是无害的, 可以用调试器来追踪到错误。

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

    8 模块程序是并发的。
         在同一时间,  不止一个进程能够试图使用你的驱动.在多核上,模块可能被多个CPU执行。
        用户程序,典型是顺序执行的,多线程例外。

    9   当前进程
        全局变量current指针,指向当前正在运行的进程.在<asm/current.h>中定义。它指向结构task_struct(在<linux/sched.h> 定义)
        内核代码可以通过使用 current 来获得进程特定的信息。

    10  内核堆栈很小,可能小到4096字节.模块函数会共享这个堆栈. 因此,不要声明一个巨大的自动变量; 需要大的结构时,应当在调用时间内动态分配.
        应用程序存在于虚拟内存中,  有一个非常大的堆栈区.

    11  有些以双下划线(__)开始的API函数名.标志它一个低层的接口组件, 应当小心使用.

2.4编译模块
    编译前的准备工作:
        1   Document/kbuild    目录下的文件,想理解表面之下的真实情况的人都要阅读一下.
        2   Documentation/Changes  列出了需要的工具版本,在向前走之前参考之。
        3   需要一个内核树,或者配置和建立内核,现在是时候去做了。 否则你无法为2.6内核建立可加载的模块(不是必要的).

    当建立起所有东西后,对于前面展示的" hello world"  例子,在Makefile中有如下一行就能够编译了:
        obj-m := hello.o

        1 表明有一个模块要从目标文件 hello.o 建立. 在从目标文件建立后结果模块命名为 hello.ko.
        2 如果建立的模块名为 module.ko,来自2个源文件 file1.c和file2.c 应如下写:
            obj-m := module.o
            module-objs := file1.o file2.o

        4 如果你的内核源码 ~/kernel-2.6目录, 则make命令添加如下命令:
            make -C ~/kernel-2.6 M=`pwd` modules

            -C 选项提供你的内核源码目录,它在那里会发现内核的顶层 makefile.
            M=  选项使 makefile 在试图建立模块目标前,回到你的模块源码目录. 其目标,是指在obj-m变量中查找模块列表,在此设成了module.o

        5 若上面的太麻烦,使用下面的 Makefile。 在命令行输入make 可直接编译出 .ko文件

            # If KERNELRELEASE is defined, we've been 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 在一次典型的建立中要被读2次.工作过程如下
            1 开始调用 makefile 时,KERNELRELEASE 变量没有设置.则使用 KERNELDIR= 选项:
                KERNELDIR ?= /lib/modules/$(shell uname -r)/build  设置KERNELDIR为内核源代码的路径。
            2 一旦发现内核源码树, makefile 调用 default下的语句:
                $(MAKE) -C $(KERNELDIR) M=$(PWD) modules     调用内核建立系统
                与 make -C ~/kernel-2.6 M=`pwd` modules 语句相同了。
            3 makefile 设置 obj-m,  并且内核的  makefile  文件完成实际的建立模块工作.

        6 下面是另外一个可以正确make的Makefile
            obj-m := hello.o                                        #模块编译的目标必须以obj-m 这样的形式指出
            KERNELDIR ?= /lib/modules/$(shell uname -r)/build       #模块的编译必须指定内核源代码的路径
            PWD := $(shell pwd)
            modules:
                $(MAKE) -C $(KERNELDIR) M=$(PWD) modules            #本行开头必须使用 Tab 键

2.4.2 加载和卸载模块

    加载:
        insmod 将成这个工作,这个程序加载模块的代码段和数据段到内核,然后连接模块中未连接的符号到内核符号表上.
            1 内核不修改模块的磁盘文件, 而是拷贝到内存.
            2 insmod 接收许多命令行选项 (详见manpage), 它在连接到当前内核之前,能够赋值给你模块中的参数.

        注:insmod: 它依赖一个在kernel/module.c 中定义的系统调用.函数  sys_init_module 使用vmalloc 来分配内核内
            存来存放模块,它接着拷贝模块代码到该区域, 在内核符号表查找引用,并调用模块的初始化函数来启动。
            在内核代码,会发现系统调用的名子都以 sys_ 为前缀。
            若出现 ‘unresolved symbols’ 时尝试下 modprobe工具。

    卸载:
        rmmod 工具从内核去除模块。
            如果内核认为模块还在用或者内核被配置成不允许模块去除,模块去除会失败.
            当然,可以配置内核允许"强行"去除模块,此时可能有严重的情况发生了。

    查看模块信息:
        lsmod  程序生成一个内核中当前加载的模块的列表.
        vi /proc/modules
        vi /sys/module  当前加载的模块的信息

    版本依赖:
        编译模块给一个特定的内核版本, 需要使用这个特定版本建立的系统和源码树。简单修改 KERNELDIR 变量, 就完成这个动作.
        在 linux/version.h 定义了版本.
    平台依赖:略。

2.5 内核符号表
    1 内核符号表包含了所有的全局内核项(即全局函数和变量)
    2 当使用堆叠的模块时, 使用modprobe工具是有帮助的.他与insmod相似。
    3 如果本模块需要输出符号给其他模块使用,应当使用下面的宏定义: 符号必须在模块文件的全局部分输出
        EXPORT_SYMBOL(name);
        EXPORT_SYMBOL_GPL(name);

2.6 预备知识
    几乎所有的模块都要包含如下两行:
        #include <linux/module.h>       包含了大量加载模块需要的函数和符号的定义
        #include <linux/init.h>         指定初始化和清理函数

    其他可包含的内容:
        moudleparam.h                       使得可以在模块加载时传递参数给模块.
        MODULE_LICENSE      ("GPL");        不包含则内核就假定它是私有的。 它可放于文件的任何位置。
        MODULE_AUTHOR       (声明谁编写了模块)
        MODULE_DESCRIPION   (一个人可读的关于模块做什么的声明)
        MODULE_VERSION      (一个代码修订版本号)
        MODULE_ALIAS        (模块为人所知的另一个名子)
        MODULE_DEVICE_TABLE (告知用户空间模块支持那些设备)

    初始化:
        static int __init initialization_function(void)    static:因为它们不会在特定文件之外可见,没有硬性规定这个。
        {
            /* Initialization code here */
        }
        module_init(initialization_function);

        __init:
            其 暗示,函数只在初始化使用.模块加载后会丢掉这个初始化函数,使它的内存可做其他用途.
            一个类似的标签(__initdata)只在初始化时用的数据.
        moudle_init
            他是强制使用的.这个宏定义增加了特别的段到模块目标代码中,表明在哪里找到模块的初始化函数.

    初始化错误处理
        1 注册可能失败.  即便最简单的内存分配.必须一直检查返回值,并且确认要求的操作全部经成功.
        2 若出现错误,要判断是否继续,若不继续,这要注销掉之前注册的动作。
        3 错误恢复有时用goto语句。
        4 <linux/errno.h> .  在Linux内核里包含了错误码,使用该错误码有好处,因为用户程序能够把它们转变为有意义的字串。

    注销函数
        static void __exit cleanup_function(void)  它注销接口,  在模块被去除之前返回所有资源给系统.
        {
            /* Cleanup code here */                 以相反的顺序注销注册的动作
        }
        module_exit(cleanup_function);

        void. __exit
            清理函数没被声明为void, __exit:标识这个代码是只用于模块卸载(
        moudle_exit
            声明对于使得内核能够找到你的清理函数是必要的.
            如果你的模块没有定义一个清理函数,  内核不会允许它被卸载.

    小技巧:
        查找 EXPORT_SYMBOL 遍可找到由不同驱动提供的入口点.
        查找 register_ 也可以,因为大部分注册函数以register_ 做前缀。

    模块参数:
        可以在命令行输入运行参数。应该包含文件moduleparam.h

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值