免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2060 | 回复: 8
打印 上一主题 下一主题

请问关于内核陷阱实现的问题~~新手请教 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-10-09 17:58 |只看该作者 |倒序浏览
在读内核代码陷阱那部分的时候想不通两点:
1。每个陷阱有一个默认的信号对应比如DIVIDE ERROR 对应SIGFPE,但是当系统在用户态运行当中遇到除0的操作的时候,系统如何陷入到内核当中(INTEL的CPU会自动地切换栈,然后将一些状态信息压入系统栈么?)那这个信号在什么时候发送给用户,而用户又什么时候对这个信号进行处理的呢?(怎么保证立即处理)

2。运行trap.c里面的程序的时候是不是栈一定是系统栈 不可能是用户栈?

希望高手指教:) 谢谢!

论坛徽章:
0
2 [报告]
发表于 2006-10-09 21:35 |只看该作者
怎么没人回答啊?这个问题真的是想不通 希望大家能帮帮忙!

论坛徽章:
0
3 [报告]
发表于 2006-10-10 09:37 |只看该作者
waiting。。。

论坛徽章:
0
4 [报告]
发表于 2006-10-10 16:21 |只看该作者

回复 1楼 andy19821122 的帖子

>1。每个陷阱有一个默认的信号对应比如DIVIDE ERROR 对应SIGFPE,
不一定,有些是不发送信号的。

>但是当系统在用户态运行当中遇到除0的操作的时候,系统如何陷入到内核当中(INTEL的CPU会自动地切换栈,然后将一些状态信息压入系统栈么?)
根据IDT中的设置来做的。 每个异常在IDT表中有一项。 至于如何自陷, 则是设置好了idtr之后的CPU自动完成的。

>那这个信号在什么时候发送给用户,而用户又什么时候对这个信号进行处理的呢?(怎么保证立即处理)
发送信号,是handler觉得需要才发的。 同一种异常,可能会发也可能不会发,关键在handler处理的结果怎样。  至于用户处理新好的时机, 我对信号不够熟悉, 不敢说了。


>2。运行trap.c里面的程序的时候是不是栈一定是系统栈 不可能是用户栈?
对,一定是系统栈。

论坛徽章:
0
5 [报告]
发表于 2006-10-10 17:10 |只看该作者
非常感谢楼上的解释。
但是第二个问题的handler是指中断处理的函数么?我看代码中比如对divide err这个trap的处理函数里面没有给当前任务的信号量置位的部分啊~指看到了在系统调用结束的时候会在内核态去执行一些信号的处理。那这些信号都是在哪里置位的啊?
thanks very much!~~~~~~~

论坛徽章:
0
6 [报告]
发表于 2006-10-11 08:28 |只看该作者
If the handler procedure is going to be executed at the same privilege level as the interrupted procedure, the handler uses the current stack.

If the handler procedure is going to be executed at a numerically lower privilege level, a stack switch occurs.

The current process takes care of the signal right after the termination of the exception handler. The signal will be handled either in User Mode by the process's own signal handler (if it exists) or in Kernel Mode. In the latter case, the kernel usually kills the process.

论坛徽章:
0
7 [报告]
发表于 2006-10-11 10:08 |只看该作者
感谢版主的解释,我还有一个问题就是,这个handler是在用户级还是在系统级执行是从哪里判断的呢?还有我看到的traps的处理代码只是调用了die函数显示一些信息并没有像系统调用一样调用do_signal相关的函数啊?呵呵~很想弄明白这些东西,谢谢大家了:)

论坛徽章:
0
8 [报告]
发表于 2006-10-11 16:21 |只看该作者
通过代码解释整个过程会比较清楚:

内核代码版本 2.6.19-rc1-git7

你所说的这些同步异常的处理在CPU的中断表中有固定的位置, 如除0, 对应第0项.
内核初始化时调用trap_init()初始化同步异常中断表.

set_trap_gate(0,&divide_error);

当用户空间除0, CPU自动跳转到内核态,并将当前寄存器信息压入内核堆栈, 然后调用entry.s中的divide_error()
我一直认为, entry.s是内核中最重要的一个文件, 完全理解这个文件, 对理解整个内核的运转非常关键.

ENTRY(divide_error)
        RING0_INT_FRAME
        pushl $0                        # no error code
        CFI_ADJUST_CFA_OFFSET 4
        pushl $do_divide_error                //将执行函数压栈
        CFI_ADJUST_CFA_OFFSET 4
        jmp error_code
        CFI_ENDPROC

