博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux内核分析-4/5/系统调用
阅读量:4284 次
发布时间:2019-05-27

本文共 12469 字,大约阅读时间需要 41 分钟。

Linux内核分析-4/系统调用

Linux内核分析-5/系统调用


文章详解

1/解释了系统调用所在的层次

2/系统调用接口的过程(无代码)

3/系统调用的不常用的两种调用方式

4/linux-0.11的调用路径

5/glibc-2.25和linux-3.10中的调用路径


系统调用所在层次

上层系统调用1/由于安全的问题,出现了系统调用,系统调用是内核提供的 唯一的 上层访问内核的 接口2/把用户从底层的硬件编程中解放出来3/使用户程序具有可移植性内核

系统调用接口的使用

系统调用不同的是他仅仅在我们开来就是一个函数,而系统调用是通过软中断向内核发出一个明确的功能请求那么还有一个问题就是所谓的软中断的入口参数是什么呢,实际上就和函数的参数列表一样,我们除了知道他的中断号还要知道功能代码还有就是要知道他的输入是什么(见linux内核分析一)这就是入口参数。那么系统调用 通过软中断或系统调用指令向内核发出一个明确的请求,内核将调用内核相关函数来实现(如sys_read() , sys_write() , sys_fork())。用户程序不能直接调用这些Sys_read,sys_write等函数。这些函数运行在内核态。理论:就如同调用了一个命令  int 80 intrupt_num argv1 argv2 ...就像这样的一个指令会陷入中断,并且会获取参数.根据不同的中断号 进行相应的中断处理函数实例:好比上层我调用了一个open file1 //open 对应的中断号是60那么 到底层就对应 int 80 60 file1然后下面调用 60 对应的中断处理函数,sys_open

系统调用过程

通常API函数库(如glibc)中的函数会调用封装例程,封装例程负责发起系统调用(通过发软中断或系统调用指令),这些都运行在用户态。内核开始接收系统调用后,cpu从用户态切换到内核态(cpu处于什么状态,程序就叫处于什么状态,所以很多地方也说程序从用户态切换到内核态,实际是cpu运行级别的切换,通常cpu 运行在3级表示用户态,cpu 运行在0级表示内核态),内核调用相关的内核函数来处理再逐步返回给封装例程,cpu进行一次内核态到用户态的切换,API函数从封装例程拿到结果,再处理完后返回给用户。但是PI函数不一定需要进行系统调用,如某些数学函数,没有必要进行系统调用,直接glibc里面就给处理了,整个过程运行在用户态。所以作为我们编写linux用户程序的时候,是不能直接调用内核里面的函数的,内核里面的函数位于进程虚拟地址空间里面的内核空间,用户空间函数及函数库都处于进程虚拟地址空间里面的用户空间,用户空间调用内核空间的函数只有一个通道,这个通道就是系统调用指令,所以通常要调用glibc等库的接口函数,glibc也是用户空间的,但glibc自己实现了调用特殊的宏汇编系统调用指令进行cpu运行状态的切换,把进程从用户空间切换到内核空间。例子:fopen 调用 open,调用 一条指令这里的指令就是 通过发软中断或者系统调用指令实现的. 例如int 80 intrupt_num argv1 argv2 ...例如这一句过后,进入cpu从用户态切换到内核态然后进入一个 system_call 函数system_call 调用与中断号相关的中断处理函数,中断函数返回system_call函数返回然后直接进入用户态?然后open返回然后fopen返回xyz()函数执行的过程中会执行SYSCALL汇编指令,此指令将会把cpu从用户态切换到内核态。-----------------------------------------------------------------------------------内核SYACALL汇编指令中会包含将要调用的内核函数的系统调用号和参数,内核在上图系统调用处理程序中去查一个sys_call_talbe数组来找到这个系统调用号对应的服务例程(如sys_xyz())函数的地址,然后调用这个地址的函数执行。(这里glibc里面的系统调用号和内核里面的系统调用号必须完全相等,当然,这是约定好的)  系统调用是一个软中断,中断号是0x80,它是上层应用程序与Linux系统内核进行交互通信的唯一接口。  所以,系统调用过程是这样的:应用程序调用libc中的函数->libc中的函数引用系统调用宏->系统调用宏中使用int 0x80完成系统调用并返回。

汇编实现调用

