在Linux内核中实现WinSock2的AcceptEx机制的方法

文档序号:6341286阅读:285来源:国知局
专利名称:在Linux内核中实现WinSock2的AcceptEx机制的方法
技术领域
本发明涉及计算机操作系统领域,尤其是一种在Linux内核中实现WinS0ck2的 Acc印tEx机制的方法。
背景技术
操作系统相当于计算机的灵魂,任何计算机都离不开操作系统,同时也都受操作 系统的限制,所有的应用软件都是基于某种特定操作系统的,都只能在这种特定的操作系 统上 运行,搬到别的操作系统上就不能运行。因此,Windows应用只能在Windows操作系统 上运行,而Linux应用也只能在Linux操作系统上运行。但是如果能让Windows应用软件 直接在Linux操作系统上运行,那么用户就有可能摆脱对Windows操作系统的依赖、而改用 Linux操作系统,这显然是很有意义的。操作系统的核心称为“内核”,但内核并不就等于操作系统。除内核以外,在应用软 件与内核之间通常还有些作为中间件的动态连接库(DLL)。根据这些DLL的来源和功能的 不同,有些是属于用户层的,有些则是属于系统层的。系统层的DLL —般被认为是操作系统 的一部分,这一点在Windows操作系统上表现得尤为突出。在Windows操作系统上,由诸多 系统层DLL合在一起向应用软件提供一个“应用编程界面”、即API,这是一组函数的定义, Windows应用软件可以在其程序中调用这些API函数。只要Windows应用软件能正常调用 这些函数,它就“以为”自己是在Windows操作系统上运行。显然,要让Windows应用软件 能在Linux操作系统上运行,就得在Linux操作系统上为其提供一个相同的API。在Windows操作系统为应用软件提供的API中,有些函数是用来提供Socket服务 的,主要用于网络操作。Socket的概念和技术最初是在Unix操作系统中发展起来的,后来 Linux继承了下来。微软则把这技术拿来为其所用,成为WinSock,这也是Windows操作系统 为应用软件所提供API的一部分。一般而言,WinSock API函数与Linux中有关Socket的库 函数或系统调用有着相同的来源,因此很容易把这些API函数嫁接到Linux中与其相对应 的函数或系统调用上。但是,微软在把Socket技术拿来为其所用以后,在这基础上也作了 些改进和提高,在WinSock的第二版、即WinS0ck2中提供了两个新的函数,就是Acc印tEx () 和TransmitFileO。这两个函数主要面对服务器应用、特别是高负荷的Web服务器应用,可 以在一定程度上提高服务器的效率。相比之下,Linux中却没有这两个函数的对应物,所以,使Linux支持Acc^ptExO和TransmitFile ()这两个函数有着双重的意义。 首先这是为使Windows应用可以在Linux操作系统上运行这个目标所需要的。当然Windows 应用并非都会用到这两个函数,但是用到这两个函数的也不在少数(特别是服务器应用)。 如果Linux不能提供这样的对应物,这部分Windows应用就不能在Linux操作系统上运行。 其次是这两个函数确实比Linux所提供的原始Socket函数在性能上有所提高,如果Linux 不能提供这样的对应物,在这一方面的性能上就会输给Windows。WinSock2的Acc印tEx ()是对原始Socket函数或系统调用accept ()的改进和提 高。在原始的Socket编程模式中、也就是Linux的Socket编程模式中,服务器应用软件的操作模式大体上是这样的1.通过socket ()(在Linux上是系统调用,在Windows上是API函数,下同)创建一个 Socket。2.通过IistenO睡眠等待客户端前来连接,一旦接收到客户端的连接请求便返 回,让服务端决定是否接受。3.如果决定接受连接,服务端便通过acc印t()接受并建立起与客户端的网络连 接。在此过程中acc印t()会新创建、并返回一个Socket(常称为“连接端口” ),作为与客户 端的连接端点,而原先的那个Socket(常称为“收听端口”)则又用来通过IistenO收听、 等待别的连接,因为很可能接连不断地有连接请求到来。通常,在从acc印t()返回以后会 创建一个新的线程,并把由acc印t()创建并返回的那个Socket交给这个新线程。而原来 的那个主线程,则回到上面的第2步。4.新线程与客户端通信并为其提供服务,直至完成了客户端所要求的服务。5.然后就断开与客户端的网络连接,并关闭用作连接端口的Socket。6.退出线程的运行,本线程消亡;或者等待主线程交给新的连接,然后为新的客 户端服务。仔细分析这个过程可以发现,每次都在第3步中由acc印t()新创建一个Socket, 而在第5步中加以关闭,实在是一种浪费。如果在第5步中只是断开连接,而不关闭,再在 第3步中用来取代新创建的Socket,那就可以在每个这样的周期中都省下一次创建并关闭 Socket所需的时间,须知一台服务器每天可能要重复这样的周期几百万次甚至几千万次。 正是因为这样,微软才开发了 Acc印tEx()。在这个API函数所实现的机制中,当要接受一个 客户端连接时,应用软件交下一个已经创建的Socket,以此作为新建连接的端点,而不是新 创建一个。在Linux内核上,要接受并建立一个客户端连接的唯一手段就是系统调用 accept (),这个系统调用在建立客户端连接会新建一个Socket。这意味着,要在Linux上实 现Acc^ptExO的机制,并没有将其嫁接到acc印t()上面的简单方法。为进一步说明这一点,下面看一下系统调用acc印t()的调用界面、即其原型int accept (int fd, struct sockaddr 氺upeer—sockaddr, int氺upeer—addrlen);这里,accept ()的第一个参数fd是用于IistenO的“收听端口”的那个Socket 的文件号;第二个参数upeerjockaddr指向用户空间的一个缓冲区,用来返回连接对端的 Socket地址,一般是其IP地址加端口号;第三个参数指向用户空间的一个整数,说明缓冲 区的大小或对端Socket地址的长度。可见,在这个界面只传下一个文件号,就是“收听端口 ”的文件号,而根本就不允 许把一个已经创建的Socket的文件号往下传。而acc印t()内部,则又总是新创建一个 Socket0有人主张在acc印t()的上面、即在用户空间解决这个问题,其方法是1.假定应用软件交下用作“连接端口”的Socket的文件号为“连接端口 1” ;2.调用acc印t(),就让它新创建一个“连接端口”,假设其文件号为“连接端口 2”;3.然后将“连接端口 1”所代表的那个Socket关闭,但设法将文件号“连接端口 1” 改过来代表新创建的Socket,并释放文件号“连接端口 2”。
这里的第三步可以通过系统调用dup2()实现,也可以通过别的办法实现。但是,这个办法只是在表面上解决了问题,实际上却是更降低了性能、而不是改善 了性能,所以不是一个好办法。相比之下,本发明所述的方法才是从根本上解决了问题。

