0%

使用UltraISO制作启动盘

  • 链接
  • 用管理员权限启动UltraISO

为何要安装实时补丁

  • Linux系统从原理上说并不是一个实时系统,因为Linux系统有很多状态是不可被抢占的,比如持有自旋锁的状态等等,在其他博客中有所涉及,这会导致Linux系统定时不准等一系列问题,对实时控制十分不利

  • 增加实时补丁不能完全使得Linux系统变为实时系统,但是可以使得Linux系统不可抢占的部分大为减少,增强实时性

    对Linux内核实时性影响比较大的情况

  • 实际上是Linux内核的进程切换频率影响比较大,决定了定时的最佳分辨率

  • Linux内核编译的时候最多只支持1000Hz,也就是时间分辨率为1ms,但是假如要求更高的话就必须自己在kernel/Kconfig.hz中添加设置如下图

    • picture 5
    • picture 7
  • 如图编译的时候就可以增加一个自定义的2000Hz选项

  • picture 10

  • picture 8

  • 然后再选择实时内核

  • picture 9

  • 注意,对定时准确性影响比较大的是内核的timer频率,甚至实时内核与否都只起到次要作用

(更新)安装参考

  • 安装依赖sudo apt-get install build-essential libncurses-dev bison flex libssl-dev libelf-dev

  • 如果下载的是形如patches-6.1.83-rt28.tar.xz之类的

  • 先将其解压缩,然后在内核源码目录下执行

    for patch in <解压缩目录>/*.patch; do
    patch -p1 < "$patch"
    done
  • 简单流程

    • 将内核源码解压到/usr/src/<你的内核名称>,目录下
      • sudo tar -xvf <>.tar.xz -C <path to kernel dir>
    • sudo su切换到root用户
    • (此处跳过打实时patch的步骤)
    • 将旧的config复制到当前源码目录下的.config文件
      • cp /boot/config-$(uname -r) .config
    • 按照上面说的修改.config
    • 使用make menuconfig修改设置
      • picture 16
      • picture 17
      • picture 18
  • 此外还要在make menuconfig中配置抢占为General setup -> Preemption Model:选择 Fully Preemptible Kernel (RT)

  • Processor type and features -> Timer frequency:设置为 1000 Hz 以获得更高的时钟分辨率

  • General setup -> Timers subsystem:启用 High Resolution Timer Support

  • 然后删除内核模块的一些校验如图picture 2

  • 注意最后要注释设置CONFIG_MODULE_SIG_ALL, CONFIG_MODULE_SIG_KEY, CONFIG_SYSTEM_TRUSTED_KEYS,CONFIG_SYSTEM_REVOCATION_LIST, CONFIG_SYSTEM_REVOCATION_KEYS, CONFIG_DEBUG_INFO=y

    • 注意,上述每个都必须注释掉,如果注释了一部分但是漏掉其余部分会导致编译内核的时候出错
  • 修改.configCONFIG_LOCALVERSION选项为自己需要的内核后缀名(记得前面加-

  • make -j8 && make modules_install -j8

  • make install

修改默认启动内核

  • 先使用sudo update-grub观察

    • picture 14
  • 一般情况下编译安装内核之后,系统默认的启动内核还是原来的

  • 此时需要修改/etc/default/grub如图

    • picture 13
    • 也就是修改为GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux <你的Linux内核版本名称>"
  • 然后执行sudo update-grub

  • 然后重启即可自动进入指定的内核

    • picture 15
  • 交大内核镜像(下载快)

    内核模块测试

  • 重点是要在一开始解压内核源码的时候,就把内核源码解压到/usr/src目录下,这样的话就可以编译自定义内核模块了

  • picture 4

  • 按照上述逻辑完美解决

  • picture 3


问题

启动Ubuntu的时候bad shim signature

  • 因为BIOS里开启了安全启动,进入BIOS关闭secure boot即可

    遇到类似于memtest86+ needs a 16bit等等问题

  • /etc/default/grub文件中添加一行GRUB_DISABLE_OS_PROBER=false,但是未能解决问题
  • 但是只要在开机的时候按下Esc到Ubuntu高级设置,找到需要的内核启动即可

    更换内核之后遇到载入Ubuntu内存盘,error: out of memory

  • 更换新内核之后遇到载入Ubuntu内存盘,error: out of memory无法开机,但是用旧的内核可以启动
  • sudo update-initramfs -u
  • 之后又报错can't find command hwmatch out of memory
  • 重新编译内核,暂时未解决
  • 怀疑是系统分区太小导致的,重新格式化并且分配系统分区解决问题

更换内核之后因为Linux下头文件与内核版本不符导致不能安装本机编译的模块

以下方法未解决问题

  • insmod报错为insmod: ERROR: could not insert module ***.ko: Invalid module format

  • dmesg查看到报错为disagrees about version of symbol module_layout

  • 可能的解决方法

    • (不要执行这一步)编译内核的时候删去模块版本检查module versioning support,在Enable loadable module support

    • 然后重新安装内核,重新编译模块安装,安装失败使用sudo dmesg查看内核log,得到version magic '6.2.0-rt3 SMP preempt mod_unload modversions ' should be '6.2.0-rt3 SMP preempt_rt mod_unload '

    • 重新安装内核之后修改内核/usr/src下的内核源码

    • /usr/src/linux-headers-系统内核名称/include/generated下的utsrelease.h中的选项

      #define UTS_RELEASE "6.2.0-rt3"
      #define UTS_UBUNTU_RELEASE_ABI 35
    • 第一个修改为需要的内核名称即可

    • 然后修改/usr/src/linux-headers-<内核名称>/include/config/kernel.release文件,内容修改为

      <需要的内核名称> // 比如 6.2.0-rt3
    • 然后修改真正产生version magic字符串的文件/usr/src/linux-headers-<内核名称>/include/linux/vermagic.h

      /* SPDX-License-Identifier: GPL-2.0 */
      #ifndef _LINUX_VERMAGIC_H
      #define _LINUX_VERMAGIC_H

      #ifndef INCLUDE_VERMAGIC
      #error "This header can be included from kernel/module.c or *.mod.c only"
      #endif

      #include <generated/utsrelease.h>
      #include <asm/vermagic.h>

      /* Simply sanity version stamp for modules. */
      #ifdef CONFIG_SMP
      #define MODULE_VERMAGIC_SMP "SMP "
      #else
      #define MODULE_VERMAGIC_SMP ""
      #endif
      /*
      #ifdef CONFIG_PREEMPT_BUILD
      #define MODULE_VERMAGIC_PREEMPT "preempt "
      #elif defined(CONFIG_PREEMPT_RT)
      #define MODULE_VERMAGIC_PREEMPT "preempt_rt "
      #else
      #define MODULE_VERMAGIC_PREEMPT ""
      #endif
      */
      // 修改此处
      #define MODULE_VERMAGIC_PREEMPT "preempt_rt "
      #ifdef CONFIG_MODULE_UNLOAD
      #define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload "
      #else
      #define MODULE_VERMAGIC_MODULE_UNLOAD ""
      #endif
      #ifdef CONFIG_MODVERSIONS
      #define MODULE_VERMAGIC_MODVERSIONS "modversions "
      #else
      #define MODULE_VERMAGIC_MODVERSIONS ""
      #endif
      #ifdef RANDSTRUCT
      #include <generated/randstruct_hash.h>
      #define MODULE_RANDSTRUCT "RANDSTRUCT_" RANDSTRUCT_HASHED_SEED
      #else
      #define MODULE_RANDSTRUCT
      #endif
      // 修改下面的部分拼出自己想要的version magic
      #define VERMAGIC_STRING \
      UTS_RELEASE " " \
      MODULE_VERMAGIC_SMP \
      MODULE_VERMAGIC_PREEMPT \
      MODULE_VERMAGIC_MODULE_UNLOAD \
      // MODULE_VERMAGIC_MODVERSIONS \
      MODULE_ARCH_VERMAGIC \
      MODULE_RANDSTRUCT

      #endif /* _LINUX_VERMAGIC_H */
    • 找到VERMAGIC_STRING中的对应项,自己修改为需要的值或者顺序,注释掉不需要的即可

    • 然后得到与前面要求的相同的version magic字符串,即可insmod

      找不到符号Unknown symbol __mutex_init (err -2)

  • insmod报错insmod: ERROR: could not insert module ***.ko: Unknown symbol in module

  • 未能解决

    最终解决方案

  • 在编译内核模块的时候,将内核模块目录下的Makefile文件中的KDIR修改为自己编译当前内核源码的目录,不要用/usr/src下的源码,然后照常make即可insmod

  • 不要make menuconfig中关闭module versioning support选项,否则会因为version magic字符串不一致导致很多预编译好的其他模组不能安装

