Authuir Notepad

Saving Memory

Authuir Cloud优化之路

前言

  Authuir Cloud其实就是我的私有云服务,为了折腾这个其实花费了我不少的功夫,今天就为各位介绍一下。

  我对私有云的期望有三点:便宜、速度快、容量大,在此基础上最好稳定且能全网访问。在现在的网盘中,不论哪家这三点都只能满足两点,而我,全都要!

介绍

  基于上面几点,我的私有云目前跑在寝室的一块ARM Cortex A7的嵌入式板卡上,板载了一个SATA接口,所以可以挂载了一块1T的硬盘,总价格大约不到700RMB,这是整体的硬件方案,可以满足我对便宜和大容量的这两点需求(1T的容量对于日常使用绰绰有余,而且已经超过绝大多数的网盘容量)。而我们寝室的全千兆网络接入可以保证我在学校的任何一个地方都能以至少300Mbps以上的速度读写云,这在一定程度上满足了我对速度快的要求,而校外的网络方案将在后面详细说一说。

阅读

RTTI、反射与闭包

前言

  说实话,弄清楚这几个概念花了我很长的时间。但是弄清楚这几个概念是非常有必要的,闲话不多说,来总结一波。

RTTI

  RTTI的英文全称是"Runtime Type Identification",它指的是程序在运行的时候才确定需要用到的对象是什么类型的。直观一点来看,举个例子:目前有一个方法可以通过读一个文件来生成一个类,类的名字就是文件的第一行。那么很显然,文件不同,生成的对象可能也不同,这就是所谓的运行的时候才确定类型,也就是所谓的动态多态

  与动态多态相对应的就是静态多态。在C++里静态多态一般指的是用模版和重载,在编译时就能确定对象类型的多态方法。

阅读

协程和异步

同步与异步

  在写我对协程的理解前,我觉得有必要先说明一下同步和异步之间的关系和区别。

同步

  可以给同步下这么一个定义:如果在调用某个函数结束的时候,就能拿到这个函数应该返回的结果,那么我们就称这个过程是同步的。在C++中,绝大部分的过程都是同步的,因为对于一个具体的函数而言,我们肯定是希望获得这个函数的结果再进入下一步的操作,举个例子,我想将用户传递给我的数据进行md5加密,再存入数据库中,其调用的过程肯定是先执行md5计算,再执行数据库存入过程。

string rtn = md5(data);
mysql_write(rtn);

  这样做是合情且合理的(废话),因为大部分情况下都需要等待函数执行并返回的结果,才能拿着这个结果进行下一步操作,那为什么还需要异步呢?

异步

  相对于同步而言,异步的定义:如果在函数返回的时候,不能立即得到想要的结果,而是需要在未来通过一定的手段得到,那么这个函数就是异步的。将上面的过程写成异步调用大概长这样:

async_md5(data, mysql_write());
//do other thing here

  在这个过程中,async_md5立即就能返回并继续向下执行其他的过程。在将来的某个时候async_md5完成了md5的计算,那么它将会调用我们注册的mysql_write()函数写入数据库中,这个过程就是异步写入。

  那么为什么需要异步?

阅读

逐帧分析系统调用

系统调用的基本概念

  Linux内核提供了一组用来实现各种系统功能的API函数,这就称为系统调用。系统调用除了将底层接口(例如驱动)的实现包装了起来,还能做到权限控制以及保护。试想一下,假设现在任意程序都能绕开系统调用直接操纵底层接口,例如能绕开页表向任意的物理内存地址写入或者读取数据,那么进程之间的数据隔离也就无法做到了,严重一点地说整个系统内核都能被任意进程破坏,也就没有秩序可言。

  为了解决这个问题,现代操作系统一般都在硬件级(也就是CPU)支持指令集权限控制,例如Intel的x86指令集处理器就提供了4个等级的状态:ring0-ring3,CPU在某一时刻可能处于这4个状态的其中一个,在ring0状态下,CPU能够调用所有指令,而在ring3状态下,CPU仅能调用基本的运算指令。

  而为什么操作系统能占据Ring0状态,而进程无法进入Ring0状态呢,这就要从系统启动过程说起了。

系统启动过程分析

  计算机从上电开始是如何一步步从硬盘加载操作系统的,这篇文章讲得已经很清楚了。在Bootloader执行之后,位于/boot目录下的Linux内核会被加载到内存并执行,这个过程又分为两步:第一步是在实模式下执行boot/setup.bin,这一步主要也是执行一些初始化的操作和硬件的检查工作;第二步是在保护模式下执行boot/vmlinux.bin,在这个过程中会初始化页表、开启分页机制,最终会跳转到start_kernel()函数,到此为止内核便加载成功了。之后会执行/sbin/init,这便是Linux系统中的第一个进程。

  上面提到了实模式保护模式,下面就来解释一下。实模式指的就是实地址模式,顾名思义,实模式是将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。而保护模式保护的就是物理内存的访问,通过段式内存管理或者页式内存管理将请求的内存地址映射到实际的物理地址,而段表或页表只有系统内核才有权限修改,从而通过这种方式实现了内存访问的控制。

  计算机的外设都会通过各种方式挂载在系统总线上的,具体来说,挂载的设备在CPU上能对应上一些物理内存地址,而通过读写这些内存地址就能达到控制外设(网卡、声卡)或者读写外设(磁盘)的目的。在实现了对物理内存访问控制之后,便能够限制普通进程操纵这些设备。

  在进入保护模式之后,进程所能够寻址的地址就全都由内核通过页表来控制了。

