linux内核模块

  • 一个简单的驱动

  模块的使用能使linux内核便于裁剪,根据不同的应用需求得到一个最小的内核,同时调试内核驱动也更为方便,比如如果调试i2c驱动,如果不采用模块的方式,那么每次修改i2c驱动就得编译整个内核,对于编译调试极其耗时,使用模块,一个简单的insmod就将模块加载进了内核,如果觉得不合适,需要调试,只需要rmmod就可以将模块卸载。

  一个简单的驱动模块:

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 MODULE_LICENSE("Dual BSD/GPL");
 4
 5 static int __init test_init(void)
 6 {
 7     printk(KERN_ALERT"test modules init\n");
 8     return 0;
 9 }
10
11 static void __exit test_exit(void)
12 {
13     printk(KERN_ALERT"test modules exit\n");
14 }
15
16
17 module_init(test_init);
18 module_exit(test_exit);

  这个驱动什么都没干,就打印了几句话,但是具备了驱动程序最基本的几个要素:

  1.  驱动初始化函数 XXX_init(void),使用宏module_init(test_init);告诉内核这是模块的初始化函数入口。
  2.  驱动退出函数XXX_exot(void),使用宏module_exit(test_exit);告诉内核这是模块的退出函数入口。
  3. 驱动模块遵循的协议 MODULE_LICENSE("Dual BSD/GPL")

  注意到初始化函数和退出函数分别被__init,__exit修限定,其实不用它们来限定2个函数也是可以的,那么用不用这2个东西限定有什么区别呢?首先看它们到底是2个什么东西:

1 #define __init        __section(.init.text) __cold notrace
2 #define __exit          __section(.exit.text) __exitused __cold notrace

  是2个宏,其中__section也是个宏

1 # define __section(S) __attribute__ ((__section__(#S)))

  __attribute__是针对gcc的特有关键字,__attribute__ 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute ),我见的比较多的是在设置数据结构的对齐属性上:

1 struct S {
2
3 short b[3];
4
5 } __attribute__ ((aligned (8)));

  在这里设置为.init.text是指把这个函数放到特殊的.init.text段,看过汇编代码就可能有映象.text段,也就是代码段,函数都是放这个段的。.init.text段的特性是等模块加载完了,这个段将被释放,因为就好比我们写一个单片机程序一样,一般都会有个init函数,它完了就是个死循环,驱动实际在运行过程中XXX_init函数就没有什么作用了,因此就可以把XXX_init函数加载到内存的那段释放掉,腾出空间来给别人用。

  __exit就是在卸载模块完成后,将它标识的函数所占用的内存释放。

  除了__init,__exit,还有__initdata,__exitdata。作用基本类似,我原本猜想带data的应该是修饰仅在初始化函数和退出函数里面用到的全局变量,比如一个数组之类的东西,但是我试了用__initdata修饰函数好像编译并不报错,暂时不深究。

  MODULE_LICENSE("Dual BSD/GPL"),少了它不影响编译,但是加载模块的时候会警告提示这个模块污染了内核:

  [22321.826339] test: module license ‘unspecified‘ taints kernel.
  [22321.826349] Disabling lock debugging due to kernel taint
  [22321.826759] test modules init

  • 编译并加载,卸载这个驱动

  编译模块的makefile

 1 ifneq ($(KERNELRELEASE), )
 2     obj-m := test.o book.o
 3 else
 4      KERNELDIR ?= /lib/modules/$(shell uname -r)/build
 5     PWD := $(shell pwd)
 6 defualt:
 7     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
 8 endif
 9
10 clean:
11         rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers
12
13 .PHONY:clean

  加载模块:  

1 sudo insmod test.ko

  为了图方便,使用一个简单的shell脚本监视dmesg的输出,脚本如下:

1 #!/bin/bash
2
3 set -o nounset                              # Treat unset variables as an error
4
5 while true
6 do
7     dmesg -c
8     sleep 1
9 done

  卸载模块:

1 sudo rmmod test

  卸载模块不需要后面的.ko。

  • 指定和导出模块的参数

  参数在驱动里面必须是一个全局变量,一般加static限定。

  声明参数,使用宏:

1 module_param(<参数名>, <参数类型>, <参数读写权限>);

  参数类型支持:byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或 invbool(布尔的反)

  例如:

1 static int num = 5;
2
3 module_param(num, int, S_IRUGO);
4
5 static int __init test_init(void)
6 {
7     printk(KERN_ALERT"num:%d\n",num);
8     return 0;
9 }

  如果在insmod的时候,指定num的值:

1 insmod num=10

  那么输出:num:10

  需要注意的是,参数在声明时要给一个默认值,因为如果insmod指定参数值,那么它就使用这个默认值。

  一个参数(在驱动里面的一个全局变量)怎么会有读写权限?加载这个模块后,进入/sys/module目录,会看到一个test的目录,这个目录记录了这个test模块的一些信息,与未加模块参数的时候相比,它多出了一个parameters的目录,进如这个目录,发现会有个与模块参数同名的num文件,cat它里面的内容就是它的默认值5,再查看这个num文件的权限:

  -r--r--r-- 1 root root 4096 5月   3 21:30 num
  和我在代码中指定的一样,原来权限就是指这个文件的权限,参数与之对应的一个文件,也就是说参数的默认值是可以被更改的,只要权限拿到了,而我们使用insmod num=10的方法并不是修改这个默认值(很明显,我指定num为10成功了,但是我并没有对num文件的写权限),只是在加载模块时覆盖变量的值。

  使用 cat /proc/kallsyms | grep "\[test\]"查看与test.ko这个模块相关的符号:

1   00000000 t test_exit    [test]
2   00000000 r __param_num    [test]
3   00000000 r __param_str_num    [test]
4   00000000 d num    [test]
5   00000000 d __this_module    [test]
6   00000000 t cleanup_module    [test]

  可以看到2个我们预料中的符号,num和test_exit,如果我们不使用__init限定test_init,那么可以看到test_init也在里面,也就证明了__init的作用是在调用初始化函数后释放了与之对应的内存:

1 00000000 t test_init    [test]
2 00000000 t test_exit    [test]
3 00000000 r __param_num    [test]
4 00000000 r __param_str_num    [test]
5 00000000 d num    [test]
6 00000000 d __this_module    [test]
7 00000000 t cleanup_module    [test]
8 00000000 t init_module    [test]

  使用module_param是指外面可以传一个值进来,如果要这个值对外可见,我们明显不能采用去掉static限定的办法,而是使用EXPORT_SYMBOL(num);这个宏是指将模块内的全局变量num对外可见,它不仅仅可以用于变量也可以对函数起作用。

模块A:

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 MODULE_LICENSE("Dual BSD/GPL");
 4
 5 static int num = 5;
 6 module_param(num, int, S_IRUGO);
 7
 8 static int __init test_init(void)
 9 {
10     printk(KERN_ALERT"test modules init\n");
11     return 0;
12 }
13
14 static void test_saymyname(void)
15 {
16     printk(KERN_ALERT"you call me\n");
17 }
18 static void __exit test_exit(void)
19 {
20     printk(KERN_ALERT"test modules exit\n");
21 }
22
23
24 module_init(test_init);
25 module_exit(test_exit);
26 EXPORT_SYMBOL(test_saymyname);

模块B调用模块A的 test_saymyname

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 MODULE_LICENSE("Dual BSD/GPL");
 4
 5 extern void test_saymyname(void);
 6
 7 static int __init book_init(void)
 8 {
 9     test_saymyname();
10     return 0;
11 }
12
13 static void __exit book_exit(void)
14 {
15     printk(KERN_ALERT"book modules exit\n");
16 }
17
18 module_init(book_init);
19 module_exit(book_exit);

分别insmod test.ko和insmod book.ko,再卸载模块时,如果先卸载test.ko会发现提示test.ko里面有函数被book.ko调用,无法卸载,只有先卸载book.ko才行。

时间: 2024-08-28 04:53:16

linux内核模块的相关文章

3、Linux内核模块学习

一.内核模块的学习   内核的整体框架是非常的大,包含的组件也是非常多,如何将需要的组件包含在内核中呢?选择一,就是将所有的组件全部编译进内核,虽然需要的组件都可以使用,但是内核过分庞大,势必带来效率影响:选择二是,将组件编译为模块,需要的时候,就自行加载进内核,这种就是我们称之为的模块,当模块被加载到内核的机制,不仅控制了内核大小,同时被加载的内核与被编译进内核的部分,功能意义.    3.1.内核的加载与卸载     将 hello.c 编译为模块,hello.ko, insmod hell

Linux内核模块编写详解

内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了.Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统,本文给大家介绍linux内核模块编写,需要的朋友可以参考下 内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了.Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统.浮点运算做起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,

