[转] 添加新的系统调用 _syscall0(int, mysyscall)
实验目的
阅读 Linux 内核源代码,通过添加一个简单的系统调用实验,进一步理解
Linux操作系统处理系统调用的统一流程。通过用kernel module的方法来实现一
个系统调用实验,进一步理解Linux的内核模块和Linux系统调用机制,对通过
module方法添加一个系统调用的步骤有所了解。
实验内容
1、在现有的系统中添加一个不用传递参数的系统调用。调用这个系统调
用,使用户的uid变成0。
2、用kernel module的方法来实现一个系统调用
实验提示 1
怎么样?觉得困难还是觉得太简单?我们首先承认,每个人接触Linux的时间长短不一
样,因此基础也会有一点差别。那么对于觉得太简单的人呢,请你迅速地合上书本,然后跑
到电脑前面,开始实现这个题目。来吧,不要眼高手低,做完之后,你就可以跳过这一节,
直接进入下一节的学习了。对于觉得有点困难的人呢,不用着急,这一节就是专门为你准备
的。我们会列出详细的实现步骤,你一定也没有问题的。
如果你前面对整个系统调用的过程有一个清晰的理解的话,我们就顺着系统调用的流程
思路,给出一个添加新的系统调用的步骤:
一、决定你的系统调用的名字
这个名字就是你编写用户程序想使用的名字,比如我们取一个简单的名字:mysyscall。
一旦这个名字确定下来了,那么在系统调用中的几个相关名字也就确定下来了。
l 系统调用的编号名字:__NR_mysyscall;
l 内核中系统调用的实现程序的名字:sys_mysyscall;
现在在你的用户程序中出现了:
#include <linux/unistd.h>
int main()
{
mysyscall();
}
流程转到标准C库。
二、利用标准C 库进行包装吗
编译器怎么知道这个mysyscall 是怎么来的呢?在前面我们分析的时候,我们知道那时
标准C 库给系统调用作了一层包装,给所有的系统调用做出了定义。但是显然,我们可能
不愿意去改变标准C库,也没有必要去改变。那么我们在自己的程序中来做:
#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意这里没有分号*/
int main()
{
mysyscall();
}
好,由于有了_syscall0 这个宏,mysyscall 将得到定义。但是现在系统会去找系统调用号,
以放入eax。所以,接下来我们定义系统调用号。
三、添加系统调用号
系统调用号在文件unistd.h里面定义。这个文件可能在你的系统上会有两个版本:一个
是C库文件版本,出现的地方是在/usr/include/unistd.h和/usr/include/asm/unistd.h;另外还有
一个版本是内核自己的unistd.h,出现的地方是在你解压出来的2.4.18 内核代码的对应位置
(比如/usr/src/linux/include/linux/unistd.h和/usr/include/asm-i386/unistd.h)。当然,也有可能
这个C 库文件只是一个到对应内核文件的连接。至于为什么会存在两个版本的头文件,这
个问题将会在5-6较高级主题一节进行说明。现在,你要做的就是在文件unistd.h中添加我
们的系统调用号:__NR_mysyscall,如下所示:
include/asm-i386/unistd.h
/usr/include/asm/unistd.h
240 #define __NR_llistxattr 233
241 #define __NR_flistxattr 234
242 #define __NR_removexattr 235
243 #define __NR_lremovexattr 236
244 #define __NR_fremovexattr 237
245 #define __NR_mysyscall 238 /* mysyscall adds here */
添加系统调用号之后,系统才能根据这个号,作为索引,去找syscall_table中的相应表项。
所以说,我们接下来的一步就是:
四、在系统调用表中添加相应表项
我们前面讲过,系统调用处理程序(system_call)会根据eax 中的索引到系统调用表
(sys_call_table)中去寻找相应的表项。所以,我们必须在那里添加我们自己的一个值。
arch/i386/kernel/entry.S
398 ENTRY(sys_call_table)
399 .long SYMBOL_NAME(sys_ni_syscall)
400 .long SYMBOL_NAME(sys_exit)
401 .long SYMBOL_NAME(sys_fork)
402 .long SYMBOL_NAME(sys_read)
403 .long SYMBOL_NAME(sys_write)
……
……
634 .long SYMBOL_NAME(sys_ni_syscall)
635 .long SYMBOL_NAME(sys_ni_syscall)
636 .long SYMBOL_NAME(sys_ni_syscall)
637 .long SYMBOL_NAME(sys_mysyscall)
638
639 .rept NR_syscalls-(.-sys_call_table)/4
640 .long SYMBOL_NAME(sys_ni_syscall)
641 .endr
到现在为止,系统已经能够正确地找到并且调用sys_mysyscall。剩下的就只有一件事情,那
就是sys_mysyscall的实现。
五、sys_mysyscall 的实现
我们把这一小段程序添加在kernel/sys.c 里面。在这里,我们没有在kernel 目录下另外
添加自己的一个文件,这样做的目的是为了简单,而且不用修改Makefile,省去不必要的麻
烦。
asmlinkage int sys_mysyscall(void)
{
current->uid = current->euid = current->suid = current->fsuid = 0;
return 0;
}
这个系统调用中,把标志进程身份的几个变量uid、euid、suid和fsuid都设为0。
到这里为止,我们所要做的添加一个新的系统调用的所有工作就完成了,是不是非常简
单?的确如此。因为Linux 内核结构的层次性还是非常清楚的,这就使得每一个开发者可以
把精力放在怎么样实现具体的功能上,而不用在一些接口函数上伤脑筋。
现在所有的代码添加已经结束,那么要使得这个系统调用真正在内核中运行起来,我们
就需要对内核进行重新编译。这个问题我们在第一章的时候就讨论到了,应该没有问题,因
此我们在这里略过。
六、编写用户态程序
要测试我们新添加的系统调用,我们可以编写一个用户程序调用我们自己的系统调用。
我们对自己的系统调用的功能已经很清楚了:使得自己的uid变成0。那么我们看看是不是
如此:
用户态程序
#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意这里没有分号*/
int main()
{
mysyscall(); /* 这个系统调用的作用是使得自己的uid为0 */
printf(“em…, this is my uid: %d. \n”, getuid());
}
实验提示2:
在这里,我们把系统调用的知识和Kernel Module的知识结合起来,用kernel module的
方法来实现一个系统调用。这个系统调用是gettimeofday的简化版本。实验的目的只是为了
使你对通过module方法添加一个系统调用的步骤有所了解。
具体代码示例如下:
/* pedagogictime.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
/* 在这个头文件里面包含了所有的系统调用号__NR_... */
#include <linux/unistd.h>
/* for struct time* */
#include <linux/time.h>
/* for copy_to_user() */
#include <asm/uaccess.h>
/* for current macro */
#include <linux/sched.h>
#define __NR_pedagogictime 238
MODULE_DESCRIPTION("My sys_pedagogictime()");
MODULE_AUTHOR("Your Name :), (C) 2002, GPLv2 or later");
/* 用来保存旧系统调用的地址*/
static int (*anything_saved)(void);
/* 这个是我们自己的系统调用函数sys_pedagogictime(). */
static int sys_pedagogictime(struct timeval *tv)
{
struct timeval ktv;
/* 这里我们需要增加模块使用计数。*/
MOD_INC_USE_COUNT;
do_gettimeofday(&ktv);
if (copy_to_user(tv, &ktv, sizeof(ktv))){
MOD_DEC_USE_COUNT;
return -EFAULT;
}
printk(KERN_ALERT"Pid %ld called sys_gettimeofday().\n",(long)current->pid);
MOD_DEC_USE_COUNT;
return 0;
}
/* 这里是初始化函数。__init标志表明这个函数使用后就可以丢弃了。*/
int __init init_addsyscall(void)
{
extern long sys_call_table[];
/* 保存原来系统调用表中此位置的系统调用*/
anything_saved = (int(*)(void))(sys_call_table[__NR_pedagogictime]);
/* 把我们自己的系统调用放入系统调用表,注意进行类型转换*/
sys_call_table[__NR_pedagogictime] = (unsigned long)sys_pedagogictime;
return 0;
}
/* 这里是退出函数。__exit标志表明如果我们不是以模块方式编译这段程序,则这个标志后的
* 函数可以丢弃。也就是说,模块被编译进内核,只要内核还在运行,就不会被卸载。
*/
void __exit exit_addsyscall(void)
{
extern long sys_call_table[];
/* 恢复原先的系统调用*/
sys_call_table[__NR_pedagogictime] = (unsigned long)anything_saved;
}
/* 这两个宏告诉系统我们真正的初始化和退出函数*/
module_init(init_addsyscall);
module_exit(exit_addsyscall);
使用kernel module的方法来实现的这个系统调用,我们需要做的只是用这条命令:
gcc -Wall -O2 -DMODULE -D__KERNEL__ -DLINUX -c pedagogictime.c.
编译成.o文件,然后使用insmod pedagogictime.o把它动态地加载到正在运行的内核中。显
然,这样的做法比起我们先前的那种要重新编译内核的办法更加灵活,更加方便。这也正是
Linux kernel module program如此受欢迎的原因。
测试用这个使用kernel module编写的系统调用的用户程序:
测试用的用户程序
/* for struct timeval */
#include <linux/time.h>
/* for _syscall1 */
#include <linux/unistd.h>
#define __NR_pedagogictime 238
_syscall1(int, pedagogictime, struct timeval *, thetime)
int main()
{
struct timeval tv;
pedagogictime(&tv);
printf("tv_sec : %ld\n", tv.tv_sec);
printf("tv_nsec: %ld\n", tv.tv_usec);
printf("em..., let me sleep for 2 second.:)\n");
sleep(2);
pedagogictime(&tv);
printf("tv_sec : %ld\n", tv.tv_sec);
printf("tv_nsec: %ld\n", tv.tv_usec);
}
假设这个程序是test.c,那么使用gcc -o test test.c得到test可执行文件,然后你可以执
行这个test看看结果。
[转] 添加新的系统调用 _syscall0(int, mysyscall)的更多相关文章
- 如何在Linux中添加新的系统调用
系统调用是应用程序和操作系统内核之间的功能接口.其主要目的是使得用户 可以使用操作系统提供的有关设备管理.输入/输入系统.文件系统和进程控制. 通信以及存储管理等方面的功能,而不必了解系统程序的内部结 ...
- ASP.NET MVC 5 - 给电影表和模型添加新字段
在本节中,您将使用Entity Framework Code First来实现模型类上的操作.从而使得这些操作和变更,可以应用到数据库中. 默认情况下,就像您在之前的教程中所作的那样,使用 Entit ...
- Asp.Net MVC4入门指南(7):给电影表和模型添加新字段
在本节中,您将使用Entity Framework Code First来实现模型类上的操作.从而使得这些操作和变更,可以应用到数据库中. 默认情况下,就像您在之前的教程中所作的那样,使用 Entit ...
- 使用percona xtradb cluster的IST方式添加新节点
使用percona xtradb cluster的IST(Incremental State Transfer)特性添加新节点,防止新节点加入时使用SST(State SnapShop Transfe ...
- spring AOP Bean添加新方法
目的:为studentAdditionalDetails中添加Student的showDetails()和ExtraShowDetails()两个方法 spring 中AOP能够为现有的方法添加额外 ...
- C# DataGridView控件 动态添加新行
DataGridView控件在实际应用中非常实用,特别需要表格显示数据时.可以静态绑定数据源,这样就自动为DataGridView控件添加相应的行.假如需要动态为DataGridView控件添加新行, ...
- C# DataGridView添加新行的2个方法
可以静态绑定数据源,这样就自动为DataGridView控件添加 相应的行.假如需要动态为DataGridView控件添加新行,方法有很多种,下面简单介绍如何为DataGridView控件动态添加新行 ...
- 自己动手为PHP7添加新的语法特性
好文章! nikic介绍了如何向PHP添加新的语法特性,原文写的非常精彩,具体是添加in语法功能,使最终实现: <?php $words = ['hello', 'world', 'foo', ...
- 【译】ASP.NET MVC 5 教程 - 9:添加新字段
原文:[译]ASP.NET MVC 5 教程 - 9:添加新字段 在本节中,我们将使用Entity Framework Code First 数据迁移功能将模型类的改变应用到数据库中. 默认情况下,当 ...
随机推荐
- js多行省略
$(function (){ // var $introduce = $(".c-introduce").html(); // $new_introduce = $introduc ...
- table表格中的内容溢出布局方式
什么是内容溢出呢?其实就是当文字很多的时候,如果内容区域只有那么长,那么多出的部分以点点点代替. 这次做的案例是在table里面,我们知道当我们在table里输入过多的文字内容的时候会撑乱表格,例如一 ...
- python 函数传递参数的多种方法
python中函数根据是否有返回值可以分为四种:无参数无返回值,无参数有返回值,有参数无返回值,有参数有返回值. Python中函数传递参数的形式主要有以下五种,分别为位置传递,关键字传递,默认值传递 ...
- Oracle逻辑备份与恢复
1. 备份的类型 按照备份方式的不同,可以把备份分为两类: 1.1 逻辑备份:指通过逻辑导出对数据进行备份.将数据库中的用户对象导出到一个二进制文件中,逻辑备份使用导入导出工具:EXPDP/IMP ...
- int unsigned实验
create table t1(a int unsigned,b int unsigned); insert into t1 select 1,2; select 1-2 from t1; Error ...
- Python查找当前路径和子路径下指定后缀名的文件
# -*- encoding:utf-8 -*- import os def SearchFile(path,text): try: files=os.listdir(path) for f in f ...
- 解决POST数据时因启用Csrf出现的400错误
第一种解决办法是关闭Csrf public function init(){ $this->enableCsrfValidation = false; } 第二种解决办法是在form表单中加入隐 ...
- USACO Milking Cows
思路: 脑抽了,一看题目,这不就是线段树么,离散化区间合并..最终发现我并不会写...于是看了下题目范围10^6...模拟水之..每个区间左端点+1,右端点-1,从左到右扫一下就行了... 代码: / ...
- hadoop机架感知与网络拓扑分析:NetworkTopology和DNSToSwitchMapping
hadoop网络拓扑结构在整个系统中具有很重要的作用,它会影响DataNode的启动(注册).MapTask的分配等等.了解网络拓扑对了解整个hadoop的运行会有很大帮助. 首先通过下面两个图来了解 ...
- 悟javascript ---------------20160705
1. 首先观察页面需求 如果js要书写多个,那么一定用到循环 或者加上if判断 或者用到switch switch (表达式){ case 值1 : 语句1 break; case 值2 : 语句 ...