http://blog.chinaunix.net/uid-25299072-id-1753746.html
一、linux内核模块
Linux 内核具有模块化设计,提供了可伸缩的、动态的内核。在引导时,只有少量的驻留内核被载入内存。这之后,无论何时用户要求使用驻留内核中没有的功能,某内核模块(kernel module),有时又称驱动程序(driver)。就会被动态地载入内存。 通过 Linux 内核模块(LKM)可以在运行时动态地更改 Linux。可动态更改 是指可以将新的功能加载到内核、从内核去除某个功能,甚至添加使用其他 LKM 的新 LKM。LKM 的优点是可以最小化内核的内存占用,只加载需要的元素(这是嵌入式系统的重要特性)。
Linux 的众多优良特性之一就是可以在运行时扩展由内核提供的特性的能力. 这意味着你可以在系统正在运行着的时候增加内核的功能( 也可以去除 ).每块可以在运行时添加到内核的代码, 被称为一个模块. Linux 内核提供了对许多模块类型的支持, 包括但不限于, 设备驱动. 每个模块由目标代码组成( 没有连接成一个完整可执行文件 ), 可以动态连接到运行中的内核中, 通过 insmod 程序, 以及通过 rmmod 程序去连接.“图内核的划分”表示了负责特定任务的不同类别的模块, 一个模块是根据它提供的功能来说它属于一个特别类别的. “图内核的划分”中模块的安排涵盖了最重要的类别, 但是远未完整, 因为在 Linux 中越来越多的功能被模块化了.
内核的划分
图 内核的划分
二、在Debian下编译内核需要安装内核头文件:
#aptitude install linux-headers-`uname -r`
(把目前所使用的内核的头文件安装上,也同样会安装上build-essential,linux-kbuild等包,如果之前没安装过的话。然后在/usr/src下会出现一个linux-header-xxxxx的目录,这个目录里就是当前使用的内核的头文件以及build模块的时候所需要用到的scripts和Makefile,这些scripts是linux-kbuild中提供的。)
三、简单的hello world 内核模块
(先写一个简单的hello world 模块,熟悉linux内核模块编程,为procfs做准备)
unanao@debian:~/programming/modules/hello$ cat hello.c
#include
#include
static int hello_init(void)//void can't be abbreviation,or we will get a warning
{
printk(KERN_EMERG "hello, world\n");
return 0
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye!\n");
}
module_init(hello_init);//There is no "",This is not a string
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("unanao");
MODULE_DESCRIPTION("simple module ,just output some words\n");
MODULE_ALIAS("hello");
这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证; 没有这样的说明, 在模块加载时内核会抱怨.
四、编写Makefile
unanao@debian:~/programming/modules/hello$ cat Makefile
ifneq ($(KERNELRELEASE),) # after ifneq ,there is a space! or,will get an error
obj-m := hello.o
else
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *mod.c [mM]odule* .*.cmd *.o.d .tmp_versions
endif
详细的信息参考linux内核源代码中“Documentation/kbuild/modules.txt”文件
五、内核模块的编译,插入,查看,卸载
debian:/home/unanao/hello$make
debian:/home/unanao/hello# insmod hello.ko
debian:/home/unanao/hello# lsmod
Module Size Used by
hello 1344 0
procfs 2096 0
ipv6 235396 10
ppdev 6468 0
lp 8164 0
vboxvfs 29536 0
nls_base 6820 1 vboxvfs
loop 12748 0
parport_pc 22500 0
parport 30988 3 ppdev,lp,parport_pc
ac 4196 0
serio_raw 4740 0
battery 10180 0
psmouse 32336 0
button 6096 0
i2c_piix4 7216 0
pcspkr 2432 0
i2c_core 19828 1 i2c_piix4
vboxadd 47008 4 vboxvfs
evdev 8000 3
ext3 105576 1
jbd 39476 1 ext3
mbcache 7108 1 ext3
ide_cd_mod 27684 0
cdrom 30176 1 ide_cd_mod
ide_disk 10496 3
floppy 47716 0
piix 6568 0 [permanent]
ide_pci_generic 3908 0 [permanent]
ide_core 96168 4 ide_cd_mod,ide_disk,piix,ide_pci_generic
ata_generic 4676 0
libata 140448 1 ata_generic
pcnet32 27396 0
mii 4896 1 pcnet32
scsi_mod 129548 1 libata
dock 8304 1 libata
thermal 15228 0
processor 32576 1 thermal
fan 4196 0
thermal_sys 10856 3 thermal,processor,fan
debian:/home/unanao/hello# rmmod hello
六、查看内核的输出
6.1、使用dmesg查看:
debian:/home/unanao/hello# dmesg |tail -2
[ 9344.308292] hello, world
[ 9389.298477] Goodbye, I will Come to a more beautiful word
6.2、直接通过pritk输出到终端:
6.2.1在xwindow环境下开的终端窗口,
printk(KERN_EMERG "hello, world\n"); //输出
printk(KERN_ALERT "Goodbye,I will Come to a more beautiful word\n");//不输出
6.2.2在黑屏的Linux控制台模式(Ctrl+Alt+(F1~F6))两个都可以显示输出。
6.3、printk()函数的总结
我们在使用printk()函数中使用日志级别为的是使编程人员在编程过程中自定义地进行信息的输出,更加容易地掌握系统当前的状况,对程序的调试起到了很重要的作用。
(下文中的日志级别和控制台日志控制级别是一个意思)
printk(日志级别 "消息文本");这里的日志级别通俗的说指的是对文本信息的一种输出范围上的指定。
日志级别一共有8个级别,printk的日志级别定义如下(在linux26/includelinux/kernel.h中):
#defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/
#defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/
#defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/
#defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/
#defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/
#defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/
#defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/
#defineKERN_DEBUG"<7>"/*调试级别的消息*/
没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到。
下面是一个比较简单的使用
printk(KERN_INFO "INFO\n"); //这里可以使用数字代替 KERN_INFO,即可以写成printk("<6> INFO\n");
在这个格式的定义中,日志级别和信息文本之间不能够使用逗号隔开,因为系统在进行编译的时候,将日志级别转换成字符串于后面的文本信息进行连接。
七、/proc文件系统
/proc 文件系统在linux 内核中是一个特殊的文件系统。他是一个许你的文件系统,它不和块设备联系,仅仅在内存中存在。/proc 文件系统包含目录(作为组织信息的方式)和虚拟文件虚拟文件可以表示内核到用户的信息,并作为从用户发送信息到内核的手段。
八、利用procfs实现“数据从用户空间-->内核空间-->用户空间”
8.1 源代码:
unanao@debian:~/programming/modules/read_write$ cat procfs.c
#include
#include
#include //如果要使用procfs的函数,需要包含头文件:
#include
#include
#include
#define MAX_LENGTH PAGE_SIZE
static struct proc_dir_entry *proc_entry;
static char *buf; //space where in the kernel sapce for input data
/*function to write the data*/
int data_write(struct file *filp,const char __user *buff,unsigned long len, void *data)
{
if(len >MAX_LENGTH){
printk(KERN_EMERG "virtual_proc:buf is full");
return -ENOSPC;//no space left on device
}
/* Copy buffer to kernel-space from user-space function:
*unsigned long copy_from_user( void *to, const void __user *from,\
*unsigned long n );
*/
if(copy_from_user(buf,buff,len)){
return -EFAULT; //asm-generic/errno-bash.h ,bad address
}
return len;
}
/* Function to read a fortune*/
int data_read(char *page,char **start,off_t off,int count,int *eof,void *data)
{
int len;
if(off > 0){
*eof=1;
return 0;
}
/*wrap around*/
len=sprintf(page,"%s\n",buf);
return len;
}
int init_virtual_proc(void)
{
int ret=0;
/*
*when vmalloc come across the error return 0(NULL address)
*success return back a pointer
*/
buf=(char *)vmalloc(MAX_LENGTH);
if(!buf){
return -ENOMEM;//out of memory
}
else{
memset(buf,0,MAX_LENGTH);
/*
*create_proc_entry function. This function accepts a file name, a
*set of permissions, and a location in the /proc filesystem in
*which the file is to reside. The return value of
*create_proc_entry is a proc_dir_entry pointer (or NULL,indicating
*an error in create). You can then use the return pointer to
*configure other aspects of the virtual file, such as the function
*to call when a read is performed on the file
*/
proc_entry=create_proc_entry("virtual_proc",0644,NULL);
if(proc_entry==NULL){
ret=-ENOMEM;//no memory
vfree(buf);
printk(KERN_EMERG "virtual_proc:couldn't create virtual_proc entry\n");
}
else{
/*
*use the read_proc and write_proc commands to plug in functions
*forreading and writing the virtual file.
*/
proc_entry->read_proc=data_read;//proc read function
proc_entry->write_proc=data_write;//proc write function
proc_entry->owner=THIS_MODULE;
proc_entry->mode=S_IRWXU|S_IRGRP|S_IROTH|S_IWOTH;
printk(KERN_EMERG "virtual_proc :Module loaded\n");
}
}
return ret;
}
void cleanup_virtual_proc(void)
{
remove_proc_entry("virtual_proc",NULL);
vfree(buf);
printk(KERN_EMERG "virtual:module unloaded\n");
}
module_init(init_virtual_proc);
module_exit(cleanup_virtual_proc);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("ProcFileSystem");
MODULE_AUTHOR("unanao");
8.2 Makefile 文件
unanao@debian:~/programming/modules/read_write$ cat Makefile
ifneq ($(KERNELRELEASE),)
obj-m := procfs.o
else
KERNELDIR ?=/lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *.mod.c [mM]odule* .*.cmd .tmp_versions
endif
九、编译、插入,执行
编译:
debian:/home/unanao/programming/modules/read_write$ make
插入模块:
debian:/home/unanao/programming/modules/read_write# insmod procfs.ko
在用户空间写入数据:
unanao@debian:~/programming/modules/read_write$ echo "jianjiao" >/proc/virtual_proc
在用户空间读出数据:
unanao@debian:~/programming/modules/read_write$ cat /proc/virtual_proc
jianjiao
写在模块:
debian:/home/unanao/programming/modules/read_write# rmmod procfs
十、模块一些函数的解释
struct proc_dir_entry 配置虚拟文件的其它方面,如:调用一个read函数。
struct proc_dir_entry {
const char *name; // virtual file name
mode_t mode; // mode permissions
uid_t uid; // File's user id
gid_t gid; // File's group id
struct inode_operations *proc_iops; // Inode operations functions
struct file_operations *proc_fops; // File operations functions
struct proc_dir_entry *parent; // Parent directory
...
read_proc_t *read_proc; // /proc read function
write_proc_t *write_proc; // /proc write function
void *data; // Pointer to private data
atomic_t count; // use count
...
};
如果procfs只是被一个模块使用,一定要把struct proc_dir_entry 中的owner设为THIS_MODULE。
struct proc_dir_entry* entry;
entry->owner = THIS_MODULE;
设置procfs的权限为和属主,如:
struct proc_dir_entry* entry;
entry->mode = S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH;
entry->uid = 0;
entry->gid = 100;
(上面只是例子,可以根据需要进行自己的设置)
struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode, struct proc_dir_entry* parent);
该函数创建一个名为“name”普通文件,在父目录中文件的模式是“mode”。在procfs的根目录创建文件,使用NULL作为“parent”的参数。当成功,函数将返回一个struct proc_dir_entry指针,否则将返回NULL。
移除一个入口(entry)
void remove_proc_entry(const char* name, struct proc_dir_entry* parent);
从procfs中,删除在“parent”目录中的“name”文件。entry是通过名字删除的,而不是由create_proc_entry 创建时返回的struct proc_dir_entry 的类型的变量。请注意,此功能不递归删除项目。
在remove_proc_entry之前,一定要释放data (即:如果有一些数据分配了内存)。
和用户空间通信
create_proc_entry 的返回值struct proc_dir_entry* 的read_proc 和 write_proc 用来初始化read和write函数。
struct proc_dir_entry* entry;
entry->read_proc = read_proc_foo;
entry->write_proc = write_proc_foo;
read 函数负责把它的信息写入buffer,read函数的格式为:
int read_func(char* buffer, char** start, off_t off, int count,int* peof, void* data);
peof:当到达文件末尾时写“1”
data:很少使用,用于回调函数
write 函数用于将用户空间的数据写入内核空间,write函数的格式:
int write_func(struct file* file, const char* buffer, unsigned long count, void* data);
其它一些函数:
* Create a directory in the proc filesystem */
struct proc_dir_entry *proc_mkdir( const char *name,struct proc_dir_entry *parent );
/* Create a symlink in the proc filesystem */
struct proc_dir_entry *proc_symlink( const char *name,struct proc_dir_entry *parent,const char *dest );
/* Create a proc_dir_entry with a read_proc_t in one call */
struct proc_dir_entry *create_proc_read_entry( const char *name,mode_t mode,struct proc_dir_entry *base, read_proc_t *read_proc,void *data );
/* Copy buffer to user-space from kernel-space */
unsigned long copy_to_user( void __user *to,const void *from,unsigned long n );
/* Copy buffer to kernel-space from user-space */
unsigned long copy_from_user( void *to,const void __user *from,unsigned long n );
/* Allocate a 'virtually' contiguous block of memory */
void *vmalloc( unsigned long size );
/* Free a vmalloc'd block of memory */
void vfree( void *addr );
/* Export a symbol to the kernel (make it visible to the kernel) */
EXPORT_SYMBOL( symbol );
/* Export all symbols in a file to the kernel (declare before module.h) */
EXPORT_SYMTAB
十一、参考资料:
11.1 http://linux.ctocio.com.cn/153/8627653.shtml
11.2 Jonathan Corbet, Alessandro Rubini 和 Greg Kroah-Hartman
《Linux 设备驱动》(第三版)
11.3 Linux 可加载内核模块剖析
http://www.ibm.com/developerworks/cn/linux/l-lkm/
11.4 Erik (J.A.K.) Mouw 《Linux Kernel Procfs Guide》
- 程序优化
- vtune
- linux性能监控软件Perf
- 系统级性能分析工具perf的介绍与使用
- perf的二级命令
- 全局性概况
- 全局细节
- 最常用功能perf record
- 可视化工具perf timechart
- perf引入的overhead
- perf stat
- gprof
- 三种Linux性能分析工具的比较
- perf+gprof+gprof2dot+graphviz进行性能分析热点
- 英特尔多核平台编程优化大赛报告
- 内存操作
- mmap
- mmap的分类
- 深入理解内存映射mmap
- 计算机底层知识拾遗(九)深入理解内存映射mmap
- 内核驱动mmap Handler利用技术(一)
- Windows内存管理机制及C++内存分配实例
- Linux内存管理初探
- Windows CPU信息查看
- Linux CPU信息查看
- 预留大内存
- Linux下试验大页面映射
- /dev/mem
- Linux中通过/dev/mem操控物理地址
- /dev/mem分析
- 用法举例
- Linux下直接读写物理地址内存
- 查看内存信息
- Cache Memory
- 页面缓存
- 查看各级cache信息的方法
- dmidecode命令查看cache size
- CPU Cache 机制以及 Cache miss
- ARM体系关闭mmu和cache
- CR0-4寄存器介绍
- 查看CR0,CR2,CR3的值
- Linux 下如何禁用CPU cache
- 7个示例科普CPU Cache
- 第一个例子的C代码
- 其中之一
- Linux 从虚拟地址到物理地址
- 内存测试例子
- 每个程序员都应该了解的内存
- Part 1
- 程序员能够做什么
- 3 CPU caches
- 6 What Programmers Can Do
- VirtualAlloc
- Large-Page Support
- Some remarks on VirtualAlloc and MEM_LARGE_PAGES
- DMA
- MOV和MOVS的效率问题?如何高效的拷贝内存 中的数据
- how to use movntdqa to avoid cache pollution
- 计算机底层知识拾遗(一)理解虚拟内存机制
- How to access the control registers cr0,cr2,cr3 from a program
- 细说Cache-L1/L2/L3/TLB
- what-is-the-meaning-of-non-temporal-memory-accesses-in-x86
- How can the L1, L2, L3 CPU caches be turned off on modern x86/amd64 chips?
- UA list
- GDB
- 程序运行参数
- Linux下GDB的多线程调试
- CMake
- CMake快速入门教程:实战
- cmake打印变量值
- function
- source_group
- cmake_parse_arguments
- 编译.S文件
- add_definitions
- CMake添加-g编译选项
- Debug模式下启动
- Mysql
- Mysql联合查询union和union all的使用介绍
- MySQL数据库导入错误:ERROR 1064 (42000) 和 ERROR at line xx: Unknown command '\Z'.
- 解决MYSQL数据库 Table ‘xxx’ is marked as crashed and should be repaired 145错误
- C/C++
- c语言中static的作用
- strlen和sizeof有什么区别?
- printf
- Libuv中文文档之线程
- RapidJSON
- gcc/g++ 实战之编译的四个过程
- __thread
- TARGET_LINK_LIBRARIES
- MAP_HUGETLB
- 使用Intel格式的汇编
- __m128i
- emmintrin.h
- _mm_stream_si128
- _mm_stream_load_si128
- _mm_load_si128
- _mm_xor_si128
- _mm_store_si128
- _mm_cvtsi128_si64
- Intel SSE指令集
- _mm_set_epi64x
- _mm_aesenc_si128
- _umul128
- _mm_malloc
- reinterpret_cast
- strlen
- 读取UTF-8的txt文件发现开头的多三个字节的问题
- PHP
- php计算函数执行时间的方法
- 框架
- Json Rpc远程调用框架
- PHP多进程
- PHP CLI模式下的多进程应用
- php多进程总结
- 优化
- PHP7 优化
- 让你的PHP7更快(GCC PGO)
- PHP的性能演进(从PHP5.0到PHP7.1的性能全评测)
- PHP字符串全排列算法
- 获取服务器基本信息
- cookie
- phpstudy2018 安装xdebug扩展
- 软件下载
- PHP mysqli_error() 函数
- PHP Session 变量
- curl
- curl_getinfo
- 获取请求头
- PHP使用CURL获取302跳转后的地址实例
- PHP基于cURL实现自动模拟登录
- PHP获取远程图片大小(CURL实现)
- CURL模拟登录
- curl模拟登录提交(从目录中获取文件)
- CURL HTTPS
- curl帮v
- rename
- copy
- JSON
- json_encode
- json_decode
- json_last_error_msg
- json_last_error
- PHP json_encode中文乱码解决方法
- var_dump
- PHPStorm与Xdebug设置
- Xdebug原理以notepad为例
- str_pad
- pack
- PHP二进制与字符串之间的相互转换
- PHP执行系统命令(简介及方法)
- 函数
- 十进制转二进制
- 字符串到ASSCI
- 字符串转二进制
- 合并两个表
- 图像识别
- Tesseract
- 虚拟机
- vmware下Kali 2.0安装VMware Tools
- 安装 VMware tools出现“正在进行简易安装时,无法手动启动VMware tools安装”
- 爬虫
- 有哪些好的数据来源或者大数据平台?
- Cygwin
- Git 常用命令
- 排列组合
- 含重复元素序列的全排列
- 全排列的非递归和递归实现(含重复元素)
- GitBook
- 编辑环境
- visual studio code
- 2名数学家或发现史上最快超大乘法运算法,欲破解困扰人类近半个世纪的问题
- 系统预定义常量
- 指令集
- SSE
- _MSC_VER
- msys2
- 安装cmake
- MSYS2 更新源
- 讲Cmake msys32使用问题解答 CXX CMAKE_C_COMPILER配置详解
- VirtualBox
- 解决virtualbox只能安装32位系统的问题
- Ubuntu
- 使用AES-NI的编译参数
- debian下安装内核源码的方法
- tar.xz结尾的文件的解压方法
- Linux命令
- insmod
- fatal error: openssl/bio.h
- 准备module的编译环境(kali)
- Ubuntu/Debian 之内核模块开发准备
- dmesg的详细用法
- Linux系统开机自动加载驱动module
- linux /Module 浅析(转载)
- Kali
- 找回gpedit
- Enable the Lock Pages in Memory Option (Windows)
- TLA
- 双系统
- 显卡
- 显示no CUDA的解决过程