官方文档

  • cython

    用法

  • 参考(详细)
  • 定义一个.pyx文件
    # try111.pyx
    import time
    def say_hello_to_c(m):
    t1 = time.time()
    sum = 0
    for i in range(m):
    sum+=i
    t2 = time.time()
    # print(t2-t1)
    return t2-t1
  • 创建setup.py
    # setup.py
    from setuptools import setup
    from Cython.Build import cythonize

    setup(
    name='Hello world app',
    ext_modules=cythonize("try111.pyx"),
    )
  • 使用cython编译文件为一个.so模块
  • python3 setup.py build_ext --inplace
  • 直接调用
    from try111 import say_hello_to_c
    import matplotlib.pyplot as plt
    import time
    from tqdm import tqdm
    import math
    def say_hello_to_py(m):
    t1 = time.time()
    sum = 0
    for i in range(m):
    sum+=i
    t2 = time.time()
    # print(t2-t1)
    return t2-t1

    t1s = []
    t2s = []
    for i in tqdm(range(100)):
    t1s.append(say_hello_to_c(10000000))
    t2s.append(say_hello_to_py(10000000))

    plt.plot(t1s, label="cython")
    plt.plot(t2s, label="python")
    plt.legend()
    plt.ylabel("time elapsed: s")
    plt.xlabel("count of try")
    avg1 = sum(t1s)/len(t1s)
    avg2 = sum(t2s)/len(t2s)
    plt.title("Cython average time: %03f, pythonaverage time: %03f"% (avg1, avg2))
    plt.show()
  • picture 0
    • 可见cython有一定的速度优势

      编译单个文件

  • 使用cythonize直接编译
  • 使用方法
    options:
    -h, --help show this help message and exit
    -X NAME=VALUE,..., --directive NAME=VALUE,...
    set a compiler directive
    -E NAME=VALUE,..., --compile-time-env NAME=VALUE,...
    set a compile time environment variable
    -s NAME=VALUE, --option NAME=VALUE
    set a cythonize option
    -2 use Python 2 syntax mode by default
    -3 use Python 3 syntax mode by default
    --3str use Python 3 syntax mode by default
    -+, --cplus Compile as C++ rather than C
    -a, --annotate Produce a colorized HTML version of the source.
    --annotate-fullc Produce a colorized HTML version of the source which includes entire generated C/C++-code.
    -x PATTERN, --exclude PATTERN
    exclude certain file patterns from the compilation
    -b, --build build extension modules using distutils/setuptools
    -i, --inplace build extension modules in place using distutils/setuptools (implies -b)
    -j N, --parallel N run builds in N parallel jobs (default: 18)
    -f, --force force recompilation
    -q, --quiet be less verbose during compilation
    --lenient increase Python compatibility by ignoring some compile time errors
    -k, --keep-going compile as much as possible, ignore compilation failures
    --no-docstrings strip docstrings
    -M, --depfile produce depfiles for the sources
  • 编译C++文件的时候需要加上--cplus
  • 比如cythonize -i <pyx文件名> -j <并行的线程数量>
    # tryCpp.pyx
    # distutils: language = c++
    from libcpp.vector cimport vector
    from libcpp.map cimport map
    from libcpp.string cimport string

    def vectorCalc(int num):
    cdef int cnt, i, j
    cdef vector[int] p
    cdef map

    p.reserve(num)
    for i in range(num*5):
    p.push_back(i)
    for j in range(num*5):
    p.pop_back()


    def useMap(int cnt):
    cdef int i, j
    cdef map[int, string] testMap
    for i in range(cnt):
    testMap[i] = <string>(b"This is %d" % i)
    return testMap

    # 以下是另一种方法
    def useMap(int cnt):
    cdef int i, j
    cdef map[int, string] testMap
    cdef pair[int, string] p
    for i in range(cnt):
    # testMap[i] = <string>(b"This is %d" % i)
    p = (i, <string>(b"This is %d" % i))
    testMap.insert(p)
    return testMap
  • # distutils: language = c++的意思是告诉cython这个文件需要做成Cpp文件
  • 从上述代码可以看出,可以在cython中使用cpp标准库中的文件

    在cython中使用C++中编写的类

  • 教程
  • 编写头文件和Cpp文件
    //tryCLass.h
    # ifndef TRYCLASS
    # define TRYCLASS

    namespace try111
    {
    class tryClass
    {
    int i, j;
    public:
    tryClass(int m, int n);


    tryClass();

    ~tryClass();
    };

    };
    # endif

    //tryCLass.cpp
    # include "tryClass.h"
    # include <iostream>

    try111::tryClass::tryClass(int m, int n):i(m),j(n)
    {
    std::cout<<"try Class created, i:"<<i<<"j:"<<j<<std::endl;
    }

    try111::tryClass::tryClass():i(1),j(1)
    {
    std::cout<<"try Class created"<<std::endl;
    }

    try111::tryClass::~tryClass()
    {
    std::cout<<"try Class destroyed, i:"<<i<<"j:"<<j<<std::endl;
    }

  • .pxd文件中声明这个文件
    # tryClass.pxd
    cdef extern from "tryClass.cpp":
    pass

    cdef extern from "tryClass.h" namespace "try111":
    cdef cppclass tryClass:
    tryClass(int i, int j) except +
    tryClass() except +

  • .pyx文件中引用这个文件中的类
  • .pyx的第一行要添加# distutils: language = c++
    # tryImp.pyx
    # distutils: language = c++

    from tryCLass cimport tryClass

    def tryObj():
    heapObj = new tryClass(99, 98)
    cdef tryClass ttt = tryClass(97, 96)
    del heapObj
  • 编译cythonize -i -f tryImp.pyx -j8
  • 在python文件中调用.pyx文件中的函数
    # main.py
    from tryImp import *
    tryObj()

    cython中使用C++开发的函数

  • 类似的,直接在cpp文件中定义一个函数
    // func.cpp
    # include <iostream>

    void func(int cnt)
    {
    std::cout<<"func:";
    for(int i = 0; i<cnt; i++)
    {
    std::cout<<i<<std::endl;
    }
    }
  • 使用.pxd文件包装
    # impFunc.pxd
    cdef extern from "func.cpp":
    void func(int cnt)
  • .pyx中调用
    #useFunc.pyx
    # distutils: language = c++
    from impFunc cimport func
    def useFunc(int i):
    func(i)
  • 编译cythonize -i -f useFunc.pyx -j8
  • 在python文件中使用
    from useFunc import useFunc
    useFunc(5)
  • 执行结果
    • picture 8

      cython中使用C++运算符重载和泛型

  • 参考
  • 使用重载运算符和类模板
    // opReload.h
    #ifndef OPERSTOR_RELOAD
    #define OPERATOR_RELOAD

    #include <iostream>


    namespace OPRELOAD
    {

    template <typename T>
    class opReload
    {
    private:
    T inner;
    /* data */
    public:
    opReload();
    opReload(const T& arg);
    opReload(const opReload<T>& o);
    opReload operator+(const opReload<T>& o1);
    opReload operator-(const opReload<T>& o1);
    T getInner();
    ~opReload();
    };

    template <typename T>
    T opReload<T>::getInner()
    {
    return this->inner;
    }

    template <typename T>
    opReload<T>::opReload()
    {

    }

    template <typename T>
    opReload<T> ::opReload(const opReload<T>& arg)
    {
    this->inner = arg.inner;
    std::cout<<"copying..."<<arg.inner<<std::endl;
    }

    template <typename T>
    opReload<T>::opReload(const T& arg)
    {
    std::cout<<"op created: "<<arg<<std::endl;
    inner = arg;
    }

    template <typename T>
    opReload<T>::~opReload()
    {
    std::cout<<"op destroyed: "<<this->inner<<std::endl;
    }

    template <typename T>
    opReload<T> opReload<T>:: operator+(const opReload<T>& o1)
    {
    std::cout<<"Adding..."<<this->inner+o1.inner<<std::endl;
    return opReload<T>(this->inner+o1.inner);
    }


    template <typename T>
    opReload<T> opReload<T>:: operator-(const opReload<T>& o1)
    {
    std::cout<<"Minusing..."<<this->inner-o1.inner<<std::endl;
    return opReload<T>(this->inner-o1.inner);
    }


    }
    #endif
    # opReload.pxd
    cdef extern from "opReload.h" namespace "OPRELOAD":
    cdef cppclass opReload[T]:
    opReload() except +
    opReload(const T& arg) except +
    opReload(const opReload[T]& o) except +
    opReload[T] operator+(opReload[T]&)
    opReload[T] operator-(opReload[T]&)
    T getInner()
  • 上面引入的时候需要传入模板类的参数opReload[T]&
  • 可以使用复制构造函数,使用方法是
    oo = opReload[double](op1)
    # distutils: language = c++
    from opReload cimport opReload
    def useClass():
    cdef double o1 = 1.5
    cdef double o2 = 2.5
    cdef opReload[double] op1 = opReload[double](o1)
    cdef opReload[double] op2 = opReload[double](o2)

    cdef opReload[double] o, o_, oo
    oo = opReload[double](op1)
    o = op1+op2
    o_ = op1-op2


    return o.getInner(), o_.getInner()
  • 调用输出(main中只调用了pyx中的函数)
    • picture 9
  • 创建栈上的对象的时候,对象必须至少有一个无参数的构造函数,否则无法创建

    在栈上和堆上分配变量

  • libc.stdlib引入mallocfree
  • 分配堆上对象
    cdef vector[char]* v = new vector[char](10)
    cdef char* strOnHeap = <char*>malloc(10)
  • 注意此处分配的动态vector对象需要使用v[0]·解引用(类似于*v)才能使用,否则会出错类似于terminate called after throwing an instance of 'std::bad_alloc'
    # distutils: language = c++
    from libc.stdlib cimport malloc, free
    from libcpp.vector cimport vector
    from libc.stdio cimport printf

    def allocAndFree():
    cdef char* strOnHeap = <char*>malloc(10)
    printf("Alloced\n")
    cdef vector[char]* v = new vector[char](10)

    cdef int m
    cdef char* strLit = "Hello"
    for m in range(len(strLit)):
    strOnHeap[m] = strLit[m]

    cdef int i = 0
    for i in range(10):
    v[0][i] = <char>(int('0'.encode("ascii"))+i)

    printf("%s\n", strOnHeap)
    # printf("Alloced\n")
    for i in range(10):
    printf("%d", v[0][i])
    printf("\n")

    del v
    free(strOnHeap)
  • 释放malloc内存空间直接free即可
  • 释放申请的动态变量使用del

    使用编译好的C/C++动态库.so(Linux下)

  • 参考
  • c 源文件calc.c
    #include "calc.h"

    void calcFunc(int max)
    {
    int i = 0;
    int j = 0;
    for (;i<max;i++)
    {
    sleep(1);
    printf("\r|");
    for(j = 0; j<=i; j++)
    {
    printf("■");
    }
    for(j = i+1; j<max; j++)
    {
    printf(" ");
    }
    printf("|");
    fflush(stdout);

    }
    }
  • c头文件calc.h
    # ifndef CALC
    # define CALC

    #include <stdio.h>
    #include <unistd.h>
    void calcFunc(int i);

    #endif
  • .pyx文件(注意导入的C库函数不可以被直接引用,需要在.pyx中包装一次)
    # useCalc.pyx
    cdef extern from "calc.h":
    void calcFunc(int i)
    def useFunc(int i):
    calcFunc(i)
  • gcc生成动态链接库
  • gcc -shared -o lib<输出文件>.so 源文件.c -fPIC
    • -fPIC是生成位置无关代码的意思
    • 注意必须遵守命名规范
  • 给出头.h文件方便调用
  • 但是直接使用命令行工具cythonize编译将会无法找到符号,报错undefined symbol: calcFunc
  • 需要创建setup.py指导编译和链接
    from distutils.core import setup, Extension
    from Cython.Build import cythonize

    ext_modules = [
    Extension(
    "useCalc", # the module name exposed to python
    ["useCalc.pyx"], # the Cython source file
    libraries=["calc"], # the name of library to link with
    library_dirs=["/home/frank/study/cython/use_So"], # the path to your library

    )
    ]

    setup(
    name="useCalc",
    ext_modules=cythonize(ext_modules, language_level=3),
    )
  • 执行python3 setup.py build_ext --inplace
  • 仍然报错libcalc.so: cannot open shared object file: No such file or directory
  • 使用ldd命令查看cython编译出的.so文件,看到
    • picture 10
    • 此时可见libcalc.sonot found
    • 将库文件的目录添加到/etc/ld.so.conf,使用vim编辑
    • picture 12
      • 自己添加的目录前面不加include
  • sudo /sbin/ldconfig使添加生效

    另:使用C语言如何调用动态链接库

    #include "calc.h"

    int main()
    {
    calcFunc(10);
    }
  • 引入头文件直接使用
  • 编译的时候需要添加搜索路径-L和添加自身库-l
  • gcc -o main main.c -L. -lmylib
    • -L.的意思是在当前目录下查找
  • 同样需要在/etc/ld.so.conf中添加并且执行sudo /sbin/ldconfig生效

