[I/O多路复用]Select Poll & Epoll Part2

转载自 https://www.jianshu.com/p/397449cadc9a

前面大概提了一下在 network 中如何通过阻塞及同步异步还有多路复用来实现 I/O,现在终于可以讲到世纪linux中是如何使用API来实现I/O模型的. 这对比Operating书来说要实际和有意义.

blocking IO - 阻塞IO & nonblocking IO - 非阻塞IO & IO multiplexing - IO多路复用 & signal driven IO - 信号驱动IO 都可以归类为synchronous IO - 同步IO,而select、poll、epoll本质上也都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

在介绍select、poll、epoll之前,首先介绍一下Linux操作系统中基础的概念

  • 用户空间 / 内核空间
    现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。
    操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
  • 进程切换
    为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的,并且进程切换是非常耗费资源的。
  • 进程阻塞
    正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得了CPU资源),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
  • 文件描述符
    文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
    文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
  • 缓存I/O
    缓存I/O又称为标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

述符数量,返回0表示超时,返回-1表示出错;

Epoll

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

Linux中提供的epoll相关函数如下:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1. epoll_create 函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。

2. epoll_ctl 函数注册要监听的事件类型。四个参数解释如下:

  • epfd 表示epoll句柄
  • op 表示fd操作类型,有如下3种
    • EPOLL_CTL_ADD 注册新的fd到epfd中
    • EPOLL_CTL_MOD 修改已注册的fd的监听事件
    • EPOLL_CTL_DEL 从epfd中删除一个fd
  • fd 是要监听的描述符
  • event 表示要监听的事件

epoll_event 结构体定义如下:

struct epoll_event {
    __uint32_t events;  /* Epoll events */
    epoll_data_t data;  /* User data variable */
};

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

3. epoll_wait 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。

  • epfd 是epoll句柄
  • events 表示从内核得到的就绪事件集合
  • maxevents 告诉内核events的大小
  • timeout 表示等待的超时事件

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  • 水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件
  • 边缘触发(ET): 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)。

LT和ET原本应该是用于脉冲信号的,可能用它来解释更加形象。Level和Edge指的就是触发点,Level为只要处于水平,那么就一直触发,而Edge则为上升沿和下降沿的时候触发。比如:0->1 就是Edge,1->1 就是Level。

ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

总结

一张图总结一下select,poll,epoll的区别:

selectpollepoll
操作方式遍历遍历回调
底层实现数组链表哈希表
IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)
最大连接数1024(x86)或2048(x64)无上限无上限
fd拷贝每次调用select,都需要把fd集合从用户态拷贝到内核态每次调用poll,都需要把fd集合从用户态拷贝到内核态调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。


既然select,poll,epoll都是I/O多路复用的具体的实现,之所以现在同时存在,其实他们也是不同历史时期的产物

  • select出现是1984年在BSD里面实现的
  • 14年之后也就是1997年才实现了poll,其实拖那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求
  • 2002, 大神 Davide Libenzi 实现了epoll

[I/O多路复用]Select Poll & Epoll

第一步先理解 阻塞/非阻塞/同步/异步. 其实计算机当中的很多概念并不只是从操作系统中衍生出来,各个领域都会有大致相同,但实则定义不一致的概念.就比如网路和操作系统的概念区别.

Unix网络编程中的五种IO模型

  • Blocking IO - 阻塞IO
  • NoneBlocking IO - 非阻塞IO
  • IO multiplexing - IO多路复用
  • signal driven IO - 信号驱动IO
  • asynchronous IO - 异步IO

这里讨论的主要是网络编程中的.同时signal driven主要是thread,直接处理IO的操作不多,所以这里不做讨论.

首先对于一个 network IO,就涉及到两个概念

  • application 调用这个IO的进程
  • kernel 系统内核

那他们经历的两个交互过程是:

  • 阶段1 wait for data 等待数据准备
  • 阶段2 copy data from kernel to user 将数据从内核拷贝到用户进程中

这大致和操作系统中的 thread_block 思路一致, 整个实现就是冲走了一遍 lock 机制. 不过就如同操作系统需要很多不同的机制来应对不同的问题,从而产生了Hoarne & Mesa Monitor Semaphore & Condition. 虽然四者可以互相实现,但是工程上需要做区分.

首先是Blocking IO, 又称阻塞IO. 在 linux 中,默认情况下所有的 socket 都是 blocking,一个典型的读操作流程大概如下图:

当用户进程调用了 recvfrom 这个 syscall, kernel 就开始了 IO 的第一个阶段,也就是收集数据, 对于network IO 来说,很多时候数据一开始还没有到达(比如,还没有收到一个完整的udp包),这个时候kernel 就要等待足够多的时间来.而在用户进程这里,整个进程都会被阻塞. 当kernel 一致等到数据准备好的时候,它就会从kernel中拷贝到用户内存.然后kernel 返回结果.用户进程才会接触block的状态,重新运行起来.

所以,blocking IO 的特点就是在IO执行的两个阶段就被block了.

这里与操作系统最大的区别就是network IO的延时通常比system 的数据流延时慢很多个时钟周期. 在操作系统中Blocking IO 一般是tick priority trigger 的.

其次是NoneBlockingIO,又称非阻塞IO

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

从图中可以看出,当用户进程发出recvfrom这个系统调用后,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个结果(no datagram ready)。从用户进程角度讲 ,它发起一个操作后,并没有等待,而是马上就得到了一个结果。用户进程得知数据还没有准备好后,它可以每隔一段时间再次发送recvfrom操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,用户进程其实是需要不断的主动询问kernel数据好了没有。

I/O多路复用(multiplexing)是网络编程中最常用的模型,像我们最常用的select、epoll都属于这种模型。以select为例:

看起来它与blocking I/O很相似,两个阶段都阻塞。但它与blocking I/O的一个重要区别就是它可以等待多个数据报就绪(datagram ready),即可以处理多个连接。这里的select相当于一个“代理”,调用select以后进程会被select阻塞,这时候在内核空间内select会监听指定的多个datagram (如socket连接),如果其中任意一个数据就绪了就返回。此时程序再进行数据读取操作,将数据拷贝至当前进程内。由于select可以监听多个socket,我们可以用它来处理多个连接。

在select模型中每个socket一般都设置成non-blocking,虽然等待数据阶段仍然是阻塞状态,但是它是被select调用阻塞的,而不是直接被I/O阻塞的。select底层通过轮询机制来判断每个socket读写是否就绪。

当然select也有一些缺点,比如底层轮询机制会增加开销、支持的文件描述符数量过少等。为此,Linux引入了epoll作为select的改进版本。

异步I/O在网络编程中几乎用不到,在File I/O中可能会用到:

这里面的读取操作的语义与上面的几种模型都不同。这里的读取操作(aio_read)会通知内核进行读取操作并将数据拷贝至进程中,完事后通知进程整个操作全部完成(绑定一个回调函数处理数据)。读取操作会立刻返回,程序可以进行其它的操作,所有的读取、拷贝工作都由内核去做,做完以后通知进程,进程调用绑定的回调函数来处理数据。

总结

我们来总结一下阻塞、非阻塞,同步和异步这两组概念。

先来说阻塞和非阻塞:

  • 阻塞调用会一直等待远程数据就绪再返回,即上面的阶段1会阻塞调用者,直到读取结束。
  • 而非阻塞无论在什么情况下都会立即返回,虽然非阻塞大部分时间不会被block,但是它仍要求进程不断地去主动询问kernel是否准备好数据,也需要进程主动地再次调用recvfrom来将数据拷贝到用户内存。

再说一说同步和异步:

  • 同步方法会一直阻塞进程,直到I/O操作结束,注意这里相当于上面的阶段1,阶段2都会阻塞调用者。其中 Blocking IO - 阻塞IO,Nonblocking IO - 非阻塞IO,IO multiplexing - IO多路复用,signal driven IO - 信号驱动IO 这四种IO都可以归类为同步IO。
  • 而异步方法不会阻塞调用者进程,即使是从内核空间的缓冲区将数据拷贝到进程中这一操作也不会阻塞进程,拷贝完毕后内核会通知进程数据拷贝结束。

