Unix编程学习笔记----对系统调用的完全理解

来源:互联网 发布:大数据 图书 编辑:程序博客网 时间:2024/05/22 11:33

系统调用的具体流程:

 

参考《linux下系统调用原理解析及增加系统调用的方法》

 

我们首先看一下,unistd.h  所处的路径:

 

我们看一下unistd.h的文件内容。

 

 

现在我们就分析一下,这个unistd.h头文件的内容:

 

在文件中形如:

 

#define --NR_syscallname  NNN

__SYSCALL(__NR_syscallname, sys_syscallname)

 

那就让我们看这里面有哪些内容??

NR   后面跟的是   系统调用函数名,   NNN是 该系统调用函数名所对应的系统调用号,

例如上面的实例中,getpid  是系统调用的函数名,而172 就是getpid这个系统调用函数名对应的系统调用号。

 

 

由此,我们可以看出,在unistd.h头文件中,它给出了每一个系统调用函数名所对应的系统调用号。

 

 

当我们确定了一个系统调用函数名的,系统调用号,之后就是根据系统调用号索引  sysz_call_table  (即:系统调用表,系统调用表中保存着所有系统调用的函数指针)

最终定位到该服务程序在内存中的地址。

 

 

具体细节:

 

Sys_call_table  每一项占用4个字节,system_call函数可以读取eax寄存器,获取当前系统调用的系统调用号,讲系统调用号乘以4生成偏移地址,然后以 sys_call_table 即系统调用表为基址,加上偏移地址所指向的的内容,就是调用的服务程序的入口地址。

 

 

 

 

 

参考论文原文如下:

 

Li n ux下系统调用原理解析及增加系统调用的方法
The
principle of syStem cal|and the methOd of add new system cal|
胡盼盼 (华中科技大学计算机学院湖北武汉430074)
摘要:系统调用是应用程序和操作系统之间进行交互的接口。程序的运行离不开系统调用。本文讨论讨论了
Iinux下系统调用的实现机理,并以一个简单的例子说明了在|.nux下增加系统调用的实现方法,本文的讨论基于
|.nux 2.4内核版本。
关键词:I.nux系统调用
1 Linu×系统调用概述
系统调用是内核提供的,功能十分强大的一系列
函数。这些函数在Linux的内核中实现,用来完成一些
系统级的功能。通过一定的方式呈现给用户,是用户
程序与内核交互的一个接口,在用户程序每调用一次
系统调用函数,都将进行一次用户空间到内核空间的
转换。如L.nux中常用的open,陀ad等函数,都属于系
统调用。
在L.nux系统中,系统调用是作为一种异常类型实
现的,它将执行相应的机器代码指令来产生异常信号。
在执行系统调用异常指令时,自动地将系统切换为核 心态(模式切换,mode刚tch),并安排异常处理程序
的执行。用户态的程序只有通过门(gate)陷入(trap)
到系统内核中去(执行.nt指令),才能执行一些具有特
权的内核函数。系统调用完成后,系统执行另一组特
征指令(iret指令)将系统返回到用户态,控制权返回
给进程。L-nux用来实现系统调用异常的实际指令是:
.nf$Ox80,这一指令使用中断/异常向量号128(即16
进制的80)将控制权转移给内核(进行模式切换)。为
达到在使用系统调用时不必用机器指令编程,在标准
的C语言库中为每一系统调用提供了一段短的子程
序,完成机器代码的编程工作。这段子程序所要做的
工作只是将送给系统调用的参数加载到CPU寄存器
中,接着执行int$Ox80指令。然后运行系统调用,系
统调用的返回值将送入CPU的一个寄存器中,标准的
库子程序取得这一返回值,并将它送回用户程序。
2 L.nu×系统调用结构分析
2.1部分重要的宏的说明
我们以L.nu也.4内核为例,跟系统调用相关的内
核代码主要有:
·/usr/src/1.nux一2.4/arch/谂86/kemel/entry.S
·/usr/src/¨nux一2.4/arch/1386/kemeI/trap.c
./usr/src/llnu×一2.4/arch/玛86/kernel/unistd.
h
这三个内核文件中定义了系统调用所需的一些信
息。下面将说明这些代码文件中与系统调用相关的一
些重要的宏定义,便于后面对系统调用过程的说明。
SA、,E—ALL
SAVE—ALL宏定义在entry.s中,代码如下:
#d酾neSAVE_ALL\
cId:\
pushI%es;\
pushI%ds;\
pushI%eQx;\
pushI%ebp;\
pushl%edi;\
pushl%esi;\
pushI%edx;\
pushl%e以;\
pushI%ebx;\
maVI$(——.}(ERNEL_DS),%edx;\
mOvl%edx,%ds;\
万方数据
计算机系统应用 2007年第8期
moVl%edx,%es;
我们可以看到SAVE』LL宏主要是保存寄存器信
息,即进行现场保留。
RESTORE—ALL
RESTORE—ALL也定义在entry.S中。其定义如下:
popl%ebx;\
pOpl%eC)(;\
popI%ed×;\
popI%es.I\
popl%edi;\
pOpl%ebp;\
pOpI%ea×;\
popl%ds;\
popI%es;\
addI$4,%esp;\
}ret·\
可以看出,REsTORE—ALL与SA、,E—ALL的过程是刚
好相反的,主要完成现场恢复。iret是返回指令。
—_NR—Sysca¨name NNN
这个宏定义在unlstd.h中,其中syscaIlname表示
系统调用函数名,NNN表示系统调用序号。
}}d酣ne—NR-e×.t 1
删efIne—NR—如水2
#define—NR—read 3
#deflne NR wr计e 4
L.nux系统给每个系统调用都编了号,当执行一个
系统调用时,将按照系统调用号来索引系统调用。
一sysca¨N(type,name,parameterl type,parameferl,
…..)
这是一个带参数宏,定义在unistd.h中,用来展开
系统调用,N是可变的,表示系统调用函数的参数个
数。宏参数分别为系统调用函数的返回类型,系统调
用函数名,系统调用参数类型,参数名。可以加多个参
数。其展开后如下(假设此系统调用没有参数):
撑define—sysca¨O(type,name)
type name(Void)
{
LOng—』es;
—qsm—voIaf.1e(“int$Ox80
:“=a”(一res)
:“”(一NR-}蝌name));
—-syscall—refum(type,一res);
}
系统调用表sys_ca¨jabIe
在entry.s文件中还有一个重要的表就是系统调
用表,系统调用表保存着所有系统调用的函数指针,以
方便进行系统调用的索引。其定义如下:
ENTRY(sys—ca¨jabIe)
.10ng SYMBOLNAME(sys』i—syscaII)
.Iong sYMBOLNAME(sysJx.f)
.Iong sYMBOLNAME(sysjork)
.Iong SYMBOLNAME(剐s—reQd)
2。2系统调用过程解析
我们以getujd()这个系统调用为示例.gefU.d()
的功能是获取当前进程的ID号。测试程序如下:
#incIude<Ijnux/unistd.h>
.nt main(){
int i=getuId();
pr.ntf(“my uid is:%d\n”,i);
}
这个程序调用getuid()函数,来获得当前进程的
ID号,并在终端输出。当程序调用getuid()这个函数
时,便会调用头文件unistd.h中的定义的一syscaIf0(.nt
,getuid)这个宏。这是个带参数宏,在unistd.h头文
件中定义。通过查看unistd.h头文件,会发现getU.d
会被展开成以下代码:
getuid
int getuid(Void){
IOng—res;
—_Qsm—VoIatlle(“int$Ox80”)
:“=a”(一res)
:“”(——NR—getuid));
—-sysca¨—.retum(int,——res);
}
万方数据
2007年第8期 计算机系统应用
从中可以看出,程序通过调用宏一NR—getuid得
到调用号,放到寄存器eax中,然后执行中断指令,“.nt
$Ox80”。
我们继续深入下去,看看.nf$0x80后面到底做
了什么。因为这是一条软中断指令,那就要看看系统
规定的这条中断指令的处理程序是什么。在traps.c
文件中,有这样一行代码:
set—system—gate(SYSCALL—VECTOR,86yStem~
caff),其中宏S丫5CALL_VECTOR在内核中是被定义成
0x80的。所以,在进入.nt$0x80中断后,系统将会自
动进行模式和堆栈切换,然后将控制转移到sy咖m—
ca¨的入口点。
在en岫。S,中,我们可以找到syslem—caH的入口
点:
ENTRY(system—calI)
pushI%eax
SAVE—ALL
GEl._CRRENT(%ebx)
testb$O×02,tsI州race(%eb×)
ine tracesys
cmpI$(NR—sysca¨s),%eax
iae badsys
Ca¨\水sYMBOL-NAME(sys_Ca¨jabIe)(,%ea×,
41
moVl%eax,EA×(%ebx)
ENTRY(refjrom_sys_ca¨)
cIi
cnRp}$O,need—resched(%eb×)
inereschedule
cmpI$O,sjgpending(%ebx)
ine sygnaI—return
Retore—all:
RESTORE—ALL
这段代码的主要功能就是保留系统调用号拷贝,
SAVE—ALL保存现场,得到当前用户进程结构指针,并保
存在ebx中,检测系统调用号是否合法,最后根据系统
调用号索引syS—ca¨一table,并找到最终的内核函数
sys—getu.d16。我们在I.nux终端下打开../kemeI/
u.d16.c,便可看到这个系统调用函数的原始定义。
AsmIinkage Iong sys—getuidl6(Void){
returnhlgh21owuid(current一>uid);
l
这个内核系统调用函数仅返回一个当前进程的
uid。然后将得到的uid保存在eax中,最后调用RE—
STORE』LL宏恢复现场。最后进程返回到用户态,用户
程序最终执行完成。整个系统调用过程也就完成。整
个过程可以用下图来表示:
图1
3增加系统调用
3.1添加源代码
首先是编写加到内核中的源程序,即将要加到一
个内核文件中去的一个函数,该函数的名称应该是新
的系统调用名称前面加上sys一标志。在/usr/src/
1.nux/kemeI/sys.c文件中添加源代码,如下所示:
asmIInkage Int sys--mycaIl(int number)
{
retum number:
}
作为一个最简单的例子,我们新加的系统调用仅
仅返回一个整型值。
3.2连接新的系统调用
添加新的系统调用后,下一个任务是使L.nux内核
的其余部分知道该程序的存在。为了从已有的内核程
万方数据
计算机系统应用2007年第8期
序中增加到新的函数的连接,需要编辑前面提到过的
两个文件。
在我们所用的Linux内核版本(2.4.18)中,第一个
要修改的文件是:
/.usr/src/Iinux/incIude/asm—i386/unistd.h
文件中找到系统调用号的宏定义,然后在后面加
上一行:
剿舒ne—N艮myCaII 259
系统调用号为259,之所以系统调用号是259,是
因为我的内核自身的系统调用号码已经用到258。
第二个要修改的文件是:

/usr/s陀/ljnLJx/arch/i386/kemeI/entⅣ.S
该文件中有类似如下的清单:
.Iong SYMBOLNAME()
该清单用来对sys_Ca¨jable[]数组进行初始化。
该数组包含指向内核中每个系统调用的指针。这样就
在数组中增加了新的内核函数的指针。我们在清单上
与系统调用号相对应的位置添加一行:
.Iong SYMBOLNAME(sys_myCa¨)
3.3重建新的Un畎内核
为使新的系统调用生效,需要重建L.nux的内核。
cd/usr/src/¨nux一2.4
超级用户在当前工作目录(/usr/src/1.nux)下,才
可以重建内核。
j|}ma埏xCon俞g,进行内核配置。
撑make dep,检测关联性。
#Imake clean,清除不用的组件。
襻make bzlmage,生成内核映像文件。
编译完毕后,系统生成一可用于安装的、压缩的内
核映象文件:/usr/src/I.nux/arch/B86/boot/bzlmage
3.4用新的内核启动系统
将/usr/src/¨nux/arCh/.386/boot/bzImage拷贝
到/b00t/bzImage
配置启动文件:
假设所用的启动文件为gnJb,修改/boot/grub/
grUb.conf,添加新的引导内核:
卅le Linu)(fest
root(hd0,4)
kemel/bool/bzImage ro roOt=LABEL=/
in计rd/boot/initrd一2.4.20一8.img
至此,新的Linux内核已经建立,新添加的系统调
用已成为操作系统的一部允,重新启动,进入Linux—

阅读全文
0 0