现在的位置: 首页 > 综合 > 正文

linux早期内核的khttpd服务器–策略污染机制

2013年10月17日 ⁄ 综合 ⁄ 共 7768字 ⁄ 字号 评论关闭

linux内核差一点就走进了windows的为需求而升级的发展道路从而偏离了它原来 的“只提供机制不提供策略”的道路,那就是在2.4内核时期,内核提供了一个叫做khttpd的内核级别的web服务器,当时linux的性能还不是很 好,比如进程调度算法还是O(n)的方式,而且不能有效利用多处理器的优势,线程的实现还是很拙劣,很多方面没有达到posix的高性能指标,因此当时的 开发者可能就钻进了解决性能瓶颈的牛角尖上,鉴于apache服务器很多都运行在linux上,而且web服务是如此的普遍和重要,因此性能瓶颈也就主要 是apache的性能瓶颈,因此面对这个如此“特化”而又如此不能不管的需求,开发者们只好就事论事地将web服务器单独加速,也就是在内核中实现一个 web加速服务,如果按照这条路走下去,linux最终还会在内核实现ftp,甚至任何用户的需求性的策略,还会把图形处理搬到内核,像windows NT做的那样,不过linux到了2.6内核以后重新找到了自己的坐标,因为没有必要再用那种特化需求的方式解决局部的性能瓶颈了,2.6内核的很多机制 都将性能提高到一个新的水平,以web服务器为例,经测试证明2.6下的web服务器完全比内核实现的khttpd加速服务拥有更好的性能,这证明 linux只提供机制是很好的,用户在这种机制上实现的策略性能并不差,这样责任便分来了,如果说用户的程序在用户使了大劲后性能还是不够好,那么就要看 看内核是否可以提供更好的机制。2.6内核的所作所为完全阻止了开发者们再在某一个方面进行优化,2.6内核的进步是整体的,比如新的调度算法,比如 2.6.17中的splice和tee系统调用,这些都使khttpd没有了存在下去的必要。
策略怎么会污染机制呢?想象一下建筑,早先的是混砖式的结构,现在的大厦都成了全框架结构,看看有何不同,住混砖结构房屋的人可能都为装修犯过愁,因为为 了使客厅显得更大,那么就必须打掉一堵墙,可是那堵墙偏偏是承重墙,于是...现在的框架结构就没有这个问题,屋子里除了几个大柱子就没有别的什么了,你 想垒墙就垒墙,想打掉就打掉,这就是机制和策略,框架就是机制,而内部的墙就是策略,如果你把墙垒到了框架里面,势必会影响美观,也会带来不利影响,你将 再次承受承重墙的压力。在操作系统中,尽量不要去通过内核完成一些需求,内核的作用就是为全体进程服务而不是满足用户的个别需求,尽量在用户空间实现需 求,因为这才是需求的实现地,你在内核实现一个你自己进程的需求,如果别的进程不需要这个需求,那人家还是要承受你这个承重墙的压力,只有一个影响全系统 的需求才可以在内核实现,比如杀毒程序,比如防火墙。而khttpd仅仅是一个web加速服务,根本没有资格进入内核独占一地。
在一篇叫做《内核比较:2.4和2.6上的Web服务》的文章中列举了2.6内核的几个新特性:1.超线程支持,多处理支持无论何时都是不错 的;2.NUMA支持;3.线程改进,在内核中增强了posix的线程支持,比如使用NPTL,撤销了管理线程;4.O(1)调度程序,这个就不多说 了;5.I/O的改进,主要是“2.4 中的全局 I/O 请求锁不再使用。在 2.6 中块 I/O 缓冲区(kiobuf)允许 I/O 请求可以比 PAGE_SIZE 大。”;6.异步I/O,这个也不说了;7.这个是比上述6点更高一层次的,就是splice和tee的加入,这俩系统调用可以在用户空间操作内核,其实 就是一个一个内核缓冲的handle。以上7点使khttpd黯然退却,没有必要继续生存。
无论如何,我还是比较欣赏khttpd本身的设计的,很简单,它的代码框架就是:1.启动一些子内核线程在监听80端口(要看内核的khttpd是主 web服务器还是辅web服务器)的套接字上进行accept,并且实时跟踪子内核线程的数量,根据策略进行数量保持,也就是少了创建新的,多了就干 掉;2.子内核线程accept客户浏览器的连接,对于每一个连接进行处理;3.如果内核可以处理,那么直接将结果回写给客户端套接字;4.如果内核处理 不了,那么就呼叫用户端的套接字进行处理,其实就是交给了用户的web服务器。这些流程间涉及到一些技巧,还是看看代码吧:
void NeedSpawn(struct socket *sock)
{       //InitCount表示已经创建的但是还没有接受连接的子线程;AcceptCount表示正在accept过程中的子线程
    while (atomic_read(&InitCount)+atomic_read(&AcceptCount)    {
        kernel_thread(khttpd_child,sock,0);
        atomic_inc(&InitCount);   
    }
}
int khttpd_child(void *TMP)
{
    struct socket *sock,*newsock;
    struct sock *sk;
    int error;
    struct proto_ops *ops;
    char *Buffer;
    size_t BufLen;
    struct http_time  *httptime;
    current->session = 1;
    current->pgrp    = 1;
    current->state    |= TASK_WAKE_ONE;
    sprintf(current->comm,"khttpd - request");
    sock = (struct socket*)TMP;
    sk = sock->sk;   
    sk->nonagle = 1;
    sk->linger  = 1;
...
    error=0;
    Buffer = (char*) get_free_page(GFP_KERNEL);
    httptime = kmalloc(sizeof(struct http_time),GFP_KERNEL);
    atomic_dec(&InitCount);   
...
    memset(httptime,0,sizeof(struct http_time));
    while (1==1)
    {
        if (atomic_read(&AcceptCount)>KHTTPD_MAXACCEPT)  //判断是否拥有太多的子内核线程,其实就是http处理线程
            break;                                     //如果太多就不再启动本次的了
        newsock = sock_alloc();
        newsock->type = sock->type;
        sock->ops->dup(newsock,sock);
        ops = newsock->ops;
        atomic_inc(&AcceptCount);
        error = ops->accept(sock,newsock,0);
        atomic_dec(&AcceptCount);
...
        error=HandleIncommingConnection(newsock,Buffer,&BufLen,httptime); //实际处理客户端请求
        if (error        {
            if (HandleUserSpace(newsock,Buffer,BufLen)                ErrorXXX(newsock,-error);
        }
...   
    }
    free_page((unsigned long)Buffer);
    kfree(httptime);
    return 0;
}
int HandleIncommingConnection(struct socket *sock, char *Buffer, size_t *BufLen,struct http_time *httptime)
{
    struct msghdr        msg;
    struct iovec        iov;
    int             len;
    mm_segment_t        oldfs;
    struct http_header     Head;   
...//数据初始化
    oldfs = get_fs(); set_fs(KERNEL_DS);
    len = sock_recvmsg(sock,&msg,1024,0);
    set_fs(oldfs);
...//出错处理,出错返回500
    /* Check if the request is finished */
    if ((len    {
        int len2;
...//数据初始化
        len2 = sock_recvmsg(sock,&msg,1024,MSG_DONTWAIT);
        set_fs(oldfs);
        interruptible_sleep_on_timeout(&sock->wait,HZ*5);
        if (len2>0)
            len+=len2;
    }
    Buffer[len+1]=0;
    *BufLen     = len;
    ParseHeader(Buffer,len,&Head);   //解析头部
    if (Head.FileName[0]!=0)
    {
        struct file   * filp;
...//出错处理,出错返回403
        filp = filp_open(Head.FileName,00,O_RDONLY); //打开客户端需要的文件
...//出错处理,出错返回404   
        if ((filp!=NULL)&&(filp->f_dentry!=NULL))
        {
            int FileLength,Permission;
            char *Header;
...//权限判定,访问控制功能
            FileLength = (int)(filp->f_dentry->d_inode->i_size);
            if (Head.IMS[0]>0)
            {
                time_t TIME;
                TIME=mimeTime_to_UnixTime(Head.IMS);
...//出错处理,出错返回304
            }
            Header = HTTPHeader(Head.FileName,FileLength,filp->f_dentry->d_inode->i_mtime,httptime);
...//出错处理,出错返回500
            khttp_copy_filp_to_sock(filp,sock,FileLength,Header);
            if (Header)
                kfree(Header);
            fput(filp);
        }
        return 200;       
    }   
    return -404;
}
注 意以上除了成功返回200之外,别的出错码都不是返回给客户端的,而是在khttpd_child经过判断出错后,交给用户空间再处理一次,因为内核只负 责静态页面的推送而不管别的,因此内核处理不了而出错不代表用户空间的web服务器就处理不了,于是交给khttpd_child中调用的 HandleUserSpace:
int HandleUserSpace(struct socket *sock, char *ReceivedSoFar, size_t length)
{
    struct msghdr        msg;
    struct iovec        iov;
    int             len;
    mm_segment_t        oldfs;
    char            *Buffer;
    struct socket        *clientsock;
    /*    struct sockaddr_un    name;*/
    struct sockaddr_in     sin;
    int            error,count=0;
    Buffer = (char*) get_free_page(GFP_KERNEL);
...//出错处理,真的返回500
    error = sock_create(PF_INET,SOCK_STREAM,IPPROTO_TCP,&clientsock);
    if (error        printk("Error during creation of socket; terminating/n");
    sin.sin_family         = AF_INET;
    sin.sin_addr.s_addr  = htonl(INADDR_LOOPBACK);  //注意,向回环接口也就是127.0.0.1接口发送客户端请求,这样写入的请求就会被用户空间的web服务器受到,某种意义上,这个khttpd更像 是一个web代理,在客户端和用户空间真正的web服务器之间的代理。
    sin.sin_port         = htons(KHTTPD_CLIENTPORT); //注意,这个端口一定要在用户空间的web服务器上配置
    error = clientsock->ops->connect(clientsock,(struct sockaddr*)&sin,sizeof(sin),0);  //连接真正的用户空间的web服务器,比如apache
...   
    SendBuffer(clientsock,ReceivedSoFar,length); //把客户端的请求发送真正的用户空间web服务器
    len=1;
    while ((len>0)&&(count    {
...//数据缓冲区初始化
        oldfs = get_fs(); set_fs(KERNEL_DS);
        len = sock_recvmsg(sock,&msg,4096,MSG_DONTWAIT);  //继续从客户端浏览器接受请求
        set_fs(oldfs);
...
        if (len>0)
            SendBuffer(clientsock,Buffer,len);  //如果有请求则继续向用户空间的web服务器转发
        if (len            break; 
...数据缓冲区初始化
        oldfs = get_fs(); set_fs(KERNEL_DS);
        len = sock_recvmsg(clientsock,&msg,4096,MSG_DONTWAIT);  //接受用户空间web服务器的回应
        set_fs(oldfs);
        if (len>0)
            SendBuffer(sock,Buffer,len);  //将回应转发给客户端
...
        count++;
    }
    sock_release(clientsock);
    free_page((unsigned long)Buffer);
    return 0;
}
注 意,这个函数返回了,如果返回值是负数,那么就说明用户空间的web服务器也处理不了,那就是真的发生了错误,于是就要真的进行错误返回了,其实就是调用 ErrorXXX(newsock,-error)函数,此时从HandleIncommingConnection返回的错误码还保留着:
void ErrorXXX(struct socket *sock, int Error)
{
    if (Error == 404)
    {   
        Error404(sock);
        return;
    }
...
}
void Error404(struct socket *sock)
{
     char Message[]=
    "HTTP/1.0 404 File Not Found/r/nServer: KHTTPD/0.0/r/nContent-type: text/html/r/n/r/n

FILE NOT FOUND!!";
    SendBuffer(sock,Message,sizeof(Message));  //真正向客户端返回错误码。
}
以 上就是khttpd的大致框架了,很有意思的就是它在内核中的作用就是客户端和用户空间web服务器的代理,虽然它作为一个以加速为目的的web服务来说 已经不再需要,但是我倒是觉得它可以作为一个很好的过滤系统经过改进后继续存在,就像netfilter一样,但是netfilter拦截五元素很方便, 虽然也可以解析数据包的具体内容,但是那很耗时,而且大多数的运行netfilter的接收过滤的上下文都是软中断上下文,因此更加不确定,更不适合做像 从sk_buff中解析用户数据并且判定那些事,因此khttpd的框架可以作为很好的用户数据过滤框架在内核中存在,新的框架不再仅仅监听端口为80的 web服务,而是可以监听所有的端口,只要用户建立一个服务器套接字,那么都可以选择将此套接字加入到过滤列表中,可以通过ioctl调用很容易做到这一 点。然后可以在内核配置一张过滤表,存放每一个端口的过滤策略,这个表的格式可以参照iptables的结构设计,如果通过的话,可以将请求直接转发给用 户空间的套接字,如果没有通过验证,那么记录审计信息后返回错误。如此的设计可能没有必要,因为linux内核可能压根就不想管像用户数据那么具体的事 情,如果你非要往内核加入这个框架的话,那么过滤的策略的设置将是一场噩梦,毕竟用户数据是一个很庞大很不具体的概念,难道过滤敏感词汇吗?如果是的话, 估计敏感词汇都存在内核瞬间内存就满了,众口难调啊,即使这个想法不被应用了内核主线,那么很可能可以满足某个老板的需求,我们公司就在做一个网页防篡改 系统,我想我可以借鉴一下这个khttpd框架,然后改改,实现我们linux版的网页防篡改系统。
     linux从来不在内核提供个别应用才会用到的策略,不允许策略污染机制,呵呵,即使在2.4内核时策略(khttpd)真的污染了那么一点点机制,然而 随着2.6内核的放出,仅有的污点还是被驱赶了,因为linux内核靠更好的机制就可以给用户空间提供更好的舞台,而想在这个舞台演得好需要成为好的舞者,这难道给用户编程带来了挑战,其实程序员需要的不是什么技巧,而是unix编程的哲学思想。

抱歉!评论已关闭.