下面的这张图很好地总结了之前讲的这五种I/O模型(来自Unix Network Programming)


hw2 Task2 改龙哥的read速度极快

不能再快了的read

credit : http://zonelikewonderland.tk/#/user/blog/details/481e27461fe6602e138b154ca8379587

const int buffsize=1e5;
char buf[buffsize],*pp=buf-1;
int readsize=0,freadsize=0;
inline void readinit(){
    fread(buf+(((readsize+buffsize-1)%buffsize>=buffsize/2-1)?0:buffsize/2),1,buffsize/2,stdin);
    freadsize+=buffsize/2;
}
inline int read(){
    if(readsize+buffsize/2>freadsize)readinit();
    while((++readsize,*++pp)<'-')if(pp==buf+buffsize-1)pp=buf-1;
    register int x=*pp&15;
    if(pp==buf+buffsize-1)pp=buf-1;
    while((++readsize,*++pp)>'-'){
        x=x*10+(*pp&15);
        if(pp==buf+buffsize-1)pp=buf-1;
    }
    if(pp==buf+buffsize-1)pp=buf-1;
    return x;
}

比我那个快。

用cuda破解ZIP存档密码


jabrown在2017年10月30日星期一提交-01:51
这将是有关如何破解受密码保护的ZIP档案的快速演练。

您将需要Hashcat和John Ripper大号。

如果使用的Linux系统没有zip2john软件包,则John Jumbo将需要编译。

将Hashcat和Ripper拆封并编译后,我们就可以获取ZIP文件的哈希值。

要获取哈希值,请运行。 / path / to / john / jumbo / run / zip2john {zip文件}

这将创建一个以冒号(:)分隔的字符串。我们仅在使用Hashcat破解哈希值时使用哈希值。

创建仅使用哈希值运行的哈希列表。 / path / to / john / jumbo / run / zip2john {zip文件} | cut -d':'-f 2> {哈希列表文件}

一旦有了哈希值,便可以使用Hashcat对其进行破解。

Hashcat命令对Winzip哈希使用单词列表攻击。 hashcat -a 0 -m 13600 {哈希列表} {wordlist}

由于Linux内核,GPU驱动程序和哈希破解系统的硬件的独特问题,我们使用的命令有所不同。

hashcat


 

只需几秒钟即可破解“ password”的简单密码。

拉链裂纹
 

使用正确的工具和单词列表,轻松破解ZIP密码非常容易。

[Compiler] 方舟编译器MapleIR基本套路

其中有两点值得注意:

  • 和LLVM一样,都是弄进一个IR统一调配进行lexical和semantic 的优化。
  • 方舟原生可执行。

Source Code已给,bug一堆。

从这张图当中,我们发现华为创新性地加入了M2M作为mid-end。

1.“M2M”(所有组件都是开源的)

语言特定降低,VTable生成,异常处理和类级分析。

2. 中端(只有一些的组件是开源的)

SS SA构造,参考计数(RC)插入,别名分析,“mplt处理” , RC优化,部分冗余消除(PRE),内联,副作用分析,去虚拟化,空指针消除,死代码消除(DCE), 边界检查消除,逃逸分析,复制传播,“跨语言优化”。等。

3. 后端(无组件是开源的)

堆栈分配,控制流优化,“EBO”优化,窥视孔,寄存器分配(RA)。等。

好的,我们知道后端,我们知道大多数中端优化,但......究竟什么是“M2M”阶段?

对于大多数中端优化,我认为是阶段化,而另一个IR(他们只是称之为Me)。现在让我们深入了解它们。

编译器 IR 设计

正如我们提到的,有两层IR:MAPLE和Me。他们在MAPLE上有相当不错的文档和规范,但基本上没有我的文档。

MAPLE是一个高级IR,表示与原始源代码关闭的概念。有三个重要的构建块:

  • Leaf 节点可以表示“存储”(例如,存储器块)的常数或地址/标识。
  • Expression 节点根据其操作数评估新值。他们不会产生任何副作用
  • Statement 节点通常用于表示控制流,例如循环和分支,或对存储单元的修改(例如,将值分配给存储)。

