Linux系统调用过程

1 系统调用的作用

系统调用是操作系统提供给用户(应用程序)的一组接口,每个系统调用都有一个对应的系统调用函数来完成相应的工作。用户通过这个接口向操作系统申请服务,如访问硬件,管理进程等等。

应用程序和文件系统的接口是系统调用。

我们经常看到的比如fork、open、write 等等函数实际上并不是真正的系统调用函数,他们都只是c库,在这些函数里将执行一个软中断 swi 指令,产生一个软中断,使CPU 陷入内核态,接着在内核中进行一系列的判断,判断出是哪个系统调用,再转到真正的系统调用函数,完成相应的功能。

2 系统调用过程

http://www.linuxidc.com/Linux/2015-04/116546.htm

系统调用是操作系统提供给用户(应用程序)的一组接口,每个系统调用都有一个对应的系统调用函数来完成相应的工作。用户通过这个接口向操作系统申请服务,如访问硬件,管理进程等等。但是因为用户程序运行在用户空间,而系统调用运行在内核空间,因此用户程序不能直接调用系统调用函数,我们经常看到的比如fork、open、write 等等函数实际上并不是真正的系统调用函数,他们都只是c库,在这些函数里将执行一个软中断 swi 指令,产生一个软中断,使CPU 陷入内核态,接着在内核中进行一系列的判断,判断出是哪个系统调用,再转到真正的系统调用函数,完成相应的功能。下面举一个简单的例子说明从用户态调用一个“系统调用”,到内核处理的整个执行流程。

  用户态程序如下:

      void pk()

  {

    __asm__(

    "ldr  r7  =365 \n"

    "swi \n"

    :

    :

    :

    );

  }

  int main()

  {

      pk();

    retrun 0;

  }

  上面的代码中,我自己实现了一个新的系统调用,具体怎么做,后面再具体描述。pk()事实上就可以类比于平时我们在用户程序里调用的 open() 等函数,这个函数只做了一件简单的事:将系统调用号传给 r7 ,,然后产生一软中断。接着CPU陷入内核

  内核态:

  CPU相应这个软中断以后,PC指针会到相应的中断向量表中取指,中断向量表在内核代码中:arch/arm/kernel/entry-armv.S  中定义

