01-编写内核模块的套路_module exit 和 release-程序员宅基地

技术标签: Linux内核模块开发套路  

文章目录

一、 Linux和内核模块的关系

二、 内核模块的编写套路

2.1 头文件

2.2 模块初始化

2.3 模块退出

2.4 模块的各种方法

2.5 模块的许可证和描述

2.6 模块的其他一些特性

2.6.1 符号导出

2.6.2 带参数的模块

3.总结

4.简单的例子:

4.1 不带参数的hello world

4.2 带参数的hello world


一、 Linux和内核模块的关系

在32位的操作系统上,Linux内核将4G的空间分为了0-3G的用户空间和3-4G的内核空间,具体可以自己画出内存的模型来分析。其中用户程序是运行在用户空间的,用户空间通过中断或者系统调用的方式进入内核空间。在Linux中很多功能或者外设都可以编译成模块,在系统运行时动态地注册或者卸载,在此过程无需重启整个系统。

二、 内核模块的编写套路

2.1 头文件

内核模块需要包含内核相关的头文件,根据功能不同,所需要的头文件也会有所不一样,但是这两个头文件是所有的内核模块都必须要包含的:

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

2.2 模块初始化

模块的初始化负责注册模块本身。只有已注册的模块,其内部方法才可以被应用程序锁使用。模块初始化的功能相当于模块和内核之间衔接的桥梁,告知内核:我注册好了,已经做好准备随时为你服务
通常的模块的初始化函数如下所示:

/*
	1. 初始化代码一般都声明为static
	2. __init 标识初始化函数仅在初始化期间使用,一旦初始化完毕,将释放初始化函数所占用的内存。
	3. module_init是必须的,当使用insmod将内核加载进模块的时候,初始化函数将会被执行。
	4. 模块初始化只与内核模块管理子系统打交道,不与应用程序交互。
*/
static int __init module_init_func(void)
{
    /*TODO: 初始化代码*/
}

module_init(module_init_func);

2.3 模块退出

当系统不再需要某个模块时,可以卸载这个模块释放该模块所占用的资源。模块的退出相当于告知内核:我要退出了,不能在为你服务了。通常的模块的退出函数如下所示:

/*
	1. 模块退出代码没有返回值。
	2. __exit标志这段代码用于模块卸载。
	3. module_exit不是必须的,但是没有module_exit定义的模块无法被卸载。
	4. 使用rmmod卸载模块时,退出函数将被执行。
	5. 模块初始化只与内核模块管理子系统打交道,不与应用程序交互。
*/
static viod __exit module_exit_func(void)
{
    /*TODO: 模块退出代码*/
}
module_exit(module_exit_func);

2.4 模块的各种方法

非必须,主要是与应用程序交互(其实就是填充file_operations结构体),常见的模块各种方法有:

xxx_open()
xxx_release()
xxx_read()
xxx_write()
xxx_ioctl()

file_operations结构体在内核源码include/linux/fs.h中定义,源码如下所示:

struct file_operations 
{
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};

用户程序通过系统调用(open/release/read/write/ioctl等),进入到内核模式,使用这些方法。常见的file_operations结构体定义如下,根据实际情况进行填充:

static struct file_operations xxx_fops = 
{
    .owner    = THIS_MODULE,/*固定的*/
    .open     = xxx_open,
    .write    = xxx_write,
    .read     = xxx_read,
    .release  = xxx_release,
    .ioctl    = xxx_ioctl,
};

2.5 模块的许可证和描述

Linux内核源码是开源的,遵守GPL协议,所以我们编写出来的模块一定要指定相关的开源协议,否则是不能被静态编译进内核模块。使用MODULE_LINCENSE来指定遵守的协议:

MODULE_LINCENSE("GPL");

模块的编写者可以为所编写的模块增加一些描述的信息,比如作者、模块描述、以及版本号等(非必须的):

MODULE_AUTHOR("wxb");
MODULE_DESCRIPTION("hello world");
MODULE_VERSION("V1.01");

2.6 模块的其他一些特性

2.6.1 符号导出

所有的内核符号默认都是不导出的。如果希望一个模块的符号能被其它模块使用,则必须显式的用 EXPORT_SYMBOL 将符号导出:

EXPORT_SYMBOL(module_symbol);

2.6.2 带参数的模块

Linux内核允许模块在加载时指定参数。模块接收UC桉树传入能够实现一个模块在多个系统上运行,可以根据传入的参数提供不同的服务。模块参数必须使用module_param来声明,通常放在文件的头部,module_param需要三个参数:变量名称,变量类型,访问掩码。如:

static int num = 5;
module_param(num,int,S_IRUGO);/* 传入int类型数据 */
static char str_buff[] = "hello world";
module_param(str_buff,charp,S_IRUGO);/* 传入char指针类型数据 */

 