linux 内核模块函数调用

在编写linux内核模块的时候,有时候我们需要调用一只内核模块里面的函数,然而如果是在不同目录下面编译生成的内核模块,此时A模块去调用B模块的函数时候会出现函数未定义,无法调用的情况.那么以前我是在同一个目录下面,先后写两个makefile,然后编译生成两个不同的内核模块,这种方式可以正常实现A模块调用B模块里面的函数,不过非常麻烦.本博文将会针对这种情况提出一种可以同时生成多个内核模块,不要再次编译的方面,下面贴出源码: 内核模块cal.ko: #include <linux/module.h

Linux内核模块编程与内核模块LICENSE -《详解(第3版)》预读

Linux内核模块简介 Linux内核的整体结构已经非常庞大,而其包含的组件也非常多.我们怎样把需要的部分都包含在内核中呢?一种方法是把所有需要的功能都编译到Linux内核.这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核. 有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?Linux提供了这样的一种机制,这种机制被称为模块(Module).模块具有这样的特点. 模块本

Linux内核模块简介

1. 宏内核与微内核 内核(Kernel)在计算机科学中是操作系统最基本的部分,主要负责管理系统资源.中文版维基百科上将内核分为四大类:单内核(宏内核):微内核:混合内核:外内核. 混合内核实质上也是微内核,而外内核是一种比较极端的设计方法,目前还处于研究阶段,所以我们就着重讨论宏内核与微内核两种内核. 简单的介绍,宏内核(Monolithickernel)是将内核从整体上作为一个大过程来实现,所有的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效.微内核(Microkernel)功

Linux内核模块编程与内核模块LICENSE -《具体解释(第3版)》预读

Linux内核模块简单介绍 Linux内核的总体结构已经很庞大,而其包括的组件或许多.我们如何把须要的部分都包括在内核中呢?一种方法是把全部须要的功能都编译到Linux内核.这会导致两个问题.一是生成的内核会很大,二是假设我们要在现有的内核中新增或删除功能,将不得不又一次编译内核. 有没有一种机制使得编译出的内核本身并不须要包括全部功能,而在这些功能须要被使用的时候,其相应的代码被动态地载入到内核中呢?Linux提供了这样的一种机制,这样的机制被称为模块(Module).模块具有这样的特点. 模

Smart210学习记录-------linux内核模块

Linux 驱动工程师需要牢固地掌握 Linux 内核的编译方法以为嵌入式系统构建可运行的Linux 操作系统映像.在编译 LDD6410 的内核时,需要配置内核,可以使用下面命令中的 一个: #make config(基于文本的最为传统的配置界面,不推荐使用) #make menuconfig(基于文本菜单的配置界面) #make xconfig(要求 QT 被安装) #make gconfig(要求 GTK+被安装) 在配置Linux 2.6内核所使用的make config.make me

Linux内核模块文件组成介绍

作者:华清远见讲师 Linux驱动开发主要的工作就是编写模块,一个典型的Linux内核模块文件.ko 主要由以下几个部分组成. 模块加载函数(必须) 当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作. Linux内核模块加载函数一般用static 关键字声明为内部链接,并以__init 标识.之所以标识为__init ,用途是如果编译内核时模块是以静态方式包含在vmlinux中,则在链接的时候标识为__init 的函数会放在.in

linux内核模块相关命令:lsmod,depmod,modprobe,modinfo,insmod,rmmod 使用说明

加载内核驱动的通常流程: 1.先将.ko文件拷贝到/lib/module/`uname -r`(内核版本号)/kernel/driver/...目录下, 根据具体用途的区别分为net.ide.scsi.usb.video.parport.md.block.ata等等. 2.运行depmod -a,更新模块依赖新,主要是更新modules.dep文件 3.运行modprobe加载内核模块 lsmod 功能:列出内核已载入模块的状态 用法:lsmod 描述: lsmod 以美观的方式列出/proc/

linux 内核模块的编写,插入,显示及卸载

环境:ubuntu 8.04 内核版本:2.6.32.59-debug 1.编写文件hello.c #include <linux/init.h> #include <linux/kernel.h> //printk /*写内核驱动的时候 必须加载这个头文件,作用是动态的将模块加载到内核中去,常用的宏定义如 MODULE_LICESENCE(),MODULE_AUTHOR(),等在此文件中*/ #include <linux/module.h> MODULE_LICEN