当前位置: 首页 > 科技观察

操作系统和web服务器的事情

时间:2023-03-17 12:25:11 科技观察

操作系统老大又开始了进程,操作系统老大感叹,毕竟自己肩上多了一份责任。令人恼火的是,新来的人都是无知的,几乎是一张白纸。一些老实人会按照自己的规矩做事,一些多刺的人喜欢提问,时不时的就想搞非法访问,想访问其他进程的地址空间,甚至想访问代码和内核的数据!这个时候只能杀了他祭天,留下一个coredumpbody给coder分析。图片来自宝途网规则很重要!想到这里,老大又看了一眼自己的内核空间。这台机器只有可怜的4G内存,0-3G是每个进程共享的,他独占了3G-4G的内存空间。新启动的进程是一个web服务器,他自称小W,是一个喜欢提问的家伙。他的第一个问题是:“老大,你怎么不混大众,反而要独占空间?”“这是给您的?”“为了我们?”“电脑的硬件资源是有限的,硬盘、内存、网卡、键盘、鼠标、时钟……如果让这些进程随意访问,大家争相抢夺,岂不是乱七八糟?”“再说了,那些底层硬件和驱动操作极其麻烦,每个进程都忍心写那些‘恶心’的代码?”老大连续三个问题震耳欲聋,小W顿时气喘吁吁。.这个抽象层下面是我的内核,就是我的代码和数据,所以我要一个人生活,不能和你们混在一起。”系统调用“那我想访问硬盘上的一个文件,怎么办?”小W问道。“很简单。我的抽象层有对外的接口,叫做系统调用,比如read、open、close等,可以打开一个文件,读取它的内容,读取完就关闭。”“听起来像是函数调用!”“没错,就是函数调用,但是和你内部的函数调用有着本质的区别。这个系统调用会让你从用户态切换到内核态,也就是切换到我的Kernel代码!”小W茫然的点点头,一副听懂的样子,估计是听不懂,也听不懂,老大操作系统心想,系统调用的复杂程度远超自己的想象。首先,所有Linux系统调用的参数都是通过寄存器而不是栈来传递的。按照惯例,寄存器EAX保存的是系统调用(例如,1表示exit系统调用,2表示fork,3表示read...),寄存器EBX、ECX、EDX、ESI、EDI最多可以包含6个任意参数。例如:write(1,"hello",5);这是一个系统调用,就是输出一个字符串到stdout(console),在运行时,必须设置寄存器:EAX=4(4表示系统调用的编号)EBX=1(1表示stdout)ECX=字符串的地址EDX=字符串的长度然后调用int0x80系统中断,这进入安装内核后,我会取出EAX,从一个内核表中找到4号对应的系统调用句柄来执行。对了,我还需要把CPU的特权级从3设置为0,也就是内核态。看,看,我容易吗?我!操作系统感觉有点难过。读写这时候,小W探出头来,兴奋地说:“老板您好,有客户要访问我们硬盘上的文件,我要读取。然后通过socket发送出去。”需要系统调用吗?”“那是肯定的,访问文件系统必须经过我,访问socket必须经过我,没有系统调用怎么行?除了open和close,还需要两个关键的系统调用:”//从文件中读取长度为len的内容(以fd表示)放入缓冲区read(fd,buffer,len);//将缓冲区中长度为len的内容写入socket(以sockfd表示)write(sockfd,buffer,len);(注:read和write应该是sys_read和sys_write的“包装”函数,我们这里简化一下,认为是直接函数调用。)“好的!”小W做了一些准备,然后开始看书,开心的等待着数据的到来。操作系统收到read调用,落入内核,正式进入内核态,然后毫不客气地暂停小W的执行,让他进入阻塞队列(假设小W只有一个线程)。小W表示不满:“你为什么不让我跑?”“读取文件太慢了,先休息一下,数据来了会通知你。”老大使用DMA(DirectMemoryAccess)将文件的数据从硬盘复制到内核的缓冲区,再复制到用户的缓冲区。read调用完成后,回到用户态,小W可以继续执行。小W想通过socket发送数据,于是又发出了write调用,再次落入内核,进入内核态。boss将数据从userbuffer复制到socketbuffer,write调用返回,返回用户态。小W问:“这次这么快就回来了?数据发送了吗?”老大说:“你不用管,网卡驱动会适时发送,这是一个异步操作。”小W画了一张图,试图理解整个过程。等他画完图,不由啧了一声:“啧啧,就这么两个简单的系统调用,成本竟然这么高。”(1)需要两次进入内核态,两次返回。(2)数据实际被复制了3次。硬盘-->内核缓冲区-->用户缓冲区-->套接字缓冲区。浪费了。老大说:“你看,系统调用的开销很大,以后少调用。”小W说:“我觉得你的内核保护了硬件,但是效率很低,能不能优化一下?一会儿,省去用户态<-->核心态之间的数据拷贝?这太浪费了!”sendfile老大笑道:“我早就预料到这个水平了,我这里还有一个系统调用叫Sendfile,你可以试试看,通过这个系统调用,可以直接把文件内容发送到插座。"sendfile(socket,file,len);小W一看不错,只需要调用一次sendfile进入内核态就可以了,老大可以把数据从硬盘拷贝到内核缓冲区,然后直接copy到socketbuffer.area,完全不用干预,直接用就好了!但是转念一想,是不是需要从内核buffer复制到socketbuffer呢?网卡驱动直接不行吗从内核缓冲区中读取数据?老大似乎看穿了小W的心思,说道:“我知道你在想什么,你放心,我已经优化过了,我不会从内核缓冲区中复制数据到socketbuffer,反之,我只会复制一些位置和数据长度,等信息复制完就好了。NIC驱动程序可以直接从内核缓冲区读取数据。》小W这才松了口气,开始使用这个sendfile方式,果然,性能有了很大的提升!这其实就是所谓的零拷贝技术,从内核的角度来说,除了读取文件,没有额外的拷贝.零拷贝技术减少上下文切换,避免数据在用户态和核心态之间不断传输,不需要CPU参与数据复制,提高系统性能,零拷贝技术在web服务器中被引入如ngnix、apache【本文为专栏作家“刘鑫”原创稿件,转载请通过作者微信获得授权公众号coderising】点此查看作者更多好文