技术标签: Linux内核模块开发套路
在32位的操作系统上,Linux内核将4G的空间分为了0-3G
的用户空间和3-4G
的内核空间,具体可以自己画出内存的模型来分析。其中用户程序是运行在用户空间的,用户空间通过中断
或者系统调用
的方式进入内核空间。在Linux中很多功能或者外设都可以编译成模块,在系统运行时动态地注册
或者卸载
,在此过程无需重启整个系统。
内核模块需要包含内核相关的头文件,根据功能不同,所需要的头文件也会有所不一样,但是这两个头文件是所有的内核模块都必须要包含的:
#include <linux/module.h>
#include <linux/init.h>
模块的初始化负责注册模块本身。只有已注册的模块,其内部方法才可以被应用程序锁使用。模块初始化的功能相当于模块和内核之间衔接的桥梁,告知内核:我注册好了,已经做好准备随时为你服务
。
通常的模块的初始化函数如下所示:
/*
1. 初始化代码一般都声明为static
2. __init 标识初始化函数仅在初始化期间使用,一旦初始化完毕,将释放初始化函数所占用的内存。
3. module_init是必须的,当使用insmod将内核加载进模块的时候,初始化函数将会被执行。
4. 模块初始化只与内核模块管理子系统打交道,不与应用程序交互。
*/
static int __init module_init_func(void)
{
/*TODO: 初始化代码*/
}
module_init(module_init_func);
当系统不再需要某个模块时,可以卸载这个模块释放该模块所占用的资源。模块的退出相当于告知内核:我要退出了,不能在为你服务了
。通常的模块的退出函数如下所示:
/*
1. 模块退出代码没有返回值。
2. __exit标志这段代码用于模块卸载。
3. module_exit不是必须的,但是没有module_exit定义的模块无法被卸载。
4. 使用rmmod卸载模块时,退出函数将被执行。
5. 模块初始化只与内核模块管理子系统打交道,不与应用程序交互。
*/
static viod __exit module_exit_func(void)
{
/*TODO: 模块退出代码*/
}
module_exit(module_exit_func);
非必须,主要是与应用程序交互(其实就是填充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,
};
Linux内核源码是开源的,遵守GPL协议,所以我们编写出来的模块一定要指定相关的开源协议,否则是不能被静态编译进内核模块。使用MODULE_LINCENSE
来指定遵守的协议:
MODULE_LINCENSE("GPL");
模块的编写者可以为所编写的模块增加一些描述的信息,比如作者、模块描述、以及版本号等(非必须的):
MODULE_AUTHOR("wxb");
MODULE_DESCRIPTION("hello world");
MODULE_VERSION("V1.01");
所有的内核符号默认都是不导出的。如果希望一个模块的符号能被其它模块使用,则必须显式的用 EXPORT_SYMBOL 将符号导出:
EXPORT_SYMBOL(module_symbol);
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指针类型数据 */
一张图进行总结,内核模块开发的套路
#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");
# 获取内核版本信息
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
使用root身份加载和卸载模块
sudo insmod hello.ko //加载模块
sudo rmmod hello.ko //卸载模块
lsmod //查看已有的模块
modinfo hello.ko //查看模块相关描述
#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");
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
执行结果如下所示:
CentOS 6.5下升级glibc到glibc-2.14时,执行configure,提示如下错误:# ./configure checking build system type... x86_64-unknown-linux-gnuchecking host system type... x86_64-unknown-linux-gnuchecking for gcc... noche
某熊周刊系列:一周推荐外文技术资料(2.6)归纳于某熊周刊:一周推荐外文技术资料是笔者每周浏览外文技术网站中时发现的不错的文章/项目/书籍/教程的集锦,可以关注笔者的专栏某熊的全栈之路及时获取更新。资讯来源包括但不限于Medium、Twitter、Google Plus、Reddit、Hacker News、DZone、Github Tr...
RHEL 7网卡默认命名规则:以太网卡(Ethernet)为enX,无线网卡(WLAN)为wlX,修改网卡命名规则为ethX如下:1、修改/etc/sysconfig/grub文件,添加net.ifnames=0 biosdevname=0点击(此处)折叠或打开[[email protected] ~]#cat /etc/sysconfig/grubGRUB_TIME...
10、除此之外,我们还可以对某个切片进行进一步的切割,右键单击某个切片,然后选择“划分切片”,打开划分切片对话框,设置横向和纵向的切片个数。11、最后,一个关键的设置就是,右键单击某个切片,然后在打开的右键菜单中选择“编辑切片选项”,打开切片选项对话框。12、在这里你要设置的有切片的名称,这个名称也就是网页图片的名称,默认系统给出的名称即可。设置url,也就是图片的链接,在网页上点击图片就能打开这...
因为uboot会使用到一些经过编译才会生成的文件,因此我们在分析的时候,需要先编译一下uboot。uboot目录文件夹:api:与硬件无关的API函数具体要查看readmearch:与架构体系有关的代码针对不同cpu特有的一些内容,match开头的文件夹是跟具体的设备有关比如:match-exynos就是跟三星的exynos系列CPU相关的文件,我们主要关心arm这是armv7的一些架构am33xx,omap–>TI的bcm–>博通的mx—>恩智浦的
Windows 10 System磁盘占用关闭或开启虚拟内存运行regeditHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\OneSyncSvc的start属性修改为3OneSyncSvc_随机数字 的start属性修改为3UserDataSvc的start属性修改为3UserDataSvc_随机数字 的start...
python pip安装包版本规约 pip install~ pip install -e的意思
BusChargeSystem项目介绍基于STM32F407+RFID的模拟公交车刷卡收费系统做这个小小的项目过程中参考了很多正点原子的资料,很多东西都是刚开始学习,所以只会实现一些简单的增删改查功能。1. 功能列表功能介绍添加用户刷卡识别需要添加的用户删除用户刷卡识别需要删除的用户刷卡消费每次刷卡消费金额为1元,同一用户可连续刷卡消费余额充值通过按键选择充值金额并刷卡识别完成充值2. 项目开发2.1 环境环境版本操作系统
2.3、dispatcher.serviceAction(request, response, servletContext, mapping);方法分析Java代码 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext co
1 背景我平常需要连接外网查阅资料,聊天等,做实验时需要将写到的代码打包部署到内网服务器中,所以需要频繁地切换内外网,修改静态网络配置。很是苦恼。 2 方法 最近,我找到了几种解决办法。方法一、购买多网口软路由,可以同时将内网、外网网线插到软路由上,配置下路由规则,我的电脑再连接软路由。但是软路由太贵了。 方法二、为我的台式机再一块网卡,连接内网。好像可以ε=(´ο`*)))唉。由于主板上没有可用 P...
auboi5机械臂初学者遇到的各种问题合集
--Oythonhill 2017-11-25 整理--查看数据表中所有列的约束 sp_helpconstraint表名; --添加主键约束 ALTER TABLE 表名 ADD CONSTRAINT 约束名 PRIMARY KEY (主键); --添加唯一约束 ALTER TABLE 表名 ADD CONS