内核模块编写

* 内核动态模块的的功能和作用*

Linux 模块是一些可以独立于内核单独编译的内核函数和数据类型集合,是可增删的内核部分。模块在内核启动时装载称为静态装载,在内核已经运行时装载称为动态装载。模块可以扩充内核所期望的任何功能,但通常用于实现设备驱动程序.

模块最基本的框架

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

static ini __init 模块名_init(void)
{
/*
* 安装模块的初始化工作 ??
*/
return 0;
}

Static void __exit 模块名_exit(void)
{
/*
* 卸载模块前的清理工作 ??
*/
}

module_init(模块名_init);
module_exit(模块名_exit);

动态模块的编译

Makefile 文件的约定

预定义的宏:

obj-m := 模块名.o

预定义的命令:

make –C 内核源代码的安装路径 M=动态模块源代码路径 modules

将生成动态安装的模块文件:模块名.ko

例如:

obj-m := hello.o

all:

make -C /lib/modules/(shelluname?r)/buildM=(shell pwd) modules

clean:

make -C /lib/modules/(shelluname?r)/buildM=(shell pwd) clean

如果有多个目标模块:

obj-m := hello.o

hello-objs := a.o b.o

与动态模块有关的 Shell 命令

列出已安装的动态模块名:

lsmod

安装一个动态模块:

insmod 模块名.ko

卸载一个动态模块:

rmmod 模块名.ko

安装依赖模块:

modprobe

显示模块信息:

modinfo 模块名.ko

模块安装时携带的可选参数

#include <linux/moduleparam.h>
module_param(变量名,类型,权限)
支持的类型:
Byte.short,ushort,int,uint,long,ulong,bool,charp
例如在 hello.c 中加入:
#include <linux/moduleparam.h>
static int test;
module_param(test,int,0644);
用法 :
insmod hello.ko test=10

许可和文档有关的宏

MODULE_LICENSE(“GPL”);
MODULE_DESCRIPTION(“xxxxxxxx”);
MODULE_AUTHOR(“xxxxxxx”);

在模块中使用内核的/proc 接口

//建立一个 proc 目录文件:
struct proc_dir_entry *proc_mkdir(const char *,
    /*要创建的 proc 目录名*/
    struct proc_dir_entry *
    /*上级目录,NULL 代表/proc*/
);

//建立一个 proc 普通文件:
struct proc_dir_entry *create_proc_entry(const char *name,
/*要创建的 proc 文件名*/
mode_t mode,
/*proc 文件的权限*/
struct proc_dir_entry *parent/*上级目录,NULL 代表/proc*/
);
//删除一个 proc 文件:
void remove_proc_entry(const char *name, /*要删除的 proc 文件名*/
struct proc_dir_entry *parent /*上级目录,NULL 代表/proc*/
);

//proc 入口数据结构:
struct proc_dir_entry {
unsigned int low_ino;
/*目录入口的 inod 节点号*/
unsigned short namelen;
/*节点名长度*/
const char *name;
/*节点名*/
mode_t mode;
/*节点类型和权限*/
nlink_t nlink;
/*节点的连接数*/
uid_t uid;
/*拥有该节点的用户 uid*/
gid_t gid;
/*拥有该节点的组 gid*/
loff_t size;
/*节点的大小,除非限制长度否则为0*/

struct inode_operations * proc_iops;
/*对该节点的操作*/
const struct file_operations * proc_fops;
/*对该文件的操作*/
get_info_t *get_info;
/*如果定义,则当有读操作时调用*/
struct module *owner;
/*拥有该节点的模块*/
struct proc_dir_entry *next, *parent, *subdir;/*节点间的关系,初始为 NULL*/
void *data;
/*节点对应文件中内容*/
read_proc_t *read_proc;
/*读操作函数*/
write_proc_t *write_proc;
/*写操作函数*/
atomic_t count;
/* 节点引用计数 */
int deleted;
/* 节点删除标志 */
void *set;
};

//其中几个重要的字段:
owner=THIS_MODULE;
data=指向文件数据区(自己定义)
read_proc=指向你的读操作处理函数(自己定义)
write_proc=指向你的写操作处理函数(自己定义)

//读写操作函数的参数:
int (read_proc_t)(char *page,
/*存读出数据的地址*/

char **start, /*内核中不用*/
off_t off, /*读出数据结构在page中的偏量*/
int count, /*读出数据字节数*/
int *eof, /*文件尾标志*/
void *data); /*文件数据的地址*/

