linux内核模块

一、模块组成

一个linux内核模块主要由如下几个部分组成:
1) 模块加载函数。当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。"module_init(函数名)",返回整型值,若初始化成功,返回0,初始化失败,返回错误编码。linux内核中,可以使用request_module(const char *fmt, ...)函数加载内核模块。

request_module(module_name); // 灵活加载其他内核模块

2)模块卸载函数。通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行。

3) 模块许可声明。LiCENSE声明描述内核模块的许可权限,如果不声明LiCENSE,模块被加载时,将受到内核被污染(kernel Tainted)的警告。

GPL
GPL v2
GPL and additional rights
Dual BSD/GPL
Dual MPL/GPL
Proprietary

大多数情况下,内核模块应遵循GPL兼容许可权。Linux内核模块最常用的是以MODULE_LICENSE("GPL v2")语句声明模块采用GPL v2。

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

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

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

导出的symbol可以在/proc/kallsyms找到,它记录了符号以及符号所在的内存地址。

一个模块mod1中定义一个函数func1;在另外一个模块mod2中定义一个函数func2,func2调用func1。
在模块mod1中,EXPORT_SYMBOL(func1);
在模块mod2中,extern int func1();
就可以在mod2中调用func1了。

6)模块声明与描述(可选)。对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,以表明该驱动模块所支持的设备。

二、模块参数

module_param(参数名, 参数类型, 参数读写权限);
module_param_array(数组名, 数组类型,数组长, 参数读写权限);

insmod module_name param=param_value

insmode不传递参数时,参数将使用模块内定义的缺省值。

如果模块被内置,就无法insmod了,但是bootloader可以通过在bootargs里设置“模块名.参数名=值”的形式给该内置的模块传递参数。

参数类型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool或invbool(布尔的反),在模块被编译时会将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。

模块被加载后,在/sys/module/目录下将出现以此模块名命名的目录。当参数读写权限为0时,表示此参数不存在sysfs文件系统下对应的文件节点。如果此模块存在“参数读/写权限”不为0的命令行参数,在此模块的目录下还将出现parameters目录,其中包含一系列以参数名命名的文件节点,这些文件的权限值就是传入module_param()的参数读写权限,而文件的内容为参数的值。

运行insmod或modprobe命令时,应使用逗号分隔输入的数组元素。

三、模块的使用计数

Linux2.6以后的内核提供了模块计数管理接口try_module_get(&module)和module_put(&module),从而取代了Linux2.4内核中的模块使用计数管理宏。模块的使用计数一般不必由模块自身管理,而且模块计数管理还要考虑SMP和PREEMPT机制的影响。
try_module_get(&module)返回0,表示调用失败,希望使用的模块没有被加载或正在被卸载中。

Linux2.6以后的内核为不同类型的设备定义了struct module *owner域,用来指向管理此设备的模块。当开始使用某个设备时,内核使用try_module_get(dev->owner)去增加管理此设备的owner模块的使用计数;当不再使用此设备时,内核使用module_put(dev->owner)减少对管理此设备的管理模块的使用计数。这样,当设备在使用时,管理此设备的模块将不能被卸载。只有当设备不再被使用时,模块才允许被卸载。

对设备驱动而言,很少需要亲自调用try_module_get()与module_put(),因为此时开发人员所写的驱动通常为支持某具体设备的管理模块,对此设备owner模块的计数管理由内核里更底层的代码(如总线驱动或是此类设备共用的核心模块)来实现,从而简化设备驱动开发。

四、模块命令

lsmod/insmod/rmmod/modinfo/modprobe

$ lsmod | grep helloworld
helloworld             16384  0

$ sudo rmmod helloworld
$ sudo modprobe helloworld
$ sudo insmod helloworld

Mar 20 23:13:47 ubuntu-wang kernel: Goodbye, world!
Mar 20 23:13:47 ubuntu-wang sudo[8621]: pam_unix(sudo:session): session closed for user root
Mar 20 23:13:56 ubuntu-wang sudo[8624]:     wang : TTY=pts/5 ; PWD=/home/wang/kernel/modules/hellox86 ; USER=root ; COMMAND=/sbin/insmod helloworld
Mar 20 23:13:56 ubuntu-wang sudo[8624]: pam_unix(sudo:session): session opened for user root by wang(uid=0)
Mar 20 23:13:56 ubuntu-wang sudo[8624]: pam_unix(sudo:session): session closed for user root
Mar 20 23:14:02 ubuntu-wang sudo[8631]:     wang : TTY=pts/5 ; PWD=/home/wang/kernel/modules/hellox86 ; USER=root ; COMMAND=/sbin/insmod helloworld.ko
Mar 20 23:14:02 ubuntu-wang sudo[8631]: pam_unix(sudo:session): session opened for user root by wang(uid=0)
Mar 20 23:14:02 ubuntu-wang kernel: Hello, world!
$ modinfo helloworld.ko
filename:       /home/wang/kernel/modules/hellox86/helloworld.ko
license:        GPL v2
vermagic:       4.4.0-21-generic SMP mod_unload modversions
depends:
srcversion:     8803357E27B7FC988B73113

