Authuir Notepad

Saving Memory

Go的随笔录

拥抱Go

  今天(6.4)是个好日子,风水也不错,所以从今天开始接触Go。到晚上为止已经把Go的基本语法看了一遍,并用开始尝试用Go刷完leetcode的几个题。结合本渣的水平来看,Go还是一个很好上手的语言的,只要会一些C的语法,半天之内就可以入门。然而Go最受欢迎的特性还是在他的原生高并发上,也就是gochan这两个关键字。所以作为一个萌新Goer,这篇文章准备长期更新来记录一下我觉得不错的设计以及遇到的坑。

阅读

协程和异步

同步与异步

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

同步

  可以给同步下这么一个定义:如果在调用某个函数结束的时候,就能拿到这个函数应该返回的结果,那么我们就称这个过程是同步的。在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

阅读

如何正确使用卷积神经网络给学校教务系统添堵

前言

  在我本科阶段的学习中,一大乐趣就是和学校的网络管理员谭X斗智斗勇。我之前所在的工作室能用各种官方的、非官方的手段拿到学校的一些数据,然后我们工作室的一大目标就是利用这些数据来让各位同学更加方便地使用学校的各项功能(例如选课、成绩查询之类的功能)。

  上面说的官方的办法就是通过指导老师出面,要到一些关键数据库的帐号密码,而非官方的办法也很粗暴,就是利用用户托管在我们平台的帐号去爬取数据。所以网络中心的谭x老师对我们还是非常恼火的,时不时就会上门找茬,例如这次,他就在我们爬取数据的登录接口加了一个验证码。

阅读