int (write_proc_t)(struct file *file,
/*通常不用*/
const char __user *buffer, /* 用 户 空 间 数 据 地 址 , 需 要 用
copy_from_user 写入*/
unsigned long count,
/*从用户空间写入的数据长度*/
void *data);
/*写入文件的地址*/

下面我们编辑一个Hello World模块

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

int init_module(void){
    printk("Hello Module!!\n");
    return 0;
}

void cleanup_module(void){
    printk("bye module!!\n");
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hello World!!");
MODULE_AUTHOR("bobo");

其中的Makefile文件为:

obj-m := hello.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

我们使用make命令进行编译,编译之后的结果是在目录中形成下面的文件:

下面我们将terminal终端的操作目录切换到当前目录下,动态加载该模块:

sudo insmod hello.ko

我们使用dmesg命令查看内核模块内容的输出:

dmesg

下面是我的模块的输出结果:

现在我们使用卸载模块的命令进行卸载:

sudo rmmod hello

模块输出信息为:

这是一个最简单的内核模块.

下面我们使用内核模块来创建proc文件,并向文件中写入内容.下面是我的程序的实现:

/********************************************
  编写创建proc文件系统的模块,该程序创建在/proc目录下
  创建mydir目录,在mydir目录下创建保存当前系统时间
  : jiffies 值的文件 myfile

***********************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
#include <linux/moduleparam.h>

#define MODULE_NAME "Myproc"
#define MYDATA_LEN 16

//放用户空间传入的数据
struct my_proc_data{
    char value[MYDATA_LEN];
};

struct my_proc_data mydata;

//proc结构变量
static struct proc_dir_entry *example_dir;
static struct proc_dir_entry *date_file;
static int param;
module_param(param,int,0644);

//读文件myfile的读驱动函数
static int proc_read(char *page,char **start,off_t off,int count,int *eof,void *data){
    int len = 0;

    struct my_proc_data *mydatap = (struct my_proc_data *)data;
    len += sprintf(page,"%s%ld\n",mydatap->value,jiffies);

    return len;
}

//写文件myfile的写驱动函数
static int proc_write(struct file *file,const char *buffer,unsigned long
 count,void *data){

    int len;

    struct my_proc_data *mydatap = (struct my_proc_data *)data;

    if(count > MYDATA_LEN)
        len = MYDATA_LEN;
    else
        len = count;

    if(copy_from_user(mydatap->value,buffer,len)){
        return -EFAULT;
    }

    mydatap->value[len-1] = ‘\0‘;

    return len;
}

//装入模块
int init_module(void){

    //创建/proc/myfile目录
    example_dir = (struct proc_dir_entry *)proc_mkdir("mydir",0);

    if(example_dir == 0){
        printk("mkdir fail\n");
        return -1;
    }

    //创建/proc/mydir/myfile文件
    date_file = (struct proc_dir_entry *)create_proc_entry("myfile",0666,example_dir);

    if(date_file == 0){
        remove_proc_entry("myfile",0);
        printk("mkfile fail\n");
        return -ENOMEM;
    }

    strcpy(mydata.value,"Ticks=");
    date_file->data = &mydata;
    date_file->read_proc = &proc_read;
    date_file->write_proc = &proc_write;
    date_file->owner=THIS_MODULE;

    return 0;
}

//卸载模块
void cleanup_module(void)
{
    remove_proc_entry("myfile",example_dir);
    remove_proc_entry("mydir",NULL);
    printk("Goodbye.\n");
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TEST");
MODULE_AUTHOR("bobo");

其中,Makefile文件为:

obj-m := mod.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

同样的道理编译完成之后,我们使用insmod命令将文件加载到内核中,我们查看/proc目录下是否有我们创建的mydir文件夹,并且该文件夹下面存在一个名为myfile的文件,同时我们查看其中的内容.

下面我们编写一个用户空间程序,来测试用户读取/proc/mydir/myfile文件所用的时间,我们使用的是系统提供的gettimeofday函数.

其中gettimeofday()函数有两个参数:

int gettimeofday(struct timeval *tv,struct timezone *tz);

其中的数据结构timeval为:

struct timeval{
    long int tv_sec; //秒数
    long int tv_usec; //毫秒
};

数据结构timezone为时区,如果不用就设置为NULL

下面是我的用户测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

#define path "/proc/mydir/myfile"

//字符串的长度
#define LEN 16

//读取之前的时间
struct timeval tv_begin;

//读取之后的时间
struct timeval tv_end;

//存储读出的字符串的长度
char buf[LEN];

int main()
{

    FILE *fp;

    gettimeofday(&tv_begin,NULL);
    fp = fopen(path,"r");
    printf("%s\n",fgets(buf,LEN,fp));
    gettimeofday(&tv_end,NULL);

    printf("used %d s %d us\n",tv_end.tv_sec-tv_begin.tv_sec,tv_end.tv_usec-tv_begin.tv_usec);

    return 0;
}

我们使用命令:

gcc test.c -o test

编译之后,运行

./test

其运行结果为:

下面一篇博客我将使用内核模块从当前进程开始,向前遍历,一直遍历到初始进程,将其信息保存到proc文件中.

## 未完待续!!!!##

时间: 2024-10-07 10:34:57

内核模块编写的相关文章

Linux内核模块编写详解

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

ARM平台的内核模块编写与安装

最近在学习arm平台下的内核模块开发,现将内核模块建立过程记录一下. 从编写代码的角度来看,创建一个简单的的内核模块可以分3步走: 1. 添加头文件(linux/init.h 和linux/module.h): 2. 加载内核(函数module_init):      3. 卸载内核(函数module_exit) 如一个简单的内核模块如下: #include <linux/init.h>#include <linux/module.h>static int hello_init()

Linux内核模块编写模板

本文讲述如何编写Linux内核模块,需要两个文件 mytest.c,Makefile. 存放到同一个文件夹目录,然后make.make install,就可以完成内核模块的编译生成和安装. 然后通过dmesg命令就可以看到从模块初始化函数输出的信息. mytest.c: #include <linux/module.h> #include <linux/crypto.h> #include <linux/kernel.h> #include <linux/init

Linux内核分析(二)----内核模块简介|简单内核模块实现

Linux内核分析(二) 昨天我们开始了内核的分析,网上有很多人是用用源码直接分析,这样造成的问题是,大家觉得很枯燥很难理解,从某种意义上来说linux系统本身就是由一个个模块构成的,所以我会结合内核模块的设计,去分析内核,从而达到对linux内核的理解. 今天我们会分析到以下内容: 1.      Linux内核模块简介 2.      简单内核模块实现 l  Linux内核模块简介 1.       何为内核模块 在上一篇博文中我们先通过内核配置,在配置的过程中我们对内核的组件进行了选择(当

Ubuntu 14.04操作系统信任链(IMA)扩展分析实验

一.实验目的 1.    了解TPM安全芯片的组成和作用 2.    掌握计算平台信任链扩展的原理及作用 3.    掌握IMA的工作原理及作用 二.实验内容 信任链扩展的准则是"Measure before load",即在加载下一阶段组件并把控制权移交给它之前首先对其进行度量,记录组件度量值并使用TPM将此度量值保护起来. 下图是计算平台信任链扩展原型图: 1.扩展Linux操作系统为其增加IMA功能,使信任链由OS层扩展到用户应用层. 2.编写以下代码加载或运行,观察IMA是如何

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

专题5-内核模块开发2内核模块设计与编写

1.范例 touch helloworld.c chmod 777 -R helloworld.c #include<linux/init.h> #include<linux/module.h> static int hello_init(void) { printk(KERN_WARNING"hello,world!\n"); return 0; } static void hello_exit(void) { printk(KERN_INFO"Go

编写你的第一个Linux内核模块(目前校对到杂项设备)

想要开始黑掉核?没有线索不知道如何开始?让我们向你展示如何做- 内核编程通常被视为黑魔法.在Arthur C Clarke的意义上说,它可能是.Linux内核与用户空间有很大的不同:抛开漫不经心的态度,你要格外小心,因为在你代码中的一个小小的bug都会影响整个系统.这里没有简单的方法来做浮点运算.堆栈既固定又小,你写的代码总是异步所以你需要考虑并发性.尽管如此,Linux内核是一个非常大而复杂的C程序,对每个人都是开放的(阅读.学习.提高),而你也可以成为其中的一部分. "最简单的方法开始内核编

编写简单Linux内核模块

模块代码如下 //main.c #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/list.h> static int my_hello(void){ struct task_struct *task = NULL; printk("Hello\n&quo