lsmod命令可以获取系统中已加载的所有模块以及模块间依赖关系。lsmod命令实际上是读取并分析"/proc/modules"文件。内核中已加载模块的信息也存在于/sys/module目录下,如加载hello.ko,则在/sys/module/hello目录包含模块相关内容。

modprobe在加载某模块时,会同时加载该模块所依赖的其它模块。使用modprobe命令加载的模块若以"modprobe -r filename"的方式卸载,将同时卸载其依赖的模块。模块之间的依赖关系存放在根文件系统的/lib/modules//modules.dep文件中,实际上是在整体编译内核的时候由depmod工具生成的。

modprobe提示无法打开“modules.dep”这个文件,因此驱动挂载失败了。我们不用手动创建 modules.dep这个文件,直接输入depmod命令即可自动生成modules.dep,有些根文件系统可能没有depmod这个命令,如果没有这个命令就只能重新配置busybox,使能此命令,然后重新编译 busybox。输入“depmod”命令以后会自动生成modules.alias、modules.symbols 和 modules.dep 这三个文件。拷贝驱动文件到/lib/modules/4.1.15目录后可能需要重启板卡。

五、模块使用

ubuntu20.04中,x86系统构建的编译环境存放在/lib/modules/$(uname -r)/build下,实际衔接到了/usr/src/$(uname -r)

$ ls /lib/modules/5.13.0-35-generic/build -al
lrwxrwxrwx 1 root root 40 Mar  7 15:40 /lib/modules/5.13.0-35-generic/build -> /usr/src/linux-headers-5.13.0-35-generic

在引用内核头文件时,经常会用到#include <asm/xxx>,这个与平台有关系,内核在编译时会自动生成链接。

$ ls /lib/modules/5.13.0-35-generic/build/arch/x86/include/ -al
total 20
drwxr-xr-x 3 root root 4096 Mar 10 09:50 .
drwxr-xr-x 6 root root 4096 Mar 10 09:50 ..
lrwxrwxrwx 1 root root   65 Mar  7 15:40 asm -> ../../../../linux-hwe-5.13-headers-5.13.0-35/arch/x86/include/asm
drwxr-xr-x 4 root root 4096 Mar 10 09:50 generated
lrwxrwxrwx 1 root root   66 Mar  7 15:40 uapi -> ../../../../linux-hwe-5.13-headers-5.13.0-35/arch/x86/include/uapi

vscode配置的c_cpp_properties.json示例如下:

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/src/linux-headers-5.13.0-35-generic/include",
                "/usr/src/linux-headers-5.13.0-35-generic/arch/x86/include",
                "/usr/src/linux-headers-5.13.0-35-generic/arch/x86/include/generated"
            ],
            "defines": [
                "__GNUC__",
                "__KERNEL__",
                "MODULE"
            ],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

注:可在defines中定义内核.config的相关的CONFIG,vscode不会导入Linux内核的.config文件,如:CONFIG_REGMAP

5.1 单模块单文件

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");
char *who = "world";

static int __init hello_init ( void )
{
    printk(KERN_ALERT "Hello, %s!\n", who);
    return 0;
}

static void __exit hello_exit ( void )
{
    printk(KERN_ALERT "Goodbye, %s!\n", who);
}

module_init(hello_init);
module_exit(hello_exit);

X86 Makefile

ARCH ?= x86
#CROSS_COMPILE  ?=  arm-linux-gnueabihf-

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

# Kernel modules
obj-m += helloworld.o

# Specify flags for the module compilation.包含调试信息
EXTRA_CFLAGS=-g -O0

PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
    -rm –rf *.o *~ core.depend .*.cmd *.ko *.mod.c
    make -C $(KERNELDIR) M=$(PWD) clean
$ sudo insmod helloworld.ko

