linux下的io模型

1、用户态和内核态

因为操作系统的资源是有限的,如果访问资源的操作过多,必然会消耗过多的资源,而且如果不对这些操作加以区分,很可能造成资源访问的冲突。所以,为了减少有限资源的访问和使用冲突,Unix/Linux的设计哲学之一就是:对不同的操作赋予不同的执行等级,就是所谓特权的概念。简单说就是有多大能力做多大的事,与系统相关的一些特别关键的操作必须由最高特权的程序来完成。Intel的X86架构的CPU提供了0到3四个特权级,数字越小,特权越高,Linux操作系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。运行于用户态的进程可以执行的操作和访问的资源都会受到极大的限制,而运行在内核态的进程则可以执行任何操作并且在资源的使用上没有限制。很多程序开始时运行于用户态,但在执行的过程中,一些操作需要在内核权限下才能执行,这就涉及到一个从用户态切换到内核态的过程

用户态和内核态的转换

a、系统调用

系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断

b、异常

当CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,典型的如缺页异常。

c、外围设备的中断

当外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换

总结:

内核态可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

2、同步和异步的区别

同步io需要主动读写数据,在读写过程中会阻塞,异步io只需要读写完成的通知,读写操作由内核态完成

3、关于异步阻塞和异步非阻塞

异步阻塞,用户进程(线程)发起读写操作,在原地等待内核态返回读写完成的结果。此时阻塞整个进程
异步非阻塞,用户进程发起读写操作后,不在原地的等待内核态完成读写操作的结果。可以先去干点别的事

4、同步的几种io模型

以read为例

1、同步阻塞

1、进程发起read,进行recvfrom系统调用;

2、内核态准备数据

3、同时进程阻塞

4、阻塞直到数据从内核态copy到用户态,内核返回结果,进程解除阻塞。

总结:准备数据和数据copy两个阶段都阻塞。

image

2、同步非阻塞

1、进程发起read操作,内核数据没有准备好,立刻返回一个error.

2、用户进程收到error,知道数据没有准备好,于是再次发起read操作,直到数据准备好。

3、用户进程收到数据准备好的信号,发送system call,copy数据,此时进程开始阻塞

4、数据copy完成,返回给用户进程解除阻塞。

总结:数据准备阶段,用户进程不断询问内核数据准备好了没,数据copy阶段进程阻塞

image

3、io多路复用

1、用户进程调用select,进程阻塞,同时内核会监听所有select负责的socket.

2、当任何一个socket的数据准备好,select就会返回。

3、用户进程调用read操作,将数据从内核copy到用户进程。

总结:I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回

如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用多线程 + 阻塞 IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
image

select、poll、epoll的区别:

select 是不断轮询去监听的socket,socket个数有限制,一般为1024个;

poll还是采用轮询方式去监听,只不过没有个数限制。

epoll并不用采用轮询方式去监听,而是当socket有变化时通过回调方式主动告知用户进程。

select支持多平台,epoll只支持linux平台。

select实现ftp

import select
import socket

server = socket.socket()
server.bind(('127.0.0.1', 9991))
server.listen(10)
server.setblocking(False)

r_list = [server, ]
w_list = []
w_data = {}

while True:
    rl, wl, xl = select.select(r_list, w_list, [], 0.5)
    print(wl)
    for sock in rl:
        if sock == server:
                conn, addr = server.accept()
                r_list.append(conn)
        else:
            try:
                data = sock.recv(1024).decode()
                if not data:
                    sock.close()
                    r_list.remove(sock)
                    continue
                w_list.append(sock)
                w_data[sock] = data.upper().encode()
    
            except Exception as e:
                print(e)
                sock.close()
                r_list.remove(sock)

selectors实现ftp

import selectors
import socket


def accept(obj, mask):
    conn,addr = obj.accept()
    sel.register(conn, selectors.EVENT_READ, read)
    

def read(obj,mask):
  
    try:
        data = obj.recv(1024).decode()
        if not data:
            sel.unregister(obj)
            obj.close()
            return
        print(data)
        obj.send(data.upper().encode())
    except Exception as e:
        print(e)
        obj.close()
        sel.unregister(obj)
    

server = socket.socket()
server.bind(('127.0.0.1', 9990))
server.listen(10)
server.setblocking(False)
sel = selectors.DefaultSelector()
sel.register(server, selectors.EVENT_READ, accept)
while True:
    events = sel.select()
    for obj, mask in events:
        callback = obj.data
        callback(obj.fileobj, mask)

大量参考:
http://www.cnblogs.com/zingp/p/6863170.html
https://www.cnblogs.com/bakari/p/5520860.html

posted @ 2018-04-05 18:36  Jason_lincoln  阅读(111)  评论(0编辑  收藏  举报