用户空间与内核空间
现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换,这种切换是由操作系统来完成的。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
保存处理机上下文,包括程序计数器和其他寄存器。
更新PCB信息。
把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
选择另一个进程执行,并更新其PCB。
更新内存管理的数据结构。
恢复处理机上下文。
注:总而言之就是很耗资源的
进程的阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
阻塞io,非阻塞io,io多路复用:
#########################################blocking IO
# import socket
#
# sk=socket.socket()
#
# sk.bind(("127.0.0.1",8080))
#
# sk.listen(5)
#
# while 1:
# conn,addr=sk.accept()
#
# while 1:
# conn.send("hello client".encode("utf8"))
# data=conn.recv(1024)
# print(data.decode("utf8"))
########################################################nonblocking IO
# import time
# import socket
# sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# sk.bind(('127.0.0.1',6667))
# sk.listen(5)
# sk.setblocking(False) #默认为阻塞io,后面加个(false)则为非阻塞io:要不接收数据往下走
#要不接收错误继续向下走
# print ('waiting client connection .......')
# while True:
# try:
#
# connection,address = sk.accept() # 进程主动轮询
# print("+++",address)
# client_messge = connection.recv(1024)
# print(str(client_messge,'utf8'))
# connection.close()
# except Exception as e:
# print (e)
# time.sleep(4)
########################################################io多路复用
# import socket
# import select #水平触发 发送系统调用的方式
# sk=socket.socket()
# sk.bind(("127.0.0.1",9904))
# sk.listen(5)
# inp=[sk,]
# while True:
#
# r,w,e=select.select(inp,[],[],5) #(输入列表,输出列表,错误列表,几秒循环)
#第二个select方法监听sk,无人接听就阻塞,有人往下走
#
# for i in r:#[sk,]
# conn,add=i.accept()
# print(conn)#套接字对象
# print("hello")
# inp.append(conn)
# print('>>>>>>')
#***********************server.py###################
##单线程下实现并发
# import socket
# import select
# sk=socket.socket()
# sk.bind(("127.0.0.1",8801))
# sk.listen(5)
# inputs=[sk,]
# while True:
# r,w,e=select.select(inputs,[],[],5)
#
# for obj in r:#[sk,]
# if obj==sk:
# conn,add=obj.accept()
# print(conn)##相当于客户端sk发送的消息
# inputs.append(conn)
# else:
# data_byte=obj.recv(1024)
# print(str(data_byte,'utf8'))
# inp=input('回答%s号客户>>>'%inputs.index(obj))
# obj.sendall(bytes(inp,'utf8'))
#
# print('>>',r)
阻塞io只发生了一次系统调用,只能同步,等待数据
非阻塞io发送了太多的系统调用,但是不能及时的接受消息(设置sleep导致)
io多路复用调用了2次系统调用(监听用户;接受消息),但是却能单线程下实现并发
多路复用几个类:epoll>poll>select(按速度排)
epoll:一个客户端完成会自动发完成信息
select:一个客户端完成系统会挨个询问,时间轮询需要时间太久
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
比如100个连接,有两个活跃了,epoll会告诉用户这两个两个活跃了,直接取就ok了,而select是循环一遍。