$ journal -b
Mar 20 23:05:48 ubuntu-wang kernel: helloworld: module verification failed: signature and/or required key missing - tainting kernel
Mar 20 23:05:48 ubuntu-wang sudo[7983]: pam_unix(sudo:session): session closed for user root
Mar 20 23:05:48 ubuntu-wang kernel: Hello, world!

arm64 Makefile

#ARCH?=arm64
#CROSS_COMPILE   ?=    arm-linux-gnueabihf-
#CROSS_COMPILE?=/home/wang/openil/openil/output/host/usr/bin/aarch64-linux-gnu-

KERNELDIR ?= /home/wang/openil/openil/output/build/linux-OpenIL-linux-201810

obj-m += helloworld.o

PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=/home/wang/openil/openil/output/host/usr/bin/aarch64-linux-gnu-

clean:
    -rm –rf *.o *~ core.depend .*.cmd *.ko *.mod.c
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

目前测试开头的ARCH和CROSS_COMPILE不起作用,需要在命令中指定。

5.2 单模块多文件

obj-m := helloworld.o

helloworld-objs :=hello.o goo.o

hello.c可直接调用goo.c中的函数,只需在hello.c中声明即可。

5.3 多模块

obj-m := hello.o goo.o

注:其中,goo.c需要导出符号表,EXPORT_SYMBOL(goo),供hello.c调用。

$ cat Makefile
ARCH ?= x86
#CROSS_COMPILE   ?=    arm-linux-gnueabihf-

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

# Kernel modules
obj-m := helloworld.o goo.o

# Specify flags for the module compilation.
EXTRA_CFLAGS=-g -O0

PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
    -rm –rf *.o *~ core.depend .*.cmd *.ko *.mod.c
    make -C $(KERNELDIR) M=$(PWD) clean
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL v2");
char *who = "world";

extern int goo_add_int(int a, int b);

static int __init hello_init ( void )
{
        int res;

        res = goo_add_int(3, 4);
        printk(KERN_ALERT "Hello, %s and result = %d!\n", who, res);

        return 0;
}

static void __exit hello_exit ( void )
{
        printk(KERN_ALERT "Goodbye, %s!\n", who);
}

module_init(hello_init);
module_exit(hello_exit);

// goo.c
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL v2");
char *who = "goo world";

int goo_add_int(int a, int b)
{
        return a+b;
}

static int __init hello_init ( void )
{
        printk(KERN_ALERT "Hello, %s!\n", who);
        return 0;
}

static void __exit hello_exit ( void )
{
        printk(KERN_ALERT "Goodbye, %s!\n", who);
}

EXPORT_SYMBOL_GPL(goo_add_int);

module_init(hello_init);
module_exit(hello_exit);

六、创建设备文件

mknod /dev/chrdev c 200 0

七、上传下载文件

通过TFTP下载上传文件

tftp -gr test.txt 192.168.1.101 // 下载
tftp -pl test.txt 192.168.1.101 // 上传
#!/bin/bash

echo "param($#): $*"

if [ $# != 1 ]; then
        echo "$0 filename"
        exit -1
fi

tftp -gr $1 192.168.4.224

if [ $? == 0 ]; then
        echo "down [$1] success"
else
        echo "down [$1] failed"
fi

exit 0

八、示例demo

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>  
#include <linux/sched.h>    //sched.h要放在uaccess.h之前引用
//Since Linux kernel 4.11, variable init_task is declared in linux/sched/task.h, not in linux/sched.h
#include <linux/sched/task.h>
//support for_each_process
#include <linux/list.h>  

static int __init sched_fun_init(void)
{
	struct task_struct *p, *ts = &init_task;
	struct list_head *pos;
	int count = 0;

	list_for_each(pos, &ts->tasks) {
		p = list_entry(pos, struct task_struct,tasks);
		count++;
		printk("<1> pid = %ld----%s\n", p->pid, p->comm);
	}

	printk("<1> count = %d\n", count);

	return 0;
}


static void __exit sched_fun_exit(void)
{
	printk("<1> sched_fun_exit\n");
}


module_init(sched_fun_init);
module_exit(sched_fun_exit);

MODULE_LICENSE("Dual BSD/GPL");

参考:

  1. linux模块导出符号 EXPORT_SYMBOL_GPL&EXPORT_SYMBOL
posted @ 2016-06-09 22:42  yuxi_o  阅读(379)  评论(0编辑  收藏  举报