Maple IR 首先把不同语言lower成一个中间语言,再进行语言有关的优化。 它还将来自不同语言的共同特征组合成单个表示,这样编译器也可以执行与语言无关的优化。

除此之外,OpenArk基本上在同一组优化和分析中使用Me IR,您可以在其他编译器框架中找到:Alias Analysis,Dominator Tree,Dead Code Eliminations ...

Summary

OpenArk是一个编译器框架,它尝试将不同的语言编译为公共中间层并生成本机二进制文件。 它采用多层IR设计,可以在不同的抽象级别进行优化和分析。更多源代码和(英文)文档即将发布。

https://github.com/oracle/graal/tree/master/truffle https://doc.ecoscentric.com/gnutools/doc/gccint/GENERIC.html#GENERIC

内网穿透的另一种打开方式

背景:在学校里有个路由器,路由器下的二级路由资源无法被学校内网访问。

目标:路由器192.168.8.1\24网段ping通学校内网10.20.81.83在寝室的主机。

细节:1.有时两者ping不通。原因是学校交换机allocate的网段不一致。2.学校allocate的ip地址是动态的。

解决方法一:内网穿透到victoryang00.xyz,速度<1m/s

解决方法二:dns污染学校服务器。或者两台机子都用自己搭的dns服务器。没那个技术。

解决方法三:192.168.8.1开npc服务器,接收端开nps服务器。速度>30m/s

总结,在任何机子上都可以开nps内网穿透。

最后 推荐个全局dns都能代理,简直了。vpn可以下线系列 proxifier。不过这软件udp没做,smb、rdp就不要想了。不过tcp的速度贼快。关键不用再挂一个路由了。

在经历了那么多,觉得还是路由器最靠谱,arm软路由不稳定,到头来还是得x86上。可是,如果上了x86,那又和unraid没有竞争力了。J1900成为一代神U是有道理的。接下来就等amd发力了。

最后的最后 我知道为什么宋老师说真机快了,虽然排除极端比较,就相差个200%性能的exsi跑lede和lede就有很大区别。科科。

[和阿三学Lex&Yacc]Scientific Calculator using LEX and YACC:Part 2

Source: https://www.youtube.com/watch?v=pMNsedoGoa4

主要是实践部分。

有几个注意点:

1.如果需要加入在输入的时候上下左右cursor 需要lex include y.tab.h,这个文件是由yacc生成的。

2. 在yacc中 seporater 用 %%。在%%(C declaration)中 yacc main 函数语法类c。着重说一下yacc的语法。

FIRST PART

%%

production action......

%%

THIRD PART

above is the Input, First part contains the declaration, the third part is the implementation in c.

P.S.

[和阿三学Lex&Yacc]Scientific Calculator using LEX and YACC:Part 1

看了眼youtube 上可以用的资源,全是阿三口音,那就看着吧。反正我觉得不错。

以上和编译原理上讲的kleebe 和其他正则表达式的写法一致。只是在C和pascal当中要注意\转义符。在自行加definition的时候一定要注意。

有一个和编译原理上说的不一样。$有shell的味道。

lex就是一个lexical analysis 的东西,现在流行的语言是flex。(作为一个语言的必须)

举个栗子

这是一个标准的lex文件,和之前的男阿三说过的一样,lex包含auxiliary definitions 和 translation rules。也就是一一对应关系。

生成lex 文件,我们轻易的发现头文件部分和变量定义被省略,之后放在预处理里面操作。token相关被置换。

比如exp的部分在写表达式时被置换成$$相当于this了。$3指代输入的词素。

Yacc处理的main function 主要用到了 yyparse 函数

linking的主要数据流

也就是

lex cal.l
yacc -d cal.y
cc lex.yy.c y.tab.c -ll -ly lm
./a.out

iSH 在iOS上跑命令行 类termux

虽已不尽完美。但基本的cpuinfo得支持一下吧。tensorflow应该能跑,这玩意儿是alpine的i686架构的。

本质还是个chroot的container吧,只是调不到ios底层的东西,所以这些信息缺失,已实现的东西都是不平台依赖的包。

不过这玩意儿 import coreML能用?那岂不是可以破解ios游戏?