进程启动过程分析

  上面一节解释了为什么用户的进程无法访问实际的物理地址,接下来就要介绍一下为什么进程无法运行ring0权限下的指令。

  在Linux中能通过fork()或者exec()函数创建一个进程。以exec函数族为例,其中一个声明是这样的:int execv(const char *path, char *const argv[]);,它会把第一个参数路径对应的可执行文件(例如ELF)加载到内存,紧接着执行分配资源(例如页表)、拷贝环境变量和命令行参数、将此进程加入进程调度队列开始调度和执行,至此大致的进程启动过程便结束了。

  那么有一个问题,Linux内核将时间片分配给这个进程并将CPU交给此进程来使用,那么在此进程的时间片耗尽或者需要重新调度的时候,Linux内核是如何重新获得CPU的使用权的呢。

  在内核加载的过程中,所有的中断向量表对应的地址都会被写入Linux内核的相应的处理函数的基址。简单一点来说,只需要定义一个硬件定时器(这个由CPU的硬件实现),并将这个定时器的中断向量注册为Linux的相应处理函数,那么时间一到,CPU会自动打断当前正在运行的代码,并将PC指向定时器的中断向量,此时Linux内核便重新获取到了CPU的使用权。

  到了这一步就能解释为什么操作系统能占据ring0状态,而普通进程无法进入ring0状态了。原因很简单,Linux在将CPU使用权交给进程之前,主动从ring0退到ring3,这个过程是不可逆的,因而普通进程执行的全过程都是在ring3下的。而在定时器中断或者其他硬件中断被捕获到的时候,CPU会恢复到ring0,而此时CPU的控制权又已经在Linux内核手中了,所以在一般情况下普通进程是无法得到ring0权限的。

系统调用过程

  虽然普通进程无法在ring0权限下操纵底层设备或者执行命令,但是很多情况下往往也是需要和设备进行交互的(例如读写磁盘),因而系统调用应运而生。

  下面来分析一个简单的系统调用过程,考虑一段最简单的系统调用代码:

#include <unistd.h>

int main() 
{
    char* message = "Hello, World!";
    write(1, message, 13);
    return 0;
}

  write的第一个参数代表stdout,这个程序会在屏幕上打印出字符串message。如前面所说,普通进程是无法操纵底层设备的,那么毫无疑问,write中执行了系统调用,Linux内核帮我们完成了将字符串输出到屏幕的这个过程。来看一下write函数在32bit下的汇编实现:

#include <sys/linux-syscalls.h>

    .text
    .type write, @function
    .globl write
    .align 4

write:
    pushl   %ebx
    pushl   %ecx
    pushl   %edx
    mov     16(%esp), %ebx
    mov     20(%esp), %ecx
    mov     24(%esp), %edx
    movl    $__NR_write, %eax
    int     $0x80
    cmpl    $-129, %eax
    jb      1f
    negl    %eax
    pushl   %eax
    call    __set_errno
    addl    $4, %esp
    orl     $-1, %eax
1:
    popl    %edx
    popl    %ecx
    popl    %ebx
    ret

  可以很清楚的看到,在将参数保存到寄存器后,write函数调用了int指令产生了一个软件触发的硬件中断。此时如上面所说,CPU的控制权将在此交接给Linux内核,并执行Linux相应的中断处理函数。中断处理函数只需读取eax寄存器,就能够知道是要执行哪个系统调用(此处为__NR_write),内核根据这个交给不同的系统调用去处理。处理完毕后内核将执行调度,并降低运行权限,再将CPU运行权交给下一个就绪的进程。

  (注:在64bit模式下,系统调用不再使用int指令,转而使用syscall指令)

使用机器学习构建自动问答系统

起因

  其实这个项目是我今年上半年的时候参加微软编程之美2017的时候的参赛作品,也是我的第一个比较完整的 NLP 的应用,在Task1 里的 MRR 分数应该是是排名前几的,所以来介(chui)绍(yi)一(bo)下。

  这个比赛会给你一堆问题和有关这个问题的正确答案和错误答案,然后在测试集中将给出另外一些问题以及和这个问题相关的一些答案,要求你将这些答案排序,正确答案越靠前则分数越高。下面是我的模型的部分测试结果:

Demo

阅读