发明内容
本发明的目的是提供一种在Linux内核中实现AcceptEx ()的方法,使Linux操 作系统支持Acc^ptEx (),从而既能支持用到这个函数的Windows应用在Linux操作系统上 直接运行,又能使Linux操作系统在网络服务器方面的性能有所提高。对Linux内核源 代码的深入研究表明,在系统调用acc印t ()的内部实现中,真正 用来实现接收并建立连接的操作其实是由底层协议的accept函数提供的,对于TCP/IP这 个底层函数是inet_aCC印t ()。问题在于,不管是什么样的底层accept函数,实际上底层 accept函数都不创建新的Socket,都是由(系统调用acc印t ()的)上层创建了之后再传 下去的。这样,只要上层不创建新的Socket,而改成把来自应用软件的Socket传给底层协 议的accept函数,就可以很好地解决这个问题。这就是本发明采用的方法。本发明采用的方法是1、为Linux内核增加一个新的系统调用acc^ptjnto 0,这个系统调用比 accept ()多一个参数,把给定用作“连接端口”的Socket文件号传入内核。2、在系统调用acc^ptjnto ()中不创建新的Socket,而改成根据作为参数从应用 程序传下来用作“连接端口”的Socket文件号找到其socket数据结构,并将其传给底层协 议的accept函数。本发明有益的效果是本发明提供了一种方法,使得可以在Linux内核中实现 AcceptEx ()机制,既使用到这个机制的Windows服务器应用有可能在Linux上运行,也提高 了服务器的效率。


