《linux内核设计与分析》内核模块编程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《linux内核设计与分析》内核模块编程相关的知识,希望对你有一定的参考价值。
一、实验环境
虚拟机:VMware Workstation 12.0;
操作系统:ubuntu16.04(32位);
当前内核版本:4.4.0-21-generic
二、知识储备
现在,先让我们了解一下什么是内核模块:
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
比较一下应用程序和内核模块的差别:
接下来,让我们先写一个小程序,以这个作为起点,来探索如何编写内核模块:
hello.c
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/init.h> 4 /* 必要的头文件*/ 5 6 static int __init hello_init( void ) 7 { 8 printk(“Hello,World! \\n”); 9 return 0; 10 } 11 12 static void __exit hello_exit( void ) 13 { 14 printk(“Finish!\\n”); 15 } 16 module_init(lkp_init); 17 module_exit(lkp_cleanup); 18 19 MODULE_LICENSE(“GPL”);
简单的分析一下:
第1行:linux/module.h是必要的头文件,内核模块代码中必须包含。
第2行:linux/kernel.h包含了常用的内核函数。
第3行:linux/init.h含了宏_init和_exit,它们允许释放内核占用的内存。
第6行:hello_init()函数是模块的入口点,是模块的初始化函数,它必需包含诸如要编译的代码、初始化数据结构等内容。它通过module_init()例程注册到系统中,在内核装载时被调用。模块的所有初始化函数必须符合这样的形式:int xx_init(void);因为init函数通常不会被外部函数直接调用,所以你不必导出该函数,故它可标记为static。Init函数会返回一个int型数值,如果初始化顺利完成,那么它的返回值为0;否则返回一个非零值。
第8行:Printk的功能类似于C语言中的printf,这个函数是由内核定义的。
第12行:hello_exit()函数是模块的出口函数,它有module_exit()例程注册到系统。在模块从内核卸载时,内核就会调用hello_exit()。退出函数可能会在返回前负责清理资源,以保证硬件处于一致状态;或者做其他的一些操作。退出函数必须符合这样的形式:void xx_exit(void),也可以标记为static。
第16/17行:函数module_init()和cleanup_exit()是模块编程中最基本也是必须的两个函数。调用module_init()不是真正的函数调用,而是一个宏调用,它唯一的参数便是模块的初始化函数。Module_exit()同,唯一的参数是出口函数。
第19行:MODULE_LICENSE(“GPL”);宏用于指定模块的版权。
代码已经有了,但是我们并不用编译c代码的方式gcc来编译它,因为在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话会非常不方便。因此,人们通常利用 make工具来自动完成编译工作。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。Makefile写法如下:
1 obj-m:=hello.o //产生hello模块的目标文件 2 CURRENT_PATH:=$(shell pwd)//模块所在的当前路径 3 LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic//linux内核源代码的绝对路径 4 all: 5 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules //编译模块 6 clean: 7 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean //清理
注意: 在每个命令前(例如make命令前)要键入一个制表符(按TAB键产生)
有了Makefile,执行make命令,会自动形成相关的后缀为.o和.ko文件。
备注:如何查看你的内核版本?
命令:uname –a
模块编译好后,将它插入内核:(需要root权限)
insmod hello.ko
输入dmesg可以查看程序的输出。
dmesg
当不需要模块时,对模块进行卸载:
rmmod hello
三、进阶模块
学姐的代码,可以直接运行,功能是输出当前进程的相关信息。
代码:1.c
1 #include<linux/init.h> 2 #include<linux/module.h> 3 #include<linux/kernel.h> 4 #include<linux/sched.h> 5 6 static struct task_struct *pcurrent; 7 static int __init print_init(void) 8 { 9 printk(KERN_INFO "print current task info\\n"); 10 printk("pid\\ttgid\\tprio\\tstate\\n"); 11 for_each_process(pcurrent){ 12 printk("%d \\t",pcurrent->pid); 13 printk("%d \\t",pcurrent->tgid); 14 printk("%d \\t",pcurrent->prio); 15 printk("%ld \\n",pcurrent->state); 16 } 17 return 0; 18 } 19 static void __exit print_exit(void) 20 { 21 printk(KERN_INFO "Finished\\n"); 22 } 23 module_init(print_init); 24 module_exit(print_exit);
Makefile文件:
1 obj-m:=1.o 2 CURRENT_PATH:=$(shell pwd) 3 LINUX_KERNEL_PATH:=/usr/src/linux-headers-4.4.0-21-generic 4 all: 5 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules 6 clean: 7 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
运行结果:
四、高级模块
①列出当前进程虚拟内存的空间的各段权限。
②查找指定虚拟地址的所在的vma,并打印该段的起始地址、终止地址和段标志。③找出指定虚拟地址对应的物理地址。
④建立文件,进行代码的测试。
源代码来源于网络,但由于其内核版本过老,许多函数接口已经发生变化,代码并不能使用,所以现在正在改进,敬请期待~~
以上是关于《linux内核设计与分析》内核模块编程的主要内容,如果未能解决你的问题,请参考以下文章