error_code:
        ......
        call *%edi
        jmp ret_from_exception
        CFI_ENDPROC

所以, 除0错误在do_divide_error()中处理, 执行完后, 执行ret_from_exception().
很明显,对用户空间任务的处理不应该在do_divide_error()中实现, 因为这个函数执行在异常上下文中,优先级非常高.

do_divide_error()
#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \
fastcall void do_##name(struct pt_regs * regs, long error_code) \
{ \
        /* 设置SIGINFO信息, 如果用户空间使用sigaction()挂了自己的信号处理函数,
           且flag & SIGINFO, 可以接收到这些信息 */
        siginfo_t info; \
        info.si_signo = signr; \
        info.si_errno = 0; \
        info.si_code = sicode; \
        info.si_addr = (void __user *)siaddr; \
        /* 执行内核设置的监控函数, 如kprobe, oprofile等 */
        if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) \
                                                == NOTIFY_STOP) \
                return; \
        /* 处理这个异常 */
        do_trap(trapnr, signr, str, 0, regs, error_code, &info); \
}

static void __kprobes do_trap(int trapnr, int signr, char *str, int vm86,
                              struct pt_regs * regs, long error_code,
                              siginfo_t *info)
{
        struct task_struct *tsk = current;
        tsk->thread.error_code = error_code;
        tsk->thread.trap_no = trapnr;

        if (regs->eflags & VM_MASK) {
                if (vm86)
                        goto vm86_trap;
                goto trap_signal;
        }
        /* 很明显,如果内核出现除0的错误,说明内核代码有bug,必须触发oops */
        if (!user_mode(regs))
                goto kernel_trap;

        /* 如果仅仅是用户空间任务除0, 发送信号给那个程序,
           而不需要触发内核bug. 发送信号的过程仅仅是将当前任务的thread_info flag
           添加上sigpending, 并将info信息添加到task->pending列表中 */
        trap_signal: {
                if (info)
                        force_sig_info(signr, info, tsk);
                else
                        force_sig(signr, tsk);
                return; //用户出错, 直接返回
        }

        kernel_trap: {
                if (!fixup_exception(regs))
                        die(str, regs, error_code);
                return;
        }

        vm86_trap: {
                int ret = handle_vm86_trap((struct kernel_vm86_regs *) regs, error_code, trapnr);
                if (ret) goto trap_signal;
                return;
        }
}

所以直接返回到ret_from_exception() (entry.s)

当用户空间任务从内核返回, 都会执行这样的路径:
ret_from_xxx() --> check_userspace() --> resume_userspace()
在resume_userspace()中:
ENTRY(resume_userspace)
        DISABLE_INTERRUPTS                # make sure we don't miss an interrupt
                                        # setting need_resched or sigpending
                                        # between sampling and the iret
        movl TI_flags(%ebp), %ecx
        andl $_TIF_WORK_MASK, %ecx        # 信号被设置, 会触发这个条件
        jne work_pending
        jmp restore_all

work_pending:
        testb $_TIF_NEED_RESCHED, %cl  #如果当前任务同时被设置了需要重新调度,
                                       #为保证实时性,会首先执行重新调度,但在除0异常中, 不可能触发这个,
                                       #因为中断始终没有放开
        jz work_notifysig
work_resched:
        call schedule
        DISABLE_INTERRUPTS                # 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
        andl $_TIF_WORK_MASK, %ecx        # is there any work to be done other
                                        # than syscall tracing?
        jz restore_all
        testb $_TIF_NEED_RESCHED, %cl
        jnz work_resched

work_notifysig:                                # deal with pending signals and
                                        # notify-resume requests
        testl $VM_MASK, EFLAGS(%esp)
        movl %esp, %eax
        jne work_notifysig_v86                # returning to kernel-space or
                                        # vm86-space
        xorl %edx, %edx
        call do_notify_resume                #deliver信号到用户空间!!
        jmp resume_userspace_sig        #循环检查是否有信号要deliver

由上可见, do_notify_resume() 才会将信号deliver到用户空间.

如果用户没有挂除0处理函数,执行路径:
i386/kernel/signal.c do_notify_resume() --> do_signal() --> get_signal_to_deliver()
在get_signal_to_deliver()中, 用户空间任务直接被do_group_exit() 杀掉.

