20135337——Linux实践二:模块
一、编译&生成&测试&删除
1.编写模块代码,查看如下
gedit 1.c(编写)
cat 1.c(查看)

MODULE_AUTHOR("Z")
MODULE_DESCRIPTION(模块用途的简单描述);
MODULE_VERSION(模块的版本字符串);
MODULE_ALIAS(模块的别名);
2.查看版本信息

3.写Makefile并查看

obj-m :这个变量是指定你要声称哪些模块模块的格式为 obj-m := <模块名>.o
modules-objs :这个变量是说明声称模块modules需要的目标文件 格式要求 <模块名>-objs := <目标文件>
切记:模块的名字不能取与目标文件相同的名字。如在这里模块名不能取成 mymod;
KDIR :这是我们正在运行的操作系统内核编译目录。也就是编译模块需要的环境
M= :指定我们源文件的位置
PWD :这是当前工作路径$(shell )是make的一个内置函数。用来执行shell命令。
4.make自动生成很多文件

5.加载、测试、删除模块
dmesg(查看内核信息)
- 加载、测试模块


- 卸载模块


- 其他


dmesg | tail -12 查看内核输出信息;tail -12 显示最后12条。
modinfo查看模块信息:
sudo modinfo 1.ko
depmod分析可加载模块的依赖性,并生成modules.dep文件和映射文件
modprobe Linux内核添加删除模块
二、系统调用(增加223号)
1.查看本机系统调用表地址

查看系统调用号


2.源码(详细代码略)、测试程序

测试程序
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned long x = 0;
x = syscall(223);
printf("Hello, %ld\n", x);
return 0;
}
分析
代码
static int clear_cr0(void) //使cr0寄存器的第17位设置为0(即是内核空间可写)
{
unsigned int cr0 = 0;
unsigned int ret;
asm volatile ("movl %%cr0, %%eax":"=a"(cr0)); //将cr0寄存器的值移动到eax的寄存器中,同时输出到cr0变量中
ret = cr0;
cr0 &= 0xfffeffff; //将cr0变量的值中的第17位清0,一会将修改后的值写入cr0寄存器
asm volatile ("movl %%eax, %%cr0": :"a"(cr0)); //将cr0变量的值做为输入,输入到寄存器eax中,同时移动到寄存器cr0中
return ret;
}
static int __init call_init(void)
{
sys_call_table_my = (unsigned long*)(SYS_CALL_TABLE_ADDRESS);
printk("call_init.......\n");
anything_saved = (int (*)(void))(sys_call_table_my[NUM]); //保存系统调用表中的NUM位置上的系统调用
orig_cr0 = clear_cr0(); //使内核地址空间可写
sys_call_table_my[NUM] =(unsigned long) &sys_mycall; //用自己的系统调用替换NUM位置上的系统调用
setback_cr0(orig_cr0); //使内核地址空间不可写
return 0;
}
asm volatile("movl %%eax,%%cr0"::"a"(val));
举例说明:volatile int i=10; int j = i; ... int k = i; volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。 volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。其中编译器编译优化是:由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
模块加载函数static int __init call_init(void):先保存系统调用表中223位置的系统调用,然后用static int clear_cr0(void)清零cr0的第十六 位,使内核空间可写,在内核空间可写的情况下用自己的系统调用asmlinkage long sys_mycall(void)替换223位置的系统调用,在完成替换后重新将cr0第十六位置位static void setback_cr0(int val)。
模块卸载函数static void __exit call_exit(void):用static int clear_cr0(void)清零cr0的第十六位,使内核空间可写,将保存的系统调用anything_saved恢复,完成后重新将cr0第十六位置位static void setback_cr0(int val)。
3.结果
测试代码为52.c
编译gcc 52.c -o b
运行 ./b


三、增加模块,功能:存在某指定文件,则输出“open success”,否则输出“open error”
asmlinkage long sys_mycall(void) //定义自己的系统调用
{
struct file *fp;
fp = filp_open("/home/zhu/mm", O_RDWR, 0644); //打开指定的文件
if (IS_ERR(fp)) //如果失败,输出“error”
{
printk("open error!!!!!!!!!!!!!!!!!!!!!!!!!\n");
return 0;
}
else //成功,输出“success”
{
printk("open success!!!!!!!!!!!!!!!!!!!!!!!\n");
}
return 0;
}
make
sudo insmod XXX.ko
./test.c