3.总结

一张图进行总结,内核模块开发的套路

4.简单的例子:

4.1 不带参数的hello world

4.1.1 编写C源文件 vi hello.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
    printk(KERN_INFO"hello init!\r\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO"hello exit!\r\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wxb");
MODULE_DESCRIPTION("hello module example");
MODULE_VERSION("V1.00");

4.1.2 编写Makefile文件,需要根据实际情况填入Linux内核源码的目录:

# 获取内核版本信息
KERN_VER = $(shell uname -r) 
# 获取内核源码树位置信息
KERN_DIR = /lib/modules/$(KERN_VER)/build
# 将hello.c编译成一个模块(m—模块,y-静态链接到zImage中,-n表示不编译)
# 这一行是必不可少的,实际上Makefile也只需要这一行就能搞定。
# 假如有个新的模块module.ko,它依赖于两个文件file1.c和file2.c的话,这一行应该改为:
# obj-m +=module.o  
# module-objs := file1.o file2.o
obj-m += hello.o 
# all 目标为 make modules 表示编译为模块
# -C 表示进入到指定目录下借用内核源码的体系进行编译链接
# M=`pwd`把当前路径记录到M中,编译完成之后拷贝回M
all: 
    make -C $(KERN_DIR) M=`pwd` modules 
# 清除目标文件
clean:
    rm -rf *.ko *.mod.c *.mod.o *.o *.order *.symvers
.PHONY: clean

4.1.3 使用root身份,使用make,正常的话,会出现以下提示信息,并生成hello.ko等文件

使用root身份加载和卸载模块

sudo insmod hello.ko //加载模块
sudo rmmod hello.ko //卸载模块
lsmod //查看已有的模块

modinfo hello.ko //查看模块相关描述

4.1.4 使用用dmesg|tail或者dmesg –c 清除内核log,然后使用dmesg | tail -2 可以查看最后两行的日志

4.2 带参数的hello world

4.2.1 编写C源文件 vi hello_param.c

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


static int num = 1;
static char *str = "hello world";

/*指定模块传入参数的类型*/
module_param(num,int,S_IRUGO);
module_param(str,charp,S_IRUGO);

static int __init hello_init(void)
{
    printk(KERN_INFO"%s,str=%s,num=%d\r\n",__FUNCTION__,str,num);
    return 0;
}

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

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wxb");
MODULE_DESCRIPTION("this is hello param example,param int num,charp str");
MODULE_VERSION("V1.01");

4.2.2 编写Makefile文件

KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build

obj-m += hello_param.o

all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    rm -rf *.ko *.mod.c *.mod.o *.o *.order *.symvers
.PHONY:clean

执行结果如下所示:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011522841/article/details/109619907

智能推荐

python实现在 Mac 10.9 远程桌面截屏抓取_mac 监控截屏-程序员宅基地

文章浏览阅读2k次。照样画葫芦,用python编写了一段小程序,可以使用ipad的web浏览器远程监控远端iMac主机界面(每秒截屏,非流控),与大家分享。1. 首先介绍一下需要下载的第三方工具:Flask,PyscreenshotFlask用来做web服务器,Pyscreenshot是用来截屏的。用pip install 分别安装即可2. 介绍程序文件架构如下,需要simplesvr_mac 监控截屏

centos7安装后一直出现pcieport 0000:00:1c.5的解决_dpc:error containment capabilities-程序员宅基地

文章浏览阅读4.7k次,点赞3次,收藏7次。安装完centos7后进入时一直不停出现pcieport 0000:00:1c.5字样,这个的具体原因尚不完全清楚,解决方法查到的都是一种,就是在/etc/default/grub中的GRUB_CMDLINE_LINUX的内容最后添加pci=nomsi或者pci=noaer或者pcie_aspm=off,这样的确可以,但是更新的步骤需要grub2-mkconfig -o /boot/efi/EFI..._dpc:error containment capabilities

目前看到的最好的RNN、LSTM、GRU博客:Understanding LSTM Networks_humans don鈥檛 start their thinking from scratch eve-程序员宅基地

文章浏览阅读735次。原文:http://colah.github.io/posts/2015-08-Understanding-LSTMs/Recurrent Neural NetworksHumans don’t start their thinking from scratch every second. As you read this essay, yo_humans don鈥檛 start their thinking from scratch every second.

maven/conf/settings.xml完整配置(3处)_apache-maven-3.9.2\conf\settings.xml-程序员宅基地

文章浏览阅读1.4k次。<?xml version="1.0" encoding="UTF-8"?><!--Licensed to the Apache Software Foundation (ASF) under oneor more contributor license agreements. See the NOTICE filedistributed with this work for additional informationregarding copyright ownersh._apache-maven-3.9.2\conf\settings.xml

基于Python的逆向工程:ELF文件_逆向工程 python-程序员宅基地

文章浏览阅读6.4k次。当解决复杂的逆向问题时,我们常使用radare2或IDA等成熟工具进行反汇编和调试。但有时也需要深入挖掘并了解它们是如何运作的。编写一些反汇编脚本对于自动化某些流程非常有用,并且可以形成自己的逆向工具链。至少,这是我现在正在尝试的事情。配置环境如标题所说的那样,你需要先安装Python 3。如果你无法确定是否安装了Python 3,可以运行如下命令:其中capstone是..._逆向工程 python

四种方法实现:找出数组中两个只出现一次的数字_一个数组中找出出现一次的2个数字-程序员宅基地

文章浏览阅读2.6k次。//先排序然后查找void FindNumsAppearOnce1(vector&lt;int&gt; data, int* num1, int *num2) { if (data.size() &lt; 2) return; sort(data.begin(), data.end()); vector&lt;int&gt; res; for (int i = 0; i &lt;..._一个数组中找出出现一次的2个数字

随便推点

开箱即用的 WebRTC 开发环境_xujianzhu webrtc开箱即用-程序员宅基地

文章浏览阅读333次。本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创 https://blog.piasy.com/2017/06/17/out-of-the-box-webrtc-dev-env/在刚刚落幕的 WWDC17 上,苹果为我们带来了一个不小的惊喜 —— 其浏览器内核WebKit将正式支持 WebRTC,而未来基于 WebKit 内核的苹果浏览器,比如m..._xujianzhu webrtc开箱即用

从ResNet101到ResNet50_resnet50 使用什么代替-程序员宅基地

文章浏览阅读3.3w次,点赞5次,收藏21次。一直用VGG训练,几天前想看下ResNet的效果如何,因为SSD源码中有python实现的ResNet网络结构实现代码,包含ResNet101和ResNet152,直接拿ResNet101来训练,GTX1060配置,batchsize竟然只降到2才跑的起来,果然一直收敛不了。看了下model_libs.py里面的实现代码:def ResNet101Body(net, from_layer, u_resnet50 使用什么代替

vivado ILA在线逻辑仪使用_vivado ila 下一触发沿-程序员宅基地

文章浏览阅读1.1w次,点赞12次,收藏131次。目录:1、在线逻辑分析仪简介2、HDL 实例化调试探针流程(实验-闪烁灯)3、Hardware Manager中观察调试信号4、网表插入调试探针流程(实验-闪烁灯)1、在线逻辑分析仪简介在线逻辑分析仪借用了传统逻辑分析仪的理念以及大部分的功能,并利用 FPGA 中的逻辑资源,将这些功能植入到 FPGA 的设计当中。一般地,在线逻辑分析仪的应用原理框图如下图所示:​ 待测设计(Design Under Test,DUT)就是用户逻辑,它和片内的在线逻辑分析仪都位于 FPGA中。在线逻辑分_vivado ila 下一触发沿

数据库索引的使用_db2数据库索引的使用-程序员宅基地

文章浏览阅读3.5k次。今天发现一个问题,问题大概是这样的,查询interface的信息,在本地使用本地的数据库访问没有问题,但是发布到服务器上以后访问速度就特别的忙,需要5分钟左右才能返回数据,这肯定是无法让人接受的,刚开始以为是服务器性能的问题,为了验证就把服务器上的数据库备份到本地,发现本地的速度也马上慢了下来,到底是什么问题的。看了一下查询interface的sql语句不禁吓了一跳: _db2数据库索引的使用

win7下mysql的安装_[root@gaojiao ~]# mysql -uroot error 1045 (28000):-程序员宅基地

文章浏览阅读3.1k次。一 , 当前mysql的最新版本是5.5.25a。到http://dev.mysql.com/downloads/mysql/下载mysql安装文件 。我们这里下载mysql-5.5.25a-win32.msi就可以了,下载完,直接点击安装。mysql有好几个版本,稍微了解下各个版本之间的区别:  MySQL Community Server :社区版本 不提供官方技术支持,是免费的_[root@gaojiao ~]# mysql -uroot error 1045 (28000): access denied for user 'r

PHP微信公众平台开发高级篇--群发接口_微信公众号根据标签群发接口支持数组传参吗-程序员宅基地

文章浏览阅读2.3k次。群发消息接口订阅号:每天一条的群发权限服务号:每月(自然月)4条群发权限实例&lt;?php/** * 群发接口 * PS:群发之前调用“预览接口”进行测试 * PS:通过第三方后台调用微信上传图片素材接口,获取图片url,如:{"url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/BdxWN2kspVgJOFpRHJojlWmbl0pM..._微信公众号根据标签群发接口支持数组传参吗