如果用户挂了除0的处理函数,执行路径:
i386/kernel/signal.c do_notify_resume() --> do_signal() --> handle_signal()
handle_signal()比较复杂, 他要构建用户栈, 修改eip等寄存器, 这样从内核返回后(entry.s中的restore_all),
会直接调用用户空间的信号处理函数. 信号处理函数执行完后, 通过vdso重新恢复上一条指令, vsyscall_sigreturn.S.

论坛徽章:
0
9 [报告]
发表于 2006-10-11 16:25 |只看该作者
通过代码解释整个过程会比较清楚:

内核代码版本 2.6.19-rc1-git7

你所说的这些同步异常的处理在CPU的中断表中有固定的位置, 如除0, 对应第0项.
内核初始化时调用trap_init()初始化同步异常中断表.

set_trap_gate(0,&divide_error);

当用户空间除0, CPU自动跳转到内核态,并将当前寄存器信息压入内核堆栈, 然后调用entry.s中的divide_error()
我一直认为, entry.s是内核中最重要的一个文件, 完全理解这个文件, 对理解整个内核的运转非常关键.

ENTRY(divide_error)
        RING0_INT_FRAME
        pushl $0                        # no error code
        CFI_ADJUST_CFA_OFFSET 4
        pushl $do_divide_error                //将执行函数压栈
        CFI_ADJUST_CFA_OFFSET 4
        jmp error_code
        CFI_ENDPROC

error_code:
        ......
        call *%edi
        jmp ret_from_exception
        CFI_ENDPROC

所以, 除0错误在do_divide_error()中处理, 执行完后, 执行ret_from_exception().
很明显,对用户空间任务的处理不应该在do_divide_error()中实现, 因为这个函数执行在异常上下文中,优先级非常高.

do_divide_error()
#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \
fastcall void do_##name(struct pt_regs * regs, long error_code) \
{ \
        /* 设置SIGINFO信息, 如果用户空间使用sigaction()挂了自己的信号处理函数,
           且flag & SIGINFO, 可以接收到这些信息 */
        siginfo_t info; \
        info.si_signo = signr; \
        info.si_errno = 0; \
        info.si_code = sicode; \
        info.si_addr = (void __user *)siaddr; \
        /* 执行内核设置的监控函数, 如kprobe, oprofile等 */
        if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) \
                                                == NOTIFY_STOP) \
                return; \
        /* 处理这个异常 */
        do_trap(trapnr, signr, str, 0, regs, error_code, &info); \
}

static void __kprobes do_trap(int trapnr, int signr, char *str, int vm86,
                              struct pt_regs * regs, long error_code,
                              siginfo_t *info)
{
        struct task_struct *tsk = current;
        tsk->thread.error_code = error_code;
        tsk->thread.trap_no = trapnr;

        if (regs->eflags & VM_MASK) {
                if (vm86)
                        goto vm86_trap;
                goto trap_signal;
        }
        /* 很明显,如果内核出现除0的错误,说明内核代码有bug,必须触发oops */
        if (!user_mode(regs))
                goto kernel_trap;

        /* 如果仅仅是用户空间任务除0, 发送信号给那个程序,
           而不需要触发内核bug. 发送信号的过程仅仅是将当前任务的thread_info flag
           添加上sigpending, 并将info信息添加到task->pending列表中 */
        trap_signal: {
                if (info)
                        force_sig_info(signr, info, tsk);
                else
                        force_sig(signr, tsk);
                return; //用户出错, 直接返回
        }

        kernel_trap: {
                if (!fixup_exception(regs))
                        die(str, regs, error_code);
                return;
        }

        vm86_trap: {
                int ret = handle_vm86_trap((struct kernel_vm86_regs *) regs, error_code, trapnr);
                if (ret) goto trap_signal;
                return;
        }
}

所以直接返回到ret_from_exception() (entry.s)

当用户空间任务从内核返回, 都会执行这样的路径:
ret_from_xxx() --> check_userspace() --> resume_userspace()
在resume_userspace()中:
ENTRY(resume_userspace)
        DISABLE_INTERRUPTS                # make sure we don't miss an interrupt
                                        # setting need_resched or sigpending
                                        # between sampling and the iret
        movl TI_flags(%ebp), %ecx
        andl $_TIF_WORK_MASK, %ecx        # 信号被设置, 会触发这个条件
        jne work_pending
        jmp restore_all

work_pending:
        testb $_TIF_NEED_RESCHED, %cl  #如果当前任务同时被设置了需要重新调度,
                                       #为保证实时性,会首先执行重新调度,但在除0异常中%
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP