📄 系统调用.txt
字号:
*vmp):输入参数为当前进程的mm、需要插入的vmp。insert_vm_struct的功能是按地址大小序列把vmp插入到当前进程mm的mmap链或mmap_avl树中,并且把vmp插入到vmp->inode的i_mmap环(循环共享链)中。
avl_insert_neighbours(struct vm_area_struct * new_node,** ptree,**
to_the_left,**
to_the_right):输入参数为当前需要插入的新vma结点new_node、目标mmap_avl树ptree、新结点插入ptree后它左边的结点以及它右边的结点(左右边结点按mmap_avl中各vma->vma_end大小排序)。avl_insert_neighbours的功能是插入新vma结点new_node到目标mmap_avl树ptree中,并且调用avl_rebalance以保持ptree的平衡树特性,最后返回new_node左边的结点以及它右边的结点。
avl_rebalance(struct vm_area_struct *** nodeplaces_ptr, int
count):输入参数为指向vm_area_struct指针结构的指针数据nodeplaces_ptr[](每个元素表示需要平衡的mmap_avl子树)、数据元素个数count。avl_rebalance的功能是从nodeplaces_ptr[--count]开始直到nodeplaces_ptr[0]循环平衡各个mmap_avl子树,最终使整个mmap_avl树平衡。
down(struct semaphore *
sem):输入参数为同步(进入临界区)信号量sem。down的功能根据当前信号量的设置情况加锁(阻止别的进程进入临界区)并继续执行或进入等待状态(等待别的进程执行完成退出临界区并释放锁)。
down定义在/include/linux/sched.h中:
extern inline void down(struct semaphore * sem)
{
if (sem->count <= 0)
__down(sem);
sem->count--;
}
up(struct semaphore *
sem)输入参数为同步(进入临界区)信号量sem。up的功能根据当前信号量的设置情况(当信号量的值为负数:表示有某个进程在等待使用此临界区 )释放锁。
up定义在/include/linux/sched.h中:
extern inline void up(struct semaphore * sem)
{
sem->count++;
wake_up(&sem->wait);
}
kfree_s(a,b):kfree_s定义在/include/linux/malloc.h中:#define kfree_s(a,b)
kfree(a)。而kfree()将在后面3.3中详细讨论。
avl_neighbours(struct vm_area_struct * node,* tree,** to_the_left,**
to_the_right):输入参数为作为查找条件的vma结点node、目标mmap_avl树tree、node左边的结点以及它右边的结点(左右边结点按mmap_avl中各vma->vma_end大小排序)。avl_
neighbours的功能是根据查找条件node在目标mmap_avl树ptree中找到node左边的结点以及它右边的结点,并返回。
avl_remove(struct vm_area_struct * node_to_delete, **
ptree):输入参数为需要删除的结点node_to_delete和目标mmap_avl树ptree。avl_remove的功能是在目标mmap_avl树ptree中找到结点node_to_delete并把它从平衡树中删除,并且调用avl_rebalance以保持ptree的平衡树特性。
remove_shared_vm_struct(struct vm_area_struct
*mpnt):输入参数为需要从inode->immap环中删除的vma结点mpnt。remove_shared_vm_struct的功能是从拥有vma结点mpnt
的inode->immap环中删除的该结点。
[目录]
添加新调用
[目录]
例子一
深入LINUX内核:为你的LINUX增加一条系统调用
充分利用LINUX开放源码的特性,我们可以轻易地对它进行修改,使我们能够随心所欲驾驭LINUX,完成一个真正属于自己的操作系统,这种感觉使无与伦比的,下面通过为LINUX增加一个系统调用来展示LINUX作为一个开放源码操作系统的强大魅力。
首先,让我们简单地分析一下LINUX中与系统调用的相关的部分:
LINUX的系统调用的总控程序是system_call,它是LINUX系统中所有系统调用的总入口,这个system_call是作为一个中断服务程序挂在中断0x80上,系统初始化时通过void
init
trap_init(void)调用一个宏set_system_ gate(SYSCALL_VERCTOR,&system_call)来对IDT表进行初始化,在0x80对应的中断描述符处填入system_call函数的地址,其中宏SYSCALL_VERCTOR就是0x80。
当发生一条系统调用时,由中断总控程序保存处理机状态,检查调用参数的合法性,然后根据系统调用向量在sys_call_table中找到相应的系统服务例程的地址,然后执行该服务例程,完成后恢复中断总控程序所保存的处理机状态,返回用户程序。
系统服务例程一般定义于kernel/sys.c中,系统调用向量定义在include/asm-386/unistd.h中,而sys_call _table表则定义在arch/i386/kernel/entry.S文件里。
现在我们知道增加一条系统调用我们首先要添加服务例程实现代码,然后在进行对应向量的申明,最后当然还要在sys_call_table表中增加一项以指明服务例程的入口地址。
OK,有了以上简单的分析,现在我们可以开始进行源码的修改,假设我们需要添加一条系统调用计算两个整数的平方和,系统调用名为add2,我们需要修改三个文件:kernel/sys.c
, arch/i386/kernel/entry.S 和 include/asm-386/unistd.h。
1、修改kernel/sys.c ,增加服务例程代码:
asmlinkage int sys_add2(int a , int b)
{
int c=0;
c=a*a+b*b;
return c;
}
2、修改include/asm-386/unistd.h ,对我们刚才增加的系统调用申明向量,以使用户或系统进程能够找到这条系统调用,修改后文件如下所示:
.... .....
#define _NR_sendfile 187
#define _NR_getpmsg 188
#define _NR_putmsg 189
#define _NR_vfork 190
#define _NR_add2 191 /* 这是我们添加的部分,191即向量 */
3、修改include/asm-386/unistd.h , 将服务函数入口地址加入 sys_call_table,首先找到这么一段:
.... .....
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */
.long SYMBOL_NAME(sys_vfork) /*190 */
.rept NR_syscalls-190
修改为如下:
.... .....
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */
.long SYMBOL_NAME(sys_vfork) /*190 */
.long SYMBOL_NAME(sys_add2) <=我们的系统调用
.rept NR_syscalls-191 <=将190改为191
OK,大功告成,现在只需要重新编译你的LINUX内核,然后你的LINUX就有了一条新的系统调用int add2(int a, int b)。
[目录]
例子二
如何在Linux中添加新的系统调用
系统调用是应用程序和操作系统内核之间的功能接口。其主要目的是使得用户可以使用操作系统提供的有关设备管理、输入/输入系统、文件系统和进程控制、通信以及存储管理等方面的功能,而不必了解系统程序的内部结构和有关硬件细节,从而起到减轻用户负担和保护系统以及提高资源利用率的作用。
1 Linux系统调用机制
在Linux系统中,系统调用是作为一种异常类型实现的。它将执行相应的机器代码指令来产生异常信号。产生中断或异常的重要效果是系统自动将用户态切换为核心态来对它进行处理。这就是说,执行系统调用异常指令时,自动地将系统切换为核心态,并安排异常处理程序的执行。
Linux用来实现系统调用异常的实际指令是:
Int $0x80
这一指令使用中断/异常向量号128(即16进制的80)将控制权转移给内核。为达到在使用系统调用时不必用机器指令编程,在标准的C语言库中为每一系统调用提供了一段短的子程序,完成机器代码的编程工作。事实上,机器代码段非常简短。它所要做的工作只是将送给系统调用的参数加载到CPU寄存器中,接着执行int
$0x80指令。然后运行系统调用,系统调用的返回值将送入CPU的一个寄存器中,标准的库子程序取得这一返回值,并将它送回用户程序。
为使系统调用的执行成为一项简单的任务,Linux提供了一组预处理宏指令。它们可以用在程序中。这些宏指令取一定的参数,然后扩展为调用指定的系统调用的函数。
这些宏指令具有类似下面的名称格式:
_syscallN(parameters)
其中N是系统调用所需的参数数目,而parameters则用一组参数代替。这些参数使宏指令完成适合于特定的系统调用的扩展。例如,为了建立调用setuid()系统调用的函数,应该使用:
_syscall1( int, setuid, uid_t, uid )
syscallN(
)宏指令的第1个参数int说明产生的函数的返回值的类型是整型,第2个参数setuid说明产生的函数的名称。后面是系统调用所需要的每个参数。这一宏指令后面还有两个参数uid_t和uid分别用来指定参数的类型和名称。
另外,用作系统调用的参数的数据类型有一个限制,它们的容量不能超过四个字节。这是因为执行int
$0x80指令进行系统调用时,所有的参数值都存在32位的CPU寄存器中。使用CPU寄存器传递参数带来的另一个限制是可以传送给系统调用的参数的数目。这个限制是最多可以传递5个参数。所以Linux一共定义了6个不同的_syscallN()宏指令,从_syscall0()、_syscall1()直到_syscall5()。
一旦_syscallN()宏指令用特定系统调用的相应参数进行了扩展,得到的结果是一个与系统调用同名的函数,它可以在用户程序中执行这一系统调用。
2 添加新的系统调用
如果用户在Linux中添加新的系统调用,应该遵循几个步骤才能添加成功,下面几个步骤详细说明了添加系统调用的相关内容。
(1) 添加源代码
第一个任务是编写加到内核中的源程序,即将要加到一个内核文件中去的一个函数,该函数的名称应该是新的系统调用名称前面加上sys_标志。假设新加的系统调用为mycall(int
number),在/usr/src/linux/kernel/sys.c文件中添加源代码,如下所示:
asmlinkage int sys_mycall(int number)
{
return number;
}
作为一个最简单的例子,我们新加的系统调用仅仅返回一个整型值。
(2) 连接新的系统调用
添加新的系统调用后,下一个任务是使Linux内核的其余部分知道该程序的存在。为了从已有的内核程序中增加到新的函数的连接,需要编辑两个文件。
在我们所用的Linux内核版本(RedHat 6.0,内核为2.2.5-15)中,第一个要修改的文件是:
/usr/src/linux/include/asm-i386/unistd.h
该文件中包含了系统调用清单,用来给每个系统调用分配一个唯一的号码。文件中每一行的格式如下:
#define __NR_name NNN
其中,name用系统调用名称代替,而NNN则是该系统调用对应的号码。应该将新的系统调用名称加到清单的最后,并给它分配号码序列中下一个可用的系统调用号。我们的系统调用如下:
#define __NR_mycall 191
系统调用号为191,之所以系统调用号是191,是因为Linux-2.2内核自身的系统调用号码已经用到190。
第二个要修改的文件是:
/usr/src/linux/arch/i386/kernel/entry.S
该文件中有类似如下的清单:
.long SYMBOL_NAME()
该清单用来对sys_call_table[]数组进行初始化。该数组包含指向内核中每个系统调用的指针。这样就在数组中增加了新的内核函数的指针。我们在清单最后添加一行:
.long SYMBOL_NAME(sys_mycall)
(3) 重建新的Linux内核
为使新的系统调用生效,需要重建Linux的内核。这需要以超级用户身份登录。
#pwd
/usr/src/linux
#
超级用户在当前工作目录(/usr/src/linux)下,才可以重建内核。
#make config
#make dep
#make clearn
#make bzImage
编译完毕后,系统生成一可用于安装的、压缩的内核映象文件:
/usr/src/linux/arch/i386/boot/bzImage
(4) 用新的内核启动系统
要使用新的系统调用,需要用重建的新内核重新引导系统。为此,需要修改/etc/lilo.conf文件,在我们的系统中,该文件内容如下:
boot=/dev/hda
map=/boot/map
install=/boot/boot.b
prompt
timeout=50
image=/boot/vmlinuz-2.2.5-15
label=linux
root=/dev/hdb1
read-only
other=/dev/hda1
label=dos
table=/dev/had
首先编辑该文件,添加新的引导内核:
image=/boot/bzImage-new
label=linux-new
root=/dev/hdb1
read-only
添加完毕,该文件内容如下所示:
boot=/dev/hda
map=/boot/map
install=/boot/boot.b
prompt
timeout=50
image=/boot/bzImage-new
label=linux-new
root=/dev/hdb1
read-only
image=/boot/vmlinuz-2.2.5-15
label=linux
root=/dev/hdb1
read-only
other=/dev/hda1
label=dos
table=/dev/hda
这样,新的内核映象bzImage-new成为缺省的引导内核。
为了使用新的lilo.conf配置文件,还应执行下面的命令:
#cp /usr/src/linux/arch/i386/boot/zImage /boot/bzImage-new
其次配置lilo:
# /sbin/lilo
现在,当重新引导系统时,在boot:提示符后面有三种选择:linux-new 、 linux、dos,新内核成为缺省的引导内核。
至此,新的Linux内核已经建立,新添加的系统调用已成为操作系统的一部分,重新启动Linux,用户就可以在应用程序中使用该系统调用了。
(5)使用新的系统调用
在应用程序中使用新添加的系统调用mycall。同样为实验目的,我们写了一个简单的例子xtdy.c。
/* xtdy.c */
#include <linux/unistd.h>
_syscall1(int,mycall,int,ret)
main()
{
printf("%d \n",mycall(100));
}
编译该程序:
# cc -o xtdy xtdy.c
执行:
# xtdy
结果:
# 100
注意,由于使用了系统调用,编译和执行程序时,用户都应该是超级用户身份。
(文/程仁田)
[目录]
[ 本文件由良友·收藏家自动生成 ]
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -