一、系统调用、API、系统命令、内核函数
系统调用:是通过软件中断向内核发送一个明确的请求,系统调用实现是在内核完成的,而用户态的函数的是在函数中实现的。
API(Application Programming Interface):遵循了UNIX世界中最流行的应用编程接口标准------POSIX标准,API其实就是一个函数的定义,说明了如何获得一个给定的服务。
系统命令:相对于应用编程接口更高一层,每一个系统调用都是一个可执行程序这些命令的实现调用了系统调用,在Linux中很多命令在/bin和/sbin/目录下。通过strace可以查看系统命令的调用的系统调用
内核函数:在内核实现,遵循一些内核编程要求;
- 一、API的调用形式可能和系统调用形式一致,比如read()函数和read()系统调用一致,但是,情况不是总是这样的:1、一种几种不同的API其内部实现可能调用了同一个系统调用;2、一个API的实现可能调用了多个系统调用。
- 二、系统调用是用户进程进入内核的接口层,它本身并不是内核函数,但是它是由内核函数实现的。进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被统称为”服务例程“。而系统调用相对于服务例程便是”封装例程“
二、系统调用的基本概念
系统调用的实质是函数调用,只是调用的函数是系统调用,处于内核态而已。用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过 此号从系统调用表中找到相应的内核函数执行,最后返回。
系统调用号:在Linux系统中有几百个函数调用,为了唯一标识每一个系统调用,Linux为每一个系统调用定义了唯一的编号,叫做系统调用号。它定义在
/usr/src/linux-headers-2.6.38-14/arch/x86/include/asm/unistd_32.h
系统调用表:为了将系统调用号和相应的服务例程
结合起来内核利用了系统调用表,这个表放在sys_call_table数组中,它是一个函数指针,每一个函数指针都指向其系统调用的封装例程。它定义在
/usr/src/linux-headers-2.6.38-14/arch/x86/kernel/syscall_table_32.S
三、系统调用的实现和使用
当用户态进程调用一个系统调用时,CPU从用户态切换到内核态并且执行一个内核函数。Linux对系统调用必须通过执行int $0x80 汇编指令,这条指令产生向量128的编程异常;EAX负责传递系统调用号。
1、在内核栈中保存大多数寄存器的内容
2、调用系统调用服务例程的相应的C函数来处理系统调用
3、通过syscall_exit_work()函数从系统调用中返回。
初始化系统调用:在内核初始化期间调用trap_init()函数建立IDT中128号向量对应的表项,语句如下:
set_system_gate(SYSCALL_VECTOR,&system_call);
其中SYSCALL_VECTOR是一个宏定义,其值为0x80,该调用将下列值装入这个门描述符的相应域。其中system_call是系统调用的入口
地址
1>段选择子:因为系统调用处理程序属于内核代码,填写内核代码_KERNEL_CS的段选择子。
2>偏移量:指向system_call()系统调用处理程序
3>类型:置为15。表示这个异常是一个陷阱,相应的处理程序不禁止可屏蔽中断
4>DPL(描述符的特权优先级):置为3,这是允许用户态进程调用这个异常处理程序
syscall()函数
:实现了系统调用的处理程序。它首先将系统调用号和这个异常处理程序可以用到所有CPU寄存器保存到相应额度栈中,当然栈中已经自动保存CPU中
EFLAGS、CS、EIP、SS、EIP寄存器的值。并且在DS和CS中装入内核数据段的选择子。然后对用户进程传递来的系统调用号进行有效性的检测,
如果大于等于NR_syscalls则出错。最后根据eax中所包含的系统调用对应的服务例程。对于系统调用表中每个一个表项占用4个字节,因此首先把
EAX中的系统调用号乘以4在加上sys_call_table系统调用表的起始地址,然后从这个地址单元中获取相应的服务例程的指针,内核就找到相应的
服务例程了。当服务例程执行结束后,system_call()从eax中获得它的返回值,并将返回值存放在栈中,让其位于用户态eax寄存器曾存放的位
置。然后执行syscall_exit代码段,终止系统调用处理程序的执行
对于syscall函数的使用:
- #include<unistd.h>
- #include<syscall.h>
- #include<stdio.h>
- #include<sys/types.h>
- int main()
- {
- long ID1,ID2;
- ID1 = syscall(SYS_getpid);
- printf("syscall(SYS_getpid) = %ld \n",ID1);
- ID2 = getpid();
- printf("getpid() = %ld\n",ID2);
- return 0;
- }
四、添加系统调用
系统调用是用户空间和内核空间交互的一种有效手段。可以手动添加自己的系统调用。 void mysyscall()
方法一:
1>添加系统调用号,在/usr/src/linux-headers-2.6.38-14/arch/x86/include/asm
/unistd_32.h 和 arch/x86/include/asm/unistd_32.h中添加如下:# define
__NR_mysyscall 333 /*系统调用号*/
2>在系统调用表中添加相应的表项:在
/usr/src/linux-headers-2.6.38-14/arch/x86/kernel/syscall_table_32.S中
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 */
……
.
long sys_mysyscall /*333*/
3>实现系统调用的服务例程
将下列程序加在kernel目录下的系统调用文件sys.c中
asmlinkage int sys_mysyscall(void){
current->uid = 0;
}
4>重新编译内核
5>编写用户态程序
#include
#include
__syscall0(int,mysyscall)
int main(){
printf("my uid is %d\n",getuid() );
mysyscall();
printf("NOw, my uid has changed uid is %d\n",getuid() );
return 0 ;}
方法二:通过内核模块编程实现系统调用
阅读(442) | 评论(0) | 转发(1) |