四、asmlinkage
0.函数定义前加宏asmlinkage ,表示这些函数通过堆栈而不是通过寄存器传递参数。
1.gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage。
1.采用asmlinkage,绝对不是因为不同架构可以通用户,而是因为用户态寄存器在系统调用进入内核态时,会把用户态的寄存器全部压栈,通过合理的构造。正好满足用户态通过寄存器传递参数,内核态通过栈取参数的标准要求。这是很巧妙的安排,是高效的体现!其实还可以发现,内核只有在系统调用时才用asmlinkage,其它函数都没有。这是有意而为之的。
2.用户调用syscall的时候,参数都是通过寄存器传进来的。中间内核由把所有的参数压栈了, 所以这个asmlinkage可以通过到gcc,恰好可以用正确的调用方式取到参数。(系统调用做的工作除了函数调用,其本质就是现场保护、压栈之类的操作)
3.内核前面的那些统一处理很重要,这样后端真正的的syscall 实现函数就可以得到统一的调用方式了,而不是之间面对不同的abi。确实比较方便了。不然每个syscall函数里面都要自己去处理不同abi,多很多重复代码。
4.也可以考虑在这个统一的处理的时候,把参数全部按照一定的规范放到寄存器。 但这个方法不能在所有的cpu架构上面都做的到。
5.“统一”要比这个“寄存器传参”要重要。
6.从用户切换到内核,要做大量的处理。相比较其他部分,参数的开销不算什么。
20135337——Linux实践二:模块的更多相关文章
- Linux及安全实践二——模块
Linux及安全实践二--模块 一.模块的编译.生成.测试.删除 1.编写模块代码 编写:gedit 3.c 2.编写Makefile obj-m :这个变量是指定你要声称哪些模块模块的格式为 obj ...
- LINUX实践之模块
模块实践 --关于模块代码部分 ---首先是.c代码: 一定会用到的函数有这几个:module_init().module_exit().MODULE_LICENSE() 会用到的头文件:module ...
- Linux实践二:模块
一.基本模块的实现: 1.进程遍历打印输出 2.简单地编写一个新的系统调用(替换空的系统调用号) 基本模块学到的知识点: 1.相关指令 make oldconfig 配置内核 make 编译内核 ma ...
- 20135218 Linux 实践二 编译模块
20135218 姬梦馨 1.编写模块代码 模块构造函数:执行insmod或modprobe指令加载内核模块时会调用的初始化函数.函数原型必须是module_init(),括号内是函数指针 模块析构函 ...
- Linux实践:模块
标签(空格分隔): 20135321余佳源 一.实践原理 Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合.之所以提供模块机制,是因为Linux本身是一个单内核.单内核由于所有内容都集 ...
- 【实践报告】Linux实践二
3.编译并安装内核与模块 sudo make bzImage –j3 编译内核 sudo make modules –j3 编译模块 sudo make modules ...
- 20135337——linux实践三:ELF文件格式分析(32位系统)
ELF文件格式分析 可重定位文件 十六进制形式显示内容 显示各个段.符号表相关信息 查看各个段信息 elf文件头信息 段表 符号表信息 查看堆栈 具体分析 1.ELF文件头信息(小字节优先,均十六进制 ...
- 20135337——Linux实践三:程序破解
程序破解 查看 运行 反汇编,查看汇编码 对反汇编代码进行分析: 在main函数的汇编代码中可以看出程序在调用"scanf"函数请求输入之后,对 [esp+0x1c] 和 [esp ...
- 20135337——Linux实践三:ELF文件格式(64位系统,简单分析)
ELF文件格式简单分析 (具体分析见上一篇ELF文件格式32位系统) ELF-header 第一行: 457f 464c :魔数: 0201 :64位系统,小端法 01 :文件头版本 剩余默认0: 第 ...
随机推荐
- 4.9Python数据类型(5)列表(新版)
前言 列表是一种python类似数组的数据结构,操作灵活,可存储多种类型的数据. 目录 1.列表的基础知识 2.列表的增删改查并操作 3.列表的其他操作 4.基本方法表 (一)列表的基础知识 1.说明 ...
- Docker容器学习与分享12
Docker多主机管理 之前在一台Centos7上安装了Docker,如果是在多台主机上都安装Docker,用手动安装的方法不光效率低下,而且有可能出错,所以可以使用Docker Machine进行多 ...
- Deepin系统安装mysql教程及相关操作
Deepin系统安装mysql教程及相关操作 1.安装MySQL sudo apt-get install mysql-server,期间需要输入两次密码,root账户的密码. sudo apt-ge ...
- 建立标准编码规则(四)-C#编码规范分类及实现
1 现实中的问题 在任何一个公司,每个程序员编写的习惯可能都是不一样的,大到命名空间,小到变量的名字.甚至,我们去github,里面的大牛每个开源的项目的命名规则可能都不一样. 我们通常会遇到下面的问 ...
- MDC的使用(Mapped Diagnostic Context)的使用
通常我们可能会有大量的任务需要提交提交到线程池执行,但是此时如果不对日志添加唯一标识进行区分的话回到错乱一坨无法进行查看.因此可以对每一天日志添加唯一的标识,例如使用userid作为日志的唯一标志.这 ...
- jconsole使用
先看一张图 根据JConsole和任务管理器对比,堆内存大小在250M左右,差不多空跑一个程序用idea启动springboot就是这个大小 项目启动初始类在一万个左右,活动线程50个上下,cpu利用 ...
- LNMP安装201812012237
发表这篇文章最初的意愿是想做个最新版的zabbix使用,后来看了下好多“软件”都升级了(如nginx.mysql等),就想干脆做个最新版本的LNMP环境得了,再单独做zabbix的最新版本省得以后升级 ...
- C++ 实现strcpy
strcpy库函数的原型: // 把src字符串拷贝到dest,并返回dest char *strcpy(char *dest, const char *src) 注意点: 1.形参src用const ...
- SkylineGlobe MFC C++ 开发示例代码
SkylineGlobe的SDK底层是跨平台的C++内核,面向不同平台封装原生的API,具有很高的执行效率, 下面是C++二次开发时的示例代码: #import "D:\Program Fi ...
- SQL Server 索引中include的魅力(具有包含性列的索引)(转载)
开文之前首先要讲讲几个概念 [覆盖查询] 当索引包含查询引用的所有列时,它通常称为“覆盖查询”. [索引覆盖] 如果返回的数据列就包含于索引的键值中,或者包含于索引的键值+聚集索引的键值中,那么就不 ...