//所以我们看到了,open 函数只是存储了一些参数到相应的寄存器,然后调用了 int 80,所以如果我们自己实现这个过程,也可以调用底层的中断处理函数,也可以实现一次系统调用.//所以我们可以说open层其实是封装了中断 的系统调用API//真正的系统调用的实现在内核中.//不过我们用的时候,也可以用在系统调用API上 再一次封装的 API//例如 fopen 封装了 openvoid main(){    char *str = "hello world\n";    asm(        "movl $13,%%edx\n\t"        "movl %0,%%ecx\n\t"        "movl $0,%%ebx\n\t"        "movl $4,%%eax\n\t"        "int $0x80\n\t"        :        :"m"(str)        :"edx","ecx","ebx"        );    asm(        "movl $42,%ebx\n\t"        "movl $1,%eax\n\t"        "int $0x80\n\t"        );}

syscall实现调用

#include 
#include
#include
#include
int main(){ int rc; rc = syscall(SYS_chmod, "/etc/passwd", 0444); if (rc == -1) fprintf(stderr, "chmod failed, errno = %d\n", errno); else printf("chmod succeess!\n"); return 0;}

下面叙述的是linux-0.11中的调用路径

  • 新版本的内核走的也是这个流程,只不过比之前的看起来复杂.

应用程序

open("./test",0775);

系统调用

//这是linux0.11中的open,已经在内核中int open(const char * filename, int flag, ...){    register int res;    va_list arg;    va_start(arg,flag);    __asm__(        "int $0x80"        :"=a" (res)        :"0" (__NR_open),"b" (filename),"c" (flag),"d" (va_arg(arg,int))        );    if (res>=0)        return res;    errno = -res;    return -1;}

  • 注意:现在open的实现已经放在glibc中了,后续我们对glibc进行观察

main(init目录)    sched_init      set_system_gate(0x80,&system_call);然后int 80 的时候,中断处理函数在 system_call (kernel/system_call.s)_system_call:    cmpl $nr_system_calls-1,%eax    ja bad_sys_call    push %ds    push %es    push %fs    pushl %edx    pushl %ecx      # push %ebx,%ecx,%edx as parameters    pushl %ebx      # to the system call    movl $0x10,%edx        # set up ds,es to kernel space    mov %dx,%ds    mov %dx,%es    movl $0x17,%edx        # fs points to local data space    mov %dx,%fs    call _sys_call_table(,%eax,4)    pushl %eax    movl _current,%eax    cmpl $0,state(%eax)        # state    jne reschedule    cmpl $0,counter(%eax)      # counter    je rescheduleret_from_sys_call:    movl _current,%eax      # task[0] cannot have signals    cmpl _task,%eax    je 3f    cmpw $0x0f,CS(%esp)        # was old code segment supervisor ?    jne 3f    cmpw $0x17,OLDSS(%esp)     # was stack segment = 0x17 ?    jne 3f    movl signal(%eax),%ebx    movl blocked(%eax),%ecx    notl %ecx    andl %ebx,%ecx    bsfl %ecx,%ecx    je 3f    btrl %ecx,%ebx    movl %ebx,signal(%eax)    incl %ecx    pushl %ecx    call _do_signal    popl %eax3:  popl %eax    popl %ebx    popl %ecx    popl %edx    pop %fs    pop %es    pop %ds    iret

下面的说的是glibc-2.25和linux-3.10中的调用路径


glibc的系统调用路径

  • i386

1/mkdir

2/INLINE_INSTALL

3/INTERNAL_SYSCALL

4/INTERNAL_SYSCALL_MAIN_x

5/INTERNAL_SYSCALL_MAIN_INLINE

6/int $0x80

  • arm

1/mkdir

2/INLINE_SYSCALL

3/INTERNAL_SYSCALL

4/INTERNAL_SYSCALL_RAW

5/swi 0x0 @ syscall ” #name