提高代码执行速度

import time
def say_hello_to_c(int m):
cdef double t1 = time.time()
cdef long sum = 0
cdef int i = 0
for i in range(m):
sum+=i
cdef double t2 = time.time()
# print(t2-t1)
return sum, t2-t1
  • picture 4

  • 可以看出增加了类型声明,会使得函数性能有很大的提高

  • sum添加了volatile关键字cdef volatile long sum = 0,没有很大的变化

  • 如果将变量变为全局变量,则

  • picture 5

  • 还是并无明显区别,判断不是编译器优化导致的不同,快了124.46倍

  • 更换需要外部空间的计算,也是C程序快非常多

    def arraySum(int m):
    cdef int a[1000000]
    cdef long sum = 0
    cdef int i = 0
    cdef int j = 0
    cdef double t1 = time.time()
    cdef double t2
    for i in range(m):
    a[i] = i
    for j in range(m):
    sum+=a[j]
    t2 = time.time()
    return sum, t2-t1
  • 比较时间

  • picture 6

    • 无论是在程序内部还是外部测量,可见二者时间没有明显的差异,都比python程序快非常多,基本上快了50倍左右

其他加速python程序的方法

  • 参考知乎

    可以给函数加缓存lru_cache

  • 函数前加一个@lru_cache()
  • picture 7

pypy解释器

  • pypy3 <代码路径>
  • 速度可以提高多倍

使用numba作为python的解释器

  • 如果代码中有很多numpy数组或者循环,使用numba会提高效率
  • 使用装饰器
    from numba import jit
    import numpy as np

    x = np.arange(100).reshape(10, 10)

    @jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
    def go_fast(a): # Function is compiled to machine code when called the first time
    trace = 0.0
    for i in range(a.shape[0]): # Numba likes loops
    trace += np.tanh(a[i, i]) # Numba likes NumPy functions
    return a + trace # Numba likes NumPy broadcasting

    print(go_fast(x))

其他参考

实时线程

  • 在Linux中,实时线程是一种特殊类型的线程,它们的调度策略和优先级可以被设置为实时的。这意味着,相比于普通的线程,实时线程有更高的优先级,而且它们的执行不会被低优先级的线程打断

    实时线程的调度策略

  • SCHED_FIFO先入先出策略
    • 线程会一直运行,直到它自己放弃CPU时间,或者有更高优先级的线程需要运行
  • SCHED_RR时间片轮转调度方案
    • 类似于SCHED_FIFO,但是每个线程会有一个固定的时间片来运行。当一个线程的时间片用完时,它会被放到同优先级线程的队列尾部

      C语言设置线程优先级的方法

  • 使用pthread多线程库
    #include <pthread.h>
    #include <sched.h>

    void *thread_func(void *arg) {
    // 这里是线程的代码
    return NULL;
    }

    int main() {
    pthread_t thread;
    pthread_attr_t attr;
    struct sched_param param;

    // 初始化线程属性
    pthread_attr_init(&attr);

    // 设置线程为分离状态,这样当线程结束时会自动释放所有资源
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 设置线程的调度策略为实时调度策略SCHED_FIFO
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

    // 设置线程的优先级为最高
    param.sched_priority = sched_get_priority_max(SCHED_FIFO);
    pthread_attr_setschedparam(&attr, &param);

    // 创建线程
    pthread_create(&thread, &attr, thread_func, NULL);

    // 销毁线程属性对象
    pthread_attr_destroy(&attr);

    return 0;
    }

  • 请注意,只有具有适当权限的用户(通常是root用户)才能创建实时线程。此外,过度使用实时线程可能会导致系统响应变慢,甚至完全无响应, 也可能导致系统直接崩溃。请谨慎使用。

    time命令

  • 使用time命令运行程序的时候,Ctrl+C可以计算程序在系统空间(sys)总时间(包括多个CPU核心)、用户空间(user)(包括多个CPU核心)总时间以及人类视角(real)运行了多长时间
  • picture 8