附图1是Linux系统调用accept ()的流程。附图2是新增系统调用acc^ptjnto ()的流程。
具体实施例方式下面结合附图和实施例对本发明作进一步说明附图1是Linux系统调用acc印t()的流程。应用程序传给这个系统调用的主要 参数只是代表着“收听端口”的文件号,而没有代表着“连接端口”的文件号。这个系统调用 先作一些参数合法性的检查,然后根据文件号找到“收听端口”的socket数据结构。因为 需要一个Socket作为新建连接的端点,所以就新创建一个Socket,以及代表着这个Socket 的file结构和文件号。接着,在实施了安全性检查之后,调用由底层协议的accept函数。 调用底层协议的acc印t函数时,传下去的是两个socket数据结构,分别代表着“收听端口” 和新建的“连接端口 ”。底层协议的accept函数则实现具体的接收/建立连接并将“连接端 口,,用作连接端点的功能。最后,在本进程的打开文件表中建立起代表着新建“连接端口,, 的文件号和file数据结构之间的关联。从这个流程可以看出,底层协议的accept函数本来并不创建Socket,是系统调用acc印t()的上层创建了之后塞给它的。如果改成把来自应 用程序用作“连接端口”的Socket传给底层协议的accept函数,就可以实现与Acc^ptExO 相同的机制,提供相同的功能。
附图2是按本发明所述方法新增的系统调用acc^ptjnto ()的程序流程。这个系 统调用比acc印t()增加了一个参数,就是代表着用作“连接端口”的Socket的文件号。由 于已经有了“连接端口”,这个系统调用不创建新的Socket,而只是把两个Socket数据结构 都传给底层协议的accept函数。此外,由于用作“连接端口”的Socket原来就已存在,原来 就有文件号,就不需要再在打开文件表中建立文件号和file数据结构之间的关联。这样, accept_into()的流程比acc印t()的简单,却实现了 Acc印tEx()的功能。本方法的具体实施涉及对Linux内核源代码的修改,下面分两个方面说明本方法 的一个实施例。注意同一个方法可以有多种不同的实施,这里所提供的只是其中之一。1、为Linux内核增加一个新的系统调用acc^pt_into ()为Linux增添系统调用的方法很多,例如通过针对/dev下面某个设备节点的系 统调用ioctlO就可以用来为内核增添实质的额外系统调用,或者通过/proc下面的节点 也可以达到同样的目的,这样的好处是无需为新增的系统调用分配系统调用号,以免与主 流Linux版本日后将同样的系统调用号另作它用而发生冲突。当然,“正宗”的方法还是为 其分配一个系统调用号并在系统调用跳转表中占一个单元。作为实施例,这里用的是修改 系统调用跳转表的方法,如果要用利用/proc或ioctlO的方法则可参考“Linux Device Drivers,,一书。在Linux 内核源代码中,文件 arch\x86\include\asm\unistd_32. h 中定义了适用 于x86系列32位CPU的各种系统调用的调用号。目前,至2. 6. 34版为止,已经分配的最 大调用号为337。作为一个实施例,我们暂且分配360作为acc^ptjntoO的系统调用号。 当然,正规的做法是与Linux的守护人联系并提出请求,由其分配一个系统调用号。为将 accept_into()的系统调用号定义为360、并在系统调用跳转表中占据相应的位置,需要在 这个.h文件中把原来“#define NR_syscalls 338”中的338改成361,并在这一行的前面 增添一行代码:#define_NR_accept_into360再在文件include\asm-generic\unistd. h 中增添一行代码_SYSCALL (_NR_ accept_into, sys_accept_into)这样,经过编译以后就会在系统调用跳转表中下标为350的单元中插入指向函数 sys_accept_into()的指针。内核中凡用来实现系统调用的函数都在前面加上前缀sys_。
然后在源文件net\socket. c中加上函数sys_accept_into ()的代码
SYSCALL_DEFINE4(accept_into, int, listen_fd, int, conn_fd,
struct sockaddr _user *,upeer—sockaddr, int __user *, upeer_addrlen)
{
return do_accept_into(listen_fd, conn—fd,upeer—sockaddr, upeer_addrlen, 0);
ι
编译Linux内核时这个函数的函数名会自动变成sys_acci5pt_int0 (),并带有4个 参数。2、内核函数do_acc^pt_into()的内部实现内核函数do_acc^pt_into()的代码可以从复制sys_acc印t4 ()的代码开始,再加 以修改。修改的内容主要有1、把前者的第一个调用参数fd改名为liSten_fd,并在第一个参数后面增添一个 参数 conn_fcL2、把创建newsock、为newsock分配newfd和newfile、以及最后通过fd_ install ()将newfd和newfile填入本进程打开文件表的语句删去。3、把代码中剩下的newfd全都改成conn_fd。下面是经这样修改之后的代码
int do_accept_into (int listen_fd, int conn_fd,
struct sockaddr —user * upeer sockaddr,
int 一user *upeer_addrlen, int flags) { 一 "
struct socket *listen_sock, *conn_sock;
int err, len,fput—needed;
struct sockaddr_storage address;if (flags & (SOCK—CLOEXEC | SOCK_NONBLOCK)) return -EINVAL;
if (SOCK—NONBLOCK != O—NONBLOCK && (flags & SOCK_NONBLOCK)) flags = (flags & SOCK_NONBLOCK) | O—NONBLOCK;
//根据文件号找到收听端口的socket数据结构 listen_sock = sockfd_lookup—light(listen_fd,&err,&fput_needed); if (!listen_sock) goto out;
//根据文件号找到连接端口的socke t数据结构 conn_sock = sockfd_lookup_light(cinn_fd, &err, &^t_needed); if (!conn—sock) goto out;
conn_sock->type 二 listen_sock->type; conn—sock->ops = listen—sock->ops;
//实施安全性检查
err = security_socket—accept(listen_sock, conn—sock); if (err)
goto out;
//调用底层协议的accept函数
err = listen_sock->ops->accept(listen_sock, con—sock, listen_sock->file->f_flags); if (err < 0) goto out;
if (upeer一sockaddr) {
//获对端的地址
if (conn_sock->ops->getname(newsock, (struct sockaddr *)&address5 &len, 2) < 0) { err 二 -ECONNABORTED; goto out;
}
//将对端的地址复制到用户空间 err = move—addr—to_user((struct sockaddr *)&address’ len, upeer—sockaddr, upeer—addrlen);
if(err<0) goto out;
}
err 二 conn—fd;
out:
return err;
} 经过这样两方面的修改和扩充,Linux内核就可以通过系统调用acc印t_into () 实现Acc^ptEx机制了。至于怎样在用户空间提供一个库函数Acc^ptEx(),再在这库函数中 调用acc印t_into(),则是一般程序员都不会感到困难的事。
权利要求
1. 一种在Linux内核中实现WinSock2的Acc印tEx机制的方法,其特征在于 1. 1)为Linux内核增加一个新的系统调用acc印t_into(),这个系统调用比acc印t () 多一个参数,把给定用作“连接端口”的Socket文件号传入内核;1.2)在系统调用ac^ptjntoO中不创建新的Socket,而改成根据作为参数从应用 程序传下来用作“连接端口”的Socket文件号找到其socket数据结构,并传给底层协议的 accept 函数。
全文摘要
本发明涉及一种在Linux内核中实现WinSock2的AcceptEx机制的方法,1)为Linux内核增加一个新的系统调用accept_into(),这个系统调用比accept()多一个参数,把给定用作“连接端口”的Socket文件号传入内核。2)在系统调用accept_into()中不创建新的Socket,而改成根据作为参数从应用程序传下来用作“连接端口”的Socket文件号找到其socket数据结构,并将其传给底层协议的accept函数。本发明有益的效果是本发明提供了一种方法,使得可以在Linux内核中实现AcceptEx()机制,既使用到这个机制的Windows服务器应用有可能在Linux上运行,也提高了服务器的效率。
文档编号G06F9/45GK102073531SQ20101062229
公开日2011年5月25日 申请日期2010年12月29日 优先权日2010年12月29日
发明者毛德操, 王承志, 金涛 申请人:浙大网新科技股份有限公司
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1