- 论坛徽章:
- 0
|
本帖最后由 71v5 于 2014-07-09 00:57 编辑
系统调用基本概念:
关于系统调用的概念,这里摘取<<操作系统设计-Xinu方法>>一书中的定义,个人觉得是比较精确的--"一般来说,一组系统调用就是定义了一种从外部观察操作系统的视角,
应用程序调用系统调用来获取服务,除了增加一层保护外,系统调用接口向应用程序隐藏了内部的实现细节,即应用程序对内部实现并不知晓,仅仅使用一组系统调用
来获取服务,同时还保护操作系统以避免非法使用"。
从上面的描述可知,系统调用是操作系统内核提供给应用程序的服务入口,应用程序想要使用操作系统提供的服务,就必须使用系统调用,但是系统调用属于操作系统
内核,操作系统内核属于最高特权级;而应用程序运行在用户态,属于最低特权级;那么如何从最低特权级切换到最高特权级,这就需要cpu提供支持,IA32提供了
软中断指令"INT n"来提供支持,从"freebsd9.2-内核如何实现IA32中断处理 "中的描述可以知道,freebsd9.2专门建立了一个陷阱门描述符,该描述符在IDT中的
索引为0x80,即异常向量号为0x80,执行INT 0x80指令后,cpu就陷入内核,经过基本处理的处理后,就会执行操作系统内核中实现相应系统调用的函数。
那么应用程序怎么调用系统调用?通过函数库中的函数,函数库中的很多函数都是借助于操作系统内核提供的系统调用实现的,并且和操作系统内核中的系统调用
名字相同;比如创建进程时,使用的是函数库中的fork函数,函数库中的封装例程帮我们设置好fork系统调用需要的参数(系统调用需要参数时,fork没有参数),
然后执行INT 0x80指令发出一个软中断陷入内核。
为了区分函数库中的fork和操作系统内核中的fork,这里将函数库中的fork称为fork函数,将操作系统内核中的fork称为fork系统调用。
一般情况下,应用程序调用fork函数创建进程,到最后成功返回给应用程序,需要经过下面几个步骤:
1:应用程序调用fork函数。
2:函数库中的封装例程设置好相应参数,代应用程序发出"int 0x80"指令陷入内核。
3:cpu硬件单元完成必要的处理,并将控制传递给系统调用异常处理程序IDTVEC(int0x80_syscall),
这个处理见"freebsd9.2-内核如何实现IA32中断处理"结尾的描述。
4:系统调用异常处理程序IDTVEC(int0x80_syscall)接管工作。
5:系统调用异常处理程序调用syscall函数,该函数获取系统调用参数,然后调用内核中实现fork系统调用的函数。
6:等待内核中实现fork系统调用的函数返回。
7:设置返回值。
8:syscall函数返回,控制转移到系统调用异常处理程序IDTVEC(int0x80_syscall)。
9:跳转到doreti函数,完成系统调用异常返回前的处理。
10:函数库中的封装例程做必要的处理,fork函数返回。
这里分析的"系统调用机制"是指上面的第4步,第5步,第7步,第8步;并且第4-9步cpu都运行在内核态,即最高特权级。
下面看看在系统调用机制实现中,需要用到的数据结构以及进程描述符和线程描述符中比较重要的成员:- [struct sysent对象]-在内核中来描述每个系统调用的相关信息:
- /*****************************************************************************************
- * 该对象可以在"$FreeBSD: release/9.2.0/sys/sys/sysent.h"中找到。
- 在freebsd9.2中,每个系统调用都对应一个struct sysent类型的数据对象,该对象
- 详细的描述了相应系统调用的详细信息,比如参数的个数,内核中实现该系统调用的
- 函数,以及一额外的标志。
- 成员如下所示,暂时先忽略sy_systrace_args_func,sy_entry,sy_return成员,这三个
- 成员和跟踪相关,不过freebsd9.2中,对于描述每个系统调用的struct sysent对象,
- 这三个成员基本上都被设置为NULL,0,0。
-
- sy_narg:系统调用参数的个数。
- sy_call:内核中实现相应系统调用的函数的地址,对于每一个系统调用,最终都是
- 将控制转移到改成员标识的方法,即该成员标识的方法实现了相应的系统
- 调用。
-
- sy_auevent:审计相关,暂时先忽略。
-
- sy_flags:
- 大概看了一下,该成员目前取值为SYF_CAPENABLED或者0.
- #define SYF_CAPENABLED 0x00000001
- 在编译内核时选择CAPABILITY_MODE时:
- 此时,如果进程p1执行了系统调用cap_enter,内核就会给p1的进程描述符中相关成员设置
- 一个CRED_FLAG_CAPMODE标志,那么当进程p1随后执行的系统调用中,只有当相应
- struct sysent对象中sy_flags成员设置了SYF_CAPENABLED标志时,才可以成功执行。
- sy_thrcnt,依然是一些标志,取值如下:
- #define SY_THR_FLAGMASK 0x7->暂时没使用
- #define SY_THR_STATIC 0x1->
- 相应的系统调用由内核实现并包含在内核中。
- #define SY_THR_DRAINING 0x2->
- 当实现一个系统调用的模块被卸载时,会设置该标志,表示该系统调用正在被卸载。
- #define SY_THR_ABSENT 0x4
- 相应的系统调用没有相应的实现,即缺失。
- #define SY_THR_INCR 0x8->
- 在函数syscall_thread_enter和syscall_thread_exit中使用。
- 当实现一个系统调用的模块被加载成功后,描述该系统调用的struct sysent对象的
- sy_thrcnt成员为0,被解释为一个计数器。
- ********************************************************/
- 56 struct sysent { /* system call table */
- 57 int sy_narg; /* number of arguments */
- 58 sy_call_t *sy_call; /* implementing function */
- 59 au_event_t sy_auevent; /* audit event associated with syscall */
- 60 systrace_args_func_t sy_systrace_args_func;
- 61 /* optional argument conversion function. */
- 62 u_int32_t sy_entry; /* DTrace entry ID for systrace. */
- 63 u_int32_t sy_return; /* DTrace return ID for systrace. */
- 64 u_int32_t sy_flags; /* General flags for system calls. */
- 65 u_int32_t sy_thrcnt;
- 66 };
复制代码 [struct sysentvec对象]:- /***************************************************************************************
- * struct sysentvec对象可以在"$FreeBSD: release/9.2.0/sys/sys/sysent.h"中找到。
-
- 下面只列出了和系统调用机制相关的成员:
- sv_table:
- struct sysent类型的数组,每个数组元素都描述内核实现的系统调用,数组的索引为
- 系统调用号,当应用程序想要使用一个系统调用提供的服务时,就必须给内核传递系统调用号,
- 内核通过系统调用号在数组sv_table中找到其对应的struct sysent对象。也可以说,系统调用
- 在内核中的**就是系统调用号。在这里,将数组sv_table简称为系统调用方法表。
-
-
- sv_size:
- 数组sv_table的索引的最大值,即系统调用号的最大值。
- sv_mask:
- 一个掩码,将数组sv_table的索引限制在规定的范围之内,这个成员值一般都为0。
- sv_set_syscall_retval:
- 设置系统调用返回值的方法。
-
- sv_fetch_syscall_args:
- 获取系统调用参数的方法。
- 内核静态定义了几个该类型的数据对象。在struct proc对象中该成员的描述:
- "Syscall dispatch info.",结合elf_brand_list数组来看,貌似还有一个作用,
- 提供ABI的兼容。
- *************************************************/
- 85 struct sysentvec {
- 86 int sv_size; /* number of entries */
- 87 struct sysent *sv_table; /* pointer to sysent */
- 88 u_int sv_mask; /* optional mask to index */
- .......................................................................................
- .......................................................................................
- 120 void (*sv_set_syscall_retval)(struct thread *, int);
- 121 int (*sv_fetch_syscall_args)(struct thread *, struct
- 122 syscall_args *);
- .......................................................................................
- .......................................................................................
- 133 };
复制代码 [struct proc对象的p_sysent成员]:- /*****************************************************************************************
- * Process structure.
-
- p_sysent:
- 指向上面描述的struct sysentvec对象。
-
- 该成员应该是在系统调用机制中相对最重要的一个成员,只有正确设置了该成员,才能
- 访问正确的系统调用方法表,才能正确获取系统调用的参数等。那么这个成员是在什么
- 时候设置的呢?应用程序调用fork函数创建一个进程后,会紧接着调用execve函数来执行
- 一个新的可执行程序(可执行的ELF文件),在execve系统调用的实现中,会根据ELF头部
- 的信息(这里要用到之前描述的elf_brand_list数组),来选择一个合适的struct sysentvec对象,
- 会做下面的设置:
- 在freebsd本地机器上编译好的可执行程序->p_sysent = &elf32_freebsd_sysvec;
- 在GNU linux上编译好的可执行程序->p_sysent = &elf_linux_sysvec;
- elf32_freebsd_sysvec和elf_linux_sysvec都是内核静态定义的。
- 这里只讨论p_sysent为&elf32_freebsd_sysvec的情况。
- ***************************************************/
- 484 struct proc {
- .......................................................................................
- .......................................................................................
- 561 struct sysentvec *p_sysent; /* (b) Syscall dispatch info. */
- .......................................................................................
- .......................................................................................
- 597 };
复制代码 [struct thread对象的td_retval成员]:- /********************************************************************************
- * Kernel runnable context (thread).
- * This is what is put to sleep and reactivated.
- * Thread context. Processes may have multiple threads.
- *
- 在struct sysent对象的sy_call方法完成时,会用正确的值设置数组td_retval,
- 该数组元素的值就是系统调用的返回值(有可能和fork函数的返回值不同),随后分析
- 系统调用机制实现时将详细说明。
- *************************************/
- 204 struct thread {
- .......................................................................................
- .......................................................................................
- 299 register_t td_retval[2]; /* (k) Syscall aux returns. */
- .......................................................................................
- .......................................................................................
- 320 };
复制代码 [struct sysentvec elf32_freebsd_sysvec]-如果在freebsd上执行本地已经编译的程序,那么p_sysent成员指向下面的数据对象:- /***********************************************************************************
- * 列出了部分成员的取值.
- 完整的数组可以在"$FreeBSD: release/9.2.0/sys/i386/i386/elf_machdep.c"找到.
- 数组sysent即即系统调用方法表,元素个数太多,下面只列出部分,。
- 数组sysent的索引即系统调用号可以在"$FreeBSD: release/9.2.0/sys/sys/syscall.h"中找到。
- 数组sysent可以在"$FreeBSD: release/9.2.0/sys/kern/init_sysent.c"中找到。
- **************************/
- 49 struct sysentvec elf32_freebsd_sysvec = {
- 50 .sv_size = SYS_MAXSYSCALL,
- 51 .sv_table = sysent,
- 52 .sv_mask = 0,
- .......................................................................................
- .......................................................................................
- 78 .sv_set_syscall_retval = cpu_set_syscall_retval,
- 79 .sv_fetch_syscall_args = cpu_fetch_syscall_args,
- .......................................................................................
- .......................................................................................
- 84 };
- 36 struct sysent sysent[] = {
- 37 { 0, (sy_call_t *)nosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_STATIC }, /* 0 = syscall */
- 38 { AS(sys_exit_args), (sy_call_t *)sys_sys_exit, AUE_EXIT, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC }, /* 1 = exit */
- 39 { 0, (sy_call_t *)sys_fork, AUE_FORK, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC }, /* 2 = fork */
- 40 { AS(read_args), (sy_call_t *)sys_read, AUE_NULL, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC }, /* 3 = read */
- ........................................................................................................................................
- ........................................................................................................................................
- 570 };
复制代码 [struct syscall_args对象结]-该数据对象用来保存系统调用的参数等:- /*******************************************************************************************
- * 成员描述:
- code:标识系统调用的一个整数,即系统调用方法表的索引或者系统调用号。
- callp:指向描述系统调用的struct sysent对象。一般被设置为数组元素sysent[code]的地址。
- args:保存了传递给系统调用的参数。
- narg:系统调用参数的数目。
- ************************************************/
- 81 struct syscall_args {
- 82 u_int code;
- 83 struct sysent *callp;
- 84 register_t args[8];
- 85 int narg;
- 86 };
复制代码 |
|