(四) linux内核模块编程

  Linux 就是通常所说的单内核(monolithic kernel),即操作系统的大部分功能都被称为内核,并在特权模式下运行。通过 Linux 内核模块(LKM)可以在运行时动态地更改 Linux。Linux可加载内核模块(从内核的 1.2 版本开始引入)是 Linux 内核的最重要创新之一。它们提供了可伸缩的、动态的内核。探索隐藏在可加载模块后面的原理,并学习这些独立的对象如何动态地转换成 Linux 内核的一部分。

 (1) linux 内核模块的创建

  LKM 包含 entry 和 exit 函数。当向内核插入模块时,调用 entry 函数,从内核删除模块时则调用 exit 函数。因为 entry 和 exit 函数是用户定义的,所以存在 module_initmodule_exit 宏,用于定义这些函数属于哪种函数。LKM 还包含一组必要的宏和一组可选的宏,用于定义模块的许可证、模块的作者、模块的描述等等,见下例。

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


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

static void __exit hello_3_exit(void)
{
        printk(KERN_ALERT "Goodbye, world 
" );
}

MODULE_LICENSE("GPL");
module_init(hello_3_init);
module_exit(hello_3_exit);

(2)构建linux 内核模块

  编译模块的make file 必须是Makefile,不能是makefile,并且要生成模块必须先编译通过。 内核模块的makefile如下

  ifneq ($(KERNELRELEASE),)

  obj-m := mytest.o

  mytest-objs := file1.o

  else
  
  KVER ?= $(shell uname -r)


  KDIR :
= /lib/modules/$(shell uname -r)/build   PWD := $(shell pwd)   default:   $(MAKE) -C $(KDIR) M=$(PWD) modules

  clean:

  
  rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions   endif

  1> KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义。所以执行else之后内容。

  2> 当执行make 时候 执行默认目标defalult。

    $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD)   指示路径;表明然后返回到当前目录继续读入、执行当前的Makefile。

    $(KVER)  要生成的模块内核版本 当前系统版本

  3> 当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。

    mytest-objs := file1.o  表示mytest.o 由file1.o 连接生成

    obj-m := mytest.o 表示编译连接后将生成mytest.o模块 mytest.ko。

(3) 编译  安装 卸载

 1> 编译 执行make 生成mytest.ko内核模块

 2> 安装内核模块 sudo insmod mytest.ko  

 3> 查看内核模块输出 dmesg -

 4> 卸载内核模块 sudo rmmod mytest.ko  

 注意:Linux命令dmesg用来显示开机信息,kernel会将开机信息存储在ring buffer中。您若是开机时来不及查看信息,可利用dmesg来查看。开机信息亦保存在/var/log目录中,名称为dmesg的文件里。

(4)内核模块的启动参数

  像我们普通的应用程序一样,有main函数启动可以有参数,内核模块启动时候也可以使用参数。这点很重要,如在视频压缩中通过最优参数达到不同压缩效率。先看一个例子 我们在解释其中的代码。

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>

#define MAX_ARRAY 66
static int int_var = 0;

static char *str_var = "default";
static int int_array[6];
int narr;

module_param(int_var, int, 0644);
MODULE_PARM_DESC(int_var, "A integer variable");

module_param(str_var,charp,0644);
MODULE_PARM_DESC(str_var, "A string variable");

module_param_array(int_array, int, &narr, 0644);
MODULE_PARM_DESC(int_array, "A integer array");

static int __init hello_init(void)
{
     int i;
     printk(KERN_ALERT "Hello, my LKM.
");
     printk(KERN_ALERT "int_var %d.
", int_var);
     printk(KERN_ALERT "str_var %s.
", str_var);
     for(i = 0; i < narr; i ++)
     {
        printk("int_array[%d] = %d
", i, int_array[i]);
     }
     return     0;
}
static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Bye, my LKM.
");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This module is a example.");

 方式一:在内核模块里,参数的用法如同全局变量。

    module_param(name, type, perm);

    name  用户看到的参数名,又是模块内接受参数的变量

    type  表示参数的数据类型,下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool

    perm 指定了在sysfs中相应文件的访问权限

  eg:

      static unsigned int int_var = 0;

    module_param(int_var, uint, S_IRUGO);

方式二:模块源文件内部的变量名与外部的参数名有不同的名

    module_param_named(name, variable, type, perm);

    name 外部可见的参数名

    variable 源文件内部的全局变量名

方式三:字符串

    static char *name;

    module_param(name, charp, 0); 

  或者

    static char species[BUF_LEN];

     module_param_string(name, string, len, perm);

方式四:数组  

    static int finsh[MAX_FISH];

    static int nr_fish;

    module_param_array(fish, int, &nr_fish, 0444); //最终传递数组元素个数存在nr_fish中

编译生成内核模块

装载内核模块 sudo insmod parm.ko int_var=100 str_var=hello int_array=100,200

 

  

原文地址:https://www.cnblogs.com/wolfrickwang/p/3261950.html