.LCvswi:  .word vector_swi

 .globl __stubs_end __stubs_end:

 .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 .globl __vectors_start __vectors_start:  ARM( swi SYS_ERROR0 )  THUMB( svc #0  )  THUMB( nop   )  W(b) vector_und + stubs_offset  W(ldr) pc, .LCvswi + stubs_offset  #响应中断后pc指向这里  W(b) vector_pabt + stubs_offset  W(b) vector_dabt + stubs_offset  W(b) vector_addrexcptn + stubs_offset  W(b) vector_irq + stubs_offset  W(b) vector_fiq + stubs_offset

 .globl __vectors_end __vectors_end:

当pc取到如上的指令后,会跳到 vector_swi 这个标号,这个标号在arch/arm/kernel/entry-commen.S 中定义。

 .align 5 ENTRY(vector_swi)  sub sp, sp, #S_FRAME_SIZE  stmia sp, {r0 - r12}   @ Calling r0 - r12  ARM( add r8, sp, #S_PC  )  ARM( stmdb r8, {sp, lr}^  ) @ Calling sp, lr  THUMB( mov r8, sp   )  THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr  mrs r8, spsr   @ called from non-FIQ mode, so ok.  str lr, [sp, #S_PC]   @ Save calling PC  str r8, [sp, #S_PSR]  @ Save CPSR  str r0, [sp, #S_OLD_R0]  @ Save OLD_R0  zero_fp

 /*   * Get the system call number.    #取出系统调用号   */

#if defined(CONFIG_OABI_COMPAT)

 /*   * If we have CONFIG_OABI_COMPAT then we need to look at the swi   * value to determine if it is an EABI or an old ABI call.   */ #ifdef CONFIG_ARM_THUMB  tst r8, #PSR_T_BIT  movne r10, #0    @ no thumb OABI emulation  ldreq r10, [lr, #-4]   @ get SWI instruction #else  ldr r10, [lr, #-4]   @ get SWI instruction   A710( and ip, r10, #0x0f000000  @ check for SWI  )   A710( teq ip, #0x0f000000      )   A710( bne .Larm710bug      ) #endif #ifdef CONFIG_CPU_ENDIAN_BE8  rev r10, r10   @ little endian instruction #endif

#elif defined(CONFIG_AEABI)

 /*   * Pure EABI user space always put syscall number into scno (r7).   */   A710( ldr ip, [lr, #-4]   @ get SWI instruction )   A710( and ip, ip, #0x0f000000  @ check for SWI  )   A710( teq ip, #0x0f000000      )   A710( bne .Larm710bug      )

#elif defined(CONFIG_ARM_THUMB)

 /* Legacy ABI only, possibly thumb mode. */  tst r8, #PSR_T_BIT   @ this is SPSR from save_user_regs  addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in  ldreq scno, [lr, #-4]

#else

 /* Legacy ABI only. */  ldr scno, [lr, #-4]   @ get SWI instruction   A710( and ip, scno, #0x0f000000  @ check for SWI  )   A710( teq ip, #0x0f000000      )   A710( bne .Larm710bug      )

#endif

#ifdef CONFIG_ALIGNMENT_TRAP  ldr ip, __cr_alignment  ldr ip, [ip]  mcr p15, 0, ip, c1, c0  @ update control register #endif  enable_irq

 get_thread_info tsk

adr tbl, sys_call_table  @ load syscall table pointer  #获取系统调用表的基地址  ldr ip, [tsk, #TI_FLAGS]  @ check for syscall tracing

#if defined(CONFIG_OABI_COMPAT)  /*   * If the swi argument is zero, this is an EABI call and we do nothing.   *   * If this is an old ABI call, get the syscall number into scno and   * get the old ABI syscall table address.   */  bics r10, r10, #0xff000000  eorne scno, r10, #__NR_OABI_SYSCALL_BASE  ldrne tbl, =sys_oabi_call_table #elif !defined(CONFIG_AEABI)  bic scno, scno, #0xff000000  @ mask off SWI op-code  eor scno, scno, #__NR_SYSCALL_BASE @ check OS number #endif

 stmdb sp!, {r4, r5}   @ push fifth and sixth args  tst ip, #_TIF_SYSCALL_TRACE  @ are we tracing syscalls?  bne __sys_trace

 cmp scno, #NR_syscalls  @ check upper syscall limit  adr lr, BSYM(ret_fast_syscall) @ return address ldrcc pc, [tbl, scno, lsl #2]  @ call sys_* routine  #跳到系统调用函数

 add r1, sp, #S_OFF 2: mov why, #0    @ no longer a real syscall  cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)  eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back  bcs arm_syscall   b sys_ni_syscall   @ not private func

从上面可以看出,当CPU从中断向量表转到vector_swi 之后,完成了几件事情:1.取出系统调用号 2.根据系统调用号取出系统调用函数在系统调用表的基地址,得到一个系统调用函数的函数指针 3. 根据系统调用表的基地址和系统调用号,得到这个系统调用表里的项,每一个表项都是一个函数指针,把这个函数指针赋给PC , 则实现了跳转到系统调用函数。

系统调用表定义在:arch/arm/kernel/Calls.S

* This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU General Public License version 2 as  * published by the Free Software Foundation.  *  *  This file is included thrice in entry-common.S  */ /* 0 */  CALL(sys_restart_syscall)   CALL(sys_exit)   CALL(sys_fork_wrapper)   CALL(sys_read)   CALL(sys_write) /* 5 */  CALL(sys_open)   CALL(sys_close)   CALL(sys_ni_syscall)  /* was sys_waitpid */   CALL(sys_creat)   CALL(sys_link) /* 10 */ CALL(sys_unlink)   CALL(sys_execve_wrapper)   CALL(sys_chdir)   CALL(OBSOLETE(sys_time)) /* used by libc4 */   CALL(sys_mknod) /* 15 */ CALL(sys_chmod)   CALL(sys_lchown16)   CALL(sys_ni_syscall)  /* was sys_break */   CALL(sys_ni_syscall)  /* was sys_stat */   CALL(sys_lseek) /* 20 */ CALL(sys_getpid)   CALL(sys_mount)   CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */   CALL(sys_setuid16)   CALL(sys_getuid16) /* 25 */ CALL(OBSOLETE(sys_stime))   CALL(sys_ptrace)   CALL(OBSOLETE(sys_alarm)) /* used by libc4 */   CALL(sys_ni_syscall)  /* was sys_fstat */   CALL(sys_pause) /* 30 */ CALL(OBSOLETE(sys_utime)) /* used by libc4 */   CALL(sys_ni_syscall)  /* was sys_stty */   CALL(sys_ni_syscall)  /* was sys_getty */   CALL(sys_access)   CALL(sys_nice) /* 35 */ CALL(sys_ni_syscall)  /* was sys_ftime */   CALL(sys_sync)   CALL(sys_kill)   CALL(sys_rename)   CALL(sys_mkdir) /* 40 */ CALL(sys_rmdir)   CALL(sys_dup)   CALL(sys_pipe)   CALL(sys_times)   CALL(sys_ni_syscall)  /* was sys_prof */ /* 45 */ CALL(sys_brk)   CALL(sys_setgid16)   CALL(sys_getgid16)   CALL(sys_ni_syscall)  /* was sys_signal */   CALL(sys_geteuid16) /* 50 */ CALL(sys_getegid16)   CALL(sys_acct)   CALL(sys_umount)   CALL(sys_ni_syscall)  /* was sys_lock */   CALL(sys_ioctl) /* 55 */ CALL(sys_fcntl)   .......

  CALL(sys_eventfd2)   CALL(sys_epoll_create1)   CALL(sys_dup3)   CALL(sys_pipe2) /* 360 */ CALL(sys_inotify_init1)   CALL(sys_preadv)   CALL(sys_pwritev)   CALL(sys_rt_tgsigqueueinfo)   CALL(sys_perf_event_open)   CALL(sys_pk)    #我自己加的系统调用

 了解了一个系统调用的执行过程就可以试着添加一个自己的系统调用了:

内核:

1. 在内核代码实现一个系统调用函数

即 sys_xxx()函数,如我在 kernel/printk.c 中添加了

void pk()

{

  printk(KERN_WARNING"this is my first sys call !\n");

}

2. 添加系统调用号

在 arch/arm/include/asm/Unistd.h

添加  #define __NR_pk    (__NR_SYSCALL_BASE+365)

3. 添加调用函数指针列表

在arch/arm/keenel/Calls.S添加 CALL(sys_pk)

4.  声明自己的系统调用函数

在include/linux/syscall.h添加asmlinkage long sys_pk()

用户空间:

      void pk()

  {

    __asm__(

    "ldr  r7  =365 \n"

    "swi \n"

    :

    :

    :

    );

  }

  int main()

  {

      pk();

     retrun 0;

  }

完成上面的编写以后就可以编译内核和应用程序了。

将生成的文件在arm开发板上运行可以打印出: This is my first sys call!

说明我添加的系统调用可以使用。

至此,描述系统调用的实现机制和添加一个新的系统调用就完成了。

3 添加自己的系统调用

了解了一个系统调用的执行过程就可以试着添加一个自己的系统调用了:

内核:

1. 在内核代码实现一个系统调用函数

即 sys_xxx()函数,如我在 kernel/printk.c 中添加了

void pk()

{

printk(KERN_WARNING"this is my first sys call !\n");

}

2. 添加系统调用号

在 arch/arm/include/asm/Unistd.h添加  #define __NR_pk    (__NR_SYSCALL_BASE+365)

3. 添加调用函数指针列表

在arch/arm/keenel/Calls.S添加 CALL(sys_pk)

4.  声明自己的系统调用函数

在include/linux/syscall.h添加asmlinkage long sys_pk()

用户空间:

      void pk()

{

__asm__(

"ldr  r7  =365 \n"

"swi \n"

:

:

:

);

}

int main()

{

   pk();

retrun 0;

}

完成上面的编写以后就可以编译内核和应用程序了。

将生成的文件在arm开发板上运行可以打印出: This is my first sys call!

说明我添加的系统调用可以使用。

至此,描述系统调用的实现机制和添加一个新的系统调用就完成了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注...

37510
来自专栏码匠的流水账

聊聊jdbc statement的fetchSize

对于查询数据量大的场景下,非常有必要设置fetchSize,否则全量拉取很容易OOM,但是使用fetchSize的时候,要求数据能够在遍历resultSet的时...

261
来自专栏大内老A

Enterprise Library深入解析与灵活应用(8):WCF与Exception Handling AppBlock集成[下]

在上篇中,我详细介绍了如何通过自定义ClientMessageInspector和ErrorHandler,实现WCF与微软企业库中的Exception Han...

17110
来自专栏java一日一条

JVM 架构解读

每个Java开发人员都知道字节码由JRE(Java运行时环境)执行。但许多人不知道JRE是Java Virtual Machine(JVM)的实现,它分析字节码...

301
来自专栏开发技术

cassandra高级操作之JMX操作

  一开始有点无头绪,后面查看cassandra官方文档看到Monitoring章节,里面说到:Cassandra中的指标使用Dropwizard Metric...

894
来自专栏我是攻城师

给Java字节码加上”翅膀“的JIT编译器

上面文章在介绍Java的内存模型的时候,提到过由于编译器的优化会导致重排序的问题,其中一个比较重要的点地方就是关于JIT编译器的功能。JIT的英文单词是Just...

865
来自专栏Jed的技术阶梯

Kafka 新版消费者 API(三):以时间戳查询消息和消费速度控制

kafka 在 0.10.1.1 版本增加了时间索引文件,因此我们可以根据时间戳来访问消息。 如以下需求:从半个小时之前的offset处开始消费消息,代码示例...

602
来自专栏函数式编程语言及工具

Scalaz(59)- scalaz-stream: fs2-程序并行运算,fs2 running effects in parallel

    scalaz-stream-fs2是一种函数式的数据流编程工具。fs2的类型款式是:Stream[F[_],O],F[_]代表一种运算模式,O代表Str...

1726
来自专栏逸鹏说道

【推荐】C#线程篇---Task(任务)和线程池不得不说的秘密(5.2)

ContinueWith? 啥东西~~??  要写可伸缩的软件,一定不能使你的线程阻塞。这意味着如果调用Wait或者在任务未完成时查询Result属性,极有...

3616
来自专栏积累沉淀

Java批处理

批处理 JDBC对批处理的操作,首先简单说一下JDBC操作sql语句的简单机制。 JDBC执行数据库操作语句,首先需要将sql语句打包成为网络字...

1695

扫描关注云+社区