实时进程

  • 实时进程和实施线程类似,因为Linux在进行CPU调度的时候线程和进程是平等的
  • 调度方式也是有SCHED_FIFOSCHED_RR两种方式
  • 设置一个进程为实时进程
    sudo chrt -f -a -p 99 pid
  • 其中pid是需要控制的线程的进程id(Linux中每个线程都有单独的进程ID,inux中每个线程都有单独的进程ID。在Linux中,线程其实是通过轻量级进程(LWP)实现的,因此Linux中每个线程都是一个进程,都拥有一个PID。换句话说,操作系统原理中的线程,对应的其实是Linux中的进程)
  • -a是进程的所有线程,可以不用这个选项,针对每个线程单独设置
  • 在C程序中设置一个进程的调度方式
    #include <sched.h>

    int main() {
    struct sched_param param;
    int policy = SCHED_FIFO; // 这里可以改为你想要的调度策略

    param.sched_priority = sched_get_priority_max(policy);
    if (sched_setscheduler(0, policy, &param) == -1) {
    perror("sched_setscheduler");
    return 1;
    }

    // 这里是进程的代码

    return 0;
    }
  • 在shell脚本中设置进程的调度方式
    sudo chrt -f 99 pid
    # -f是FIFO调度方式
    sudo chrt -m
    # 上面这句命令可以显示所有可用的调度策略和优先级

    进程和CPU的关系

  • 将一个进程绑定到一个特定的CPU
    taskset -cp <cpuID> pid
    # 或者
    taskset -cp <cpu-list> pid
    #其中cpu-list是数字化的cpu列表,从1开始。多个不连续的cpu可用逗号连接,连续的可用短现连接,比如1,2,5-11等
  • pid是需要改变的进程的ID,cpuID是需要绑定的CPU核心id
  • 或者使用掩码的方式设置
  • picture 10
  • taskset -a -p <掩码> <pid>
  • 在C程序中将进程绑定到某个特定的CPU
    #define _GNU_SOURCE
    #include <sched.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>

    int main(int argc, char **argv) {
    cpu_set_t cpuset;

    CPU_ZERO(&cpuset); // 初始化CPU集合,将cpuset置为空
    CPU_SET(2, &cpuset); // 将本进程绑定到CPU2上

    // 设置进程的CPU亲和性
    if (sched_setaffinity(0, sizeof(cpuset), &cpuset) == -1) {
    printf("Set CPU affinity failed, error: %s\n", strerror(errno));
    return -1;
    }

    return 0;
    }
  • 线程绑定到某个CPU
  • picture 9
  • Linux中线程可以在不同CPU核心之间来回移动
  • 中断也可以设置CPU亲和性
  • picture 11
  • picture 12
    • 上述补丁的意义是将软中断负载均衡到每个核心

      进程群

  • picture 13
  • 设置进程群的CPU使用比率,先调度进程群,再调度其中的进程
  • 现决定一个进程群内部所有进程能用的所有CPU时间,再考虑分配给谁

    如何创建

  • 进入/sys/fs/cgroup/cpu/下创建目录dir
  • picture 14
  • 进入创建的目录查看cpu.shares权重
  • 添加某个进程到进程组
    • 将进程的pid添加到cgroup.procs文件中
  • cpu.cfs_period_uscpu.cfs_quota_us是一个进程组中的cfs进程在一个period范围内能运行最多quota微秒
  • quota可以大于period,因为是多核的,可以设置为period*核心数

硬实时

  • 从创建一个任务到他开始被调度,不会超过一个截止期限
  • Linux是一个软实时的系统,因此可能会超过这个时间
  • Linux无法实现硬实时
  • picture 15
    • Linux下假如你休眠10ms,因为Linux的调度抖动,可能会导致进程就绪之后无法被调度,因此两次调度会间隔大于10ms,会随着系统负载变大而延迟变大,具有不确定性

      Linux不可调度区域

  • picture 16
    • 打上硬实时补丁之后可以进一步减小不可调度的范围,将系统变为硬实时的
  • picture 17
    • 中断,软中断和自旋锁执行时都不能被调度,不能被抢占
    • 一个CPU拿到spin lock的时候,这个核心就不能被调度了
    • 软中断中可以嵌套中断,硬中断不行
    • 如果需要抢占的任务发生在上述三种情况中,则只能在上述三种状态执行结束的瞬间立即抢占
  • picture 18
    • 因为当一个进程占有不可打断的任务的时候,中断无法抢占他导致中断被延迟处理,不满足实时性,延迟的长度是橙色箭头

      实时补丁

  • picture 19
  • preempt_rt补丁
  • 第四个选项就是完全硬实时补丁的选项

    自旋锁和互斥锁的区别

  • 自旋锁是CPU一个核心拿到锁,开始处理,另一个核心拿不到则原地自旋
  • 互斥锁是一个进程拿到锁开始执行,另一个线程没拿到,则睡眠直到上一个进程释放锁唤醒

    优先级继承

  • 在低优先级的进程持有高优先级的进程试图获取的锁的时候,临时提高这个低优先级进程的优先级到跟高优先级进程一样,使得他能够在调度上获得优势从而赶快执行完,释放锁给高优先级进程使用,防止高优先级进程因为争抢锁等待低优先级进程

    Linux的进程调度策略

  • 进程调度参考1
  • 进程调度参考2

    进程调度的基础知识

  • 进程调度本身所需要的时间很短,基本就是更改一些寄存器等等,但是因为这个原因导致的上下文变化引起的CPU内部高速缓存的不命中可能在更大程度上导致程序执行时间受到影响
  • 调度的基本单位是线程
  • picture 0
    • Linux内核的抢占设置
    • 服务器一般讲究的是吞吐量而不是响应速度,但是桌面电脑和手机必须讲究响应速度否则导致卡顿
    • 第一个的话操作系统几乎没有抢占调度
    • 内核不能被抢占
    • 第三个内核也可以被抢占
  • 进程的特性
    • CPU消耗型和IO消耗型
    • IO消耗型任务得到CPU要求的较为及时,因为不及时的话会导致IO速度下降,用户体验下降,但是CPU性能对其影响不大

      早期调度器设计

  • picture 2
  • 内核优先级0-139,内核数字越小优先级越高
  • 0-99算是实时线程,99-139是非实时线程,0-99之间的数字越大优先级越高,内核实际计算的时候是99-用户设置的优先级
  • 调度看的就是从高优先级到低优先级,谁先有进程就绪就调度谁
  • 优先级高的进程可以抢占优先级低的进程
  • picture 3
  • 以上是优先级再0-99期间的进程的调度策略,二者的区别是FIFO同等优先级是先进先出,RR是时间片轮转方式(同等优先级)
  • 所有前面的进程都跑完了,才会跑100-139的进程
  • 普通进程的优先级是nice,也就是-20-19,值越大优先级越低
  • 普通进程优先级高不会形成对低优先级的绝对优势
  • 前面不会堵着后面
  • picture 4
    • RT的门限:上面一条设置的是实时进程在一个sched_rt_period中能运行的最多时间是sched_rt_runtime
    • 修改上述门限的方法:
    • 直接消除门限:sudo sysctl -w kernel.sched_rt_runtime_us=-1
      • picture 21
      • 看得出解除门限之后确实CPU使用不受限制了
    • sudo sh -c 'echo "1000000" > /proc/sys/kernel/sched_rt_runtime_us'
    • 因此,在一些情况下猛然将一个进程从普通进程转换为实时进程可能导致进程运行速度下降,因为实时进程的运行时间比率是有限的,但是普通进程没有这个限制
    • 但是可能会导致系统运行明显卡顿,因为实时进程的优先级太高了,比很多系统进程优先级都高
    • RT进程创建的线程也是RT的
    • RT类型的进程应该尽可能的小

      Linux系统的实时进程

  • 使用命令ps -eo pid,rtprio,cmd可以查看系统中所有具有实时优先级的进程
  • 非实时进程没有实时优先级,因此是-,实时进程则会显示实时优先级
  • picture 20
  • 但是系统中也有一些其他自身的实时进程,比如/usr/libexec/tracker-miner-fs-3优先级是0,irq/24-pciehpwatchdogd, idle_inject等的实时优先级是50,migration/9等是99
  • 不建议超过50,否则可能导致看门狗进程无法正常运行,使得系统崩溃

    CFS调度算法(也就是所谓的OTHER调度算法)

  • 针对非实时进程的调度算法,实时进程的优先级是大于这些进程的
  • 追求的是每个进程的vruntime接近
  • 线程才有nice,线程是调度单位,进程资源分配单位(不包括CPU)
  • 默认的线程的nice是0
  • 无法设置进程是IO消耗还是CPU消耗
  • picture 5
  • CFS能在真实硬件上模拟出一种“公平的、精确的任务多处理CPU”
  • 参考
  • vruntime += 实际运行时间(time process run) * 1024 / 进程权重(load weight of this process)
    • picture 6
  • 调度的是虚拟运行时间最短的进程(利用红黑树排序)
  • vruntime并不是无限小的,有一个最小值来限定。假如新进程的vruntime初值为0的话,比老进程的值小很多,那么它在相当长的时间内都会保持抢占CPU的优势,老进程就要饿死了
  • 每个CPU的运行队列cfs_rq都维护一个min_vruntime字段,记录该运行队列中所有进程的vruntime最小值,新进程的初始vruntime值就以它所在运行队列的min_vruntime为基础来设置,与老进程保持在合理的差距范围内
  • 唤醒抢占
    • 休眠进程在唤醒时会获得vruntime的补偿,它在醒来的时候有能力抢占CPU是大概率事件,这也是CFS调度算法的本意,即保证交互式进程的响应速度,因为交互式进程等待用户输入会频繁休眠

      调度API

  • picture 7

  • 有时候使用网线将Jetson插到电脑上之后并不能有效的链接jetson和电脑
  • 此时是因为jetson的网络接口不能上网导致的
  • 需要在网络设置中修改jetson对应的以太网接口的设置
  • picture 0
    • 打开自己电脑访问网络的渠道(比如WLAN)
    • 设置属性
    • 打开共享
    • 将其与jetson连接的网络端口共享即可
  • 可能还需要重启jetson的网络配置sudo service network-manager restart

    如何寻找本地连接到本设备的设备的IP

  • 先使用ipconfig测试每个端口的ip
  • 使用arp -a命令,查看每个端口连接的设备的ip和MAC地址

