c程序协程的实现和openssl 1.x版本的应用

一、什么是协程

一段代码能否把机器硬件性能发挥到极致,我们通常用cpu和IO利用率(本地存储io和网络io)来衡量。

我们考核cpu在用户代码和陷入系统调用中的时间占比,如果程序不在这两个时间,cpu在wait io时间是否占比比较大。当cpu在wait io的时间缩短(或者说利用起来),可以提高cpu利用率。

当前提供CPU利用率比较成熟的模型有:

1.1 多线程:

多线程模拟多个执行者并线操作。当有正在执行的线程陷入io等待中,其他空闲线程夺得cpu的执行权。当io数据准备完毕时,cpu在可以调用的时候切回原线程,执行之后的操作。所以这里有几个要点linux内核帮我们做了。多线程调度机制。

linux内核实现的调度策略有SCHED_NORMAL、SCHED_FIFO、SCHED_RR、实时调度等。用户也要做代码的拆分(将一个涉及io函数分拆成一个执行非阻塞io,并且把剩下的函数放到回调中)。

线程在cpu多核的环境下,可以做到真正的"同时"并行做事情。但是线程创建和销毁是个很重量级的操作,线程间的cpu控制权切换非常耗时,cpu从当前线程的用户态陷入到内核态,保存当前的执行环境,加载目标线程的执行环境,然后在从内核态恢复到用户态。线程的调用栈一般很大(ulimit -s,最大8M,一般是2M)。线程一般在应用程序上,可创建的数量就是几k个。因为线程的调度如果是抢占式的调度模型,cpu每10ms会切换一次线程。如果线程数远大于cpu核数,那么cpu浪费在切换线程的时间将会很多。

1.2 AIO

IO繁忙或者IO耗时较多时,可以将IO操作交给AIO,程序处理其他逻辑,实现并行加速,AIO一定程度上也能够减少IO操作的频率,减少IO负担。AIO是将read/write的数据提交给内核,当内核完成io,将使用信号/或者回调函数进行异步通知。AIO有定义了一套函数接口。AIO脱离了select/epoll框架,使得异步读写代码可容易编写。

图1、AIO是异步非阻塞
图2. AIO的流水线

1.3 协程:

利用类似于函数间的goto语言机制,实现多个机制同时进行。协程机制可以让一个函数不是从第一句执行,而是从上次暂停的任务继续往下执行。首次调用协程函数,会在堆上分配一个协程上下文来保存caller的返回地址,入口参数,局部变量,交出控制权的代码位置等信息保存在协程上下文上。当协程再次调用函数时,会恢复调用环境,然后从上次执行的位置继续执行。

二、协程的实现

2.1 函数级别的类goto系统调用

系统调用:_setjmp, _longjmp, setcontext

_setjmp: 保存当前堆栈

_longjmp: 根据堆栈跳转

作用:函数间的goto。可以实现exception语义和协程

例子:

jmp_buf bufferA, bufferB;

int main(int argc, char **argv)

{

routineA();

return 0;

}

图3、setjump和longjump函数让routineA()和routineB()交互执行下去

二、openssl的应用

openssl实现了非对称密钥算法。非对称密钥根据加密学需要在大数按照约定的算法计算出加密密钥。这个多次的大数计算是很耗CPU性能。在众多硬件厂商有专门定制了硬件加速卡完成这些计算。

openssl的async job通过协程机制实现了用户层代码和硬件加速卡计算中交互调用。

CPU执行的用户操作和硬件卡的加密签名操作分布对应了routineA和rountineB函数。CPU无需等待当前请求的签名完成之后,而是在这段时间去处理第2个加密请求。

图4 并发请求的协程模拟图

三、高并发服务器应用

nginx作为高并发服务器可以承载着海量请求,每个请求都自带着异步的加密卡操作。这些加密卡操作和CPU接收请求都是并行处理。加密卡的操作完成事件通知是通过fd的epoll事件。利用nginx的event框架,可以轻松的把间断性的请求异步处理。而CPU只需要不断接收请求,使用签名完的数据发送给用户端。

图5 高并发服务器的协程框架

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券