- 论坛徽章:
- 0
|
假设:
1:当前正在运行的进程为p1,其对应的struct proc对象简记为p1_proc,相关联的线程为th1,其对应的struct thread对象简记为th1_thread。
2:cpu已经完成了调用系统调用异常处理程序前的硬件处理,此时将开始执行系统调用异常处理程序。
3:执行的是rfork系统调用,系统调用号为251,十六进制为0xfb;该系统调用只有一个参数flags,这样可以看一下参数是如何传递的。
4:p1_proc的p_sysent成员指向elf32_freebsd_sysvec对象。
先来一张线程th1_thread内核栈和用户栈的结构图,下面的分析结合图会更清楚:
[函数IDTVEC(int0x80_syscall)]-系统调用异常处理程序:- /***********************************************************************************************
- * Trap gate entry for syscalls (int 0x80).
- * This is used by FreeBSD ELF executables, "new" NetBSD executables, and all
- * Linux executables.
- *
- * Even though the name says 'int0x80', this is actually a trap gate, not an
- * interrupt gate. Thus interrupts are enabled on entry just as they are for
- * a normal syscall.
-
- 这里的宏IDTVEC展开后为Xint0x80_syscall
- 260:
- 当执行指令"int 0x80"时,返回地址指向紧接该指令后面那条指令,返回地址由保存在线程th1_thread
- 内核栈上的CS和EIP寄存器来描述。
- 这里将数值常量2(指令"int 0x80"的字节数)压入线程th1_thread的内核栈,就可以通过struct trapframe
- 对象的tf_err成员访问这个数值常量,在需要重新执行系统调用时,只要将保存在线程th1_thread内核栈上
- 的EIP寄存器的值减去2,系统调用返回后就会重新执行指令"int 0x80",这样就能重新执行系统调用。
- 见后面cpu_set_syscall_retval函数中的描述。
- pushal指令:
- Pushes the contents of the general-purpose registers onto the stack. The registers
- are stored on the stack in the following order: EAX, ECX, EDX, EBX, ESP (original
- value), EBP, ESI, and EDI (if the current operand-size attribute is 32)。
- 266:SET_KERNEL_SREGS宏见下面的描述。
- 267:
- 清DF标志,string operations increment the index registers (ESI and/or EDI).
- 270:调用函数syscall。
- ***************************************/
- 258 SUPERALIGN_TEXT
- 259 IDTVEC(int0x80_syscall)
- 260 pushl $2 /* sizeof "int 0x80" */
- 261 subl $4,%esp /* skip over tf_trapno */
- 262 pushal
- 263 pushl %ds
- 264 pushl %es
- 265 pushl %fs
- 266 SET_KERNEL_SREGS
- 267 cld
- 268 FAKE_MCOUNT(TF_EIP(%esp))
- 269 pushl %esp
- 270 call syscall
- 271 add $4, %esp
- 272 MEXITCOUNT
- 273 jmp doreti
复制代码 [宏SET_KERNEL_SREGS]:- /**********************************************************************************
- * Setup the kernel segment registers.
- 此时处理器x运行在内核态,段寄存器也要更改。
-
- 173-175:
- 用内核数据段的segment selector,即KDSEL加载DS,ES两个寄存器,访问内核数据。
- 176-177:
- 用KPSEL加载FS寄存器,这样就能访问数据对象__pcpu[x],也是宏PCPU_*的基础。
- ******************************************/
- 172 #define SET_KERNEL_SREGS \
- 173 movl $KDSEL, %eax ; /* reload with kernel's data segment */ \
- 174 movl %eax, %ds ; \
- 175 movl %eax, %es ; \
- 176 movl $KPSEL, %eax ; /* reload with per-CPU data segment */ \
- 177 movl %eax, %fs
复制代码 [函数syscall]:- /******************************************************************************
- * syscall - system call request C handler. A system call is
- * essentially treated as a trap by reusing the frame layout.
- 参数描述:
- frame:
- 从上面线程th1_thread的内核栈结构可以明显看出,调用syscall函数时,传递
- 的是一个指向struct trapframe对象的指针,而不是整个struct trapframe结构体。
- ********************************************/
- 1140 void
- 1141 syscall(struct trapframe *frame)
- 1142 {
- /***************************************************************************
- * 局部变量描述:
- td:指向描述当前运行线程的struct thread对象。
- sa:一个类型struct syscall_args的对象,用来保存系统调用的参数等信息。
- orig_tf_eflags:用来临时保存线程th1_thread内核栈上的EFLAGS寄存器的值。
- error:保存系统调用的返回值。
- ksi:信号相关,暂时忽略。
- *******************************************/
- 1143 struct thread *td;
- 1144 struct syscall_args sa;
- 1145 register_t orig_tf_eflags;
- 1146 int error;
- 1147 ksiginfo_t ksi;
- 1148
- /********************************************************************************
- * 1149-1154:
- 编译内核时选择了DIAGNOSTIC这个选项时,才执行额外的检查。
-
- #define SEL_UPL 3
- #define ISPL(s) ((s)&3) what is the priority level of a selector
- 这里将检查执行"int 0x80"指令陷入内核时cpu是否运行在用户态。
- 这是通过检查保存在线程th1_thread内核栈上的CS寄存器的值来实现的,根据之前的
- 描述,CS寄存器保存的是segment selector,而segment selector中RPL字段的值
- 标识了cpu运行在用户态还是内核态,如果RPL字段的值为3,就表示cpu运行在
- 用户态。
- 从这里可以看出,只有在cpu运行在用户态时才能触发系统调用异常。
- **************************************/
- 1149 #ifdef DIAGNOSTIC
- 1150 if (ISPL(frame->tf_cs) != SEL_UPL) {
- 1151 panic("syscall");
- 1152 /* NOT REACHED */
- 1153 }
- 1154 #endif
- 1155 orig_tf_eflags = frame->tf_eflags;
- 1156
- /*******************************************************************************
- * 1157:
- 这里td的值为&th1_thread。
-
- 1158:
- th1_thread的td_frame用来访问保存在线程th1_thread内核栈上的用户态硬件上下文,
- 即通过类型为struct trapframe的对象来描述。
- 和前面描述"处理器J间中断-接收处理"中对比一下,是通过td_intr_frame这个成员访问
- struct trapframe对象的。
- 从这里可以看出,貌似这两个成员在不同的场合中使用。
- 1160:
- 调用syscallenter函数,这个函数任务有点杂,见下面的描述。
- 1162-1172:
- single-step mode for debugging, 暂时将其忽略。
- *****************************************************/
- 1157 td = curthread;
- 1158 td->td_frame = frame;
- 1159
- 1160 error = syscallenter(td, &sa);
- 1161
- 1162 /*
- 1163 * Traced syscall.
- 1164 */
- 1165 if ((orig_tf_eflags & PSL_T) && !(orig_tf_eflags & PSL_VM)) {
- 1166 frame->tf_eflags &= ~PSL_T;
- 1167 ksiginfo_init_trap(&ksi);
- 1168 ksi.ksi_signo = SIGTRAP;
- 1169 ksi.ksi_code = TRAP_TRACE;
- 1170 ksi.ksi_addr = (void *)frame->tf_eip;
- 1171 trapsignal(td, &ksi);
- 1172 }
- 1173
- 1174 KASSERT(PCB_USER_FPU(td->td_pcb),
- 1175 ("System call %s returning with kernel FPU ctx leaked",
- 1176 syscallname(td->td_proc, sa.code)));
- 1177 KASSERT(td->td_pcb->pcb_save == &td->td_pcb->pcb_user_save,
- 1178 ("System call %s returning with mangled pcb_save",
- 1179 syscallname(td->td_proc, sa.code)));
- 1180
- /*********************************************************************************
- * 1181:
- 函数syscallret完成的工作的还是挺多的,比如重新设置线程th1_thread的优先级,
- 当线程th1_thread是通过vfork创建的,就要进行相应的处理,进程跟踪相关处理等等。
- 这里暂时将其忽略。
- 现在系统调用已经执行完,主要的工作也就完成,这个函数留作以后再分析。
- ******************************/
- 1181 syscallret(td, error, &sa);
- 1182 }
复制代码 [函数syscallenter]:- /******************************************************************************
- * 参数描述:
- td:这里为&th1_thread。
- sa:指向一个类型为struct syscall_args的对象,保存系统调用的参数。
- 这里略去了进程跟踪等相关的代码,重点看一下系统调用处理相关的代码。
- 完整的函数可以在"$FreeBSD: release/9.2.0/sys/kern/subr_syscall.c"中找到。
- **************************************/
- 55 static inline int
- 56 syscallenter(struct thread *td, struct syscall_args *sa)
- 57 {
- 58 struct proc *p;
- 59 int error, traced;
- 60
- /*********************************************************************************
- * 61:递增相关的计数器。
- cnt是内核定义的一个类型为struct vmmeter的对象,该对象的成员相当于一个计数器。
- u_int v_syscall; calls to syscall() 即调用函数syscall的次数。
- 62:p为&p1_proc
- *****************************************/
- 61 PCPU_INC(cnt.v_syscall);
- 62 p = td->td_proc;
- .........................................................................................
- .........................................................................................
- /*********************************************************************************************************
- * 75:
- 这里将调用函数cpu_fetch_syscall_args获取系统调用参数,系统调用号等,并以系统调用号为索引,在数组sysent
- 中找到其对应的数组元素,见下面的简单分析。
- 从函数cpu_fetch_syscall_args的实现来看,在freebsd9.2中,系统调用号是通过
- EAX寄存器传递的,而系统调用的参数是通过用户栈来传递的,这点和linux有点区别,
- linux中系统调用的参数是通过寄存器来传递。
- 当cpu_fetch_syscall_args函数执行完后:
- 1->系统调用号超过最大限制时,忽略传递进来的系统调用号,此时sa指向的对象的成员被设置为:
- sa->callp被设置为&sysent[0].
- sa->narg被设置为sysent[0].sy_narg,这里的值为0。
- sa->code被设置为系统调用号,此时该成员的值被忽略。
- sa->args中保存了系统调用的参数。
-
- 数组元素sysent[0]的各成员取值如下:
- sysent[0].sy_narg = 0;
- sysent[0].sy_call= nosys;
- sysent[0].sy_auevent = AUE_NULL;
- sysent[0].sy_systrace_args_func = NULL;
- sysent[0].sy_entry = 0;
- sysent[0].sy_return = 0;
- sysent[0].sy_flags = 0;
- sysent[0].sy_thrcnt = SY_THR_STATIC;
- 2->系统调用号在正常的大小范围内,这里执行的是系统调用rfork,其系统调用号是251,此时sa指向的对象的
- 成员被设置为:
- sa->callp被设置为&sysent[251].
- sa->narg被设置为sysent[251].sy_narg,这里的为AS(rfork_args),值为1,即1个4字节。
- sa->code被设置为系统调用号251。
- sa->args中保存了系统调用的参数,系统调用rfork只有一个参数flags。
- 数组元素sysent[251]的各成员取值如下:
- sysent[251].sy_narg = AS(rfork_args);
- sysent[251].sy_call= sys_rfork;
- sysent[251].sy_auevent = AUE_RFORK;
- sysent[251].sy_systrace_args_func = NULL;
- sysent[251].sy_entry = 0;
- sysent[251].sy_return = 0;
- sysent[251].sy_flags = 0;
- sysent[251].sy_thrcnt = SY_THR_STATIC;
-
- ****************************************/
- 75 error = (p->p_sysent->sv_fetch_syscall_args)(td, sa);
- .........................................................................................
- .........................................................................................
- 86 if (error == 0) {
- .........................................................................................
- .........................................................................................
- /******************************************************************************************************
- * 119:
- 如果通过模块向内核注册了一个系统调用,那么描述该系统调用的struct sysent对象的sy_thrcnt
- 成员将被设置为0,从函数syscall_thread_enter的实现来看,该函数只对这种系统调用感兴趣,此时
- sy_thrcnt成员相当于一个计数器,每次执行相应的系统调用时,函数syscall_thread_enter都给其递增
- 一个常量SY_THR_INCR。
- 135:
- 这里将调用内核中实现相应系统调用的内核函数。
- 情况1->系统调用号超过最大限制时,从上面可以看出,此时将调用函数nosys,该函数将向线程th1_thread
- 发送一个信号SIGSYS,该信号的默认操作是杀死线程th1_thread。
- #define SIGSYS 12 non-existent system call invoked
- 情况2->系统调用号在正常的大小范围内,这里将调用sys_rfork函数,此时控制将传递给内核中实现
- rfork系统调用的内核函数。
- 从这里还以看出,不管实现系统调用的内核函数是否需要参数,都将sa->args作为参数传递给相应的内核函数,
- 因为相应的内核函数很清楚自己是否需要参数,如果需要,此时sa->args中已经包含了所需要的参数;
- 如果不需要,sa->args中也没有包含参数,相应的内核函数也不会使用sa->args这个参数。
- 152:
- 和syscall_thread_enter函数的操作相反,这里将执行递减操作。
- ************************************************************/
- 119 error = syscall_thread_enter(td, sa->callp);
- 120 if (error != 0)
- 121 goto retval;
- ........................................................................................
- ........................................................................................
- 135 error = (sa->callp->sy_call)(td, sa->args);
- ........................................................................................
- ........................................................................................
- 152 syscall_thread_exit(td, sa->callp);
- 153 CTR4(KTR_SYSC, "syscall: p=%p error=%d return %#lx %#lx",
- 154 p, error, td->td_retval[0], td->td_retval[1]);
- 155 }
- 156 retval:
- ........................................................................................
- ........................................................................................
- /**************************************************************************
- * 162: 对返回值进行处理。
- 这里将调用cpu_set_syscall_retval函数。
- ****************************/
- 162 (p->p_sysent->sv_set_syscall_retval)(td, error);
- 163 return (error);
- 164 }
复制代码 [函数cpu_fetch_syscall_args]-获取系统调用的参数等,可以结合上面的图一起看:[函数cpu_set_syscall_retval]- 处理返回值,一般情况下,EAX寄存器包含的是返回值:- 391 void
- 392 cpu_set_syscall_retval(struct thread *td, int error)
- 393 {
- 394
- 395 switch (error) {
- /**********************************************************************
- * 396-400:
- 系统调用成功执行,error为0。
- 此时td_retval[0],td_retval[1]已经被相应的内核函数设置为有意义的值。
- 清除EFLAGS寄存器中的进位标志,表示没有错误发生。
- ***************/
- 396 case 0:
- 397 td->td_frame->tf_eax = td->td_retval[0];
- 398 td->td_frame->tf_edx = td->td_retval[1];
- 399 td->td_frame->tf_eflags &= ~PSL_C;
- 400 break;
- 401
- /**********************************************************************************
- * 402-408:
- 系统调用被中断,但是内核返回错误码ERESTART:
- #define ERESTART (-1) restart syscall
- 将保存在线程th1_thread内核栈上的EIP寄存器的值减去2,在系统调用异常返回后,
- EIP寄存器的值为指令"int 0x80"的地址,此时将重新执行系统调用。
- **************************************/
- 402 case ERESTART:
- 403 /*
- 404 * Reconstruct pc, assuming lcall $X,y is 7 bytes, int
- 405 * 0x80 is 2 bytes. We saved this in tf_err.
- 406 */
- 407 td->td_frame->tf_eip -= td->td_frame->tf_err;
- 408 break;
- 409
- /**************************************************************
- * 410-411:
- * #define EJUSTRETURN (-2) don't modify regs, just return
- 不做任何处理。
- ************************/
- 410 case EJUSTRETURN:
- 411 break;
- 412
- /****************************************************************
- * 413-422:
- 由于某种原因,系统调用执行失败,error包含了相应的错误码。
- 414-419:不做处理
- elf32_freebsd_sysvec对象的sv_errsize成员为0。
-
- 420:
- 将系统调用执行失败的的错误码通过eax寄存器返回给应用程序。
- 注意,如果没有函数库或者封装例程的介入,此时系统调用的返回值
- 就是执行失败的错误码,而不是-1;返回-1是因为函数库或者封装例程
- 做了额外的处理,这个额外的处理就是errno变量的引入。
-
- 421:
- 设置EFLAGS寄存器的进位标志,表示系统调用执行失败。
- *******************************/
- 413 default:
- 414 if (td->td_proc->p_sysent->sv_errsize) {
- 415 if (error >= td->td_proc->p_sysent->sv_errsize)
- 416 error = -1; /* XXX */
- 417 else
- 418 error = td->td_proc->p_sysent->sv_errtbl[error];
- 419 }
- 420 td->td_frame->tf_eax = error;
- 421 td->td_frame->tf_eflags |= PSL_C;
- 422 break;
- 423 }
- 424 }
复制代码 |
|