多进程库及其API

  • import multiprocessing

    # 创建进程
    Process(主函数)
    # 注意调用的时候使用
    multiprocessing.Process(target=worker)
    # 启动进程
    process.start()
    # 回收进程(阻塞)
    process.join()
  • 注意,在实验过程中多进程和单进程方式执行程序,消耗的总时间几乎没有区别,同时从任务管理器上可以看出,python并没有创建真正意义上的多个进程

  • 推测仍然是利用并发而不是并行实现的

    • picture 0
    • CPU端也没有明显看到多CPU并行计算的痕迹
    • picture 1
  • picture 3

    • main线程计时出现问题可

      相关的计时函数

  • time.thread_timetime.process_time都是Python中time模块中的函数。thread_time函数返回当前线程的系统和用户CPU时间之和(以秒为单位)。它不包括睡眠期间经过的时间,因此它是线程特定的。而process_time函数返回当前进程的系统和用户CPU时间之和(以秒为单位)。这两个函数都可以用来测量代码执行的性能,但它们所测量的对象不同:一个是线程,另一个是进程

  • time.perf_counter是Python中time模块中的一个函数,它返回一个性能计数器的值(以秒为单位)。这个计数器具有最高可用的分辨率,用于测量短时间内的时间。它包括睡眠期间经过的时间,并且是系统范围内的。返回值的参考点是未定义的,因此只有连续调用之间的结果差异是有效的。您可以使用这个函数来测量代码执行的性能。

  • time.time()是Python中time模块的一个函数,它返回当前时间的时间戳(以1970年1月1日00:00:00为起点的秒数)。这个时间戳表示从1970年1月1日00:00:00(UTC)到当前时间经过的秒数,通常不包括闰秒

    进程间通信(套接字)

  • 因为单纯的使用python并无法实现真正的多进程并行计算,因此需要使用多个程序分开运行,之间通过socket套接字通信

    python中启动另一个(真正意义上的)进程

  • 使用subprocess

    subprocess.run(['python', './localClient.py'], close_fds=True)
  • 其中 close_fds=True的作用是防止子进程继承父进程的文件描述符导致出现不可预知的问题(比如socket无法链接等)

  • windows下无法使用os库的os.fork()创建子进程

    进程与子进程之间通过套接字通信

    # localServer.py
    import socket
    import time
    import os
    import subprocess
    import multiprocessing

    def startClient():
    print("proc Start")
    subprocess.run(['python', './localClient.py'], close_fds=True)
    # print(result.stdout.decode("utf-8"))

    if __name__ == "__main__":
    server = socket.socket()
    host = socket.gethostname()
    port = 11111
    print("server on ", host)
    server.bind((host, port))
    # 在这个端口等待链接
    server.listen(2)
    print("Waiting... ")

    proc = multiprocessing.Process(target=startClient)
    proc.start()

    # while True:
    client, addr = server.accept()
    time.sleep(1)
    b = bytes("hello", "utf-8")
    client.send(b)
    rec = client.recv(5)
    print("server time stamp: %f" % time.time())
    print("Server rec: ", rec.decode("utf-8"))
    proc.join()

    # localClient.py
    import socket
    import time

    print("client running")

    client = socket.socket()
    host = socket.gethostname()
    port = 11111
    print("Trying to connect...on ", host)
    time.sleep(0.5)
    client.connect((host, port))
    print(client.recv(5).decode("utf-8"))
    time.sleep(2)
    b = bytes("hello", "utf-8")
    client.send(b)
    print("client time stamp: %f"%time.time())

    真正的多进程测试

    from multiprocessing import Process, Queue
    import subprocess
    import time
    def calcFunc(cnt, id):
    subprocess.run(['python', './calcProc.py', str(cnt), str(id)])

    if __name__ == "__main__":
    cnt = round(5e8)
    proLen = 10
    proArr = []
    for i in range(proLen):
    proArr.append(Process(target=calcFunc, args = (cnt, i)))
    start = time.time()
    for i in range(proLen):
    proArr[i].start()
    for i in range(proLen):
    proArr[i].join()
    end = time.time()
    print("Time of main thread: %f"%(end-start))
  • 测试过程

    • picture 4
      • 可见是真的创建了很多进程
    • picture 8
      • 以上是使用time.time()计时的结果, 可见主进程使用的时间跟任何一个计算进程相当
    • picture 6
      • 以上使用time.process_time()的计时结果,可见计时出现了一些问题
    • 在主进程中的顺序执行结果为
      • picture 7
      • 可见执行的时间很长,但是其中单个进程的执行时间较短

多个父进程与子进程通过套接字通信

# 主进程
import socket
import time
import os
import subprocess
import multiprocessing

def startClient():
print("proc Start")
subprocess.run(['python', './localClient.py'], close_fds=True)
# print(result.stdout.decode("utf-8"))

if __name__ == "__main__":
server = socket.socket()
host = socket.gethostname()
port = 11111
procLen = 10
procArr = []
print("server on ", host)
server.bind((host, port))
# 在这个端口等待链接
server.listen(procLen)
print("Waiting... ")
for i in range(procLen):
proc = multiprocessing.Process(target=startClient)
proc.start()
procArr.append(proc)

for i in range(procLen):
client, addr = server.accept()
time.sleep(1)
b = bytes("hello", "utf-8")
client.send(b)
rec = client.recv(5)
print("server time stamp: %f" % time.time())
print("Server rec: ", rec.decode("utf-8"))

for i in range(procLen):
procArr[i].join()

# 子进程
import socket
import time

print("client running")

client = socket.socket()
host = socket.gethostname()
port = 11111
print("Trying to connect...on ", host)
time.sleep(0.5)
client.connect((host, port))
print(client.recv(5).decode("utf-8"))
time.sleep(2)
b = bytes("hello", "utf-8")
client.send(b)
print("\nclient time stamp: %f"%time.time())
  • 运行效果
  • picture 9

C++语法复习

运算符操作

  • 从左往右多个连续不等号的时候是从左往右依次运算的
  • 连续的多个赋值操作是从右往左的
  • 移位运算最好针对无符号数使用,否则可能因为使用1填充高位导致出错

    sizeof

  • 是个运算符
    sizeof a;
    sizeof *p; // 相当于直接求p的指向的类型的大小,与p是否是有效的指针无关
  • sizeof计算数组大小的时候会返回整个数组的大小而不是指针占的空间
  • sizeof用在string或者vector之类的数据结构的时候只考虑这些机构固定部分的内容,不考虑其中可变元素的体积

逗号运算符

  • 含有两个运算对象,从左往右运算
    int a, b;
    /******/
    for(;;++a, ++b)
    {}
  • 返回的是右侧表达式的值

    对无符号数的操作

  • 假如对无符号数赋值负数的话,得到的结果是这个负数对无符号数最大值(2的位数次方)求模的结果
  • 数字溢出的话是未定义行为,程序也许会崩溃
  • 有符号和无符号的数字不能简单相加,会先把有符号数字转化为无符号数字,编码不同
    • 假如有符号数字是负数的话,会变为前面说的对最大值求模的结果,再相加,以至于结果错误

      不同数字

  • 十进制:直接写
  • 八进制:开头有个0,比如024
  • 16进制:0x15
  • 浮点数可以写3.14, 3.14e5, .314等等

    转义序列

  • 换行\n, 制表符横向\t, 报警符号\a, 纵向制表符\v, 退格符\b, 双引号\", 反斜线\\, 问号\?, 单引号\', 回车符\r, 进纸符\f
  • 如果\后面是x说明是16进制数字转义,如果没有x说明是八进制的,\115\x4d一样

switch case

  • case后面的必须是常量,不能是变量
  • 不可写初始化的语句
    • 可以创建,但是不能初始化

强制类型转换

  • static_cast<type>类似于C的强制类型转换
    • 只要底层不包含const,都可以,速度快一些
  • dynamic_cast<type>比static安全一些
  • const_cast<type>转化的时候只能去掉底层的const不能改变类型,但是可能导致未定义的行为
  • reinterpret_cast<type>对运算对象按底层位进行重新解释
    • 旧式的强制转换,少用

      字符串换行

      cout<<"Hello World"<<endl;
      // 等同于
      cout<<"Hello "
      "World"<<endl;

      字面值的类型(支持大写或小写)

  • 后缀u是无符号
  • 后缀L是long,小数后缀L是long double
  • 后缀ll是long long
  • 小数后缀f是浮点数

    变量的初始化

    // 都一样
    int a = 0;
    int a = {0};// 列表初始化
    int a{0};
    int a(0);
  • 调用new int;创建的时候,假如不加括号的话是个未初始化的int,加括号的话是0
  • 列表初始化对类型检查比较严格,比如用long double初始化int的时候就会报错,但是用传统的 初始化方式不会报错
  • 一个int类型的变量,再函数体内部(包括main),不会初始化
  • 但是假如不在任何函数内部,会初始化为0
  • 赋值操作同样可以用花括号,这样的话会严格检查是否有数据丢失,比如舍弃小数位等

    变量的声明和定义

  • 如果想声明一个变量而不是定义(在别处定义过),那就在变量前面添加extern关键字并且不要赋值
  • extern int a = 0;这句话是定义,但是不能放在任何函数的内部,这个语法的意思是这个变量是给别的文件中的人用的,放在函数中会导致这个变量只能在该函数的作用域中使用,无法被他人看到
  • 但是如果将extern的位置替换为extern,会使其无法被其他文件使用extern应用

数据类型 |字节数(16位编译器)| 字节数(32位编译器) 字节数|(64位编译器)| 总结
|—|—|—|—|—|
char |1| 1 |1 |char一直占用1个字节
char* |2 |4 |8 |char* 是指针位宽,N位系统含有N个位,占用N/8个字节
short |2 |2 |2 |short一直占用2个字节
int |2 |4 |4 |int在x86和x64都是4个字节
unsigned int |2 |4 |4 |无符号int与int本身的占位一样,不区分编译器
long |4 |4 |8 |long一直等同于float
long long|8 |8 |8 |long long一直等同于double
float |4 |4 |8 |float由于带浮点至少需要4字节,在x64编译器里是8字节
double| 8 |8 |8 |double一直占8个字节

标识符

  • 只能用数字,字母和下划线,数字不能开头
  • 不能用C++自己的关键字冲突
  • 不能与操作符的替代名冲突

    引用类型

  • 引用类型只能引用左值
  • 引用必须初始化
  • 不存在指向引用的指针
  • 常量类型的引用必须也是常量
  • 如果引用类型嵌套的话,上层的引用类型也会绑定到最底层被引用的对象上
  • 指针的引用
    int *p;
    int *&i = p;// 一个对指针的引用

    常量

  • 默认情况下,const对象只对当前文件生效,如果希望在其他文件中使用,必须使用extern声明

    指针

  • 指针只能指向与自己类型符合的变量
    • 不能不同
  • void*类型的指针可以指向任何类型
    int *const a = &b; // 此处的a一定指向b的位置,不可修改
    const int * a = &b; // 此处的a指向b,但是不能修改a指向的位置的内容
    不可以对临时对象取地址,比如&(&a)
  • 从右往左看,const离谁近,谁就是const

    常量表达式

  • 编译过程就能知道结果并且值不会改变的式子
  • 可以在语句前面加一个constexpr修饰
  • 常量和指针
    • picture 2
    • 被引用的变量必须定义在函数体外面,否则会导致变量生命周期提前结束

      typedef

  • typedef a b;是b定义为a的别名
  • typedef a *b是将b定义为a*
  • 注意使用const关键字的时候,用法的不同特性
  • typedef A B;using B = A;等价
    typedef char* cstr;
    const cstr a;// 指向恒定的某个对象
    const char* b;// 指向的对象是常量

auto

  • 让编译器使用初始值判断类型
  • 不可以使用auto i = 0, p = 3.14;二者类型不能不同
  • 用auto推导引用类型的时候会给变量设置为引用原本的类型,不会设置为引用
  • 推导const类型的指针的时候会保留const
  • 但是推导用const类型初始化的变量的时候不会保留const
  • 可以手动指定const或者`&
  • 可以写const auto & i = 42;

decltype

  • 类型分析符
    int i = 0;
    decltype(i) a = 1;// 此处a是与i类型一样的int
  • *p解引用会被认为是对应基础类型的引用类型
  • int i是int类型,但是假如decltype((i))或者是decltype(((i)))都是int的引用类型,不存在引用的引用,无论嵌套多少层都是引用

引用类型

  • const T&可以接受右值类型的传参

struct

  • 默认是public
  • C++11开始,定义的时候可以对其中的变量赋初值
  • 声明成员类型的时候要用分号

命名空间

#include <iostream>
using std::cin;
using std:: cout;
// 在下文就可以直接使用cin和cout了
  • 也可以用using namespace std;

for循环的执行顺序

  • picture 0
    • 判断条件
    • 执行循环体
    • 执行循环表达式
  • 循环

cin如何判断输入已经结束

= picture 1

  • cin的返回值可以拿来判断用户是输入了还是退出了
  • 遇到Ctrl Z之类的EOF符号或者是输入的值与目标变量类型不匹配(比如要求int遇到字符)也会退出

数组

  • 数组不可以使用auto推断类型
  • 数组的长度必须是constexpr
    • 比如常量数字
  • 可以不给大小,初始化的时候使用花括号,让编译器推断大小
  • 数组部分没给初始值的时候,会是0或者空字符串
  • 数组初始化不可以拷贝
  • 不存在引用数组
    int (*arr)[10] = &array;// 指向一个含有十个元素的数组
    int (&arr)[10] = array;// 引用一个含有十个元素的数组
    int *(&arr)[10] = ptrs; // 引用一个含有十个int类型指针的数组
  • 判断的时候从右往左,从里往外,离谁近先结合谁先跟右边的结合再考虑左边的,结合后的部分可以删去,方便判断

    decltype和auto推断类型的不同

    int a[10];
    auto a1(a); // 此时a1被推断为一个指针类型
    decltype(a) a2; // a2也是一个数组

    函数传参的时候传递数组的引用

  • 形参设置为int (&a)[10]
  • 此时在函数内部可以像使用数组一样使用参数,比如使用范围for循环等等
  • 而不会退化为指针
  • 注意传递多维数组的时候除了最高维度别的必须给出明确大小
    • int matrix [][10]或者int (*matrix)[10]

借用迭代器找到数组头尾

#include <iterator> 
int ia[] = {0, 1, 2, 3};
int *beg = std::begin(ia);
int *end = std::end(ia);
// 上述代码就找到了数组的头和尾部的位置
  • 数组的下标可以是负数

    多维数组

  • 对多维数组非最低维度直接求加法,会跳行而不是连续往前
  • 也可以用范围for
    int a[10][10];
    for(auto& row:a)
    {
    for(auto& col:row)
    {
    // todo
    }
    }
  • 上述代码必须用auto&,不使用的话auto会将类型推断为一个int指针,导致下一层范围循环无法使用

    C风格的字符串

  • 操作在<cstring>头文件中
  • 使用头文件中操作函数操作的话必须保证字符串是以\0结尾的
  • string类型的.c_str()函数会返回一个c风格的字符串
    • 注意假如之后修改了string的话,会导致这个字符串失效

STL库函数

swap

  • swap(a, b)交换两个容器的元素,比拷贝快很多

    assign

  • 清除原有内容
  • 接受两个参数,开始迭代器和结束迭代器
  • 或者接受一个初始化列表
  • 或者接受n和k两个参数,表示n个k元素,类似于赋初值的操作

    unordered_map

  • 初始化
  • 可以用{{key, val}, {key, val}, ...}的形式初始化
  • unordered_map不能在使用
    for (auto &i : m){m.erase(/*blabla*/)}
    类型的循环中删除元素,否则可能会因为删除了i指向的元素导致迭代器失效,引起内存错误
  • 安全的删除方式为
    for (auto it = m.begin(); it != m.end();) 
    {
    it = m.erase(it);
    }
  • 对于方法erase而言
    • iterator erase(const_iterator position):删除迭代器 position 指向的元素,并返回指向下一个元素的迭代器
    • size_type erase(const key_type& k):删除容器中键为 k 的元素,并返回被删除的元素个数
    • iterator erase(const_iterator first, const_iterator last)删除迭代器范围 [first, last) 内的所有元素,并返回指向最后一个被删除元素之后的元素的迭代器
    • template< class K > size_type erase( K&& x ):C++23 引入了这种重载形式,它可以删除容器中与 x 等价的所有元素,并返回被删除的元素个数
  • 可以直接使用[]操作尚未添加的键的值,这样会导致这个键对应的值先被初始化位默认值(比如int的0),然后再进行对应的操作

其他获得某个元素的方式

  • find(key)返回的是迭代器,不存在就指向的是尾后

    遍历map

    for (auto it = m.begin(); it != m.end(); it++) 
    {
    cout << it->first << " " << it->second << endl;
    }
  • 随机获得容器中的第某个元素
    unordered_set<int> um;
    auto it = um.begin();
    advance(it, rand() % um.size());
    return *it;
  • 注意此处不能直接it+数字,会出错,需要使用advance函数`

    vector

  • 使用vector(对象值, 对象数量)初始化vector的时候,会创建一个对象然后调用复制构造函数创建剩余的

    初始化

    // 类似数组的初始化方法
    vector<int> v{1, 2, 3, 4};

    大小比较

  • 逐个元素比较,都相同的时候看谁先没,没了的小
  • 前提是容器存储的元素本身是可以比较大小的

    迭代器

  • 操作与指针类似
  • 不支持递减,只能递增
  • vector.cbegin()vector.cend()是常量迭代器,不能修改其中的内容
  • 任何可能改变vector元素capacity(容量)的操作都会使得迭代器失效,因为内存位置变了
  • 迭代器可以像指针一样做差,得到的结果是相差的元素的个数

    insert

  • insert会返回插入的元素的迭代器
  • 可以向当前尚且不存在的位置(比如尾后)插入元素
  • insert也可以用初始化列表的方式插入,只需指定位置和初始化列表即可
  • 也可以给定插入位置,迭代器开始和迭代器结束实现同样功能

    erase

  • 删除某个迭代器指定位置的元素
  • 删除某两个迭代器指定范围的元素(不包括最后一个迭代器指定的元素)
  • 返回被删元素后的第一个元素

    clear

  • 清空

    resize(size, val)

  • 假如超出的话,截断后面的元素
  • 假如没超出的话,用val填充多出来的
  • 可以用来增加大小但是不改变原来的元素
  • 但是此时其中的元素是真实存在的,不只是扩大了占用的内存空间

reserve(size)

  • 这个函数会增加容器的容量而不会改变其size,只是在内存为其分配更多的空间
  • 容器的实际长度不会发生改变,只是向其中添加元素的时候不需要增加额外的空间了

emplace_back

  • 可以给定参数调用容器存储的类型的构造函数构造对象插入

    back()函数

  • 返回的是尾元素的引用

    front()函数

  • 返回的是首元素的引用

    at(n)函数

  • 返回的是下标为n元素的引用

其他技巧

  • 提取一个数字最右侧为1的位i & (~i + 1)

cpp如何初始化一个类的静态数据成员

  • 类似于初始化全局变量
    class MyClass {
    public:
    static int myStaticVar;
    };

    int MyClass::myStaticVar = 0; // 初始化静态成员

  • 注意在一个文件中定义的类静态变量在其他文件中使用的时候需要先用extern声明

    string的一些用法

    其他的初始化方法

  • string(<char array>, i)从字符数组中拷贝i个字符
  • string(s, pos)s从pos开始的字符的拷贝
  • string(s, pos, len)s从pos开始长度为len的字符串

    初始化某个重复多次的字符

    string s1(10, 'c');

    整数或者小数转换为字符串

    #include <iostream>
    to_string(<int or double>);

    字符串转化为整数或小数

    函数 转化方式
    stoi(s,p,b) 把字符串s从p开始转换成b进制的int
    stol(s,p,b) 把字符串s从p开始转换成b进制的long
    stoul(s,p,b) 把字符串s从p开始转换成b进制的unsigned long
    stoll(s,p,b) 把字符串s从p开始转换成b进制的long long
    stoull(s,p,b) 把字符串s从p开始转换成b进制的unsigned long long
    stof(s,p) 把字符串s从p开始转换成float
    stod(s,p) 把字符串s从p开始转换成double
    stold(s,p) 把字符串s从p开始转换成long double

取子串

string.substr(startPos, length);
string.substr(startPos);// 取到结束

删除特定位置的字符(串)

string.erase(startIndex, length);

在特定位置插入字符

string.insert(index, <个数>, <字符>);
string.insert(index, <string>);
string.insert(index, <string>, begin, len);// 可以选取部分字符串

寻找字符串位置

  • find(s, index)函数。从pos开始查找字符或者字符串在哪,返回第一次出现的下标(index默认是0,可以不给)
  • 没找到的话会返回一个string::npos

    插入字符串

    string.insert(index, string);

    字符串字面量不支持直接相加,必须有至少一个string类型的量才行

lower_bound函数

  • lower_bound函数在一个有序的序列中查找不小于给定值的最小值
  • 底层是二分查找
  • 如果没找到,会返回数组尾迭代器
    class Solution {
    public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
    for (const auto& row: matrix) {
    auto it = lower_bound(row.begin(), row.end(), target);
    if (it != row.end() && *it == target) {
    return true;
    }
    }
    return false;
    }
    };

  • C++的STL模板库中很多容器都需要谓词,比如有序集合set等,此时定义谓词主要有两种方式

    使用重载运算符作为谓词

  • 新建一个struct
    typedef struct compFun
    {
    bool operator()(const shared_ptr<test>& a, const shared_ptr<test>& b) const
    {
    return a->id>b->id;
    }
    /* data */
    };
  • 传参的时候使用
    set<shared_ptr<test>, compFun> setTest;
  • 上面不需要显式的传入比较函数,因为类型已经包含了比较函数

    使用lambda表达式作为谓词

  • 定义
    function<bool(const test&, const test&)> funComp = [](const test& a, const test& b)
    {
    cout<<"comparing!"<<endl;
    return (a.id)<(b.id);
    };
  • 使用
    set<test, function<bool(const test&, const test&)>> setTest(funComp);
  • 注意,不显式的传入lambda表达式的时候不能向set中插入超过一个元素,否则会因为触发比较而报错what(): bad_function_call
  • 根据leetcode的测试,使用内联函数作为STL标准库的函数的谓词,会比使用static inline但是功能相同的函数作为谓词快一些,比如
    static inline bool cmp(int& a, int& b)
    {
    return a>b;
    }
    // 更快的方法是
    auto cmp = [](int a, int b)->bool{return a>b;};
  • 此处注意使用
    auto cmp = [](int a, int b)->bool{return a<b;};
    // 以下二者结果不同,混用可能得到类型错误
    decltype(cmp);function<bool(int, int)>;
  • 注意对于cpp的优先级队列而言,使用大于号作为比较符得到的是最小堆,小于号是最大堆
  • 优先级队列在使用谓词之前必须先指定存储的数据结构,比如如下代码
    auto cmp = [](int a, int b)->bool{return a<b;};
    priority_queue<int, vector<int>, function<bool(int, int)>> pq(cmp);

修改Linux内核编译选项

  • 参考
  • 注意 这个选项可能找不到
    • picture 0
  • 还需要修改menuconfig中的一个设置
    • picture 3
    • 关闭下面这一条,否则也无法实现在规定的断点位置停下
    • picture 4

进入内核

  • 使用qemu执行如下命令
    qemu-system-x86_64 -s -S \
    -netdev "user,id=eth0" \
    -device "e1000,netdev=eth0" \
    -object "filter-dump,id=eth0,netdev=eth0,file=dump.dat" \
    -kernel $kernel_image \
    -append "root=/dev/am rdinit=sbin/init ip=10.0.2.15::10.0.2.1:255.255.255.0 console=ttyS0" \
    -nographic \
    -initrd $rootfs_img
  • -s参数表示在1234端口接受GDB调试,-S参数表示冻结CPU直到远程GDB输入相应命令
  • 然后输入gdb vmlinux
  • target remote localhost:1234
  • 然后picture 1
  • 然后输入continuec继续执行

    gdb的使用

  • picture 2
    • b <函数名>在函数位置设置断点
  • picture 5
  • 使用step单步执行
  • info locals显示当前环境的局部变量
  • info args显示函数的传入参数等等
  • 有时候一些变量会因为编译过程中被优化掉了导致无法查看,显示为<optimized out>
    • picture 6
  • 可以通过设置为volatile或者防止优化改变上述状况

    一些内核调试经验

    warning trace

  • 如下的trace
    [  111.555855] ------------[ cut here ]------------
    [ 111.560683] WARNING: CPU: 0 PID: 129 at kernel/dma/mapping.c:533 dma_free_attrs+0x4a/0x60
    [ 111.562335] Modules linked in: r4l_e1000_demo(OE)
    [ 111.563461] CPU: 0 PID: 129 Comm: ip Tainted: G W OE 6.1.0-rc1 #38
    [ 111.564123] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
    [ 111.564595] RIP: 0010:dma_free_attrs+0x4a/0x60
    [ 111.565445] Code: 40 00 a9 00 02 00 00 74 21 48 85 d2 74 13 4d 85 c9 74 10 4d 8b 59 10 4d 85 db 74 05 e8 ef c0
    [ 111.566470] RSP: 0018:ffffc90000673a80 EFLAGS: 00000046
    [ 111.567058] RAX: 0000000000000046 RBX: ffff88800425a0d0 RCX: 000000000608b000
    [ 111.567506] RDX: ffff88800608b000 RSI: 0000000000000080 RDI: ffff88800425a0d0
    [ 111.568004] RBP: ffffc90000673a80 R08: 0000000000000000 R09: 0000000000000000
    [ 111.568556] R10: ffffffff82ab6ff0 R11: ffffffff818fa860 R12: 0000000000000080
    [ 111.569160] R13: 0000000000000041 R14: 000000000608b000 R15: ffff88800608b000
    [ 111.569613] FS: 00000000024fa3c0(0000) GS:ffff888007600000(0000) knlGS:0000000000000000
    [ 111.570234] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
    [ 111.570839] CR2: 00000000005305a1 CR3: 0000000006068000 CR4: 00000000000006f0
    [ 111.571537] Call Trace:
    [ 111.572137] <TASK>
    [ 111.573023] rust_helper_dma_free_coherent+0x3c/0x50
    [ 111.573691] _RINvNtCs3yuwAp0waWO_4core3ptr13drop_in_placeINtNtCsa5tTp5JGY9w_14r4l_e1000_demo8ring_buf7RingBu]
    [ 111.574911] ? _RNvXs4_NtNtNtCs3yuwAp0waWO_4core3fmt3num3impxNtB9_7Display3fmt+0x30/0x30
    [ 111.576226] _RNvXs7_Csa5tTp5JGY9w_14r4l_e1000_demoNtB5_9NetDeviceNtNtCsfATHBUcknU9_6kernel3net16DeviceOperat]
    [ 111.577870] _RNvMs2_NtCsfATHBUcknU9_6kernel3netINtB5_12RegistrationNtCsa5tTp5JGY9w_14r4l_e1000_demo9NetDevic]
    [ 111.579888] __dev_close_many+0x124/0x170
    [ 111.580119] __dev_change_flags+0xf5/0x200
    [ 111.580311] dev_change_flags+0x27/0x60
    [ 111.580567] devinet_ioctl+0x4ec/0x600
    [ 111.580943] inet_ioctl+0xec/0x1a0
    [ 111.581246] ? _copy_to_user+0x1d/0x30
    [ 111.581408] ? put_user_ifreq+0x49/0x60
    [ 111.581569] ? sock_do_ioctl+0xae/0x100
    [ 111.581925] sock_do_ioctl+0x3e/0x100
    [ 111.582206] sock_ioctl+0x2ac/0x360
    [ 111.582796] __se_sys_ioctl+0x7c/0xc0
    [ 111.583239] __x64_sys_ioctl+0x1d/0x20
    [ 111.583579] do_syscall_64+0x62/0x90
    [ 111.583833] ? do_user_addr_fault+0x3b5/0x4f0
    [ 111.584116] ? exit_to_user_mode_prepare+0x3c/0xa0
    [ 111.584622] ? irqentry_exit_to_user_mode+0x9/0x20
    [ 111.585147] ? irqentry_exit+0x12/0x40
    [ 111.585467] ? exc_page_fault+0x8e/0x210
    [ 111.585871] entry_SYSCALL_64_after_hwframe+0x63/0xcd
    [ 111.586505] RIP: 0033:0x4afa6f
    [ 111.587134] Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 40
    [ 111.588324] RSP: 002b:00007ffe25bed850 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
    [ 111.589367] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00000000004afa6f
    [ 111.589720] RDX: 00007ffe25bed910 RSI: 0000000000008914 RDI: 0000000000000003
    [ 111.589961] RBP: 0000000000640287 R08: 0000000000000000 R09: 0000000000000000
    [ 111.590853] R10: 00007ffe25beef40 R11: 0000000000000246 R12: 0000000000000003
    [ 111.591466] R13: 00007ffe25bed910 R14: 0000000000000000 R15: 0000000000000000
    [ 111.592168] </TASK>
    [ 111.592524] ---[ end trace 0000000000000000 ]---
  • 是Warning Trace,并不是内核出现错误,出现段错误一般会有segmentation fault或者core dumped
  • 这个仅仅是警告
  • 触发原因是内核dma_free_attrs函数有一个语句WARN_ON(irqs_disabled())
    • 会在irq被禁止的条件下发出一条warning

      但是此时并不能看到Rust部分的调用栈,也没法使用Rust编译的模块中的符号设置断点

  • todo

  • 启动系统的时候在启动之前按F2进入VMWare的BIOS配置界面
  • 移动启动顺序,将CD-ROM移动到第一位
  • picture 0
  • 提示安装Ubuntu的时候取消安装,进入live系统
  • 找到gparted
  • picture 1
  • picture 2
  • 给已经存在的空间扩容
  • picture 3
  • apply
    • picture 4
  • 执行完成之后关机
  • 重启,将启动顺序改回之前的
  • picture 5
  • 系统正常启动

向量化编程

  • 使用向量化即“批量操作”,批量操作在物理生活中也很常见,在计算机中最常见的执行模型就是SIMD(Single Instruction Multiple Data),即对批量的数据同时进行同样的操作以提高效率。
  • Intel向量化操作手册
  • Intel向量化编程示例

    代码案例

    #include <stdio.h>
    #include <stdint.h>
    #include <time.h>
    #include <immintrin.h> // Header for AVX intrinsics

    float dot_productVec(float* a, float* b, int length) {
    __m256 sum = _mm256_setzero_ps();
    int i;
    for (i = 0; i < length; i += 8) {
    __m256 vecA = _mm256_loadu_ps(&a[i]);
    __m256 vecB = _mm256_loadu_ps(&b[i]);
    __m256 mul = _mm256_mul_ps(vecA, vecB);
    sum = _mm256_add_ps(sum, mul);
    }

    // Sum the packed floats
    float result[8];
    _mm256_storeu_ps(result, sum);
    return result[0] + result[1] + result[2] + result[3] + result[4] + result[5] + result[6] + result[7];
    }

    float dot_productLoop(float* a, float* b, int length)
    {
    int i = 0;
    for(int j = 0; j<length;j++)
    {
    i+=a[j]*b[j];
    }
    return i;
    }

    // Function to get the current time in nanoseconds
    int64_t get_time_ns() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000000000LL + ts.tv_nsec;
    }

    void runTrial(int length, int64_t* tVec, int64_t* tLoop)
    {
    if(length == 0){*tVec = 0;*tLoop = 0;return;}

    float* a = malloc(sizeof(float)*length);
    float* b = malloc(sizeof(float)*length);

    // int length = sizeof(a) / sizeof(float);
    for (int i = 0; i<length; i++)
    {
    a[i] = (float)i/10;
    b[i] = (float)(length-i)/10;
    }
    int64_t tVec1, tVec2, tLoop1, tLoop2 = 0;
    tVec1 = get_time_ns();
    float result = dot_productVec(a, b, length);
    tVec2 = get_time_ns();
    tLoop1 = get_time_ns();
    float result2 = dot_productLoop(a, b, length);
    tLoop2 = get_time_ns();

    free(a);
    free(b);

    *tVec = tVec2 - tVec1;
    *tLoop = tLoop2 - tLoop1;
    }


    int main() {
    int maxCnt = 5000;
    int64_t tVecs[maxCnt];
    int64_t tLoops[maxCnt];
    for (int i = 0;i<maxCnt;i++)
    {
    runTrial(i, &tVecs[i], &tLoops[i]);
    printf("length %d completed!\nVec: %ld, Loop: %ld\n", i, tVecs[i], tLoops[i]);
    }

    FILE* file;
    file = fopen("data.csv", "w");
    fprintf(file, "tVec,tLoop\n");
    for(int i = 0;i<maxCnt; i++)
    {
    fprintf(file, "%ld, %ld\n", tVecs[i], tLoops[i]);
    }
    // fprintf(file, "\n");
    // for(int i = 0;i<maxCnt; i++)
    // {
    // fprintf(file, "%ld, ", tLoops[i]);
    // }
    // fprintf(file, "\n");
    fclose(file);

    return 0;
    }


  • 编译指令
    gcc  -o dot_product vecCalc.c -O0 -mavx -lrt

    结果展示

  • picture 0
  • 上图是向量化计算和纯循环计算的时间比较,可见有一些毛刺,尚且不清楚原因
  • 下图是循环计算的时间/向量化计算的时间之商

    结果拟合

  • 向量化计算的斜率为0.9288296964491878,y轴的截距为172.55817372525428
  • 循环计算的斜率为5.851229649697184, y轴截距为236.63629058187928
  • 可见循环计算的斜率几乎是向量化计算的6.30
  • picture 1