If you are smart enough, you will understand that I'm not a simple boy!

2009年9月2日星期三

在内核下使用异常处理的必要性

为何要在内核下使用_try和_catch呢?

首先我们来看一段代码

//创建进程
int sys_process_create( const char* cmdline, const char** var, void* cinfo, uint* retp )
{
  process_t* p;
  int len;
  p = kmalloc( sizeof(process_t) );
  len = strlen( cmdline );
  memcpy( p->cmdline, cmdline, len+1 );
  ...
}
创建进程的时候,需要拷贝字符串cmdline。
那么要拷贝这个串,首先要得到长度。一般来说,通过库函数strlen可以最快得到字符串的长度。

但是如果cmdline指向一个不可用的地址,例如0xF0000000,那么就会触发异常。内核态下发生异常是不可能由用户态捕捉处理的,也只能结束线程。但是别忘了,上面代码中的p指向的内核内存还没有释放,这就导致了内存泄漏。

但是我们不会写一个safe_strlen来不断检查内存是否是用户内存吧。这样严重影响效率。

那么Linux是如何解决这个问题的呢?
Linux内核下复制字符串采用copy_strings来完成,我们先看一下代码:

int copy_strings(int argc,char ** argv, struct linux_binprm *bprm)
{
        struct page *kmapped_page = NULL;
        char *kaddr = NULL;
        int ret;

        while (argc-- > 0) {
                char *str;
                int len;
                unsigned long pos;

                if (get_user(str, argv+argc) ||
                                !(len = strnlen_user(str, bprm->p))) {
                        ret = -EFAULT;
                        goto out;
                }

可见,linux是使用一个名为strnlen_user的函数。
strnlen_user是用汇编写的,其中代码如下
long direct_strnlen_user(const char __user *s, long n)
{
        unsigned long mask = -__addr_ok(s);
        unsigned long res, tmp;

        might_sleep();

        __asm__ __volatile__(
                "       testl %0, %0\n"
                "       jz 3f\n"
                "       andl %0,%%ecx\n"
                "0:     repne; scasb\n"
                "       setne %%al\n"
                "       subl %%ecx,%0\n"
                "       addl %0,%%eax\n"
                "1:\n"
                ".section .fixup,\"ax\"\n"
                "2:     xorl %%eax,%%eax\n"
                "       jmp 1b\n"
                "3:     movb $1,%%al\n"
                "       jmp 1b\n"
                ".previous\n"
                ".section __ex_table,\"a\"\n"
                "       .align 4\n"
                "       .long 0b,2b\n"
                ".previous"
                :"=r" (n), "=D" (s), "=a" (res), "=c" (tmp)
                :"0" (n), "1" (s), "2" (0), "3" (mask)
                :"cc");
        return res & mask;
}

这里设置了一个异常处理用的table,即一旦发生异常的时候,strncpy就返回0值。这样是很好的,但拷贝函数不只是有这个,还有memcpy,strcpy等。而且有时候需要对非法访问内存进行一定的处理。因此如果在内核下可以使用_try和_catch处理发生的异常,能够容易把握代码结构和灵活响应。

使用异常处理机制比读写前检查效率更高。因为一般来说,100多次系统调用,可能有一次或者最多一次的参数是不正确的,那么对于这99次读写检查,cpu就做了很多无用功了。

当然,_try和_catch要做得处理速度相当好,例如进入_try时,只需要把异常处理句柄放进异常处理链表,然后马上继续执行系统调用的代码即可。

目前没有研究过Windows如何处理这个问题。但我发觉不少朋友自己写的OS不是不作检查,就是每次都对读写范围进行有效性检查,前者安全性不好,后者效率低下,代码繁琐。

没有评论:

发表评论

关注者