fork的各种门道
COW(copy on write)子进程完全复制父进程的所有资源存在巨大的浪费
父子进程以只读方式共享同一个地址空间,只有在需要写入的时候才进行资源的复制
fork的实际开销变为复制父进程的页表以及给子进程创建唯一的进程描述符
cow的单位是页,子进程对页表中的某一页写入,只复制该页
复制什么,不复制什么复制内存内核栈,thread_info,task_struct
打开的文件,文件系统信息,信号处理函数,进程地址空间和命名空间
关于fd复制的细节每个进程独立管理自己打开文件的fds,fd是针对每一个进程来说的,非整个系统,在同一个进程中,同一个fd可以复用,fd数值一般连续increase
同一个文件,被不同进程打开,在不同进程中有不同的fd,但这些不同的fd会对应到系统相同的v节点
示意图如下
fork要注意什么问题多线程程序fork可能死锁linux地址空间分为内核空间和用户空间,一般内核空间在高位,用户空间在低位32位系统共4Guser:0x0->0xC0000000
kernel:0xC0000000->0xFFFFFFFF
64位系统基本无上限/arch/x86/include/asm/page_64_types.h
user:0x0->0xffffffff80000000
其余为内核空间,基本无上限
用户程序陷入系统调用,则进入内核空间,在用户空间与内核空间来回切换,效率会比较差,所以锁一般实现在用户空间
fork只会复制当前子线程(调用fork的线程),只会复制用户空间的内容
fork拷贝,拷贝的是用户空间的内容,也就是会拷贝实现于用户空间的所有锁
在 fork 之前,有一个子线程 lock 了某个锁,获得了对锁的所有权。fork 以后,在子进程中,所有的额外线程都人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。当子进程想 lock 这个锁时,没有任何方法可以解开,死锁。
服务器进程,fork之后子进程要释放什么资源fork之后,子进程完全复制了父进程的所有fds(查看/proc/pid/fd目录)
client C原先tcp连接到父进程A,A fork出B,则C会同时与A,B都有连接(连接关系在系统层就是一个socker类型的fd),此时如果C往服务器发包,A,B都会收到,这很可能会触发socker上的竞态条件,可能导致其中一个进程core掉(亲测)
所以,如果不是为了父子进程需要同时处理fd的使用场景,正确的姿势是,子进程执行的第一步,是关掉父进程打开的fd。示例代码如下:
MAXFD = os.sysconf("SC_OPEN_MAX")for i in xrange(3, MAXFD): // 0,1,2为标准输入,标准输出,标准错误,不关 try:
os.close() except:
print("close error")如果服务器上还跑着timer,看需求决定要不要关掉。(游戏服务,timer+协议就是主循环做的事情,关掉fd就不会有协议,剩下就是timer了)
epoll原理
socket可读写的本质网卡收到数据包
网卡给内核发中断
内核处理中断(epoll中就是调用fd的回调函数)
用到的数据结构红黑树存放所有的fd,用于快速插入,删除,检索
epoll_ctl,如果是ADD,检查红黑树中是否有socket fd,有则立即返回,没有则添加到树干上,并向内核注册fd回调函数。删除同理
就绪链表存放所有可以读写的socket fd
fd加入epoll的时候注册了回调函数,这个回调函数的作用大概就是,让内核在f中断到了之后,将它放到就绪链表中。
epoll_wait只关注就绪链表,如果就绪链表有可读写的fd,就将这些可读写的fd返回到用户态,返回之后把就绪链表清空
时间复杂度
注意到没有,其实epoll有两种时间复杂度add,delete一个新的fd,是在红黑树上操作,O(logN)
epoll_wait是直接观察就绪列表,是O(k),也可以认为是O(1)
epoll相对于select,poll快在哪里提升内核态和用户态的数据拷贝效率epoll在内核开了一块高速缓冲区,用来做用户空间数据与内核空间数据的高速拷贝
select会把所有的fd在内核空间与用户空间之间来回拷贝,epoll只会每个fd新增的时候从用户空间拷贝到内核空间,后续只会讲可读写的fd拷贝会用户空间,拷贝量减少居多
减少了对所有注册fd的遍历select只会返回可读写fd的数量,需要通过遍历所有注册的fd确定操作目标
epoll直接返回可读写的fd
LT与ETLT
只要socket上有未读取的数据,每次epoll_wait都返回。freebsd默认
ET
除非有新的中断到,否则即使socket上的事件还没有处理完,也不会每次都返回。
默认都是水平触发,操作系统希望你能尽快处理掉socket上的数据
无锁队列那些事
概念解析wait-free
An algorithm is wait-free if every operation has a bound on the number of steps the algorithm will take before the operation completes.
lock-free
An algorithm is lock-free if, when the program threads are run for a sufficiently long time, at least one of the threads makes progress (for some sensible definition of progress). All wait-free algorithms are lock-free.
需要用到什么技术CAS+,-等c指令编译之后,是多条汇编指令(3条至少)
并发情况下,游标计算可能不正确,浪费空间
CAS其实就是个粒度很小的自旋锁
memory fence(内存屏障)CPU,编译器都可能重排指令,所见非所得
通过内存屏障,通知CPU,编译器不要重排指令,现代c++提供了原语支持
常见实现方案基于数组ringbuffer,实现简单
无法动态扩容,生产者消费者不匹配情况下,可能丢数据
基于链表支持动态扩容
指针修改的地方有更多边界,更难保证正确性
现有方案一读一写kfifo 内核源码的实现。一读一写的情况下可无锁。(感觉还是有空间浪费,没想明白)
一写多读
多读多写