精华内容
下载资源
问答
  • 最近在看ORB的特征提取,在运行代码的时候,输出kp点发现,是一串看不懂的数字,我们知道kp点包含很多信息,有坐标,方向,大小范围等。 这是我输出kp点的内容 kp [<KeyPoint 000001781B561150>, <KeyPoint...

    最近在看ORB的特征提取,在运行代码的时候,输出kp点发现,是一串看不懂的数字,我们知道kp点包含很多信息,有坐标,方向,大小范围等。
    这是我输出kp点的内容

    kp [<KeyPoint 000001781B561150>, <KeyPoint 000001781B5613C0>]
    

    这是orb提取特征的源码

    import numpy as np
    import cv2 as cv
    import os
    import time
    
    img = cv.imread('.\yolo\\2.jpg')
    orb = cv.ORB_create()
    kp = orb.detect(img,None)
    print("kp",kp)
    kp, des = orb.compute(img, kp)
    
    print("kp",kp)
    print("des",des)
    
    img = cv.drawKeypoints(img,kp,img,color=(0,255,0), flags=0)
    
    cv.imshow('p',img)
    
    cv.waitKey(0)
    

    下面,我们把kp(Keypoints) 关键点转化为普通点集

    print(kp[0].pt)
    print(kp[0].size)
    print(kp[0].angle)
    print(kp[0].response)
    print(kp[0].octave)
    print(kp[0].class_id)
    

    结果:

    (75.0, 31.0)
    31.0
    27.31970977783203
    4.206578159937635e-05
    0
    -1
    

    参数说明:

    pt:关键点的位置
    size:关键点的范围
    angle:关键点角度
    response:能够给某个关键点更强烈响应的检测器,有时能够被理解为特性实际存在的概率
    octave:标示了关键点被找到的层级,总是希望在相同的层级找到对应的关键点
    class_id:标示关键点来自于哪一个目标

    参考:
    关键点的描述符KeyPoint对象
    python opencv把普通的点集转换为KeyPoint类型
    python + opencv 中 kp(关键点) 的形式及提取坐标方法
    OpenCV中KeyPoint类

    展开全文
  • 自我监督的3D关键点学习以进行自我运动估计 在2020年CoRL会议上被接受为全体会议演讲。 概述 稀疏mono-SfM:一种新的框架,可通过结合可区分的姿势估计模块来同时学习关键点检测,匹配和3D提升。 多视图自适应:一...
  • 今天大致看了一下CornerNet的代码,对其中的关键代码做一些整理。 由于CenterNet(CenterNet:Keypoint Triplets for Object Detection)是在CornerNet的基础上修改来的,所以基本是一致的 cornernet的主要结构基本都...

    今天大致看了一下CornerNet的代码,对其中的关键代码做一些整理。
    由于CenterNet(CenterNet:Keypoint Triplets for Object Detection)是在CornerNet的基础上修改来的,所以基本是一致的

    cornernet的主要结构基本都定义在./models/py_utils文件夹下,主干结构定义在./models/py_utils/kp.py这个文件夹内,部分结构也在kp_utils.py中实现,corner pooling在_cpools文件夹下使用c++语言实现。

    接下来我主要总结了一下,网络的结构定义class kp(nn.Module),_decode()函数以及,corner pooling中的left pooling函数。

    首先,我来介绍一下网络的定义 class kp(nn.Module),其在kp.py文件中定义,该类主要实现了网络的整体结构,以及train和test的前向的过程,可以说是本网络的精髓所在。

    
    class kp(nn.Module):
        def __init__(
            self, n, nstack, dims, modules, out_dim, pre=None, cnv_dim=256, 
            make_tl_layer=make_tl_layer, make_br_layer=make_br_layer,
            make_cnv_layer=make_cnv_layer, make_heat_layer=make_kp_layer,
            make_tag_layer=make_kp_layer, make_regr_layer=make_kp_layer,
            make_up_layer=make_layer, make_low_layer=make_layer, 
            make_hg_layer=make_layer, make_hg_layer_revr=make_layer_revr,
            make_pool_layer=make_pool_layer, make_unpool_layer=make_unpool_layer,
            make_merge_layer=make_merge_layer, make_inter_layer=make_inter_layer, 
            kp_layer=residual
        ):
            super(kp, self).__init__()
            ## nstack是一个最开始我也没弄懂是干嘛的函数,后来突然想起来论文中给了intermediate supervision的介绍,才知道这个其实是实现的这个,翻译为中继监督,后面会有介绍,这个在作者的代码中默认取的是2
            self.nstack    = nstack
            ## decode就是网络输出了heatmap,embedding,offset后如何进行点匹配以及最终选择哪些点对作为结果的函数,这个类介绍完会去介绍那个函数。   
            self._decode   = _decode  
    
            curr_dim = dims[0]
            
            ## self.pre定义的是网络的头部,网络先接了一个kernel size 7x7的conv以及一个residual结构
            self.pre = nn.Sequential(
                convolution(7, 3, 128, stride=2),
                residual(3, 128, 256, stride=2)
            ) if pre is None else pre  
            
            ### CornerNet的主干结构是hourglasses,这里是就是其主干结构,make_xx_layer都是定义在kp_utils.py文件中的,感兴趣可以看一下,这里不详细介绍了,知道其实hourglasses主干结构就可以了。**并且注意到了吗,这里的定义都使用了for循环 for _ in range(nstack),其实作者所有的结构都定义了两个,两个结构通过前面提到的中继监督连接到一起。**
            self.kps  = nn.ModuleList([
                kp_module(
                    n, dims, modules, layer=kp_layer,
                    make_up_layer=make_up_layer,
                    make_low_layer=make_low_layer,
                    make_hg_layer=make_hg_layer,
                    make_hg_layer_revr=make_hg_layer_revr,
                    make_pool_layer=make_pool_layer,
                    make_unpool_layer=make_unpool_layer,
                    make_merge_layer=make_merge_layer
                ) for _ in range(nstack)
            ])
            ### hourglasses输出后,接一个卷积层
            self.cnvs = nn.ModuleList([
                make_cnv_layer(curr_dim, cnv_dim) for _ in range(nstack)
            ])
            
            ## 然后定义的是接的两个分支,分别去输出top left 以及 bottom right的分支
            self.tl_cnvs = nn.ModuleList([
                make_tl_layer(cnv_dim) for _ in range(nstack)
            ])
            self.br_cnvs = nn.ModuleList([
                make_br_layer(cnv_dim) for _ in range(nstack)
            ])
            
            ## keypoint heatmaps  ,用于输出tl以及br的热图,这里是8 * 256 *256的
            self.tl_heats = nn.ModuleList([
                make_heat_layer(cnv_dim, curr_dim, out_dim) for _ in range(nstack)
            ])
            self.br_heats = nn.ModuleList([
                make_heat_layer(cnv_dim, curr_dim, out_dim) for _ in range(nstack)
            ])
    
            ## tags ## 用于输出 embeddings值  1 * 256 * 256的
            self.tl_tags  = nn.ModuleList([
                make_tag_layer(cnv_dim, curr_dim, 1) for _ in range(nstack)
            ])
            self.br_tags  = nn.ModuleList([
                make_tag_layer(cnv_dim, curr_dim, 1) for _ in range(nstack)
            ])
    
            for tl_heat, br_heat in zip(self.tl_heats, self.br_heats):
                tl_heat[-1].bias.data.fill_(-2.19)
                br_heat[-1].bias.data.fill_(-2.19)
            
            ## 下面这三个其实是中继结构,即将输出再接入下一个输入,后面的train以及test函数中会用到。
            self.inters = nn.ModuleList([
                make_inter_layer(curr_dim) for _ in range(nstack - 1)
            ])
    
            self.inters_ = nn.ModuleList([
                nn.Sequential(
                    nn.Conv2d(curr_dim, curr_dim, (1, 1), bias=False),
                    nn.BatchNorm2d(curr_dim)
                ) for _ in range(nstack - 1)
            ])
            self.cnvs_   = nn.ModuleList([
                nn.Sequential(
                    nn.Conv2d(cnv_dim, curr_dim, (1, 1), bias=False),
                    nn.BatchNorm2d(curr_dim)
                ) for _ in range(nstack - 1)
            ])
            ### 这里定义的是输出的回归坐标 : 2 * 256 * 256
            self.tl_regrs = nn.ModuleList([
                make_regr_layer(cnv_dim, curr_dim, 2) for _ in range(nstack)
            ])
            self.br_regrs = nn.ModuleList([
                make_regr_layer(cnv_dim, curr_dim, 2) for _ in range(nstack)
            ])
    
            self.relu = nn.ReLU(inplace=True)
    
        def _train(self, *xs):
            image   = xs[0]
            tl_inds = xs[1]
            br_inds = xs[2]
            ## image 最先过的网络,是7x7的卷积,数据表示为inter,这里注意一下这个inter,后面会用到。
            inter = self.pre(image)
            ## 保存输出的
            outs  = []
    
            layers = zip(
                self.kps, self.cnvs,
                self.tl_cnvs, self.br_cnvs,
                self.tl_heats, self.br_heats,
                self.tl_tags, self.br_tags,
                self.tl_regrs, self.br_regrs
            )
            ## 这个for循环的意思就是对应的nstack。
            for ind, layer in enumerate(layers):
                kp_, cnv_          = layer[0:2]
                tl_cnv_, br_cnv_   = layer[2:4]
                tl_heat_, br_heat_ = layer[4:6]
                tl_tag_, br_tag_   = layer[6:8]
                tl_regr_, br_regr_ = layer[8:10]
                
                #### 下面都没什么好说的,就是网络一层一层的过。
                kp  = kp_(inter)
                cnv = cnv_(kp)
    
                tl_cnv = tl_cnv_(cnv)
                br_cnv = br_cnv_(cnv)
    
                tl_heat, br_heat = tl_heat_(tl_cnv), br_heat_(br_cnv)
                tl_tag,  br_tag  = tl_tag_(tl_cnv),  br_tag_(br_cnv)
                tl_regr, br_regr = tl_regr_(tl_cnv), br_regr_(br_cnv)
    
                tl_tag  = _tranpose_and_gather_feat(tl_tag, tl_inds)
                br_tag  = _tranpose_and_gather_feat(br_tag, br_inds)
                tl_regr = _tranpose_and_gather_feat(tl_regr, tl_inds)
                br_regr = _tranpose_and_gather_feat(br_regr, br_inds)
                
                # 结果保存一下
                outs += [tl_heat, br_heat, tl_tag, br_tag, tl_regr, br_regr]
                
                ##这里比较重要,这里就是中继结构的核心,还记得前面提到的inter吗?这里就是先将inter进行了self.inters_操作,然后将前面的输出cnv(哪里输出的上面找),过一下self.cnvs_结构,然后对其进行求和,之后过了relu以及self.inters结构,最后作为输入进入到nstack==1的结构,在来一遍,其实self.inters_与self.cnvs_的结构是一样的,都是卷积层。
                if ind < self.nstack - 1:
                    inter = self.inters_[ind](inter) + self.cnvs_[ind](cnv)
                    inter = self.relu(inter)
                    inter = self.inters[ind](inter)
            return outs
    
    ### test与train函数同理,唯一不同的是,train函数将nstack==0和nstack==1的输出都放到了output中,而test只试讲nstack==1的结果放到了output中这里就不详细介绍了。
        def _test(self, *xs, **kwargs):
            image = xs[0]
    
            inter = self.pre(image)
            outs  = []
    
            layers = zip(
                self.kps, self.cnvs,
                self.tl_cnvs, self.br_cnvs,
                self.tl_heats, self.br_heats,
                self.tl_tags, self.br_tags,
                self.tl_regrs, self.br_regrs
            )
            for ind, layer in enumerate(layers):
                kp_, cnv_          = layer[0:2]
                tl_cnv_, br_cnv_   = layer[2:4]
                tl_heat_, br_heat_ = layer[4:6]
                tl_tag_, br_tag_   = layer[6:8]
                tl_regr_, br_regr_ = layer[8:10]
    
                kp  = kp_(inter)
                cnv = cnv_(kp)
    
                if ind == self.nstack - 1:
                    tl_cnv = tl_cnv_(cnv)
                    br_cnv = br_cnv_(cnv)
    
                    tl_heat, br_heat = tl_heat_(tl_cnv), br_heat_(br_cnv)
                    tl_tag,  br_tag  = tl_tag_(tl_cnv),  br_tag_(br_cnv)
                    tl_regr, br_regr = tl_regr_(tl_cnv), br_regr_(br_cnv)
    
                    outs += [tl_heat, br_heat, tl_tag, br_tag, tl_regr, br_regr]
    
                if ind < self.nstack - 1:
                    inter = self.inters_[ind](inter) + self.cnvs_[ind](cnv)
                    inter = self.relu(inter)
                    inter = self.inters[ind](inter)
    
            return self._decode(*outs[-6:], **kwargs)
    
    
    

    decode这个函数的作用是处理模型的输出结果,利用(heatmap, emd,offset)的输出,求出模型的检测结果,下面介绍一下这个函数。

    def _decode(
        tl_heat, br_heat, tl_tag, br_tag, tl_regr, br_regr, 
        K=100, kernel=1, ae_threshold=1, num_dets=1000
    ):
        batch, cat, height, width = tl_heat.size()
        ## 首先将top_left以及bottom right 利用sigmoid映射到0-1,
        tl_heat = torch.sigmoid(tl_heat)
        br_heat = torch.sigmoid(br_heat)
    
        # perform nms on heatmaps  对其进行nms操作,其实就是maxpooling,保留max部分,kernel_size = 3 x 3。
        
        tl_heat = _nms(tl_heat, kernel=kernel)
        br_heat = _nms(br_heat, kernel=kernel)
        
        ## 在top left以及bottom right,找到最大的前K个点,并记录下他们的得分,位置,类别,坐标等信息,下面返回的结果分别代表的是:
        ## 类别得分,位置索引,类别,y坐标,x坐标
        tl_scores, tl_inds, tl_clses, tl_ys, tl_xs = _topk(tl_heat, K=K)
        br_scores, br_inds, br_clses, br_ys, br_xs = _topk(br_heat, K=K)
        #下面是将坐标扩充, 为后面拿到所有的坐标组合做准备。这里扩充完之后变成了下面的样子 左边是横向的扩充,右边是纵向的扩充
        #[1,1,1     [ 1,2,3,
        # 2,2,2       1,2,3,
        # 3,3,3]       1,2,3 ]
        # 这样就可以组合出所有的枚举坐标了。也就是下面干的事情
        tl_ys = tl_ys.view(batch, K, 1).expand(batch, K, K)
        tl_xs = tl_xs.view(batch, K, 1).expand(batch, K, K)
        br_ys = br_ys.view(batch, 1, K).expand(batch, K, K)
        br_xs = br_xs.view(batch, 1, K).expand(batch, K, K)
        
        #根据上面的索引,将offset拿出来。
        if tl_regr is not None and br_regr is not None:
            tl_regr = _tranpose_and_gather_feat(tl_regr, tl_inds)
            tl_regr = tl_regr.view(batch, K, 1, 2)
            br_regr = _tranpose_and_gather_feat(br_regr, br_inds)
            br_regr = br_regr.view(batch, 1, K, 2)
            
            #更新坐标,将热图求的坐标跟offset做求和操作。
            tl_xs = tl_xs + tl_regr[..., 0]
            tl_ys = tl_ys + tl_regr[..., 1]
            br_xs = br_xs + br_regr[..., 0]
            br_ys = br_ys + br_regr[..., 1]
    
        # all possible boxes based on top k corners (ignoring class)
        ## 这里首先不考类别,暴利的求出左上角点和右下角点的所有的组合框,即每个左上角点都与右下角点组合
        bboxes = torch.stack((tl_xs, tl_ys, br_xs, br_ys), dim=3)
    
        ### 拿出所有的左上角点和右下角点的embedding的值,用于后面验证距离,只有距离相近,才能被判断为是同一个类别
        tl_tag = _tranpose_and_gather_feat(tl_tag, tl_inds)
        tl_tag = tl_tag.view(batch, K, 1)
        br_tag = _tranpose_and_gather_feat(br_tag, br_inds)
        br_tag = br_tag.view(batch, 1, K)
        ### 计算左上角点以及右下角点的距离的绝对值。
        dists  = torch.abs(tl_tag - br_tag)
        
        #### 拿出所有的左上角和右下角的 类别得分
        tl_scores = tl_scores.view(batch, K, 1).expand(batch, K, K)
        br_scores = br_scores.view(batch, 1, K).expand(batch, K, K)
        ##### 将所有的得分求平均
        scores    = (tl_scores + br_scores) / 2
    
        # 由于前面是枚举了所有可能的组合情况,所以肯定会有很多错误的匹配情况,这里开始,根据一系列条件,干掉错误的匹配情况。
        # reject boxes based on classes 将左上角和右下角类别不同的干掉
        tl_clses = tl_clses.view(batch, K, 1).expand(batch, K, K)
        br_clses = br_clses.view(batch, 1, K).expand(batch, K, K)
        cls_inds = (tl_clses != br_clses)
    
        # reject boxes based on distances 将距离大于阈值的干掉,这里是0.5
        dist_inds = (dists > ae_threshold)
    
        # reject boxes based on widths and heights 左上角不在右下角上方的干掉
        width_inds  = (br_xs < tl_xs)
        height_inds = (br_ys < tl_ys)
    
        ##将上面提到的全部干掉
        scores[cls_inds]    = -1
        scores[dist_inds]   = -1
        scores[width_inds]  = -1
        scores[height_inds] = -1
    
        scores = scores.view(batch, -1)
        ### 拿到过滤后的topk的得分,以及topk的index
        scores, inds = torch.topk(scores, num_dets)
        scores = scores.unsqueeze(2)
        
        ##下面分别利用index过滤,拿到topkscore对应的坐标以及类别等
        bboxes = bboxes.view(batch, -1, 4)
        bboxes = _gather_feat(bboxes, inds)
    
        clses  = tl_clses.contiguous().view(batch, -1, 1)
        clses  = _gather_feat(clses, inds).float()
    
        tl_scores = tl_scores.contiguous().view(batch, -1, 1)
        tl_scores = _gather_feat(tl_scores, inds).float()
        br_scores = br_scores.contiguous().view(batch, -1, 1)
        br_scores = _gather_feat(br_scores, inds).float()
        
        ##拼接到一起后返回
    
        detections = torch.cat([bboxes, scores, tl_scores, br_scores, clses], dim=2)
        return detections
    

    Corner Pooling是用C++来完成的,这里,这里主要简单介绍一下left pooling的做法,其他的同理,其实实现的就是下面这个过程
    在这里插入图片描述

    std::vector<at::Tensor> pool_forward(
        at::Tensor input
    ) {
        // Initialize output  output的形状跟input是一致的,所以先根据input构建出output
        at::Tensor output = at::zeros_like(input);
    
        // Get width  拿到长度
        int64_t width = input.size(3);
    
        // Copy the last column,left pooling是一行,从右往左进行的,所以最后一个的input的值和output的值是一致的,下面三行代码就是实现复制的代码。
        at::Tensor input_temp  = input.select(3, width - 1);
        at::Tensor output_temp = output.select(3, width - 1);
        output_temp.copy_(input_temp);
        // 接下来就是从倒数第二个开始,逐个比较,永远把最大的放到output当前的位置上。
        at::Tensor max_temp;
        for (int64_t ind = 1; ind < width; ++ind) {
            input_temp  = input.select(3, width - ind - 1);  
            output_temp = output.select(3, width - ind);  
            max_temp    = output.select(3, width - ind - 1); 
    
            at::max_out(max_temp, input_temp, output_temp);
        }
    
        return { 
            output
        };
    }
    
    展开全文
  • kp = fast.detect(img,None) img2 = cv2.drawKeypoints(img, kp, color=(255,0,0)) print kp[0] #多少个角点,就有多少个下标 tu = kp[0].pt #(提取坐标) pt指的是元组 tuple(x,y) print tu[0],tu[1]
    # find and draw the keypoints
    kp = fast.detect(img,None)
    img2 = cv2.drawKeypoints(img, kp, color=(255,0,0))
    print kp[0]    #多少个角点,就有多少个下标
    tu = kp[0].pt   #(提取坐标) pt指的是元组 tuple(x,y)
    print tu[0],tu[1]
    展开全文
  • Spectre 漏洞1.2 KPTI补丁2. KPTI原理2.1 页表隔离2.2 TLB刷新策略3. 代码实现3.1 pgd切换3.2 系统调用3.3 进程切换3.4 pgd的初始化4. 相关知识4.1 ALTERNATIVE()4.2 .pushsection4.3 宏计数器`\@`4.4 pt_regs参考...

    1. 背景介绍

    KPTI(Kernel page-table isolation)内核页表隔离,把进程页表按照成用户态、内核态独立的分割成两份,为了杜绝用户态通过一些旁路漏洞来窃取内核态的数据。这类漏洞最有名的有Meltdown & Spectre,因为是CPU层面的漏洞造成的影响也是非常巨大的。

    1.1 Meltdown & Spectre 漏洞

    Meltdown 和 Spectre 这两个漏洞厉害的地方就在于,利用现代CPU speculative execution (预测执行)的漏洞,在 rax 被清零之前把信息传递出去。Meltdown 的攻击代码(简化版):

    mov rax byte[x]                 // 非法操作
    shl rax 0xC                     // rax * 4096, page alignment
    mov rbx qword [rbx + rax]       // [rbx] 为用户空间的一个array,合法操作
    

    攻击原理:

    • 1、对于第一行mov代码,操作系统会事先标注好内核的内存地址范围,如果 x 在内核的这个地址范围内,并且 CPU 不是以内核模式运行的话,那么该指令会被 CPU 标注为非法,引起异常,异常处理程序会将 rax 清空为0,并且终结此程序,这样后续指令再来读 rax 的时候就只能读到0了。
    • 2、理论上讲,在执行第二条指令之前,rax应该已经被清零了。然而在实际的 CPU 运行中,为了达到更好的性能,第二条和第三条指令在异常处理生效之前都会被部分执行,直到异常处理时 rax 和 rbx 被清零。目前看起来也没什么问题,因为rbx 也会被清零,关于 [x] 的任何信息都没有留下。
    • 3、但问题的关键就在第三行指令:如果地址 rbx + rax 不在cache中的话,CPU 会自动将这一地址调入cache中,以便之后访问时获得更好的性能,然而异常处理并不会将这个cache flush掉。而这条 cache 的地址是和 rax 直接相关的,这样就相当于在 CPU 硬件中留下了和rax 相关的信息。
    • 4、那么如何还原 rbx + rax 这个被cache的地址呢?这时候需要用到的原理就是利用cache的访问延时,即已经被cache的数据访问时间短,没有被cache的数据访问时间长。由于[rbx]这个array是在用户地址空间内的,可以自由操作,首先我们要确保整个 [rbx]这个array 都是没有被cache的,然后执行上述攻击代码,这时候 rbx + rax 这个地址就已经被cache了,接下来遍历整个[rbx] array,来测量访问时间,访问时间最短的那个 page 就可以确定为 rbx + rax。

    最接近的比喻 :

    我们把CPU比做学校食堂,把黑客比作两个男生A,B,用户则是女神。
    
    这天,男生A,B总要想办法获得女神的一点私密信息——比如,女神今天午饭吃的啥~
    
    中午,女神来到食堂打饭,点了一份小笼包。
    男生A在女神后面跟食堂大娘说:我也来一份,跟她一样的~
    然而这会食堂大娘表示,你等会,你前面还有人哦。
    好吧,虽然说是这么说,但是后面的厨房师傅已经听到了对话,已经提前开始准备好了另一份小笼包.....
    后来女神点好走了,轮到男生A点,他表示,我要一个跟她一样的...
    
    然而这会,
    食堂大娘表示,人家是人家,你是你,我们不能透露女神隐私喔,你可以走了,下一个! (这就是目前CPU的内置的安全防线)
    
    然而!
    当下一个男生B走到食堂大娘面前,直接说:随便,哪道菜最快给我上哪道...
    于是乎,既然之前厨房师傅已经提前多准备好了一份小笼包,就干脆直接把小笼包给了男生B....
    这下男生知道了,女神中午吃了小笼包......
    

    1.2 KPTI补丁

    KPTI补丁基于KAISER,它是一个用于缓解不太重要问题的早期补丁,当时业界还未了解到Meltdown的存在。

    如果没有KPTI,每当执行用户空间代码(应用程序)时,Linux会在其分页表中保留整个内核内存的映射,并保护其访问。这样做的优点是当应用程序向内核发送系统调用或收到中断时,内核页表始终存在,可以避免绝大多数上下文切换相关的开销(TLB刷新、页表交换等)。

    KPTI通过完全分离用户空间与内核空间页表来解决页表泄露。支持进程上下文标识符(PCID)特性的x86处理器可以用它来避免TLB刷新,但即便如此,它依然有很高的性能成本。据KAISER原作者称,其开销为0.28%[2];一名Linux开发者称大多数工作负载下测得约为5%,但即便有PCID优化,在某些情况下开销高达30%。[1]

    使用内核启动选项“pti=off”可以部分禁用内核页表隔离。依规定也可对已修复漏洞的新款处理器禁用内核页表隔离[16]。

    2. KPTI原理

    2.1 页表隔离

    在这里插入图片描述
    进程页表分割成用户态页表和内核态页表的具体方案是什么样的?

    • 1、在运行userapplication 的时候,将kernel mapping 减少到最少,只保留必须的user到kernel的exception entry mapping. 其他的kernel mapping 在运行user application时都去掉,变成无效mapping,这样的话,如果user访问kernel data, 在MMU地址转换的时候就会被挡掉(因为无效mapping).
    • 2、设计一个trampoline 的kernel PGD给运行user时用。Trampoline kernel mapping PGD只包含exception entry必需的mapping.
    • 3、当user通过系统调用,或是timer或其他异常进入kernel是首先用trampoline的mapping,接下来tramponline的vector处理会将kernel mapping 换成正常的kernel mapping(SWAPPER_PGD_DIR), 并直接跳转到kernel原来的vector entry, 继续正常处理。我们把上述过程称之为map kernel mapping.
    • 4、当从kernel返回到user时,正常的kernel_exit会调用trampoline的exit,tramp_exit会重新将kernel mapping 换成是trampoline. 这个过程叫unmap kernel mapping.

    2.2 TLB刷新策略

    TLB是页表的高速缓存,虚拟地址到物理地址转换都要经过TLB。
    在这里插入图片描述
    TLB刷新策略的发展史是这样的:

    • 1、初始状态。操作系统中存在多个进程,每个进程都由自己虚拟地址空间。进程的虚拟地址空间时重叠的,如果存在多-份地址转换将出现混乱。那么在进程切换的时候,会进行TLB刷新,将旧进程的页表缓存无效。
      在这里插入图片描述

    • 2、Global TLBnon-Global TLB。现代OS都将地址空间分为内核空间和用户空间,进程间的用户空间独立,内核空间一样。

    为了性能,可以把内核态空间的页表设置G标志:
    在这里插入图片描述

    这样这类页表被加载进TLB以后会变成Global TLB。这样在进程切换刷新TLB时,只会清理旧进程用户态的non-Global TLB,而不会清理旧进程内核态的Global TLB。这样新的进程会开始一个半新的TLB,效能提高不少:
    在这里插入图片描述

    • 3、PCID(Process-Context Identifiers)和ASID(Address-Space Identifier)。

    在kpti出现以后对TLB有了两个新的需求:
    1、内核空间不能设置成全局,因为这样就没有隔离的效果了。
    2、内核态和用户态的切换就会引起页表切换,这种场景下要求TLB不要刷新,因为如果刷新就会带来非常大的性能开销,但是不刷新又怎么做到页表隔离呢?

    针对上述的需求,诞生了新的TLB机制PCID/ASID。每一个进程在运行时,都会动态分配一个pcid/asid,如果进程切换到本进程开始运行,把对应的pcid/asid配置到cr3中:
    在这里插入图片描述

    在进程运行过程中,根据本进程的pgd产生的页表转换关系会缓存到TLB中,所有产生的TLB条目会根据当前cr3中的pcid/asid打上标签。TLB条目有了标签以后,页表切换就不需要去刷新旧的条目了,因为当前cpu只会认和当前cr3中asid相同的TLB条目,这样TLB就不用频繁的去刷新,且相互之间也是隔离的。
    在这里插入图片描述
    为了同一进程内的用户态页表和内核态页表隔离,每个进程需要两个asid。用最高位bit11来区分,bit11=0 为内核态asid,bit11=1 为用户态asid。

    3. 代码实现

    3.1 pgd切换

    内核增加了一组宏用来在进程进行用户态、内核态切换时进行页表切换。一个进程的内核态pgd(4K)和用户态pgd(4K)放在一起形成一个8K的pgd。

    cr3的bit47-bit11为pgd的物理地址,最低位bit12来进行pgd切换:bit12=0 为内核态pgd,bit12=1 为用户态pgd。

    cr3的bit0-bit11为asid,asid也分成内核态和用户态,最高位bit11来进行asid切换:bit11=0 为内核态asid,bit11=1 为用户态asid。
    在这里插入图片描述

    SAVE_AND_SWITCH_TO_KERNEL_CR3()宏用来把cr3中加载成内核态pgd:

    linux-source-4.15.0\arch\x86\entry\calling.h:
    
    /*
     * PAGE_TABLE_ISOLATION PGDs are 8k.  Flip bit 12 to switch between the two
     * halves:
     */
    #define PTI_USER_PGTABLE_BIT		PAGE_SHIFT          
    #define PTI_USER_PGTABLE_MASK		(1 << PTI_USER_PGTABLE_BIT)     // bit12,用来切换内核态/用户态pgd,0=内核态/1=用户态
    #define PTI_USER_PCID_BIT		X86_CR3_PTI_PCID_USER_BIT
    #define PTI_USER_PCID_MASK		(1 << PTI_USER_PCID_BIT)            // bit11,用来切换内核态/用户态asid,0=内核态/1=用户态
    #define PTI_USER_PGTABLE_AND_PCID_MASK  (PTI_USER_PCID_MASK | PTI_USER_PGTABLE_MASK)
    
    .macro SAVE_AND_SWITCH_TO_KERNEL_CR3 scratch_reg:req save_reg:req
        /* (1) 如果X86_FEATURE_PTI特性没有使能,直接跳转到".Ldone_\@",即本宏为空操作 */
    	ALTERNATIVE "jmp .Ldone_\@", "", X86_FEATURE_PTI                
        /* (2) 备份cr3到scratch_reg和save_reg */
    	movq	%cr3, \scratch_reg
    	movq	\scratch_reg, \save_reg
    	/*
    	 * Test the user pagetable bit. If set, then the user page tables
    	 * are active. If clear CR3 already has the kernel page table
    	 * active.
    	 */
        /* (3) 判断cr3,如果已经是内核态pgd,放弃操作直接返回 */
    	bt	$PTI_USER_PGTABLE_BIT, \scratch_reg
    	jnc	.Ldone_\@
    
        /* (4) 进行用户态到内核态的切换 */
    	ADJUST_KERNEL_CR3 \scratch_reg
    	movq	\scratch_reg, %cr3
    
    .Ldone_\@:
    .endm
    
    ↓
    
    .macro SWITCH_TO_KERNEL_CR3 scratch_reg:req
        /* (4.1) 如果X86_FEATURE_PTI特性没有使能,直接跳转出去,本宏为空操作 */
    	ALTERNATIVE "jmp .Lend_\@", "", X86_FEATURE_PTI
        /* (4.2) 读出cr3的值到scratch_reg */
    	mov	%cr3, \scratch_reg
        /* (4.3) 计算内核态下,cr3的新值 */
    	ADJUST_KERNEL_CR3 \scratch_reg
        /* (4.4) 使用新值来配置cr3 */
    	mov	\scratch_reg, %cr3
    .Lend_\@:
    .endm
    
    ↓
    
    .macro ADJUST_KERNEL_CR3 reg:req
        /* (4.3.1) 如果X86_FEATURE_PTI特性使能,设置noflush标志 */
    	ALTERNATIVE "", "SET_NOFLUSH_BIT \reg", X86_FEATURE_PCID 
    	/* Clear PCID and "PAGE_TABLE_ISOLATION bit", point CR3 at kernel pagetables: */
        /* (4.3.2) 将bit12和bit11清零,把pgd和asid从用户态切换到内核态 */
    	andq    $(~PTI_USER_PGTABLE_AND_PCID_MASK), \reg
    .endm
    
    ↓
    
    .macro SET_NOFLUSH_BIT	reg:req
        /* (4.3.1.1) noflush标志为cr3的bit63 */
    	bts	$X86_CR3_PCID_NOFLUSH_BIT, \reg                             // bit63,noflush标志
    .endm
    

    SAVE_AND_SWITCH_TO_KERNEL_CR3()宏用来把cr3中加载成用户态pgd:

    .macro SWITCH_TO_USER_CR3_STACK	scratch_reg:req
        /* (1) 备份rax寄存器 */
    	pushq	%rax
        /* (2) 使用rax充当scratch_reg2,调用内核态到用户态的切换 */
    	SWITCH_TO_USER_CR3_NOSTACK scratch_reg=\scratch_reg scratch_reg2=%rax
        /* (3) 恢复rax寄存器 */
    	popq	%rax
    .endm
    
    ↓
    
    #define THIS_CPU_user_pcid_flush_mask   \
    	PER_CPU_VAR(cpu_tlbstate) + TLB_STATE_user_pcid_flush_mask
    
    .macro SWITCH_TO_USER_CR3_NOSTACK scratch_reg:req scratch_reg2:req
        /* (2.1)  如果X86_FEATURE_PTI特性没有使能,直接跳转出去,本宏为空操作 */
    	ALTERNATIVE "jmp .Lend_\@", "", X86_FEATURE_PTI
        /* (2.2) 读出cr3到scratch_reg中 */
    	mov	%cr3, \scratch_reg
    
        /* (2.3) 如果X86_FEATURE_PCID特性没有使能,直接跳转到切换pgd处 */
    	ALTERNATIVE "jmp .Lwrcr3_\@", "", X86_FEATURE_PCID
    
    	/*
    	 * Test if the ASID needs a flush.
    	 */
        /* (2.4.1) 取出cr3中的asid,判断是否需要flush */
    	movq	\scratch_reg, \scratch_reg2
    	andq	$(0x7FF), \scratch_reg		/* mask ASID */
    	bt	\scratch_reg, THIS_CPU_user_pcid_flush_mask
    	jnc	.Lnoflush_\@
    
    	/* Flush needed, clear the bit */
        /* (2.4.2) 如果需要flush,则不设置noflush标志位 */
    	btr	\scratch_reg, THIS_CPU_user_pcid_flush_mask
    	movq	\scratch_reg2, \scratch_reg
    	jmp	.Lwrcr3_pcid_\@
    
    .Lnoflush_\@:
        /* (2.4.3) 如果不需要flush,则设置noflush标志位 */
    	movq	\scratch_reg2, \scratch_reg
    	SET_NOFLUSH_BIT \scratch_reg
    
    .Lwrcr3_pcid_\@:
    	/* Flip the ASID to the user version */
        /* (2.5) 将bit11置位,把asid从内核态切换到用户态 */
    	orq	$(PTI_USER_PCID_MASK), \scratch_reg
    
    .Lwrcr3_\@:
    	/* Flip the PGD to the user version */
        /* (2.6) 将bit12置位,把pgd从内核态切换到用户态 */
    	orq     $(PTI_USER_PGTABLE_MASK), \scratch_reg
        /* (2.7) 使用新值来配置cr3 */
    	mov	\scratch_reg, %cr3
    .Lend_\@:
    .endm
    

    RESTORE_CR3()宏用来恢复备份的cr3:

    .macro RESTORE_CR3 scratch_reg:req save_reg:req
        /* (1)  如果X86_FEATURE_PTI特性没有使能,直接跳转出去,本宏为空操作 */
    	ALTERNATIVE "jmp .Lend_\@", "", X86_FEATURE_PTI
        /* (2) 如果X86_FEATURE_PCID特性没有使能,直接跳转到切换cr3处 */
    	ALTERNATIVE "jmp .Lwrcr3_\@", "", X86_FEATURE_PCID
    
    	/*
    	 * KERNEL pages can always resume with NOFLUSH as we do
    	 * explicit flushes.
         * 内核页面始终可以使用NOFLUSH恢复,因为我们执行显式刷新。
    	 */
        /* (3) 如果save_reg中备份的是内核态pgd,设置noflush标志 */
    	bt	$PTI_USER_PGTABLE_BIT, \save_reg
    	jnc	.Lnoflush_\@
    
    	/*
    	 * Check if there's a pending flush for the user ASID we're
    	 * about to set.
    	 */
        /* (4) 否则save_reg中备份的是用户态pgd,则根据asid中的标志来判断是否flush */
    	movq	\save_reg, \scratch_reg
    	andq	$(0x7FF), \scratch_reg
    	bt	\scratch_reg, THIS_CPU_user_pcid_flush_mask
        /* (4.1) 用户态pgd,不flush,设置noflush标志 */
    	jnc	.Lnoflush_\@
    
    	btr	\scratch_reg, THIS_CPU_user_pcid_flush_mask
        /* (4.2) 用户态pgd,flush,不设置noflush标志 */
    	jmp	.Lwrcr3_\@
    
    .Lnoflush_\@:
        /* (5) 设置bit63,noflush标志 */
    	SET_NOFLUSH_BIT \save_reg
    
    .Lwrcr3_\@:
    	/*
    	 * The CR3 write could be avoided when not changing its value,
    	 * but would require a CR3 read *and* a scratch register.
    	 */
        /* (6) 使用新值来配置cr3 */
    	movq	\save_reg, %cr3
    .Lend_\@:
    .endm
    

    3.2 系统调用

    系统调用过程中内核态和用户态的切换,调用上一节的各种宏来切换页表:

    linux-source-4.15.0\arch\x86\entry\entry_64.S:
    
    ENTRY(entry_SYSCALL_64_trampoline)
    
        /* Note: using %rsp as a scratch reg. */
        /* (1) 切换到进程的内核态页表 */
    	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
    
        movq	$entry_SYSCALL_64_stage2, %rdi
    	JMP_NOSPEC %rdi
    END(entry_SYSCALL_64_trampoline)
    
    ↓
    
    ENTRY(entry_SYSCALL_64_stage2)
    	UNWIND_HINT_EMPTY
    	popq	%rdi
    	jmp	entry_SYSCALL_64_after_hwframe
    END(entry_SYSCALL_64_stage2)
    
    ↓
    
    ENTRY(entry_SYSCALL_64)
    GLOBAL(entry_SYSCALL_64_after_hwframe)
        /* (2) 执行具体的系统调用 */
    	call	do_syscall_64		/* returns with IRQs disabled */
    
    syscall_return_via_sysret:
    /*
    	 * We are on the trampoline stack.  All regs except RDI are live.
    	 * We can do future final exit work right here.
    	 */
        /* (3) 切换到进程的用户态页表 */
    	SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
    END(entry_SYSCALL_64)    
    

    3.3 进程切换

    进程切换的过程也涉及到pgd、asid和cr3的切换:

    schedule() → __schedule() → context_switch() → switch_mm_irqs_off():
    
    void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,
    			struct task_struct *tsk)
    {
            /* (1) 分配新进程的asid,并判断tlb是否需要flush */
    		choose_new_asid(next, next_tlb_gen, &new_asid, &need_flush);
    
    		if (need_flush) {
    			this_cpu_write(cpu_tlbstate.ctxs[new_asid].ctx_id, next->context.ctx_id);
    			this_cpu_write(cpu_tlbstate.ctxs[new_asid].tlb_gen, next_tlb_gen);
                /* (2.1) 按照新的asid和新的pgd来加载cr3,并且flush旧tlb */
    			load_new_mm_cr3(next->pgd, new_asid, true);
    
    			/*
    			 * NB: This gets called via leave_mm() in the idle path
    			 * where RCU functions differently.  Tracing normally
    			 * uses RCU, so we need to use the _rcuidle variant.
    			 *
    			 * (There is no good reason for this.  The idle code should
    			 *  be rearranged to call this before rcu_idle_enter().)
    			 */
    			trace_tlb_flush_rcuidle(TLB_FLUSH_ON_TASK_SWITCH, TLB_FLUSH_ALL);
    		} else {
    			/* The new ASID is already up to date. */
                /* (2.2) 按照新的asid和新的pgd来加载cr3,不需要flush旧tlb */
    			load_new_mm_cr3(next->pgd, new_asid, false);
    
    			/* See above wrt _rcuidle. */
    			trace_tlb_flush_rcuidle(TLB_FLUSH_ON_TASK_SWITCH, 0);
    		}
    }
    

    3.4 pgd的初始化

    • 1、init_top_pgt

    KPTI特性开启时,在系统初始化pgd即init_top_pgt中包留了两个page,page 0用于内核态的pgd,page 1用于用户态的pgd:

    linux-source-4.15.0\arch\x86\kernel\head_64.S:
    
    NEXT_PGD_PAGE(init_top_pgt)
    	.quad   level3_ident_pgt - __START_KERNEL_map + _KERNPG_TABLE_NOENC
    	.org    init_top_pgt + PGD_PAGE_OFFSET*8, 0
    	.quad   level3_ident_pgt - __START_KERNEL_map + _KERNPG_TABLE_NOENC
    	.org    init_top_pgt + PGD_START_KERNEL*8, 0
    	/* (2^48-(2*1024*1024*1024))/(2^39) = 511 */
    	.quad   level3_kernel_pgt - __START_KERNEL_map + _PAGE_TABLE_NOENC
    	.fill	PTI_USER_PGD_FILL,8,0           // 预留了一个page给用户态pgd使用
    
    #ifdef CONFIG_PAGE_TABLE_ISOLATION
    /*
     * Each PGD needs to be 8k long and 8k aligned.  We do not
     * ever go out to userspace with these, so we do not
     * strictly *need* the second page, but this allows us to
     * have a single set_pgd() implementation that does not
     * need to worry about whether it has 4k or 8k to work
     * with.
     *
     * This ensures PGDs are 8k long:
     */
    #define PTI_USER_PGD_FILL	512
    #else
    #define PTI_USER_PGD_FILL	0
    #endif
    
    • 2、pti_init()

    pti_init()用来创建init_top_pgt中的page 1(用户态pgd)中的内核映射。

    因为工作在用户态时,如果要切换到内核态需要能访问部分内核代码的映射,这些部分和临时的映射在pti_init()创建,在fork()时每个进程来拷贝init_top_pgt中值。

    start_kernel() → mm_init() → pti_init():
    linux-source-4.15.0\arch\x86\mm\pti.c:
    
    void __init pti_init(void)
    {
    
        /* (1) 创建各个需要临时映射的,在用户态可以访问的内核地址映射 */
        pti_clone_user_shared();
    
    	/* Undo all global bits from the init pagetables in head_64.S: */
    	pti_set_kernel_image_nonglobal();
    	/* Replace some of the global bits just for shared entry text: */
    	pti_clone_entry_text();
    	pti_setup_espfix64();
    	pti_setup_vsyscall();
    }
    
    • 3、fork()

    进程创建时,对KPTI下两个pgd的处理:

    _do_fork() → copy_process() → copy_mm() → dup_mm() → mm_init() → mm_alloc_pgd() → pgd_alloc():
    
    pgd_t *pgd_alloc(struct mm_struct *mm)
    {
    	pgd_t *pgd;
    	pmd_t *u_pmds[PREALLOCATED_USER_PMDS];
    	pmd_t *pmds[PREALLOCATED_PMDS];
    
        /* (1) 如果KPTI使能,分配两个page的pgd(page 0用于内核态的pgd,page 1用于用户态的pgd) */
    	pgd = _pgd_alloc();
    
    	if (pgd == NULL)
    		goto out;
    
    	mm->pgd = pgd;
    
    	if (preallocate_pmds(mm, pmds, PREALLOCATED_PMDS) != 0)
    		goto out_free_pgd;
    
    	if (preallocate_pmds(mm, u_pmds, PREALLOCATED_USER_PMDS) != 0)
    		goto out_free_pmds;
    
    	if (paravirt_pgd_alloc(mm) != 0)
    		goto out_free_user_pmds;
    
    	/*
    	 * Make sure that pre-populating the pmds is atomic with
    	 * respect to anything walking the pgd_list, so that they
    	 * never see a partially populated pgd.
    	 */
    	spin_lock(&pgd_lock);
    
        /* (2) pgd的构造函数,初始化pgd结构 */
    	pgd_ctor(mm, pgd);
    	pgd_prepopulate_pmd(mm, pgd, pmds);
    	pgd_prepopulate_user_pmd(mm, pgd, u_pmds);
    
    	spin_unlock(&pgd_lock);
    
    	return pgd;
    
    out_free_user_pmds:
    	free_pmds(mm, u_pmds, PREALLOCATED_USER_PMDS);
    out_free_pmds:
    	free_pmds(mm, pmds, PREALLOCATED_PMDS);
    out_free_pgd:
    	_pgd_free(pgd);
    out:
    	return NULL;
    }
    
    static inline pgd_t *_pgd_alloc(void)
    {
    	return (pgd_t *)__get_free_pages(PGALLOC_GFP, PGD_ALLOCATION_ORDER);
    }
    
    #ifdef CONFIG_PAGE_TABLE_ISOLATION
    /*
     * Instead of one PGD, we acquire two PGDs.  Being order-1, it is
     * both 8k in size and 8k-aligned.  That lets us just flip bit 12
     * in a pointer to swap between the two 4k halves.
     */
    #define PGD_ALLOCATION_ORDER 1
    #else
    #define PGD_ALLOCATION_ORDER 0
    #endif
    
    ↓
    
    static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd)
    {
    	/* If the pgd points to a shared pagetable level (either the
    	   ptes in non-PAE, or shared PMD in PAE), then just copy the
    	   references from swapper_pg_dir. */
    	if (CONFIG_PGTABLE_LEVELS == 2 ||
    	    (CONFIG_PGTABLE_LEVELS == 3 && SHARED_KERNEL_PMD) ||
    	    CONFIG_PGTABLE_LEVELS >= 4) {
            /* (2.1) 使用swapper_pg_dir(init_top_pgt)中pgd来初始化新分配的pgd */
    		clone_pgd_range(pgd + KERNEL_PGD_BOUNDARY,
    				swapper_pg_dir + KERNEL_PGD_BOUNDARY,
    				KERNEL_PGD_PTRS);
    	}
    
    	/* list required to sync kernel mapping updates */
    	if (!SHARED_KERNEL_PMD) {
    		pgd_set_mm(pgd, mm);
    		pgd_list_add(pgd);
    	}
    }
    
    ↓
    
    static inline void clone_pgd_range(pgd_t *dst, pgd_t *src, int count)
    {
        /* (2.1.1) 把init_top_pgt中的page 0(内核态pgd)中的内核映射,
                    拷贝到新分配的pgd的page 0(内核态pgd) 
         */
    	memcpy(dst, src, count * sizeof(pgd_t));
    #ifdef CONFIG_PAGE_TABLE_ISOLATION
    	if (!static_cpu_has(X86_FEATURE_PTI))
    		return;
    	/* Clone the user space pgd as well */
        /* (2.1.2) 把init_top_pgt中的page 1(用户态pgd)中的内核映射,
                    拷贝到新分配的pgd的page 1(内核态pgd) 
                    注意这个内核映射是部分的临时的,仅供用户态切换内核态时临时使用
                    新进程没有拷贝父进程用户态的映射,而只是简单拷贝了它的vma红黑树
         */
    	memcpy(kernel_to_user_pgdp(dst), kernel_to_user_pgdp(src),
    	       count * sizeof(pgd_t));
    #endif
    }
    

    4. 相关知识

    4.1 ALTERNATIVE()

    内核的开发一直遵循向前兼容的特性,最新版本的内核还可以在最古老的机器上运行。怎么样做到最新的代码能同时兼任新、旧CPU呢?内核实现了一个在运行时根据cpu支持的feature来动态选择的技巧。

    内核使用ALTERNATIVE()宏来对一段操作定义两段指令oldinstrnewinstr,默认使用oldinstr,如果feature满足则使用newinstr

    ALTERNATIVE()宏的定义:

    #define ALTERNATIVE(oldinstr, newinstr, feature)            \                                                                                                                                                      
        OLDINSTR(oldinstr, 1)                       \                                   
        ".pushsection .altinstructions,\"a\"\n"             \                           
        ALTINSTR_ENTRY(feature, 1)                  \                                   
        ".popsection\n"                         \                                       
        ".pushsection .altinstr_replacement, \"ax\"\n"          \                       
        ALTINSTR_REPLACEMENT(newinstr, feature, 1)          \                           
        ".popsection" 
    

    展开以后:

    asm volatile("
    661:
        lock; addl $0,0(%%esp)  //原始指令
    662:
    	//如果优化指令长度大于原始指令长度,使用nop在原始指令后面填充,最后原始指令>=优化指令长度
    	.skip -(((6651f-6641f)-(662b-661b)) > 0) * ((6651f-6641f)-(662b-661b)),0x90 
    663:
    	.pushsection .altinstructions,"a"   //创建一个新的section,pushsection和popsection的内容放到该section中
    	.long 661b - .  //中间整个是alt_instr对象,管理如何修改指令
    	.long 6641f - .
    	.word __stringify(X86_FEATURE_XMM2) 
    	.byte 663b-661b
    	.byte 6651f-6641f
    	.byte 663b-662b
    	.popsection
    	.pushsection .altinstr_replacement, "ax"
    6641:
        mfence                         //优化指令放在altinstr_replacement section中
    6651:
        .popsection" ::: "memory", "cc");
    

    ALTERNATIVE宏会在链接阶段创建两个特殊的section .altinstructions和.altinstr_replacement,而且arch/x86/kernel/vmlinux.lds.S脚本将两个section顺序放在一起,所以可使用偏移来计算指令地址。

    在内核启动的时候会调用apply_alternatives()开始修改指令,遍历.altinstructions节中的内容,判断当前cpu是否支持该特性来决定是否应该使用优化指令来覆盖原始指令。还有两个处理:如果优化指令长度 > 原始指令长度,且不会覆盖,会尝试优化原始指令后面填充的nop指令;如果优化指令有相对跳转,对其跳转地址进行重新计算。

    start_kernel() → check_bugs() → alternative_instructions() → apply_alternatives()
    

    4.2 .pushsection

    通过内联汇编新增一个 Section:

    asm(".pushsection .interp,\"a\"\n"
        "        .string \"/lib/i386-linux-gnu/ld-linux.so.2\"\n"
        ".popsection");
    

    通过上述代码新增了一个 .interp Section,用于指定动态链接器。简单介绍一下这段内联汇编:

    • asm 括号内就是汇编代码,这些代码几乎会被“原封不动”地放到汇编语言中间文件中(hello.s)。这里采用 .pushsection, .popsection,而不是 .section 是为了避免之后的代码或者数据被错误地加到这里新增的 Section 中来。
    • .pushsection .interp, “a”,这里的 “a” 表示 Alloc,会占用内存,这种才会被加到程序头表中,因为程序头表会用于创建进程映像。
    • .string 这行用来指定动态链接器的完整路径。

    4.3 宏计数器\@

    我们在.macro SAVE_AND_SWITCH_TO_KERNEL_CR3等宏的定义中可以看到一个特殊的符号\@

    \@
    as maintains a counter of how many macros it has executed in this pseudo-variable; you can copy that number to your output with ‘\@’, but only within a macro definition.
    

    它的作用是一个计数器,如果宏被多次引用,\@的值就会累加编号。用\@定义的标号.Ldone_\@:就不会重复,而是根据计数分成多个标号。

    .macro SAVE_AND_SWITCH_TO_KERNEL_CR3 scratch_reg:req save_reg:req
    	ALTERNATIVE "jmp .Ldone_\@", "", X86_FEATURE_PTI
    	movq	%cr3, \scratch_reg
    	movq	\scratch_reg, \save_reg
    	/*
    	 * Test the user pagetable bit. If set, then the user page tables
    	 * are active. If clear CR3 already has the kernel page table
    	 * active.
    	 */
    	bt	$PTI_USER_PGTABLE_BIT, \scratch_reg
    	jnc	.Ldone_\@
    
    	ADJUST_KERNEL_CR3 \scratch_reg
    	movq	\scratch_reg, %cr3
    
    .Ldone_\@:
    .endm
    

    4.4 pt_regs

    用户态寄存器的保存。

    linux程序通过系统调用、中断、异常等手段从用户态切换到内核态时,内核态需要保存用户态的寄存器上下文,通常内核态会在内核态堆栈的最顶端保留一段空间来存储用户态的寄存器上下文,这段空间的存储格式为pt_regs

    // 宏task_pt_regs(),用来获取本进程`内核态堆栈`栈顶保存的`pt_regs`数据
    #define task_pt_regs(task) \
    ({									\
    	unsigned long __ptr = (unsigned long)task_stack_page(task);	\
    	__ptr += THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;		\
    	((struct pt_regs *)__ptr) - 1;					\
    })
    

    pt_regs的格式定义如下:

    struct pt_regs {
    // 低地址 ↓   
    // 区域3结束:
            unsigned long r15;
            unsigned long r14;
            unsigned long r13;
            unsigned long r12;
            unsigned long rbp;
            unsigned long rbx;
    // 区域3开始: 保存的是`callee save`和`caller save`类的寄存器
    // 区域2结束: // arguments: non interrupts/non tracing syscalls only save up to here //
            unsigned long r11;
            unsigned long r10;
            unsigned long r9;
            unsigned long r8;
            unsigned long rax;
            unsigned long rcx;
            unsigned long rdx;
            unsigned long rsi;
            unsigned long rdi;
            unsigned long orig_rax;
    // 区域2开始:保存的是x86_64的函数参数寄存器 // end of arguments //
    // 区域1结束: cpu exception frame or undefined //
            unsigned long rip;
            unsigned long cs;
            unsigned long eflags;
            unsigned long rsp;
            unsigned long ss;
    // 区域1开始:保存的是一些关键指针 (sp/ip/cs)       
    // 高地址 ↓     // top of stack page // 栈顶,保存的时候从高地址开始保存
    };
    

    pt_regs保存的寄存器信息分为3个区域,x86_64寄存器的详细说明见下图:
    在这里插入图片描述

    参考文档:

    1.内核页表隔离
    2.KPTI——可以缓解“熔断” (Meltdown) 漏洞的内核新特性
    3.解读 Meltdown & Spectre CPU 漏洞
    4.Arm64 Linux Kernel KPTI (Meltdown防御)方案解释
    5.分页寻址(Paging)机制详解
    6.KPTI补丁分析
    7.PTI(page table isolation)–代码分析
    8..macro
    9.通过操作 Section 为 Linux ELF 程序新增数据
    10.linux内核的指令替换-alternative
    11.TLB、PCID与ASID
    12.内核地址空间布局详解

    展开全文
  • KPI为关键业绩指标 KSF为核心成功要素会影响KPI指标的达成。 KPI和KSF都是经营指标 基于业务理解进行直标的构建 数仓构建和主题域 电商的核心指标
  • 战神Z7-kp7gt+gtx1060+cuda8.0安装

    千次阅读 2018-01-21 22:09:35
     网上安装教程较多,不再赘述,指出安装成功的一个关键是 在SSD上压缩出一部分空间(我压缩出了5G),在安装ubuntu时将/boot安装在压缩出来的空间上,其余分区还放在机械硬盘上。  2,安装必要依赖库 ...
  • Interspecies Knowledge Transfer for Facial Keypoint Detection关键点检测 Github地址:Interspecies Knowledge Transfer for Facial Keypoint Detection(迁移学习检测动物头部关键点) 基于torch进行论文中想法的...
  • 《【文章导读】什么是旁道攻击?Meltdown Redux英特尔漏洞(MDS攻击);KAISER:从用户空间隐藏内核(KAISER);Meltdown/Spectre分析》 《Meltdown(熔断漏洞)- ...Meltdown漏洞与KPTI 实现 用户空间 地址空间
  • NB-IoT网管关键KPI报表定制操作简要说明
  • CREATE PROCEDURE [CL] (@st datetime, --起始日期@et datetime --终止日期)AS --fqyz一卡通系统kp考勤子系统,对原始记录进行处理的SQL语句 SELECT orgrec.*, ProcRecords.PlanWorkID, ProcRecords.PlanStart, ...
  • 这是整理发布的一款食品行业事务文员关键业绩指标(KPI),食品行业事务文员关键业绩指标(KP...该文档为食品行业事务文员关键业绩指标(KPI),是一份很不错的参考资料,具有较高参考价值,感兴趣的可以下载看看
  • 这是整理发布的一款食品行业机修主管关键业绩指标(KPI),食品行业机修主管关键业绩指标(KP...该文档为食品行业机修主管关键业绩指标(KPI),是一份很不错的参考资料,具有较高参考价值,感兴趣的可以下载看看
  • DevOps成功的5个关键

    千次阅读 2021-04-22 09:10:07
    Devops文化契合度的关键: DevOps是一个永无止境的旅程。 DevOps流程需要进行IT以外的更改。产品管理,安全性,工程,销售,运维等在交付客户价值方面都可以发挥作用。 增量方法,要从引入DevOps哲学开始。制定计划...
  • 之前对ThreeDPoseTracker的深度学习模型和unity中的驱动方法进行过解析,还有一个比较重要的就是从深度学习模型出来的3D关键点数据会有抖动,在ThreeDPoseTracker源码中有做两次平滑,一部分是卡尔曼滤波,还有一...
  • 火车订票系统java源码开放KP 自动提取对文档意义显着的关键短语是语义文档理解的重要步骤。 为了促进这一研究领域,我们创建了 OpenKeyPhrase(OpenKP),这是一个大规模的开放域关键短语提取数据集。 该数据集包含 ...
  • OpenCV SIFT检测关键

    2021-04-25 21:53:57
    关键点描述:对关键点周围图像区域分块,计算块内的梯度直方图,生成具有特征向量,对关键点信息进行描述。 在OpenCV中利用SIFT检测关键点的流程如下所示: 1.实例化sift sift = cv.xfeatures2d.SIFT_create() 2....
  • - 制造运营管理的关键绩效指标 (KP Is) - 第 1 部分:概述、概念和术语)。ISO 22400-1:2014: - 概述了 KPI 是什么, - 提出与使用 KPI 相关的概念,包括构建 KPI 的标准, - 指定与 KPI 相关的术语,以及 - 描述...
  • 销售/一般工厂中可用性检查输入KP] TCODE:MR21 修改会计视图中移动平均价 3、 价格主数据 TCODE: VK11、VK12、VK13[ZPMN红线价、PR01系统参考价]、ZXSJGP销售价格批量维护 4、 信用额度 TCODE: FD32、FD33 透明表...
  • 7.1 定义关键指标KPI

    2008-05-30 13:38:00
    定义关键指标KPI: 若要在 Microsoft SQL Server 2005 中定义关键性能指标 (KPI),应当首先定义与 KPI 关联的 KPI... 值表达式是物理度量值(如销售)、计算度量值(如利润)或使用多维表达式 (MDX) 表达式在 KP...
  • 局部建图线程 地图点融合 留下观测次数多的 局部地图 优化 关键帧剔除 博文末尾支持二维码赞赏哦 /** * This file is part of ORB-SLAM2. * * LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表...
  • img1 = load_image('',cv2....kp1, des1 = sift.detectAndCompute(img1, None) print(kp[1].pt) KeyPoint::KeyPoint(Point2f _pt, float _size, float _angle, float _response, int _octave, int _class_id):..
  • OpenCV+Python实现 shif算子 (一)SIFT算法分解为如下四步: 1.尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。...4.关键点描述:在每个关键点周围的邻域

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,736
精华内容 1,894
关键字:

关键kp