第四章Linux内核模块之一(内核模块简介)

本章导读

Linux设备驱动会以内核模块的形式出现,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。

4.1 Linux内核模块简介

Linux内核的整体架构非常庞大,其包含的组件也非常多。怎样把需要的部分都包含在内核中呢?

一种方法是把所有需要的功能都编译到Linux内核中。这会导致两个问题,一是生成的内核会很大,二是如果要在现有的内核中新增或删除功能,将不得不重新编译内核。

有没有另一种机制可使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?

Linux提供了这样的机制,这种机制被称为模块(Module)。模块具有如下的特点:

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

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

为了使读者初步建立对模块的感性认识,先来看一个最简单的内核模块“Hello World”,如代码清单4.1所示,源代码文件:

#include <linux/init.h>  

#include <linux/module.h> 


#define DRIVER_AUTHOR "[email protected]"
#define DRIVER_DESC   "A sample driver" 

static int __init hello_init(void)
{
    printk(KERN_INFO "----hello_init----\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "----hello_exit----\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

MODULE_ALIAS(DRIVER_DESC);

Makefile文件:

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:                               
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:                                             
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
else
    obj-m := hello_world.o

endif

    这个最简单的内核模块只包含内核模块加载函数、卸载函数和对GPL v2许可权限的声明以及一些描述信息,编译它会产生
hello_world.ko目标文件,通过“sudo insmod hello_world.ko”命令可以加载它,通过“sudo rmmod hello_world”命令可以卸载它,加载时输出“----hello_init----”,卸载时输出“----hello_exit----”。

    内核模块中用于输出的函数是内核空间的printk()而不是用户空间的printf(),printk()的用法和printf()基本相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段。

在Linux中,使用lsmod命令可以获得系统中已加载的所有模块以及模块之间的依赖关系,例如:

Module                  Size  Used by
hello_world            16384  0
intel_rapl             20480  0
x86_pkg_temp_thermal    16384  0
intel_powerclamp       16384  0
coretemp               16384  0
kvm_intel             172032  0
joydev                 20480  0
input_leds             16384  0
kvm                   548864  1 kvm_intel

lsmod命令实际上是读取并分析“/proc/modules”文件,与上述lsmod命令结果对应的“/proc/modules”文件如下:

$ cat /proc/modules

hello_world 16384 0 - Live 0x0000000000000000 (OE)
intel_rapl 20480 0 - Live 0x0000000000000000
x86_pkg_temp_thermal 16384 0 - Live 0x0000000000000000
intel_powerclamp 16384 0 - Live 0x0000000000000000
coretemp 16384 0 - Live 0x0000000000000000
kvm_intel 172032 0 - Live 0x0000000000000000
joydev 20480 0 - Live 0x0000000000000000
input_leds 16384 0 - Live 0x0000000000000000
kvm 548864 1 kvm_intel, Live 0x0000000000000000

内核中已加载模块的信息也存在于/sys/module目录下,加载hello_world.ko后,内核中将包含/sys/module/hello_world目录,该目录下又有一个refcnt(引用计数)文件和v和一个sections(段)目录,如下:

ubuntu@ubuntu2018:/sys/module/hello_world$ cd sections/
ubuntu@ubuntu2018:/sys/module/hello_world/sections$ ls -al
total 0
drwxr-xr-x 2 root root    0 May 14 14:03 .
drwxr-xr-x 5 root root    0 May 14 14:00 ..
-r--r--r-- 1 root root 4096 May 14 14:03 .exit.text
-r--r--r-- 1 root root 4096 May 14 14:03 .gnu.linkonce.this_module
-r--r--r-- 1 root root 4096 May 14 14:03 .init.text
-r--r--r-- 1 root root 4096 May 14 14:03 .note.gnu.build-id
-r--r--r-- 1 root root 4096 May 14 14:03 .rodata.str1.1
-r--r--r-- 1 root root 4096 May 14 14:03 .strtab
-r--r--r-- 1 root root 4096 May 14 14:03 .symtab

在/sys/module/hello_world目录下运行“tree–a”可得到如下目录树:

xiezhi@ubuntu2018:/sys/module/hello_world$ tree -a
.
├── coresize
├── holders
├── initsize
├── initstate
├── notes
│   └── .note.gnu.build-id
├── refcnt
├── sections
│   ├── .exit.text
│   ├── .gnu.linkonce.this_module
│   ├── .init.text
│   ├── .note.gnu.build-id
│   ├── .rodata.str1.1
│   ├── .strtab
│   └── .symtab
├── srcversion
├── taint
├── uevent

└── version

3 directories, 16 files

使用modinfo<模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic:

modinfo hello_world.ko

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$ modinfo hello_world.ko
filename:       /home/ubuntu/share/my_work/linux_drivers/three/hello_world.ko
alias:          A sample driver
description:    A sample driver
author:         [email protected]
license:        GPL v2
version:        v1.0
srcversion:     C630BE3949B6861A20E54C0
depends:        
vermagic:       4.4.0-116-generic SMP mod_unload modversions retpoline 

modprobe命令比insmod命令要强大,它在加载某模块时,会同时加载该模块所依赖的其他模块。使用modprobe命令加载的模块若以“modprobe -r filename”的方式卸载,将同时卸载其依赖的模块。模块之间的依赖关系存放在根文件系统的/lib/modules/<kernel-version>/modules.dep文件中,实际上是在整体编译内核的时候由depmod工具生成的,它的格式非常简单:

kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko
kernel/lib/pm-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko
kernel/lib/lru_cache.ko:
kernel/lib/cordic.ko:
kernel/lib/rbtree_test.ko:
kernel/lib/interval_tree_test.ko:
updates/dkms/vboxvideo.ko: kernel/drivers/gpu/drm/drm.ko

备注:

使用modprobe命令注意事项:

要想使用modprobe必须将ko模块文件拷贝到/lib/moudels/uname -r/kernel目录中,然后执行depmod -A,否则无法使用modprobe加载模块。

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$sudo cp hello_world.ko /lib/modules/4.4.0-116-generic/kernel/

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$sudo depmod -A

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$sudo modprobe hello_world(加载内核模块)

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$lsmod | grep hello_world

hello_world            16384  0

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$ sudo modprobe -r hello_world(卸载内核模块)

ubuntu@ubuntu2018:~/share/my_work/linux_drivers/three$lsmod | grep hello_world

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80284410