//sysdeps/unix/sysv/linux/generic/mkdir.cint__mkdir (const char *path, mode_t mode){  return INLINE_SYSCALL (mkdirat, 3, AT_FDCWD, path, mode);}weak_alias (__mkdir, mkdir)//weak_alias(name1,name2) //为标号name1定义一个弱化名name2。//仅当name2没有在任何地方定义时,连接器就会用name1解析name2相关的符号。//在文件中定义的标号name1也会同样处理。
arm中的INLINE_SYSCALL//sysdeps/unix/sysv/linux/arm/sysdep.h#undef INLINE_SYSCALL#define INLINE_SYSCALL(name, nr, args...)               \    ({ unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args);  \     if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0))    \     {                              \     __set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, ));      \     _sys_result = (unsigned int) -1;               \     }                              \     (int) _sys_result; })//sysdeps/unix/sysv/linux/arm/sysdep.h#undef INTERNAL_SYSCALL#define INTERNAL_SYSCALL(name, err, nr, args...)        \    INTERNAL_SYSCALL_RAW(SYS_ify(name), err, nr, args)//sysdeps/unix/sysv/linux/arm/sysdep.h# undef INTERNAL_SYSCALL_RAW# define INTERNAL_SYSCALL_RAW(name, err, nr, args...)       \    ({                                          \     register int _a1 asm ("r0"), _nr asm ("r7");       \     LOAD_ARGS_##nr (args)                  \     _nr = name;                        \     asm volatile (\            "swi 0x0 @ syscall " #name   \            : "=r" (_a1)               \            : "r" (_nr) ASM_ARGS_##nr          \            : "memory"\            );               \     _a1; \    })
i386中的INLINE_SYSCALL//sysdeps/unix/sysv/linux/i386/sysdep.h# define INLINE_SYSCALL(name, nr, args...) \  ({                                          \    unsigned int resultvar = INTERNAL_SYSCALL (name, , nr, args);         \    __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (resultvar, ))             \    ? __syscall_error (-INTERNAL_SYSCALL_ERRNO (resultvar, ))             \    : (int) resultvar; })//sysdeps/unix/sysv/linux/i386/sysdep.h#define INTERNAL_SYSCALL(name, err, nr, args...) \  ({                                          \    register unsigned int resultvar;                          \    INTERNAL_SYSCALL_MAIN_##nr (name, err, args);                 \    (int) resultvar; })//sysdeps/unix/sysv/linux/i386/sysdep.h#define INTERNAL_SYSCALL_MAIN_0(name, err, args...) \    INTERNAL_SYSCALL_MAIN_INLINE(name, err, 0, args)#define INTERNAL_SYSCALL_MAIN_1(name, err, args...) \    INTERNAL_SYSCALL_MAIN_INLINE(name, err, 1, args)#define INTERNAL_SYSCALL_MAIN_2(name, err, args...) \    INTERNAL_SYSCALL_MAIN_INLINE(name, err, 2, args)#define INTERNAL_SYSCALL_MAIN_3(name, err, args...) \    INTERNAL_SYSCALL_MAIN_INLINE(name, err, 3, args)#define INTERNAL_SYSCALL_MAIN_4(name, err, args...) \    INTERNAL_SYSCALL_MAIN_INLINE(name, err, 4, args)#define INTERNAL_SYSCALL_MAIN_5(name, err, args...) \    INTERNAL_SYSCALL_MAIN_INLINE(name, err, 5, args)//sysdeps/unix/sysv/linux/i386/sysdep.h#  define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \    LOADREGS_##nr(args)                         \    asm volatile       (\                          \        "int $0x80"                             \        : "=a" (resultvar)                          \        : "a" (__NR_##name) ASMARGS_##nr(args) \        : "memory", "cc"\      )

内核中调用路径

  • 从上面来看,已经进入中断,所以就只能进入中断处理函数来处理了.

中断处理函数

//start_kernel//init/main.c  trap_init      #ifdef CONFIG_X86_32      set_system_trap_gate(SYSCALL_VECTOR, &system_call);      set_bit(SYSCALL_VECTOR, used_vectors);      #endif//arch/x86/include/asm/irq_vectors.h#ifdef CONFIG_X86_32# define SYSCALL_VECTOR         0x80#endif
$ grep system_call * -nr  | grep x86arch/x86/kernel/traps.c:70:asmlinkage int system_call(void);arch/x86/kernel/traps.c:768:    set_system_trap_gate(SYSCALL_VECTOR, &system_call);arch/x86/kernel/entry_64.S:608:ENTRY(system_call)arch/x86/kernel/entry_64.S:620:GLOBAL(system_call_after_swapgs)arch/x86/kernel/entry_64.S:635:system_call_fastpath:arch/x86/kernel/entry_64.S:720: jmp system_call_fastpatharch/x86/kernel/entry_64.S:829:END(system_call)arch/x86/kernel/cpu/common.c:1122:      wrmsrl(MSR_LSTAR, system_call);arch/x86/kernel/entry_32.S:506:ENTRY(system_call)arch/x86/kernel/entry_32.S:604:ENDPROC(system_call)arch/x86/xen/xen-asm_64.S:131:  jmp system_call_after_swapgsarch/x86/include/asm/proto.h:8:void system_call(void);//arch/x86/kernel/entry_32.S/* * syscall stub including irq exit should be protected against kprobes */    .pushsection .kprobes.text, "ax"    # system call handler stubENTRY(system_call)    RING0_INT_FRAME         # can't unwind into user space anyway    ASM_CLAC    pushl_cfi %eax          # save orig_eax    SAVE_ALL    GET_THREAD_INFO(%ebp)                    # system call tracing in operation / emulation    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)    jnz syscall_trace_entry    cmpl $(NR_syscalls), %eax    jae syscall_badsyssyscall_call:    call *sys_call_table(,%eax,4)    movl %eax,PT_EAX(%esp)      # store the return valuesyscall_exit:    LOCKDEP_SYS_EXIT    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt                    # setting need_resched or sigpending                    # between sampling and the iret    TRACE_IRQS_OFF    movl TI_flags(%ebp), %ecx    testl $_TIF_ALLWORK_MASK, %ecx  # current->work    jne syscall_exit_workrestore_all:    TRACE_IRQS_IRETrestore_all_notrace:    movl PT_EFLAGS(%esp), %eax  # mix EFLAGS, SS and CS    # Warning: PT_OLDSS(%esp) contains the wrong/random values if we    # are returning to the kernel.    # See comments in process.c:copy_thread() for details.    movb PT_OLDSS(%esp), %ah    movb PT_CS(%esp), %al    andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax    cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax    CFI_REMEMBER_STATE    je ldt_ss           # returning to user-space with LDT SSrestore_nocheck:    RESTORE_REGS 4          # skip orig_eax/error_codeirq_return:    INTERRUPT_RETURN//arch/x86/kernel/syscall_32.cconst sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {    /*     * Smells like a compiler bug -- it doesn't work     * when the & below is removed.     */    [0 ... __NR_syscall_max] = &sys_ni_syscall,#include 
};

