⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 系统调用.txt

📁 讲解linux内核 系统调用 部分经典讲义
💻 TXT
📖 第 1 页 / 共 3 页
字号:
系统调用  
      系统调用

目 录
  系统调用 
    系统调用简述 
      系统调用表 
      系统调用入口函数 
    系统调用实现过程 
      函数名约定 
      系统调用号 
      系统调用表 
      从ptrace系统调用命令到INT 0X80中断请求的转换 
      系统调用功能模块的初始化 
      内核服务 
    代码分析:mlock() 
      主要数据结构 
      重要常量 
      代码函数功能分析 
    添加新调用 
      例子一 
      例子二




系统调用

    
在系统中真正被所有进程都使用的内核通信方式是系统调用。例如当进程请求内核服务时,就使用的是系统调用。一般情况下,进程是不能够存取系统内核的。它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结构保证了这一点。只有系统调用是一个例外。进程使用寄存器中适当的值跳转到内核中事先定义好的代码中执行,(当然,这些代码是只读的)。在Intel结构的计算机中,这是由中断0x80实现的。
    
进程可以跳转到的内核中的位置叫做system_call。在此位置的过程检查系统调用号,它将告诉内核进程请求的服务是什么。然后,它再查找系统调用表sys_call_table,找到希望调用的内核函数的地址,并调用此函数,最后返回。
    
所以,如果希望改变一个系统调用的函数,需要做的是编写一个自己的函数,然后改变sys_call_table中的指针指向该函数,最后再使用cleanup_module将系统调用表恢复到原来的状态
[目录]




系统调用简述

    linux里面的每个系统调用是靠一些宏,,一张系统调用表,一个系统调用入口来完成的。

[目录]





    
宏就是_syscallN(type,name,x...),N是系统调用所需的参数数目,type是返回类型,name即面向用户的系统调用函数名,x...是调用参数,个数即为N。
    例如:
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
                  "d" ((long)(arg3))); \
if (__res>=0) \
        return (type) __res; \
errno=-__res; \
return -1; \
}
(这是2.0.33版本)
    
这些宏定义于include\asm\Unistd.h,这就是为什么你在程序中要包含这个头文件的原因。该文件中还以__NR_name的形式定义了164个常数,这些常数就是系统调用函数name的函数指针在系统调用表中的偏移量。
[目录]




系统调用表

    系统调用表定义于entry.s的最后。
    
这个表按系统调用号(即前面提到的__NR_name)排列了所有系统调用函数的指针,以供系统调用入口函数查找。从这张表看得出,linux给它所支持的系统调用函数取名叫sys_name。


[目录]




系统调用入口函数

    系统调用入口函数定义于entry.s:
ENTRY(system_call)
        pushl %eax                      # save orig_eax
        SAVE_ALL
#ifdef __SMP__
        ENTER_KERNEL
#endif
        movl $-ENOSYS,EAX(%esp)
        cmpl $(NR_syscalls),%eax
        jae ret_from_sys_call
        movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
        testl %eax,%eax
        je ret_from_sys_call
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%edx)
        movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
        movl SYMBOL_NAME(current_set),%ebx
#endif
        andl $~CF_MASK,EFLAGS(%esp)
        movl %db6,%edx
        movl %edx,dbgreg6(%ebx)
        testb $0x20,flags(%ebx)
        jne 1f
        call *%eax
        movl %eax,EAX(%esp)
        jmp ret_from_sys_call
    
这段代码现保存所有的寄存器值,然后检查调用号(__NR_name)是否合法(在系统调用表中查找),找到正确的函数指针后,就调用该函数(即你真正希望内核帮你运行的函数)。运行返回后,将调用ret_from_sys_call,这里就是著名的进程调度时机之一。
    当在程序代码中用到系统调用时,编译器会将上面提到的宏展开,展开后的代码实际上是将系统调用号放入ax后移用int 
0x80使处理器转向系统调用入口,然后查找系统调用表,进而由内核调用真正的功能函数。
    自己添加过系统调用的人可能知道,要在程序中使用自己的系统调用,必须显示地应用宏_syscallN。
    
而对于linux预定义的系统调用,编译器在预处理时自动加入宏_syscall3(int,ioctl,arg1,arg2,arg3)并将其展开。所以,并不是ioctl本身是宏替换符,而是编译器自动用宏声明了ioctl这个函数。


[目录]




系统调用实现过程


[目录]




函数名约定

系统调用响应函数的函数名约定
    
函数名以“sys_”开头,后跟该系统调用的名字,由此构成164个形似sys_name()的函数名。因此,系统调用ptrace()的响应函数是sys_ptrace() 
(kernel/ptrace.c)。


[目录]




系统调用号

系统调用号
    文件include/asm/unistd.h为每个系统调用规定了唯一的编号:
#define __NR_setup                  0
#define __NR_exit                            1
#define __NR_fork                  2
…        …
#define __NR_ptrace                  26
    以系统调用号__NR_name作为下标,找出系统调用表sys_call_table 
(arch/i386/kernel/entry.S)中对应表项的内容,正好就是该系统调用的响应函数sys_name的入口地址。
[目录]




系统调用表

系统调用表
    系统调用表sys_call_table (arch/i386/kernel/entry.S)形如:
ENTRY(sys_call_table)
        .long SYMBOL_NAME(sys_setup)                /* 0 */
        .long SYMBOL_NAME(sys_exit)
        .long SYMBOL_NAME(sys_fork)
                …        …
                .long SYMBOL_NAME(sys_stime)                /* 25 */
        .long SYMBOL_NAME(sys_ptrace)
                …        …
    
sys_call_table记录了各sys_name函数(共166项,其中2项无效)在表中的位子。有了这张表,很容易根据特定系统调用在表中的偏移量,找到对应的系统调用响应函数的入口地址。NR_syscalls(即256)表示最多可容纳的系统调用个数。这样,余下的90项就是可供用户自己添加的系统调用空间。
[目录]




从ptrace系统调用命令到INT 0X80中断请求的转换

从ptrace系统调用命令到INT  0X80中断请求的转换
    宏定义syscallN()(include/asm/unistd.h)用于系统调用的格式转换和参数的传递。
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
          "d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}
    
N取0与5之间任意整数。参数个数为N的系统调用由syscallN负责格式转换和参数传递。例如,ptrace()有四个参数,它对应的格式转换宏就是syscall4()。
    syscallN()第一个参数说明响应函数返回值的类型,第二个参数为系统调用的名称(即name),其余的参数依次为系统调用参数的类型和名称。例如,
_syscall4(int, ptrace, long request, long pid, long addr, long data)
    说明了系统调用命令
int sys_ptrace(long request, long pid, long addr, long data)
    宏定义的余下部分描述了启动INT 0X80和接收、判断返回值的过程。也就是说,以系统调用号对EAX寄存器赋值,启动INT 
0X80。规定返回值送EAX寄存器。函数的参数压栈,压栈顺序见下表:
参数        参数在堆栈的位置        传递参数的寄存器
arg1        00(%esp)        ebx
arg2        04(%esp)        ecx
arg3        08(%esp)        edx
arg4        0c(%esp)        esi
arg5        10(%esp)        edi
    若INT 0X80的返回值非负,则直接按类型type返回;否则,将INT 0X80的返回值取绝对值,保留在errno变量中,返回-1。
[目录]




系统调用功能模块的初始化

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -