简析linux系统调用
在i386系统中,每个系统调用都是通过一个单一的入口点多路传入内核。eax寄存器用来标识应当调用的某个系统调用,这在C库中做了指定(来自用户空间应用程序的每个调用)。当加载了系统的C库调用索引和参数时,就会调用一个软件中断(0x80中断),他将执行system_call函数(通过中断处理程序),这个函数会按照eax内容中的标识处理所有的系统调用。内核使用system_call_table和eax中包含的索引来执行真正的系统调用。从系统调用中返回后,最终执行syscall_exit,并调用resume_userspace返回用户空间。然后继续在C库中执行,它将返回到用户程序中。要添加一个新的系统调用,需要执行三个步骤: 1 添加新函数。即更改源文件,添加新的内核函数。 2 更新头文件。典型的是文件unistd.h。在此中主要是将系统调用编号和函数本身建立一种对应关系。如: #define __NR_getjiffies 320 这里的__NR是符号常量,在在 /usr/include/asm/unistd.h 中定义。 3 根据新函数更新系统调用表。即文件:linux/arch/i386/kernel/syscall_table.S 文件 对新的系统调用的使用有两种方法:1 使用syscall系统调用。2 使用_syscall0~_syscall6宏。 对第一种方法,例子为: #include <linux/unistd.h> #include <sys/syscall.h> #define __NR_getjiffies 320 int main() { long jiffies; jiffies = syscall( __NR_getjiffies ); printf( "Current jiffies is %lx\n", jiffies ); return 0; } 对第二种方法,例子为: #include <stdio.h> #include <linux/unistd.h> #include <sys/syscall.h>
#define __NR_getjiffies 320 #define __NR_diffjiffies 321 #define __NR_pdiffjiffies 322
_syscall0( long, getjiffies ); _syscall1( long, diffjiffies, long, ujiffies ); _syscall2( long, pdiffjiffies, long, ujiffies, long*, presult );
int main() { long jifs, result; int err; jifs = getjiffies(); printf( "difference is %lx\n", diffjiffies(jifs) ); err = pdiffjiffies( jifs, &result ); if (!err) { printf( "difference is %lx\n", result ); } else { printf( "error\n" ); } return 0; } _syscall 宏的参数格式为_syscall2(return-type,func-name,arg1-type,arg1-name,arg2-type,arg2-name)。它最多有6个参数。
linux提供了几个函数用来将系统调用参数移动到用户空间中,或从中移出。例如: int access_ok(type,,address,size);用来验证给定操作的用户空间指针。其中type取值为:VERIFY_READ或VERIFY_WRITE。如果调用成功就返回0。 int get_user( var, ptr ); int put_user( var, ptr );用来在用户和内核空间移动一些简单类型(int 或long)。 unsigned long copy_from_user( void *to, const void __user *from, unsigned long n ); unsigned long copy_to_user( void *to, const void __user *from, unsigned long n ); 这些用来在用户和内核空间中移动较大的对象,如数组或结构。 long strncpy_from_user( char *dst, const char __user *src, long count ); strlen_user( str ); 我们可以可以使用 strncpy_from_user 函数将一个以 NULL 结尾的字符串从用户空间移动到内核空间中。 在调用这个函数之前,您可以通过调用 strlen_user 宏来获得用户空间字符串的大小。 有关内核的一些别的概念: 内核 jiffies Linux 内核具有一个名为 jiffies 的全局变量,它代表从机器启动时算起的时间滴答数。这个变量最初被初始化为 0,每次时钟中断时都会加 1。您可以使用get_jiffies_64 函数来读取 jiffies 的值,然后使用jiffies_to_msecs 将其换算成毫秒或使用 jiffies_to_usecs 将其换算成微秒。jiffies 的全局定义和相关函数是在 ./linux/include/linux/jiffies.h 中提供的。系统调用多路分解有些系统调用会由内核进一步进行多路分解。例如,BSD(Berkeley Software Distribution)socket 调用(socket、bind、 connect 等)都与一个单独的系统调用索引(__NR_socketcall)关联在一起,不过在内核中会进行多路分解,通过另外一个参数进入适当的调用。请参看 ./linux/net/socket.c 中的 sys_socketcall 函数。
|