sys_open

//在内核中是没有open的,只有sys_open//下面这一句在open.c中SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)根据#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)  转换成SYSCALL_DEFINEx(3, _open, const char __user *, filename, int, flags, umode_t, mode)根据#define SYSCALL_DEFINEx(x, name, ...)                   \      asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__));       \      static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__));   \      asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__))        \      {                               \          __SC_TEST##x(__VA_ARGS__);              \          return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__));    \      }                               \      SYSCALL_ALIAS(sys##name, SyS##name);                \      static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))  转换成asmlinkage long sys_open(__SC_DECL3(const char __user *, filename, int, flags, umode_t, mode));       \  static inline long SYSC_open(__SC_DECL3(const char __user *, filename, int, flags, umode_t, mode));   \  asmlinkage long SyS_open(__SC_LONG3(const char __user *, filename, int, flags, umode_t, mode))        \  {                               \      __SC_TEST3(const char __user *, filename, int, flags, umode_t, mode);              \      return (long) SYSC_open(__SC_CAST3(const char __user *, filename, int, flags, umode_t, mode));    \  }                               \  SYSCALL_ALIAS(sys_open, SyS_open);                \  static inline long SYSC_open(__SC_DECL3(const char __user *, filename, int, flags, umode_t, mode))

参考资料

转载地址:http://qtigi.baihongyu.com/

你可能感兴趣的文章
市面上的单板计算机
查看>>
OK6410A 开发板 (八) 3 linux-5.11 OK6410A lcd wxcat43 移植
查看>>
OK6410A 开发板 (八) 4 linux-5.11 OK6410A 外围驱动
查看>>
OK6410A 开发板 (八) 18 linux-5.11 OK6410A start_kernel 功能角度 第二阶段之idle进程
查看>>
OK6410A 开发板 (八) 19 linux-5.11 OK6410A start_kernel 功能角度 第三阶段之init进程
查看>>
OK6410A 开发板 (八) 20 linux-5.11 OK6410A start_kernel 功能角度 第三阶段之kthreadd进程
查看>>
OK6410A 开发板 (八) 21 linux-5.11 OK6410A schedule 的 __switch_to 部分
查看>>
OK6410A 开发板 (八) 22 linux-5.11 OK6410A start_kernel 功能角度 第一阶段
查看>>
OK6410A 开发板 (八) 23 linux-5.11 OK6410A start_kernel 功能角度 第二阶段
查看>>
OK6410A 开发板 (八) 24 linux-5.11 OK6410A start_kernel 功能角度 第三阶段
查看>>
OK6410A 开发板 (八) 25 linux-5.11 OK6410A 进程角度 裸机和进程的区别
查看>>
getopt 解析
查看>>
文章标题
查看>>
linux前后台切换
查看>>
nmap
查看>>
uboot执行顺序main_loop
查看>>
uboot编译内容详解
查看>>
uboot Makefile 分析
查看>>
uboot网络验证
查看>>
烧写uboot
查看>>