菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
330
0

随笔!!

原创
05/13 14:22
阅读数 8825
小技巧
vim 小技巧
  1. vim ctags 自动更新
    noremap <F6> <ESC>:!ctags -R *<CR>:set tags=./tags,./TAGS,tags,TAG<CR>
  2. vim cscope 自动更新
    noremap <F6> <ESC>:!cscope -Rbq *<CR><CR>:cs add cscope.out
  3. vim 统计当前选择文本字数
    1. 选择需要统计的文本
    2. 先后按下键盘下的按键 g -> ctrl + g
  4. vimrc 中 *map 的区别:
    首先需要注意的是 vim 有三种工作模式: visual, insert, normal;
    然后 map 分为两种:map(递归映射),noremap(非递归映射,not recursive map);
    解释一下这里映射的概念:
    如果存在映射: A -> B and B -> C
    如果是递归映射:则在执行 A 的时候最终会执行到 C 代表的命令。
    如果是非递归的: 执行 A 时只会执行到 B。
    然后是作用域的说明:
    n       normal
    v       visual + option
    s       option(在可是模式下 C^ + G 进入)
    x       可视
    o       操作符等
    !       插入和命令行
    i       插入
    I       插入,命令行和 Lang-Arg
    c       命令行
    功能域说明:
            递归映射
    nore    映射
    un      取消映射
    clear   取消所有
    然后就有:
    nmap, xmap, smap, noremap, nnoremap, nunmap ...
    具体参考 :help map
        
  5. tab

    VIM 7.0 开始支持标签页打开方式

    :tabnew [cmd] [file_path]   // 打开一个标签页
    :tabc                       // 关闭当前标签页
    :tabo                       // 关闭其他标签页
    :tabs                       // 显示所有标签叶打开的文件
    :tabp [n] or  gT               // 切换到前一标签页
    :tabn [n] or  gn               // 切换到后一标签页
    :tabfirst                   // 切换到第一个标签签页
    :tablast                    // 切换到最后一个标签页
    :tab split                  // 在新的标签页中打开当前缓冲区文件
    :tabf                       // 搜索当前目录中的文件并打开,如 :tabf img.*
    :tabm [n]                   // 移动到第 n 个标签页处
    :set showtabline=[1,2,3]    // 0 完全不显示标签栏;1 只有用户新建时才显示;2 总是现实标签栏
    :tabdo [cmd]                // 执行命令,将作用到标签叶中,比如: :tabdo %s/food/drink/g 将替换多个标签文件中的 food 为 drink
        
  6. 批量操作
    :%s/pattern/replace/gc      // % 标定范围,也可以用具体多少行到多少行表示,比如当前替换规则适用于 10 到 20 行,则可以这样写 10,20
                            // pattern 正则表达式
                            // replace 需要替换的字符串,可以用 & 表示之前匹配到的目标串
    :%g/pattern/d               // 删除匹配到的行
    :%g!/pattern/d              // 删除除匹配行的所有行
    :%v/pattern/d               // 删除除匹配行的所有行
        
  7. 设置状态栏现实文件完整路径
    在状态栏中,添加%F以显示完整路径,如 set statusline+=%F, 如果你的状态栏还是没有变化,那么你还需要加上 set laststatus=2
    也可以执行 Ctr g 查看
  8. spaceVim 的常用功能
    1. :SPUpdate SpaceVim 打开 SpaceVim 的插件管理器,更新 SpaceVim
    2. :h SpaceVim-options 查看 spaceVim 的所有配置
    3. ~/.SpaceVim.d/init.toml 用户的 SpaceVim 的配置目录
    4. SPC t 进入 SpaceVim 的控制面板
    5. SPC t t 打开标签管理器
    6. SPC f t 或者 F3 打开文件树
    7. SPC v d 打开当前配置
    8. F2 打开关闭语法树
    9. 修改 ~/.SpaceVim/autoload/SpaceVim.vim 文件中 g:spacevim_lint_on_save 参数为 0, 可禁止 SpaceVim 在编写代码时候弹出语法检查看板
  9. 我的 spaceVim 的配置
    ..[$] <()> cat ~/.SpaceVim.d/init.toml 
    [options]
        colorscheme = "gruvbox"
        colorscheme_bg = "dark"
        enable_guicolors = false
        statusline_separator = "nil"
        statusline_iseparator = "bar"
        buffer_index_type = 4
        windows_index_type = 3
        enable_tabline_filetype_icon = false
        enable_statusline_mode = false
        statusline_unicode_symbols = false
        vimcompatible = true
        default_indent = 4
        expand_tab = true
        autocomplete_method = "deoplete"
        snippet_engine = "ultisnips"
    
    
    # Enable autocomplete layer
    [[layers]]
        name = 'autocomplete'
        auto_completion_return_key_behavior = "nil"
        auto_completion_tab_key_behavior = "smart"
        auto_completion_delay = 200
        auto_completion_complete_with_key_sequence = "nil"
        auto_completion_complete_with_key_sequence_delay = 0.1
    
    
    [[layers]]
    name = 'shell'
    default_position = 'top'
    default_height = 30
    
    [[layers]]
        name = "lang#c"
        enable_clang_syntax_highlight = true
    
    [layer.clang_std]
        c = "c11"
        cpp = "c++1z"
        objc = "c11"
        objcpp = "c++1z"
    
    [[layers]]
        name = "lsp"
        filetypes = [
            "c",
            "cpp"
        ]
    
    [layers.override_cmd]
        c = ["clangd", "--limit-results=5"]
        cpp = ["clangd", "--limit-results=5"]
    
终端调试时日志的颜色设置

如果要给输出的内容加元素,那么输出的字符串应该被以下模板包围:( \033[显示方式;前景色;背景色mYOUR_STRING \033[0m ), 如果想使用默认背景则可以选用以下模板( \033[显示方式;前景色mYOUR_STRING \033[0m)。 其中 \033[显示方式;前景色;背景色m 为设置接下来的字符串以何种颜色显示;\033[0m 为结束前面的设置。 接下来详细介绍 显示方式, 前景色,背景色的值。

  • 显示方式
    • 0 终端默认设置
    • 1 高亮显示
    • 4 使用下划线
    • 5 闪烁
    • 7 反白显示
    • 8 不可见
  • 前景色(文字颜色)
    • 30 黑色
    • 31 红色
    • 32 绿色
    • 33 黄色
    • 34 蓝色
    • 35 紫红色
    • 36 青蓝色
    • 37 白色
  • 背景色
    • 40 黑色
    • 41 红色
    • 42 绿色
    • 43 黄色
    • 44 蓝色
    • 45 紫红色
    • 46 青蓝色
    • 47 白色
HACK

注意,一下命令都在 kali 中执行,其他系统请自行判断。

  • 破解 zip file
    使用 fcrackzip 命令, 详情请参考 man fcrackzip
    fcrackzip -u -b -v -l 6-8 xxx.zip 暴力破解,密码长度为 6 - 8 位的 zip 文件
    fcrackzip -u -b -v -c 1aA -l 6 xxx.zip 暴力破解,从字符集 [0-9,a-z,A-Z] 中选取破解字符
    fcrackzip -u -D -p password.set xxx.zip 从 password.set 字典中破解 xxx.zip 文件
    locate wordlists 查看当前系统中存在的字典库
  • 扫描局域网内打开某端口的 IP
    sudo masscan -p 22 --range 192.168.3.0/24 或者 sudo masscan -p 22 --range 192.168.3.1-192.168.3.100
  • 扫描局域网中正在联网的设备
    nmap -sP 192.168.0.1/24 or nmap -sP 192.168.0.* or nmap -sP 192.168.0.1-255。 该方法为使用 ICMP 的 ping 来实现的,但有可能局域网中某些主机禁止了 ping 的响应,有可能检测不出某些联网设备
    nmap -sS 192.168.2.224 or nmap -sS 192.168.2.230-255 or nmap -sS -p 0-30000 192.168.2.230-255。 该类方法 nmap 会通过发送 TCP SYN 数据包支持 TCP 半开放扫描,扫描主机 TCP 端口的开放状态。 SYN 扫描相比与完成三次握手的全开放扫描速度更快,也不易被检测。 nmap 默认会扫描 1-1024 端口和其他一些常用端口,如果要扫描其他端口可以用 -p 选项来指定。
    nmap -sT 192.168.2.230-255 or nmap -sT -p 0-30000 192.168.2.230-255。 nmap 的 Connect 扫描是通过 TCP 完成三次握手来检测的,所以速度相对于 SYN 半开放扫描要慢,但结果更可靠。 默认扫描端口及端口的指定与 SYN 扫描相同。
    nmap -sU 192.168.2.230-255 or nmap -sU -p 0-30000 192.168.2.230-255。 nmap也支持UDP端口的扫描。 UDP相比于TCP协议被防火墙拦截的几率更小。
  1. 最常用到的工具: openssh-server, vim, git, gcc, g++, axel, automake, tree, htop, python2.7(3.4/3.6), python-pip, ipython, ctags/cscope, Vscode, octave, RStudio, Wireshark, cmake, nginx, node
  2. Virtualbox 扩容

    找到虚拟机挂载的盘,然后执行以下命令: vboxmanage modifymedium YOUR.vdi --resize 102400 (YOUR.vid 为你要修改盘大小的路径, --resize 后跟修改后的大小,单位为 M)

  3. ssh 以及其相关
    • ssh 登陆很慢!

      两个步骤,第一步,关闭 gassp 认证,vi /etc/ssh/ssh_config; GSSAPIAuthentication yes; GSSAPIDelegate Credentials no; ;步骤二,关闭 ssh 的 UseDNS,vi /etc/ssh/ssh_config; UseDNS no;(关了这个,在登陆云的时候可能会出问题,谨慎使用。) 。具体格式要百度到处都有了,思路就是这样的。如果这样还不行,那就去吐槽吧!

    • 添加公钥报错:sign_and_send_pubkey: signing failed: agent refused operation

      执行下面两行代码可以解决这个问题:

      eval "$(ssh-agent -s)"
      ssh-add
      
  4. python

    pip 修改更新源码,在 /etc/pip.conf 或者 ~/.pip/pip.conf 加入以下代码:

    [global]
    trusted-host = pypi.doubanio.com
    index-url = https://pypi.doubanio.com/simple
    

    也可以在安装的时候用参数 i 手动指定源地址,如: pip install web.py -i http://pypi.douban.com/simple

  5. 在 Windows gitbash 中 使用 adb push 报错
    在 Windows 的 GitBash 中使用 adb push 推送文件到设备中时可能推送 Windows 系统本地的目录中, 类似于 C:/Users/xxx/AppData/Local/xxx
    原因在于 GitBash 是基于 MSYS shell 开发的,而 MSYS shell 自己存在一些规则会自动补全命令中的路径,参考:https://stackoverflow.com/questions/16344985/how-do-i-pass-an-absolute-path-to-the-adb-command-via-git-bash-for-windows
    解决方法就是在设备的绝对路径前再加上 / 来使 GitBash 防止自动补全到本地路径,如 adb push ota //tmp/ota
  6. 系统在执行 sudo 时出现 “sudo: unable to resolve host xxx”

    如果你重新配置了 /etc/hostname,但又忘记在 /etc/hosts 里加反解,那么你在运行 sudo 是出现该警告是正常的,而且 sudo 会因为尝试去解析你的主机而造成系统变慢或者网络不好的假象。正确的姿势是在 /etc/hosts 里加上 127.0.0.1 your_hostname::1 your_hostname

  7. 我的 chromium 插件(这里只记录名字)

    AdBlock, ChromeReloadPlus, Dark Reader, EditThisCookie, Feedly Subscribe Button, Fika - Reader Mode, Flash Video DownloaderFocus To-Do: Pomodoro Timer & To Do ListGoogle Calendar, Google Translate, Grammarly for Chrome, Hackbar, Headless Recorder, Markdown Here, MeddleMonkey, Nimbus Screenshot & Screen Video Recorder, OneTab, Pomotodo, Proxy SwitchyOmega, Slack Deleter, Speedtest by Ookla, Tampermonkey, The Great Suspender, Video Downloader professional, Vimium, Web Scraper - Free Web Scraping, WorldBrain's Memex

  8. sphinx
    1. 修改主题
      1. 首先修改 sphinx 的 conf.py 文档中的两个变量, html_theme = "theme_name", html_theme_path = ["./theme"]。
      2. 在 conf.py 文件目录下创建 theme 目录。
      3. sphinx 安装目录下的 themes/sphinxdoc 目录复制到 theme 目录并重命名为主题的名字 theme_name。
      4. 在 theme_name 目录下有一个 theme.conf 的文件,该文件是 sphinx 生成样式的模板文件,可在这里面加载自己的样式 css 或者 js,
      附:
      如果免的麻烦,可以直接修改上叙步骤 1 中的两个变量重定向到该主题.如何修改的详细步骤请参考本节下面的参考连接[link1]。
              
  9. systemd
    • systemd 查看 journalctl 占用空间大小 journalctl --disk-usage
    • 手动清理日志 journalctl --disk-usage 或者 rm -rf /var/log/journal/*
    • 自动清理: 限制日志保留一周 journalctl --vacuum-time=1w, 限制日志大小 journalctl --vacuum-size=500M
  10. Linux 统计当前文件数目
    // 统计当前文件夹数目
    ls -l |grep "^d"|wc -l
    // 统计当前文件数目
    ls -l |grep "^-"|wc -l
    // 递归统计当前文件夹下的文件数目
    ls -lR|grep "^-"|wc -l
    // 递归统计当前文件夹下文件夹数目
    ls -lR|grep "^d"|wc -l
    // 统计当前文件夹下文件以及目录数目
    ls -lR|grep -E "^d|^-"|wc -l
        
  11. FFmpeg 使用方法汇总
    // 将视频拆分成图片
    ffmpeg -i xxx.mp4 %d.jpg
    
    // 手动指定每秒生成图片数量
    ffmpeg -i vid.mp4 frame%03d.png -r 20
    
    // 将散列的图片拼凑成视频
    ffmpeg -i frame%3d.png out.mp4
    
    // 分离视频音频流
    ffmpeg -i input_file -vcodec copy -an output_file_video  //分离视频流
    ffmpeg -i input_file -acodec copy -vn output_file_audio  //分离音频流
    
    // 视频解复用
    ffmpeg –i test.mp4 –vcodec copy –an –f m4v test.264
    ffmpeg –i test.avi –vcodec copy –an –f m4v test.264
    
    // 视频转码
    ffmpeg –i test.mp4 –vcodec h264 –s 352*278 –an –f m4v test.264              //转码为码流原始文件
    ffmpeg –i test.mp4 –vcodec h264 –bf 0 –g 25 –s 352*278 –an –f m4v test.264  //转码为码流原始文件
    ffmpeg –i test.avi -vcodec mpeg4 –vtag xvid –qsame test_xvid.avi            //转码为封装文件
    //-bf B帧数目控制,-g 关键帧间隔控制,-s 分辨率控制
    
    // 视频封装
    ffmpeg –i video_file –i audio_file –vcodec copy –acodec copy output_file
    
    // 视频剪切
    ffmpeg –i test.avi –r 1 –f image2 image-%3d.jpeg        //提取图片
    ffmpeg -i vid.mp4 -y -f image2 -t 0.001 -ss 10 -s 1920x1080 haha.png //提取图片
    
    ffmpeg -ss 0:1:30 -t 0:0:20 -i input.avi -vcodec copy -acodec copy output.avi    //剪切视频
    //-r 提取图像的频率,-ss 开始时间,-t 持续时间
    
    // 视频录制
    ffmpeg –i rtsp://192.168.3.205:5555/test –vcodec copy out.avi
    
    // YUV序列播放
    ffplay -f rawvideo -video_size 1920x1080 input.yuv
    
    // YUV序列转AVI
    ffmpeg –s w*h –pix_fmt yuv420p –i input.yuv –vcodec mpeg4 output.avi
    
    转换成 Gif 图
    ffmpeg -i vid.mp4 -vframes 5 -y -f gif out5.gif        // 将视频的前五帧转换成 Gif 图
    ffmpeg -ss 00:00:00.000 -i vid.mp4 -pix_fmt rgb24 -r 10 -s 320x240 -t 00:00:10.000 out6.gif
    
    // v4l2 列举所有设备
    v4l2-ctl --list-devices
    
    // v4l2 列举设备支持的编码格式
    v4l2-ctl -d /dev/video0 --list-formats
    
    // v4l2 列举设备编码格式支持的分辨率
    v4l2-ctl -d /dev/video0 --list-framesizes=H264/MJPG/YUYV
    
    // 综合上叙两者信息 ffmpeg 给出的方案
    ffmpeg -f v4l2 -list_formats all -i /dev/video0
    
    // ffmpeg 从 video 导出视频
    ffmpeg -f v4l2 -framerate 25 -video_size 640x480 -i /dev/video0 output.mkv
    
    // ffmpeg 指定格式导出视频
    ffmpeg -f v4l2 -input_format mjpeg -i /dev/video0 -c:v copy output.mkv
    ffmpeg -f v4l2 -input_format yuyv422 -i /dev/video0 -c:v libx264 -vf format=yuv420p output.mp4
    ffmpeg -f v4l2 -input_format yuyv422 -framerate 30 -video_size 640x480 -i /dev/video0 -c:v libx264 -vf format=yuv420p output.mp4
    
    
    // 下面是 windows 相关的指令
    // 列出当前系统中存在的 A/V 设备
    ffmpeg -list_devices true -f dshow -i dummy
    
    // 查看指定设备支持的配置项
    ffmpeg -f dshow -list_options true -i video="HP Truevision HD"
    
    // 捕获音视频流
    ffmpeg -f dshow -i video="AJA Capture Source":audio="AJA Capture Source"
    ffmpeg -f dshow -s/-video-size 1080*1920  -framerate 60 -vcodec mjpeg -i video="AJA Capture Source" \
         -f dshow -ac 2 -ar 8000 -i audio="AJA Capture Source"  \
         output.mkv
    // 如果发现 list 的 video 设备存在多个同名设备,ffmpeg 可以支持选定某个 pin 进行采集
    // 具体配置请参考: https://www.ffmpeg.org/ffmpeg-devices.html#dshow
    ffmpeg -f dshow -audio_pin_name "Audio Out" -video_pin_name 2 、
        -i video=video="@device_pnp_\\?\pci#ven_1a0a&dxxxxx..xxxxxx23196}\{xxx...xxxx}":audio="Microphone"
    
    
    // 以下是一些参考网站,之前的一些因为年代久远就没有列举在这里了
    // https://superuser.com/questions/494575/ffmpeg-open-webcam-using-yuyv-but-i-want-mjpeg
    // https://trac.ffmpeg.org/wiki/Capture/Desktop
    // https://www.ffmpeg.org/ffmpeg-devices.html#dshow
    // https://www.bogotobogo.com/VideoStreaming/ffmpeg_webcam_capture_Windows_Linux.php
    
  12. mp4box
  13. Linux 安装 dota2
    1. 1. 安装 staem
    2. 2. 从 http://store.steampowered.com/app/570/?cc=us 处打开开始安装。
  14. virtualbox 挂载实体硬盘

    首先用 lsblk 找到你需要挂在的硬盘。

    然后用 sudo vboxmanage internalcommands createrawvmdk -filename ./sugr_cop_rawdisk.vmdk -rawdisk /dev/sdc -relative 来生成实体硬盘的维护信息。

    其中 -filename 参数用于指定存放实体硬盘维护信息的文件路径,之后创建虚拟机的时候也就是直接挂载这个文件,用于启动;

    -rawdisk 参数用于指定你想要挂载的实体硬盘。需要注意的是这个硬盘一定是普通用户可读的,因为一盘你在 Linux 上运行虚拟机都是在普通用户运行,所以如果你挂载的硬盘是 /dev/sdc 那么你需要执行这条命令( sudo chmod 666 /dev/sdc )来开放普通用户对硬盘的读写权限。

    最后按照下面的步骤你就可以成功的将你实体硬盘上的系统跑起来了:(假设你的系统是 ubuntu 64)open virtualbox Manager -> Machine -> new -> name( your machine name ) -> type( Linux ) -> Ubuntu (64-bit) -> next -> 内存分配 -> next -> select : Use an existing virtual hard disk file -> select : 你之前创建的文件 -> create

  15. 多线程下载工具

    多线程下载工具 axel,mwget。(暂时还没有看到那个比较好的评价)

  16. 查看网卡的 UUID

    NetworkManager 中提供的 nmcli 这个程序可以查看网卡的 UUID,用法:nmcli con | sed -n '1,2p'

  17. GRUB
    1. 【IBM】引导系统
    2. 【IBM】引导加载程序之争:了解 LILO 和 GRUB
    3. Grub 简介
    4. Grub 入门
  18. HTTPS

    Let’s Encrypt 是一个于2015年推出的数字证书认证机构,将通过旨在消除当前手动创建和安装证书的复杂过程的自动化流程,为安全网站提供免费的SSL/TLS证书。(以上的信息来自 wikipedia, 更详细的信息请去 wikipedia 获取)

    如果你要用 certbot 你可以先按照他 官网 的说明来安装好 certbot 的环境。基本上类 unix 平台上的 apache2, nginx, haproxy... 都支持,

  19. Mysql 更改密码

    首先停掉 mysql 服务 /etc/init.d/mysqld stop,然后运行以下命令 mysqld_safe --skip-grant-tables&;再这之后,直接运行 mysql 可以进入 mysql 终端界面;到这之后运行下面的命令就可以更新你的密码了: update user set authentication_string=password('YOUR_PASSWOR') where user='root';

  20. mysql 报 No directory, logging in with HOME=/

    执行以下命令

        sudo service mysql stop
        sudo usermod -d /var/lib/mysql/ mysql
        sudo service mysql start
        
  21. Shell
    • 在命令行生成随机数字:使用 $$RANDOM 变量
    • 使用 /dev/random 或者 /dev/urandom, 如 cat /dev/urandom | head -n 10 | md5sum
    • 使用 Linux UUID 机制,如 cat /proc/sys/kernel/random/uuid
  22. linux /proc 下文件的详细说明 man 5 proc
  23. /proc/diskstate 的格式说明文档 Linux 内核源码工程的 Documentation/iostats.txt 文件
  24. AS 控制台乱码
    快速按下 shift 按键,搜索 Edit Custom VM Options..., 并在其中配置如下选项 -Dfile.encoding1=UTF-8, 该配置文件在 ${AS_CONFIG_PATH}/AndoirdStudio4.0/config/studio64.exe.vmoptions
  25. Android 工程升级到 1.8
    在 build.gradle 中加入以下配置:
    android{
        compileOptions{
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
        
  26. Execution failed for task ':app:checkDebugDuplicateClasses' ...
    首先确认你发生冲突的包是哪些,然后根据实际情况判断做法, 如果你的系统是定制系统,有可能为了编译通过你需要引入你司定制类,而你司定制类与原生依赖有冲突,则可能出现此问题, 而如果出现此问题,的解决方案就是与你司定制依赖的开发人员协商,看如何处理。 比如说 push 定制依赖支持你所应用功能集,或者 push 依赖独立冲突类。 还一种解决方法是跟据你的实际情况采用 compileOnly( provided )选项来处理依赖。
  27. 电脑设备带宽列表
    PCIe
    其他请参考 电脑设备带宽列表
  28. 硬件虚拟化项目
    VMWare, Xen, KVM
  29. 生成各格雷码的函数
    def bitcode2graycode(x):
        return x^(x>>1)
    
    def graycode2bitcode(x):
        x = x ^ (x>>16)
        x = x ^ (x>>8)
        x = x ^ (x>>4)
        x = x ^ (x>>2)
        x = x ^ (x>>1)
        return x
    
  30. 设计模式
    设计模式无处不再,不仅仅是在面向对象的代码编写中。如果你打开 Linux 内核, ffmpeg, chromium,你会看到很多模式很一致的结构代码,个人觉得设计模式是为了解决一类问题从而 Build 的一个模型,随着经验的增加也会创造出很多属于自己的模式
    不过对于面试过程中所用到的,可以参考此书: https://refactoringguru.cn/design-patterns
  31. 前端开发

    响应式 Web 设计, 随着移动设备的普及,如何让用户通过移动设备浏览您的网站获得良好的视觉效果,已经是一个不可避免的问题了。响应式 Web 设计就是为实现这个目的的有效方法。响应式 Web 设计是一个让用户通过各种尺寸的设备浏览网站获得良好的视觉效果的方法。例如,您先在计算机显示器上浏览一个网站,然后再智能手机上浏览,智能手机的屏幕尺寸远小于计算机显示器,但是你却没有感觉到任何差别,两者的用户体验几乎一样,这说明这个网站在响应式设计方面做得很好。

  32. 敏捷开发
    1. 搭建 jenkins 持续集成平台
              1. 安装 jenkins
                  1. openjdk(版本请参考 jenkins 需求)
                  2. daemon(版本请参考 jenkins 需求)
                  3. tomcat(版本请参考 jenkins 需求)
                  4. 下载 jenkins deb 安装包,运行 sudo dpkg -i jenkins.deb
              5. 配置 jenkins
                  1. 确定 jenkins 服务有没有起来,默认的端口是 8080,修改默认端口请到 /etc/default/jenkins 修改 HTTP_PORT 环境变量,修改之后记得重启。
                  2. 如果还是没有启动服务,请查看端口是否被占用,依赖是否都有安装。服务是否启动成功。
                  3. 在 /var/lib/jenkins/secrets/initialAdminPassword 中拷贝 jenkins 的初始密码进入 jenkins 管理界面。
                  4. 可在浏览器里面访问 http://jenkins_host:jenkins_port/jenkins/restart 重启 jenkins.
          
  33. 计算机原理(名词解释)
    ABI (Application Binary Interface)
    AL( 8bit ) + AH( 8bit ) == AX( 16bit ) == EAX( 32bit ) 累加器(Accumulator)
    BL( 8bit ) + BH( 8bit ) == BX( 16bit ) == EBX( 32bit ) 基地址寄存器(Base Register)
    CL( 8bit ) + CH( 8bit ) == CX( 16bit ) == ECX( 32bit ) 计数寄存器(Count Register)
    DL( 8bit ) + DH( 8bit ) == DX( 16bit ) == EDX( 32bit ) 数据寄存器(Data Register)
    BP( 16bit ) == EBP( 32bit ) 堆基指针 (base pointer)
    SI( 16bit ) == ESI( 32bit ) 变址寄存器 ( index register )
    DI( 16bit ) == EDI( 32bit ) 变址寄存器
    SP( 16bit ) == ESP( 32bit ) 堆栈顶指针(stack Point)



性能
网络/性能
  • 一个服务器的一个端口理论支持多大连接数
    答: 源地址(IPV4 /IPV6) * 源端口范围。 比如说 IPV4 有 (2^32 * 2^16 - n) 个,为什么要减 n,是因为一些专有(保留)地址和保留端口要除开。
  • 如何提高一个服务器的并发
    修改文件可打开数量,系统级修改 file-max 属性,临时修改执行命令 echo "YOUR_NUMBERS" >/proc/sys/fs/file-max, 永久修改,修改 /etc/sysctl.conf 文件,添加一行属性 fs.file-max = YOUR_NUMBERS, 修改之后最好运行网络重启 /etc/rc.d/init.d/network restart 或者 systemctl restart NetworkManager, 具体依据你的系统而定。
    如果是用户级别,则修改 /etc/security/limits.conf, 具体的使用方法请参考 man limits.conf 中的内容。 比如限制 test 用户最大仅能打开 1000 个文件, user hard nofile 1000
    进程级别修改 fs.nr_open, 和修改 fs.file-max 类似
    关于 fs 下的更多配置参考: Documentation/sysctl/fs.txt
  • 修改接收缓冲区大小
    查看接受缓冲区大小: sysctl -a | grep rmem
    查看发送缓冲区大小: sysctl -a | grep wmem
    关于具体属性的描述请参考 Documentation/sysctl/net.txt, 属性的配置方法,参考 fs.file-max
  • 系统可配置项
    sysctl -a | grep -E "tcp|net"
    建议: 一般为了使 10G 的网卡能得达到全速,最大的输入输出缓冲区最好设置大小超过 16M,其中涉及的参数有 net.core.rmem_max, net.core.wmem_max
    启用 tcp 缓冲区自动调节 tcp_moderate_rcvbuf, 其中要指定 net.ipv4.tcp_rmem, net.ipv4.tcp_wmem, 如 net.ipv4.tcp_rmem = 1024 65536 16777216
    tcp 半开连接挤压队列大小: tcp_max_sync_backlog = 4096, accept 监听队列大小 net.core.somaxconn = 1024
    每 cpu 网络设备积压队列长度: net.core.netdev_max_backlog
    查看支持的 tcp 阻塞控制算法:sysctl -a | grep "congestion_control“,指定算法 sysctl net.ipv4.tcp_congestion_control = cubic
    启用 SACK 扩展: net.ipv4.tcp_sack
    启用 FACK 扩展: net.ipv4.tcp_fack
    重用 TIMEWAIT 会话:net.ipv4.tcp_tw_resue, 还一个不太安全的属性 net.ipv4.tcp_tw_recycle
  • 修改 TX 队列长度
    ifconfig INTERFACE txqueuelen QUEUELEN_NUMBS
  • 其他优化网络性能的方法
    巨型帧,如果基础网络支持的话
    链路聚合,将多个物理网络接口的带宽聚合为一个接口
    socket 参数调节(下面有简要介绍)
  • 查看活动连接数
    ss -n | grep ESTAB | wc -l
  • 网络性能分析工具
    netstat 多种网络栈和接口统计信息
    ss socket 统计信息
    lsof 统计进程打开文件
    sar 统计信息历史
    ifconfig 接口配置
    ip 网络接口统计信息
    nicstat 网络接口吞吐量和使用率
    ping 测试网络连通性
    traceroot 测试网络路由
    pathchar 确定网络路径特征
    tcpdump 网络数据包嗅探器
    Wireshark 图形化网络数据包检查器
    DTrace / perf TCP/IP 跟踪,连接,数据包,丢包,延时
    nethogs / iftop 网络吞吐量
    /proc/net 网络信息统计文件
  • 关于 socket 的设置
    获取 socket 的配置可以参考 man getsockopt, 设置 socket 的属性可以参考 man setsockopt, 其中 linux 中支持的属性可以参考 include/uapi/asm-generic/socket.h

    下面是一些 SOL_SOCKET 常用属性的说明:
    SO_BROADCAST 设置是否允许发布广播数据,常用在 UDP 广播,多播场景
    SO_DEBUG 将该 socket 设置为 Debug 模式,其会记录 socket 建立时候的参数, 采用的具体协议,以及出错的代码都,记录位置
    SO_DONTROUTE 不查找路由
    SO_ERROR 获取 socket 错误信息
    SO_KEEPALIVE 设置保持链接属性,其中配套的可能还要设置发送心跳包的 interval 和次数。
    SO_OOBINLINE 带外数据放入正常数据流
    SO_SNDTIMEO 设置 socket 的发送超时
    SO_RCVTIMEO 设置 socket 的接收超时
    SO_RCVBUF 设置 socket 的接收缓冲区大小,如果你能快速的处理完接收的数据,而又不希望经历由系统缓冲区到 socket 缓冲区的多次拷贝影响性能,则可以把接收缓冲区设置为 0
    SO_SNDBUF 设置 socket 的发送缓冲区大小,如果不希望经历由系统缓冲区到 socket 缓冲区的多次拷贝影响性能,则可以把发送缓冲区设置为 0
    SO_RCVLOWAT 设置最低接收缓冲区
    SO_SNDLOWAT 设置最高接收缓冲区
    SO_TYPE 获取 sokcet 类型
    SO_BSDCOMPAT 与 BSD 兼容
    SO_CONDITIONAL_ACCEPT 设置为 ture 则 socket 不会主动的 accept 客户端的连接请求, 除非服务端主动调用 accept() 明确指示,参考 这里
    SO_REUSEADDR 调用 closesocket 之后想重用该 socket 则对 socket 设置该选项为 ture;
    SO_DONTLINGER 如果想在 closesocket 之后不经历 TIMEWAIT 过程则,设置此项为 ture;
    SO_LINGER, 如果想在关闭 socket 之后等待数据发送完成之后才真正关闭, 则可以将 struct linger 中 l_onoff 属性设置为 1,而 l_linger 属性指定等待的超时时长。 另外,如果将 l_onoff 设置为 0 则和 SO_DONTLINGER 的功能类似。

  • /proc/net/snmp
  • /proc/net/netstat
其他
  1. 性能分析方法 USE (Utilization Saturation and Errors Method)
    USE 方法为分析软件性能问题的一个重要方法,其精髓可总结成一句话: 对于所有资源,查看他的使用率,饱和度和错误, 其三个维度主要是指:
    • Utilization 使用率: 在规定时间内,资源用于服务的时间百分比。虽然资源繁忙,但资源还是有能力接受更多的工作,不能接受更多工作则以为着资源已经达到饱和
    • Saturation 饱和度:资源在一定的时间内不能接受更多的工作,通常还会有工作队列长度的限制, 因此不仅仅是要判断是否饱和度已经达到 100%,还要判断等待队列是否超出目标值
    • Error 错误:错误事件个数和具体类型(通常会存在各种日志里面,或者错误计数器中)

    而是使用 USE 方法分析问题的过程可参考如下流程:
  2. Hash 锁
    在设计程序的过程中,为了保证资源操作的一致性而给操作资源的方法加上了同一个锁,但某些资源可能因为资源本身的特性,从物理隔绝了操作, 这时我们可以引入 hash 锁,更具特性来选择性的选择 Hash Table 中的锁,从而避免不必要的程序阻塞导致的性能问题。
利器
  • prometheus: Prometheus 受启发于 Google 的 Brogmon 监控系统(相似的 Kubernetes 是从 Google 的 Brog 系统演变而来), 从 2012 年开始由前 Google 工程师在 Soundcloud 以开源软件的形式进行研发, 并且于 2015 年早期对外发布早期版本。 2016 年 5 月继 Kubernetes 之后成为第二个正式加入 CNCF 基金会的项目, 同年 6 月正式发布 1.0 版本。2017 年底发布了基于全新存储层的 2.0 版本,能更好地与容器平台、云平台配合。 其中文的介绍可参考 https://yunlzheng.gitbook.io/prometheus-book/introduction
参考文档
Linux
Archlinux
Q&A
  • 安装
    参考本小节参考文档中的安装指南
    首先是 UEFI 还是 BIOS 的问题,因为这个可能会设计到后面分区的问题 BIOS+GPT+GRUB 大约 1M 就够了,通常我们会给 boot 分大约 200M, 但是如果是 UEFI 推荐引导就超过 256M ,因此最好先看一看自己系统是不是只支持 UEFI ,像苹果那种,我之前就踩了这个坑。
    有两条建议,还有就是在进行安装的时候建议在安装阶段就把必要的像网络管理工具和编辑工具安装好,比如说我就非常不适应 vi 因此还是先把 vim 装上吧。 另外就是可以升级了之后再退出安装。这样会避免一些不必要的麻烦。
  • 如何登陆 Windows 远程管理界面
    安装 freerdp sudo pacman -S freerdp ( ubuntu 安装 sudo apt install freerdp2-x11
    运行以下命令 xfreerdp /v:1.2.3.4 /u:Administrator /p:PASSWORD
  • 安装 deepin-screenshot-copy
    因为最新的 deepin-screen-recorder 出现了一些不兼容的情况,请参考 https://wiki.archlinux.org/index.php/Screen_capture, 因此在这里我们需要安装 https://aur.archlinux.org/packages/deepin-screenshot-copy-patch/
    在命令行执行以下脚本安装
    cd /tmp/; git clone https://aur.archlinux.org/deepin-screenshot-copy-patch.git; cd /tmp/deepin-screenshot-copy-patch; makepkg -si
        
参考链接
  1. Getting and installing Arch(其中介绍了怎么制作安装介质)
  2. Arch Linux 安装指南[2016.01]
  3. 文件系统,以及支持文件系统所需要安装的包
ubuntu
Q&A
  • 查找软件安装位置: dpkg -S
  • 以及安装了那些软件: dpkg -L
  • ubuntu 包管理

    查看当前安装的包dpkg -l,卸载软件时卸载之依赖包(如果此依赖包还被其他依赖,将不被卸载),请使用 aptitude 命令(其中 remove 是删除软件以及其依赖,purge,删除软件,以及其依赖,以及其配置文件), apt-getautoremove 参数也可以用来卸载当前指定包以及其依赖。但真心不好用,参考一下 aptitude , pacman 就知道。不过 ubuntu 上的包资源很全面。

kali Linux
Q&A
  • 升级更新
    同 ubuntu apt-get update; apt-get upgrade
  • Kali 源
    在 /etc/apt/source-list 中添加以下两行
    deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
    deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
        
  • virtualbox 全屏问题
    执行以下代码修复 apt-get update; apt-get install -y virtualbox-guest-x11; reboot, 参考 此处
  • 分区规划
    如果跟分区与 home 分区分开,建议至少保证根分区大于 11G 否则后续更新会比较吃力,我自己的是 20G, 安装在 virtualbox 上,应付日常开发足矣。
参考文档
raspberry pi
Q&A
  • 清华源( Debian 10( buster ) )
    # 编辑 `/etc/apt/sources.list` 文件,删除原文件所有内容,用以下内容取代:
    deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main non-free contrib rpi
    deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main non-free contrib rpi
    
    # 编辑 `/etc/apt/sources.list.d/raspi.list` 文件,删除原文件所有内容,用以下内容取代:
    deb http://mirrors.tuna.tsinghua.edu.cn/raspberrypi/ buster main ui
    
  • 使能 Camera
    在终端运行下面这条命令: sudo raspi-config,然后顺着下面的步骤设置就可以了: Select : 5 Interfacing Options Configure connections to peripherals --> Select : P1 Camera Enable/Disable connection to the Raspberry Pi Camera --> Select yes --> sudo reboot
    检查是否安装成功执行以下命令: raspistill -o image.jpg, 该命令会在当前目录保存一个从摄像头撷取的一张图片,并保存到 image.jpg 中。
  • 安装了 Camera 之后 无法发现 /dev/video 节点
    如果你按照上面的步骤打开了树梅派的 Camera 功能,那么你可能在 /dev/目录下找不到 video[x] 节点。我查到的原因是树莓派中的camera module是放在/boot/目录下以固件的形式加载的,不是一个标准的v4l2的摄像头ko驱动。所以加载起来之后会找不到/dev/video[x] 的设备节点,这是因为这个驱动是在底层的,v4l2这个驱动框架还没有加载,所以要在 /etc/modules-load.d/modules.conf 里面添加一行 bcm2835-v4l2,然后重启,就可以了。(这句话意思是在系统启动之后会加载这个文件中模块名,加载模块会在 /lib/modules 中。)
    # sudo vim /etc/modules-load.d/modules.conf
    
        # 在文尾添加下面这行
        bcm2835-v4l2
    
    # find /lib/modules/ -name "*bcm2835-v4l2*"
    
        /lib/modules/4.9.35+/kernel/drivers/media/platform/bcm2835/bcm2835-v4l2.ko
        /lib/modules/4.9.35-v7+/kernel/drivers/media/platform/bcm2835/bcm2835-v4l2.ko
    
    
  • 对 exfat 支持
    安装 apt-get install exfat-fuse
  • 对 ntfs 支持
    安装 apt-get install ntfs-3g
  • raspberry pi 发射热点 参考: https://www.raspberrypi.org/documentation/configuration/wireless/access-point-routed.md
参考资料
  • [1]raspberry pi 应用程序仓库
  • [2]树莓派raspbian系统下,如何开启摄像头/camera模块,并使用简单的命令
  • [3]Camkit ( 使用C语言写成,包含了从:图像采集-->色彩转换-->H264编码-->RTP打包-->网络发送的全套接口 )
  • 清华大学镜像站
Linux 环境使用备忘
Q&A
  • 解决 mount nfs 的时候出现 mount: wrong fs type, bad option, bad superblock on **** 的问题。

    安装 nfs-utils 。

  • awesome 中配置 PrtScr 键值功能
    1. 获取 PrtScr 的键值
      安装截屏工具 pacman -S scrot
      安装 xev 或者 evtest pacman -S xorg-xev
      运行 xev 并且按 PrtSrc 你会收到如下信息, Print 则是该按键的名字
      KeyPress event, serial 33, synthetic NO, window 0x2400001,
      root 0x1a1, subw 0x0, time 58322539, (691,875), root:(692,916),
      state 0x0, keycode 107 (keysym 0xff61, Print), same_screen YES,
      XLookupString gives 0 bytes: 
      XmbLookupString gives 0 bytes: 
      XFilterEvent returns: False
              

      在 awesome 的配置文件( ~/.config/awesome/rc.lua )中配置该功能即可:
      263 globalkeys = gears.table.join(
      ...
      383     awful.key({ }, "Print", function (),
      384         awful.util.spawn("scrot -e 'mv $f ~/Pictures/ 2> /dev/null'") end)
      ...
              

  • oh_my_zsh zsh 开发中的一个利器
    以下是我的 ~/.zshrc 的配置
    export ZSH=/home/mojies/.oh-my-zsh
    ZSH_THEME=mikeh
    plugins=(git)
    source $ZSH/oh-my-zsh.sh
    
    alias vi='vim -O'
    alias CP='rsync -avh --progress -v --links'
    alias gitd='git difftool'
    alias rm.origin='/usr/bin/rm'
    alias rm='trash-put'
    alias rm.ls='trash-list'
    alias rm.rm='trash-rm'
    alias rm.recovery='trash-restore'
    alias rm.empty='trash-empty'
    alias g++11='g++ -std=c++11'
    alias g++14='g++ -std=c++14'
    alias g++17='g++ -std=c++17'
    alias g++20='g++ -std=c++2a'
    alias top='glances'
    alias AS='android-studio'
    alias df='duf'
    
    # awsome need pull up startx first
    isrunning startx; if [ $? -ne 0 ]; then startx;  fi
    
    export WORKON_HOME='~/.virtualenvs'
    source /usr/bin/virtualenvwrapper.sh
    
  • zsh_autosuggestion 一个在命令行自动补全命令的工具, 建议终端开 256 色,
  • 释放缓存操作
    echo 1 > /proc/sys/vm/drop_caches 释放页缓存
    echo 2 > /proc/sys/vm/drop_caches 释放 dentries 和 inode 缓存
    echo 3 > /proc/sys/vm/drop_caches 释放所有缓存(以上三种)
参考资料
  1. oh-my-zsh 仓库地址
  2. oh-my-zsh 官网
  3. zsh-autosuggestions
知识点
  • POSIX 标准
    POSIX(Portable Operating System Interface for Computing Systems)是由IEEE 和ISO/IEC 开发的一簇标准。该标准是基于现有的UNIX 实践和经验,描述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。它是在1980 年早期一个UNIX 用户组(usr/group)的早期工作的基础上取得的。该UNIX 用户组原来试图将AT&T 的系统V 和Berkeley CSRG的BSD 系统的调用接口之间的区别重新调和集成,从而于1984 年产生了/usr/group 标准。1985 年,IEEE操作系统技术委员会标准小组委员会(TCOS-SS)开始在ANSI 的支持下责成IEEE 标准委员会制定有关程序源代码可移植性操作系统服务接口正式标准。到了1986 年4 月,IEEE 就制定出了试用标准。第一个正式标准是在1988 年9 月份批准的(IEEE 1003.1-1988),也既以后经常提到的POSIX.1 标准。
    更加详细的资料请参考:这里
  • Linux 内核知识点(摘抄自《性能之巅》)
    操作系统层级 系统调用 多任务处理 进程 进程属性 虚拟内存 全局文件系统 文件系统权限 设备文件 缓冲区高速缓存 换页虚拟内存 按需换页 快文件系统(fast file system, FFS) TCL/IP 协议栈 套接字 VFS NFS 页缓存 统一页缓存 slab 分配器 DFS DTrace( atrace, eBPF ) 资源 fork
  • Linux 与性能有关知识点(摘抄自《性能之巅》)
    CPU 调度级别 I/O 调度级别 TCP 拥塞 Overcommit Futex 巨型页 Oprofile RCU epoll 模块 I/O 调度 DebugFs Cpusets 自愿内核抢占 inotify blktree splice 延时审计 IO 审计 DynTicks SLUB CFS cgroups latencytop Tracepoints perf 透明巨型页 Uprobs KVM
  • 在分析一个应用的性能之前因先熟悉该应用的相关知识
    功能, 操作, CPU 模式, 配置, 指标, 日志, 版本, Bugs List, 社区, , 专家
  • 线程调度策略
    • SCHED_OTHER 分时调度策略
    • SCHED_FIFO 实时调度策略,先到先服务
    • SCHED_RR 实时调度策略,时间片轮转
  • OOM
    各个进程的 oom_adj 调节路径 /proc/${PID}/oom_score_adj, 数值越小,说明等级越重要,数值范围 [-17, 15], 当值为 -17 的时候该进程将不会纳入 oom 的考量范围。
    进程的 score 路径 /proc/${PID}/oom_score
    oom 的决策源码路径: linux/mm/oom_kill.c
    因为 Linux 进程的创建机制,子进程会继承父进程的 adj 的值
  • 写时拷贝( copy-on-write, COW )
    当 fork 线程的时候,fork 中线程的数据结构为夫进程的引用,而当在执行过程中真正要修改某项参数的时候才会分配新的内存(创建新的副本)
  • 进程的生命周期
    进程创建的时候为 idle,随后会被内核转移到 ready-to-run 运行队列中,只有 ready-to-run 的进程可以进入 on-proc 状态
    正在运行的进程可能因为阻塞,或者主动睡眠进入 sleep 状态,等到资源就绪之后会重新进入 read-to-run 的状态
    子进程调用 exit 之后会进入 zombie 状态
    假设子进程结束时父进程仍存在,而父进程 fork() 之前既没设置 SIGCHLD 信号处理函数调用 waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为常驻 zombie 进程
    zombie 进程即使是 root 身份 kill -9 也不能杀死。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为"孤儿进程",过继给 1 号进程 init ,init 始终会负责清理僵尸进程。
  • 系统调用
    通过 man syscalls 可以查看系统支持的系统调用,在用 man ${func} 可以查看用法,其次还可参考 《UNIX 环境高级编程》 或者内核源码
  • 绑定一个进程到一个 CPU
    如果是命令行,可以通过 taskset 实现,如 taskset -pc 2-3 1234 将进程号为 1234 的 进程绑定到 2-3 号 CPU 上
    如果是在代码里面,可以通过 sched_setaffinity 系统调用接口实现,头文件为 sched.h, 使用 extern long sched_getaffinity(pid_t pid, struct cpumask *mask); 来获取进程被固定到那些 CPU 上, 使用 extern long sched_setaffinity(pid_t pid, const struct cpumask *new_mask); 将进程固定到某些 CPU 上, 其中 pid 为进程号,如果你设置为 0, 则设置默认为当前进程, cpumask 被定义为 typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;, 而 DECLARE_BITMAP 又被定义为 #define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)], 不同内核版本之间有不同的区别,具体请参考 include/linux/sched.h
    如果想要绑定线程到 CPU 上,则使用 pthread.h 中的 pthread_setaffinity_np 方法,使用方式和进程的类似, 请直接查询 pthread.h 头文件。
    请注意如果你把一个进程固定到了一个 CPU 上,其衍生的
  • 创建独占 CPU 组
    这是内核文档 scheduler/sched-deadline.txt 给出的一个例子,也可以参考: https://man7.org/linux/man-pages/man7/cpuset.7.html
        mkdir /dev/cpuset
        mount -t cgroup -o cpuset cpuset /dev/cpuset
        cd /dev/cpuset
        # 创建一个名为 cpu0 的 cpuset
        mkdir cpu0
        # 指定该 cpuset 中包含哪些 cpu, 也可以 1-3 的方式指定该 cpuset 包含 [1,2,3],
        # 具体的格式也在 scheduler/sched-deadline.txt 文档中
        echo 0 > cpu0/cpuset.cpus
        # 指定 cpuset 的 memory node
        echo 0 > cpu0/cpuset.mems // 
    
        # 指定为 1 之后各兄弟间 cpuset 中指定的 cpu 资源不能互相冲突
        echo 1 > cpuset.cpu_exclusive
        # 指定为 1 之后将允许内核在该 set 中对进程自动做负载均衡
        echo 0 > cpuset.sched_load_balance
        echo 1 > cpu0/cpuset.cpu_exclusive
        echo 1 > cpu0/cpuset.mem_exclusive
        # 将指定进程划到该 cpuset
        echo $$ > cpu0/tasks
        # 请参考 https://github.com/scheduler-tools/rt-app
        rt-app -t 100000:10000:d:0 -D5 (it is now actually superfluous to specify
        task affinity)
    

    下面是 scheduler/sched-deadline.txt 对 rt-app 的描述: The first testing application is called rt-app and can be used to start multiple threads with specific parameters. rt-app supports SCHED_{OTHER,FIFO,RR,DEADLINE} scheduling policies and their related parameters (e.g., niceness, priority, runtime/deadline/period). rt-app is a valuable tool, as it can be used to synthetically recreate certain workloads (maybe mimicking real use-cases) and evaluate how the scheduler behaves under such workloads. In this way, results are easily reproducible. rt-app is available at: https://github.com/scheduler-tools/rt-app.
  • 页表相关
    获取当前系统页表大小 getconf PAGE_SIZE
    Linux在v2.6.11以后,最终采用的方案是4级页表, 一个64位的虚拟空间,就需要:2^9 个PGD + 2^9 个PUD + 2^9 个PMD + 2^9 个PTE = 2048个页表数据结构。 现在的页表数据结构被扩展到了8byte。仅仅需要(2048*8=)16K就可以支持起(2^48 =)256T的进程地址空间。
    PGD:page Global directory(47-39), 页全局目录
    PUD:Page Upper Directory(38-30),页上级目录
    PMD:page middle directory(29-21),页中间目录
    PTE:page table entry(20-12),页表项
    TLB: 因为对内存的访问需要进行四次 IO, 加上最后的读取,需要 5 次 IO, 这对资源造成了极大的浪费, 因此为了解决该问题,一些硬件就拥有了 TLB(Translation Lookaside Buffer) 的单元,这个单元的作用就是尽可能地把页表 cache 起来,
    有了TLB之后,CPU访问某个虚拟内存地址的过程如下: CPU产生一个虚拟地址 -> MMU 从 TLB 中获取页表,翻译成物理地址 -> MMU把物理地址发送给L1/L2/L3/内存 -> L1/L2/L3/内存将地址对应数据返回给CPU
    查看您进程的 TLB 命中率: perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses -p $PID
    因为TLB并不是很大,只有4k,而且现在逻辑核又造成会有两个进程来共享。所以可能会有cache miss的情况出现。 而且一旦TLB miss造成的后果可比物理地址cache miss后果要严重一些,最多可能需要进行5次内存IO才行。 建议你先用上面的perf工具查看一下你的程序的TLB的miss情况,如果确实不命中率很高,那么Linux允许你使用大内存页,很多大牛包括PHP7作者鸟哥也这样建议。 这样将会大大减少页表项的数量,所以自然也会降低TLB cache miss率。所要承担的代价就是会造成一定程度的内存浪费。在Linux里,大内存页默认是不开启的。
    来源信息: 作者:yanfeizhang 链接:https://www.jianshu.com/p/9ed1e2a32e08
    如何查看 tlb size: 请参考该工具 http://www.etallen.com/cpuid.html, 使用命令 cpuid | grep -i tlb 获取 TLB size
  • 如何调节 Linux 系统内存回首阈值
    运行以下指令,修改阈值 echo ${Value} > /proc/sys/vm/min_free_kbytes, value 是数值,单位为 KB, 因此如果你填写 1024 那么阈值就是 1M
    运行以下指令,清理页缓存 echo 3 > /proc/sys/vm/drop_caches
  • vmstat
    vmstat 源码,在 procps 工程中: https://github.com/mmalecki/procps
    vmstat 各个字段的含义
    procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
    r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
    1  0      0 5464424 186244 3845832    0    0     0    24 1167 3385  4  3 93  0  0
    
    r: 正在运行中的线程
    b: 处于不可中断睡眠的线程数
    swpd: 可用的虚拟内存(即你最开始创建系统时分配的 swap 分区的可用内存数)
    free: 包括页缓存,空闲链表在内的可用内存(KB)
    buff: 被用来当作 buffer 的内存
    cache: 被用来当作 cache 的内存
    si: 从内存换入的内存数
    so: 从内存换出的内存数
    bi: 从块设备读取的数据大小
    bo: 向块设备写入的数据大小
    in: 每秒的中断次数,包含时钟中断(即每秒 100HZ 或者 1000HZ 的中断)
    cs: 每秒上下文切换次数
    us: 非内核代码运行耗时
    sy: 内核代码运行耗时
    id: 空闲时间(us + sy + id 一般等于 100%, 这里会对 CPU 做核平均)
    wa: IO wait
    st: 其他虚拟系统的耗时操作(一般你的机器是虚拟机,就会存在该字段)
        
  • 释放文件系统缓存(需要进入 root 账户)
    释放页缓存( pagecache ) echo 1 > /proc/sys/vm/drop_caches
    释放目录(dentries)和节点 (inodes) 缓存 echo 2 > /proc/sys/vm/drop_caches
    释放 pagecache, dentries, inodes 缓存 echo 3 > /proc/sys/vm/drop_caches
  • 应用层可以对内存映射区域和打开的文件指定缓存策略
    对文件指定缓存策略使用 posix_fadvise(fd, offset, len, advice), 其中 advice 用来指定应用层对待该指定区域的策略,内核会根据这些策略选择缓存方式
    POSIX_FADV_SEQUENTIAL   数据会以顺序的方式访问
    POSIX_FADV_RANDOM       数据会以随机的方式访问
    POSIX_FADV_NOREUSE      数据不会被重用
    POSIX_FADV_WILLNEED     数会在不远的将来重用
    POSIX_FADV_DONTNEED     数据不会在不远的将来重用
        

    对内存映射区域指定策略使用 madvise(addr, len, advice), 其中 advice 用来指定应用层对待该指定区域的策略,内核会根据这些策略选择缓存方式
    MADV_RANDOM             数据会以顺序的方式访问
    MADV_SEQUENTIAL         数据会以随机的方式访问
    MADV_WILLNEED           数会在不远的将来重用
    MADV_DONTNEED           数据不会在不远的将来重用
        
  • DTrace
    以下内容基本上摘抄于 Brendan D. Gregg 的个人博客站
    Begin 是最难的,因此如果你现在还不知道如何使用 Dtrace 那么,可以先使用 DTrace 脚本,然后再研究这些脚本是如何实现的:
    这是 Brendan D Gregg 收集的 DTrace 脚本,其中有对每个脚本的简单说明: http://www.brendangregg.com/dtracetoolkit.html
    这里是在命令行使用 DTrace 的一些实例,http://www.brendangregg.com/DTrace/dtrace_oneliners.txt
    而关于 DTrace 的更详细使用指南可以参考这个网站: http://www.dtracebook.com/
    See the DTrace scripts and one-liners in the DTrace book.
    Use scripts found in /usr/demo/dtrace, or in the DTrace Guide.
    Download Scripts from this website or Other websites.
    Search the DTrace mailing list for useful scripts, or elsewhere on the Internet.


gdb
vmstat
mpstat
iostat
netstat
sar
top
atop
pmap
tcpdump
blktrace
perf
pstack
pstrace
strace
Oprofile
给内核添加 nfs 启动文件系统( 以下部分的修改针对 3.0.8 内核 )

make menuconfig之后修改下列内容。

    File systems  --->
        [*] Network File Systems  --->
            [*]   NFS client support
            [ ]     NFS client support for NFS version 3
            [ ]     NFS client support for NFS version 4
            [*]   Root file system on NFS
    [*] Networking support  --->
        Networking options  --->
        [*]   IP: kernel level autoconfiguration
        [*]     IP: DHCP support
        [*]     IP: BOOTP support
        [*]     IP: RARP support
内核和 busybox 支持 telnet 登陆。
  1. 系统调用
    系统调用调用号设置表 ./arch/x86/entry/syscalls/syscall_64.tbl
    系统函数原型声明 include/linux/syscalls.h
    定义函数原型的时候可以在大部分内核文件中,但是一般会放在 kernel 目录下,如 ./kernel/sys.c
    其中用于辅助申明系统调用的宏也在 include/linux/syscalls.h 中,如 #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
  2. 触发进程调度的原因
    其一,为了保证所有进程可以得到公平调度,CPU时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待CPU的进程运行。
    其二,进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
    其三,当进程通过睡眠函数sleep这样的方法将自己主动挂起时,自然也会重新调度。
    其四,当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
    最后一个,发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序。
    vmstat 中的 in interrupt 字段为周期内全局中断次数,而 cs context switch 则为周期内上下文切换的次数(包括进程和线程)
    如果想观察进程的上下文切换次数则可以使用 pidstat -w, 其中 cswch 表示自愿上下文切换次数(voluntary context switches) 而 nvcswch 为非自愿上下文切换(non voluntary context switches)的次数。
  3. 配置内核
        Device Drivers  --->
        Character devices-->
        [*] Legacy (BSD) PTY support
        (256) Maximum number of legacy PTY in use
    
  4. 配置 busybox 1.23.0
        Busybox Settings  --->
            General Configuration  --->
                [*] Use the devpts filesystem for Unix98 PTYs
    
        Login/Password Management Utilities  --->
            [*] login
            [*]   Run logged in session in a child process
            [*]   Support for login scripts
            [*]   Support for /etc/nologin
            [*]   Support for /etc/securetty
    
        Networking Utilities  --->
            [*] inetd
            [*]   Support echo service
            [*]   Support discard service
            [*]   Support time service
            [*]   Support daytime service
            [*]   Support chargen service
            [*]   Support RPC services
    
            [*] telnet
            [*]   Pass TERM type to remote host
            [*]   Pass USER type to remote host
            [*] telnetd
            [*]   Support standalone telnetd (not inetd only)
            [*]     Support -w SEC option (inetd wait mode)
    
  5. 在 /etc/init.d/rcS 中加入下面这几行
        echo -e '\npts/0\npts/1\npts/2\npts/3\npts/4\npts/5\npts/6\npts/7\n' > /etc/securetty; cat /etc/securetty
        echo 'telnet 23/tcp' > /etc/services
        echo 'telnet stream tcp nowait root /sbin/telnetd' > telnetd
        telnetd
    
  6. Q&A and smoe tips, notes:
        注意:(一下某些注意的项因博主在配置时已经存在,或者不需要,因此没有给出详细的配置方法,需要自行查找解决方法)
        1. 取消busybox telnet 登录时的密码问题:
        配置 busybox
        Login/Password Management Utilities  --->
        [ ] login
    
        2. /etc 目录下下面的文件必不可少
        fstab
        service
        inetd.conf
        passwd
    
        3. fstab 中需要自动挂载 /dev/pts
        none /dev/pts devpts mode=0622 0 0
    
        4. /dev/ptmx 设备节点需要存在
    
        5. 遇到配置不过的时候建议借助 syslogd
        先在 busybox 配置支持
        然后运行 syslogd -n -m 0 -L & 即可将log 信息打印到串口终端
    
打开 coredump 调试接口
  1. 打开内核中的两个宏:
        step one:
        # meke menuconfig
        General setup  --->
            -*- Configure standard kernel features (expert users)  --->
                [*]   Enable ELF core dumps
    
        step two:
        # make menuconfig
        Executable file formats  --->
            [*] Kernel support for ELF binaries
            [*] Write ELF core dumps with partial segments
    
  2. 在启动的时候需要执行 ulimit -c unlimited 以接触内核创建文件大小的限制。
  3. 制定 coredump 生成文件路径

    一般来说 core 这个生成文件与可执行文件在同一个位置(使用 cat /proc/sys/kernel/core_pattern 可以查看/设置 coredump 文件的生成位置),但是当程序中用到了 chdir 的时候,core 文件会转移到 change 的那个目录去,因此我们最好设置一下文件生成到固定的位置。如我们想将生成的 coredump 文件放到 /tmp/core 这个文件中,则可以在系统启动的时候执行,echo "/tmp/core" > /proc/sys/kernel/core_pattern。但这样又会存在一个问题,如果多个程序,或者一个程序生成多个 coredump 文件,那么这个文件将被覆盖,下面介绍了一些转意符,在生成文件的时候使用这些转意符可以区分不同时期生成的 coredump 文件。比如如果你想将生成的 coredump 文件放到 /tmp/[进程名]-[进程 id].coredump 这个文件中可以在启动的时候执行这条指令: echo /tmp/%e.%p.coredump > /proc/sys/kernel/core_pattern

        %% 单个%字符
        %p 所dump进程的进程ID
        %u 所dump进程的实际用户ID
        %g 所dump进程的实际组ID
        %s 导致本次core dump的信号
        %t core dump的时间 (由1970年1月1日计起的秒数)
        %h 主机名
        %e 程序文件名
    
配置 ZM8620 无线网卡
  1. ID Pin 低电平为 OTG (host) 模式,模块内部已经将 ID PIN 和 PD(2) 关联起来了,如果设备要支持热插拔,那么这个引脚应该使能。在 board.h 中,但是如果你的设备自始至终都只有一个 USB 设备,比如声卡,无线网卡,那么在开机的时候在驱动中在 PD(2) 模拟一个插入 USB 的事件即可。(也就是设置成输出,然后设置成低电平)
  2. 首先在 github 上将 PPP 下载下来
  3. 然后在驱动中将下面这些项目打开
        1. Device Drivers --->
            USB support --->
                USB Serial Converter support --->
                    USB Generic Serial Driver
                    USB driver for GSM and CDMA modems
        2. Device Drivers --->
            Network device support --->
                PPP (point-to-point protocol) support
                PPP multilink support
                PPP filtering
                PPP support for async serial ports
                PPP support for sync tty ports
        3. Device Drivers --->
            Network device support --->
                USB Network Adapters --->
                    Multi-purpose USB Networking Framework
    
        CONFIG_USB_SERIAL=y
        CONFIG_USB_SERIAL_GENERIC=y
        CONFIG_USB_SERIAL_OPTION=y
        CONFIG_PPP=y
        CONFIG_PPP_ MULTILINK=y
        CONFIG_PPP_FILTER=y
        CONFIG_PPP_ASYNC=y
        CONFIG_PPP_SYNC_TTY=y
        CONFIG_USB_NET_CDCETHER=y (For modem with ECM/NDIS interface)
    
  4. 配置设备 ID

    编译内核的时候可能出现内核版本太老不支持 USBID 那么此时你应该找到无线网卡的 ID (你在使用其他 USB 的时候也是一样) lsusb 可以查询到插入设备的 ID。如下:

        # lsusb
        Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
        ...
        

    其中 1d6b:0003 就是 ID 你在内核地 driver/usb/serial/options.h 这个文件中要加入你添加的设备 ID,如中兴的 ZM8620 的 ID 可能是 19d2:0396 那么你在 option_ids 这个数组中应该在结尾添加上如下这行

        static const struct usb_device_id option_ids[] = {
            ...
            // or { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0396, 0xff, 0xff, 0xff) }
            {USB_DEVICE( 0x19d2, 0x0396 )},
            {},
        }
        
  5. 然后将下面的这几个文件放到一个文件夹中, 编译出来的文件 pppoe-discovery, chat, pppd, pppstats, pppdump 需要自己编辑 zte_options, ppp-on, pppoe-discovery, disconnect

    以下拿联通的举例:

            ppp-on
    
                #!/bin/sh
                set -e
                RELATIVE_DIR=`dirname $0`
                CUR_DIR=`cd ${RELATIVE_DIR} && pwd && cd -`
                PPPD=${CUR_DIR}/pppd
                CHAT=${CUR_DIR}/chat
    
                OPTION_FILE="zte_options"
                DIALER_SCRIPT="$(pwd)/zte_ppp_dialer"
                exec ${PPPD} file $OPTION_FILE connect "${CHAT} -v -f ${DIALER_SCRIPT}"
    
                disconnect
    
                    #!/bin/sh
                killall pppd
    
    
            zte_options:
    
                /dev/ttyUSB2
                115200
                crtscts
                modem
                persist
                lock
                noauth
                noipdefault
                debug
                nodetach
                user Anyname
                password Anypassword
                ipcp-accept-local
                ipcp-accept-remote
                defaultroute
                usepeerdns
                noccp
                nobsdcomp
                novj
                dump
    
            zte_ppp_dialer
    
                ABORT       "NO CARRIER"
                ABORT       "ERROR"
                TIMEOUT     120
                ""          ATE
                SAY         "ATE"
                ECHO        ON
                OK          ATH
                OK          ATP
                OK          "AT+CGDCONT=1,\"IP\", \"APN\""
                OK          ATD*98#
                CONNECT
        
Linux 内核
  • 平台设备注册
    // include/linux/platform_device.h
    #define module_platform_driver(__platform_driver) \
        module_driver(__platform_driver, platform_driver_register, \
            platform_driver_unregister)
    
  • 内核打印
    在调试内核时可用 printf 的孪生哥哥 printk,printk 的使用语法与 printf 相差不大,但可在格式化字符串前加上打印等级,如:printk( KERN_DEBUG "hello, world!" );

     

        #define KERN_EMERG      "<0>"    /* 系统不可使用 */
        #define KERN_ALERT      "<1>"    /* 需要立即采取行动 */
        #define KERN_CRIT       "<2>"    /* 严重情况 */
        #define KERN_ERR        "<3>"    /* 错误情况 */
        #define KERN_WARNING    "<4>"    /* 警告情况 */
        #define KERN_NOTICE     "<5>"    /* 正常情况, 但是值得注意 */
        #define KERN_INFO       "<6>"    /* 信息型消息 */
        #define KERN_DEBUG      "<7>"    /* 调试级别的信息 */
        /* 使用默认内核日志级别 */
        #define KERN_DEFAULT    ""
        /*
        * 标注为一个“连续”的日志打印输出行(只能用于一个
        * 没有用 \n封闭的行之后). 只能用于启动初期的 core/arch 代码
        * (否则续行是非SMP的安全).
        */
        #define KERN_CONT       ""
    
  • ubuntu 下 buildroot 编译过依赖,不完全列表
    sudo apt update; sudo apt upgrade -y; sudo apt install -y virtualbox-guest-additions-iso net-tools htop vim tree git gitk bc unzip python bison flex libncurses5-dev libncursesw5-dev device-tree-compiler expect cmake
  • Buildroot 交叉编译
    1. make download V=s -- 下载所有依赖项
Linux 音频架构
参考文档
参考文档
Program
Makefile
  1. Makefile 冷门知识点汇总(持续更新中...)
    • make file 的组成部分
      1. make file 由 变量,目标,规则,注释,函数等部分组成
      2. 其中目标则代表编译的目标,如果编译目标的规则并不会生成目标文件时,则需要 .PHONY 指明
      3. 变量的复制有如下几种: a = b, a := b, a ?= b, a += b
      4. 规则为执行目标生成,或特定任务处理的一组脚本集合,该集合的脚本将会由 /bin/sh 指定的 shell 解释器来执行,而且此处的注释也交由解释器解释
      5. 常见的函数有: $(error xxx), $(warning xxx), $(subst from,to,text), $(patsubst pattern,replacement,text), $(strip string), $(findstring find,in), $(filter pattern...,text), $(sort list), $(call ...), $(shell ...), $(foreach var, list, text), $(join ...), $(basename ...), $(nodir ...), $(dir ...) ...
    • make 如何知道你的代码应该被更新的?
      make 会根据目标依赖的文件的 mtime 是否新于目标文件来判断该目标是否需要重新编译。 如果你的目标脚本执行并没有生成目标文件,或者生成的目标文件并不等于目标名称,则每次执行到该目标时都会重新编译
    • 编写目标规则的三种形式
      如下所示,但注意, command 如果不是和 target 在同一行,则需要以一个 TAB 作为缩进
      targets : prerequisites
          command
          ...
      
      targets : prerequisites
          command
          ...
      
      targets : prerequisites ; command
          command
          ...
      
    • 多目标推到
      多目标的编写方式如下,make 会根据变量中目标名称以及后面的规则进行展开,其中 $< 会替换成依赖,$@ 会替换成目标名称。
      $(objects): %.o: %.c
          $(CC) -c $(CFLAGS) $< -o $@
      
    • 特殊变量
      VPATH 该变量保存一个路径列表,路径与路径用冒号分割,该变量主要指示 make 在进行自动推导的时候搜索文件的目录, make 会按照(当前目录,以及该变量指示目录的顺序)来便利。当然也可以使用 vpath 关键字,这里后面带的目录以空格分割即可
      $$$$ 生成一组随机数字符串
      $$ 代表 $ 字符
    • make 的工作流程
      1. 读入所有的Makefile。
      2. 读入被include的其它Makefile。
      3. 初始化文件中的变量。
      4. 推导隐晦规则,并分析所有规则。
      5. 为所有的目标文件创建依赖关系链。
      6. 根据依赖关系,决定哪些目标要重新生成。
      7. 执行生成命令。
      
    • 命令包
      命令包以 define 开始, 以 endif 结束,如:
      define run-yacc
          yacc $(firstword $^)
          mv y.tab.c $@
      endef
              

      在使用的时候后直接在规则里面调用 $(run-yacc) 即可
      命令包中使用的 $@ $^ 在实际的调用位置会替换成实际的目标和依赖名
    • 变量
      使用 = 对变量赋值时,在只有使用到该变量的时候才会展开,因此如果你的变量 A 的值包含了另外一个变量 B,而变量 B 的值会在之后发生变化,那么在引用 A 的时候,会使用到 B 改变之后的值
      如果要避免上述情况可以使用 := 来进行赋值,使用这种方式赋值,值会在赋值的时候便展开
      如果变量定义过长还可以使用 += 来进行追加
      其次还有一个赋值
      高级用法 1: $(var:a=b) 或者 $(foo:%.o=%.c) , 把 var 变量中以 a 结尾的值替换成 b 结尾的值
      如果要定义多行命令可以使用如下方式:
      define two-lines
      echo foo
      echo $(bar)
      endef
              

      这和命令包非常相似,区别在于,命令包中的规则以 TAB 开头,而这个没有,make 在解释的时候也是依据此来区分的。
    • 变量的 override
      通常,如无特殊申明,makefile 中的变量是可以在执行 make 的时候携带参数来改变的。
      比如,你在 makefile 中定义了一个 var 变量(比如 var=old_value ),然后你在对该 makefile 进行编译的时候给 var 传递了一个值:比如 make var=new_value 那么此时 var 就会被 new_value 所取代
      而如果你想在 makefile 中忽略参数指定的值,则可以使用 override 指示符,如: override var=old_value
    • 目标变量
      顾名思义,专为目标定义的变量,该变量会覆盖外部的全局变量,使用方法如下:
      target: var1=value1
      target: var2=value2
      target:
          use $(var1) $(var2) do something
              

      此外,make 还支持模式变量,即可以给符合该模式的所有 target 定义变量,如:
      %.o: CFLAGS ?= -O2
      $(Objects):%.o:%.c
          $(CC -c $@ $(CFLAGS) $^
              
    • 条件判断
      make 仅支持一种条件判断方式即 fieq condition, 其中最常见的方式是:
      ifeq ($(VAR),'some_value')
          ...
      else
          ...
      endif
              

      而 condition 可以是变量和变量的对比,也可以时变量和参数的对比
      在 condition 中也可以使用 make 支持的函数
      同时还可以使用单变量,有值为真,无值为假
    • Tips
      1. make 会将 makefile 中找到的第一个 target 作为默认 target
      2. make -C /dir1 /dir2 make 将会改变当前的工作目录,并且在新的目录中寻找 makefile 文件来解释执行,如果 -C 后跟随多个目录,则会顺序进入执行
      3. make 默认会寻找 Makefile 为名的文件(或者是 makefile ,GNUmakefile 视具体的工具而定),但如果你使用的 -f 参数,则会直接采用指定的文件
      4. 在 makefile 中使用 include 包含外部文件之后,外部文件会直接展开到 include 的位置
      5. 在编写目标的规则脚本前加上 @ 可以在编译的时候不打印规则本身
      6. 正常情况下,规则脚本执行如果报错,则会导致 make 终止运行,但是在规则之前加上 - 可以让 make 忽略报错
      7. 在 make 的时候还可以追加 -n 或者 --just-print 参数,这回打印出目标规则所有的执行语句,但不会执行他们,非常适合调试的时候使用。
      8. 当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。
  2. gcc 中常用的一些语法

    首先是编译,两次面试都碰到这个问题,其实在实际应用中很少用到,但到特殊情况下不知道也很麻烦,从 c 的源码编译到可执行文件经历着这么四个步骤 预处理(生成预处理文件)--> 编译(产生汇编文件) --> 汇编(产生机器可识别文件) --> 链接(将各个分散的文件链接到一起,并生成程序入口)。通常在编译的时候,可能要自己加入一些指定的库,头文件目录等,其中 -I/path 指定头文件目录,-L/path 指定库文件的目录,-lname (ex:-lmath) 指定库文件的名称(可省略 lib 的前缀),-D 指定全局的符号常量(也就是 C 文件中的宏定义常量,在编译预处理的时候将会作为文件中的普通宏定义常量加入计算),-Wall 该选项会发现程序中一系列的常见错误报告,具体包含哪些选项请 man 一下(不知道 man 什么意思,google 一下 ‘linux man’),-Werror 把所有的警告当作错误处理。

  3. debug 程序时在文件中用到的宏

    __DATA__ 获取当前日期,__TIME__ 获取当前时间,__LINE__ 获取打印信息在当前文件中的行号,__FILE__ ���取当前代码所在文件的文件名,__STDC__ 如果当前编译器符合 ISO 标准,其宏值为1,__STDC_VERSION__C标准版本(如果版本为 C89 那么值为 199409L,如果版本为 C99 那么值为 199901L),__STDC_HOSTED__本地系统(hosted,表示拥有完整的标准 C 库)值为1。

  4. 平台区分(Machine-Dependent)

    这里之列举常见的,这些参数也是可在编译不同平台做代码兼容而使用,x86/Windows 如果在文件中发现这个宏为真,则标识当前编译平台为 x86/Windows 平台,注意有 x86/Windows 和 x86 之分,Darwin 苹果 OSX 平台,MIPS 一看便知,GNU/Linux同上...., 用法嘛自己去搜索,也可以在 github 上下个多平台兼容的项目下来研究研究。

  5. C++11 新新特性

    在这里只是简单的介绍一下各个特性的关键作用,详细的解析与用法请参考后面的[link3]传送门。auto 相当一个泛型变量类型,可用于生成任何变量,变量的类型会由编译器自动分配;decltype 获取一个变量的变量类型,可用于申请新的变量;nullptr空指针,以前的空指针用 0 表示,但是 0 被隐式的保存为整形,目的就是为了避免此种情况;for 支持像 java 类似的 foreach 特性;Lambda 表达式相当于 java 中的闭包;make_tuple() 变长参数申请; 新类型指针(unique_ptr, shared_ptr, weak_ptr)新枚举类型像 java 一样封装到一个类里面......(参考文档中,这篇很值得看 --> C++开发者都应该使用的10个C++11特性)

  6. gcc 参数
            -x      // 指定文件所用的语言类型;可选项("c","objective-c","c-header","c++",
                    //      "cpp-output","assembler","assembler-with-cpp")
            -x none // 关闭对任何语言的说明
            -M      // 输出文档所直接和间接依赖头文件, 用法:gcc -M main.c)
            -MM     // 输出文档所直接依赖的头文件
            -O[n]   // 不使用`-O'选项时,编译器的目标是减少编译的开销,使编译结果能够调试.
                    //      意味着语句是独立的:如果在两条语句之间用断点中止程序,你可以对任何变量重新
                    //      赋值,或者在函数体内把程序计数器指到其他语句,以及从源程序中 精确地获取你期待的结果.
                    // 使用了`-O'选项,编译器会试图减少目标码的大小和执行时间.
                    // n=1: 对于大函数,优化编译占用稍微多的时间和相当大的内存.
                    // n=2: 包含 n=1 的情况,几乎执行所有的优化工作,
                    // n=3: 在 n=1 的情况下还打开了 -finline-functions 选项
                    // n=0: 不优化
            .. 详细请移步至 LINK1 or LINK5.
        
  7. 查看当前系统中存在那些可以连接到的库 ldconfig -p ,更多选项在下面说明:

        ldconfig 的参数
        -v      ldconfig 将扫描以及打印能搜到的目录(包括默认的 /lib,/usr/lib 和配置到 /etc/ld.so.conf 中的)。
        -n      仅仅扫描打印指定目录。
        -N      不重建 /etc/ld.so.cache 目录,但为指定 -X 选项运行 ldconfig 的时候照样会更新文件的连接。
        -X      不更新文件的连接
        -f      此参数指定动态库的配置文件,默认是 /etc/ld.so.conf
        -C      此参数指定动态库的缓存文件,默认是 /etc/ld.so.cache
        -r      此参数指定动态索引时的根目录,默认为 / ,比如在调用 /etc/ld.so.conf 时实际调用的是 //etc/ld.so.conf
        -l      进入专家模式手动建立动态库的连接
        -p     打印所有共享库的名字
        -c      用于指定缓存文件所使用的格式,共有三种:old(老格式),new(新格式)和compat(兼容格式,此为默认格式).
        -V      打印 ldconfig 的版本信息
    
《重构》 Tips
  • 如果你要给程序添加一个特性, 但发现代码因缺乏良好的结构而不易于进行更改, 那就先重构那个程序, 使其比较容易添加该特性, 然后再添加该特性.
  • 重构前, 先检查自己是否有一套可靠的测试集。 这些测试必须有自我检验能力。
  • 重构技术就是以微小的步伐修改程序。 如果你犯下错误, 很容易便可发现它。
    论每次重构多么简单, 养成重构后即运行测试的习惯非常重要。 犯错误是很容易的——至少我知道我是很容易犯错的。 做完一次修改就运行测试, 这样在我真的犯了错时, 只需要考虑一个很小的改动范围, 这使得查错与修复问题易如 反掌。 这就是重构过程的精髓所在: 小步修改, 每次修改后就运行测试。 如果我改动了太多东西, 犯错时就可能陷入麻烦的调试, 并为此耗费大把时间。 小步修 改, 以及它带来的频繁反馈, 正是防止混乱的关键
  • 傻瓜都能写出计算机可以理解的代码。 唯有能写出人类容易理解的代码的, 才是优秀的程序员。
  • 因此对于重构过程的性能问题, 我总体的建议是: 大多数情况下可以忽略它。 如果重构引入了性能损耗, 先完成重构, 再做性能优化。
  • 编程时, 需要遵循营地法则: 保证你离开时的代码库一定比来时更健康。
  • 好代码的检验标准就是人们是否能轻而易举地修改它。(我不完全认同)
  • 如果有人说他们的代码在重构过程中有一两天时间不可用, 基本上可以确定, 他们在做的事不是重构。 (个人认为重构应该是用一系列手法在不改变程序本身功能的前提下提高代码的可维护性和可阅读性, 而重构的过程采用小步进应该)
  • 不过就用户应该关心的行为而言, 不应该有任何改变。 如果我在重构过程中发现了任何 bug, 重构完成后同样的bug应该仍然存在(不过, 如果潜在的bug还没有被任何人发现, 也可以当即把它改掉) 。
  • 重构与性能优化有很多相似之处: 两者都需要修改代码, 并且两者都不会改变程序的整体功能。 两者的差别在于其目的: 重构是为了让代码“更容易理解,更易于修改”。 这可能使程序运行得更快, 也可能使程序运行得更慢。 在性能优化时, 我只关心让程序运行得更快, 最终得到的代码有可能更难理解和维护, 对此我有心理准备。
  • 果没有重构, 程序的内部设计(或者叫架构) 会逐渐腐败变质。 当人们只为短期目的而修改代码时, 他们经常没有完全理解架构的整体设计, 于是代码逐渐失去了自己的结构。 程序员越来越难通过阅读源码来理解原来的设计。 代码结构的流失有累积效应。 越难看出代码所代表的设计意图, 就越难保护其设计, 于是设计就腐败得越快。 经常性的重构有助于代码维持自己该有的形态。 完成同样一件事, 设计欠佳的程序往往需要更多代码, 这常常是因为代码在不同的地方使用完全相同的语句做同样的事, 因此改进设计的一个重要方向就是消除重复代码。
  • 我们应该改变一下开发节奏, 让代码变得更易于理解。 重构可以帮我让代码更易读。 开始进行重构前, 代码可以正常运行, 但结构不够理想。 在重构上花一点点时间, 就可以让代码更好地表达自己的意图——更清晰地说出我想要做的。
  • 对代码的理解, 可以帮我找到bug。
  • 重构帮我更快速地开发程序。需要添加新功能时, 内部质量良好的软件让我可以很容易找到在哪里修改、 如何修改。 良好的模块划分使我只需要理解代码库的一小部分, 就可以做出修改。 如果代码很清晰, 我引入bug的可能性就会变小, 即使引入了bug, 调试也会容易得多。 理想情况下, 我的代码库会逐步演化成一个平台, 在其上可以很容易地构造与其领域相关的新功能。
  • “设计耐久性假说”: 通过投入精力改善内部设计, 我们增加了软件的耐久性, 从而可以更长时间地保持开发的快速。 我还无法科学地证明这个理论, 所以我说它是一个“假说”。
  • 重构不是与编程割裂的行为。 你不会专门安排时间重构, 正如你不会专门安排时间写if语句。 我的项目计划上没有专门留给重构的时间, 绝大多数重构都在我做其他事的过程中自然发生。 不过, 说了这么多, 并不表示有计划的重构总是错的。 如果团队过去忽视了重构, 那么常常会需要专门花一些时间来优化代码库, 以便更容易添加新功能。在重构上花一个星期的时间, 会在未来几个月里发挥价值。 有时, 即便团队做了 日常的重构, 还是会有问题在某个区域逐渐累积长大, 最终需要专门花些时间来解决。 但这种有计划的重构应该很少, 大部分重构应该是不起眼的、 见机行事的。
  • 还有一种常见的误解认为, 重构就是人们弥补过去的错误或者清理肮脏的代码。 当然, 如果遇上了肮脏的代码, 你必须重构, 但漂亮的代码也需要很多重构。
  • 大多数重构可以在几分钟——最多几小时——内完成。 但有一些大型的重构可能要花上几个星期, 即便在这样的情况下, 我仍然不愿让一支团队专门做重构。 可以让整个团队达成共识, 在未来几周时间里逐步解决这个问题, 这经常是一个有效的策略。 每当有人靠近“重构区”的代码, 就把它朝想要改进的方向推动一点。 这个策略的好处在于, 重构不会破坏代码——每次小改动之后, 整个系统仍然照常工作。 (这个策略叫作Branch By Abstraction[mf-bba]。)
  • 代码复审的过程中代码的原作者在旁边会好很多,因为作者能提供关于代码的上下文信息, 并且充分认同复审者进行修改的意图。 对我个人而言, 与原作者肩并肩坐在一起, 一边浏览代码一边重构, 体验是最佳的。 这种工作方式很自然地导向结对编程: 在编程的过程中持续不断地进行代码复审
  • 怎么对经理说? 如果这位经理懂技术, 能理解“设计耐久性假说”, 那么向他说明重构的意义应该不会很困难。 如果他们不理解代码库的健康对生产率的影响。 这种情况下我会给团队一个较有争议的建议: 不要告诉经理! 这是在搞破坏吗? 我不这样想。 软件开发者都是专业人士。 我们的工作就是尽可能快速创造出高效软件。 我的经验告诉我, 对于快速创造软件, 重构可带来巨大帮助。 如果需要添加新功能, 而原本设计却又使我无法方便地修改, 我发现先重构再添加新功能会更快些。 如果要修补错误, 就得先理解软件的工作方式,而我发现重构是理解软件的最快方式。 受进度驱动的经理要我尽可能快速完成任务, 至于怎么完成, 那就是我的事了。 我领这份工资, 是因为我擅长快速实现新功能; 我认为最快的方式就是重构, 所以我就重构。 (这条的争议可能会比较大,但是如果把重构当成开发的一部分就想的过去了)
  • 何时不应该重构? 1)如果我看见一块凌乱的代码, 但并不需要修改它; 2)如果重写比重构还容易, 就别重构了;
  • 我推荐团队代码所有制, 这样一支团队里的成员都可以修改这个团队拥有的代码, 即便最初写代码的是别人。 程序员可能各自分工负责系统的不同区域, 但这种责任应该体现为监控自己责任区内发生的修改, 而不是简单粗暴地禁止别人修改
  • 分支合并本来就是一个复杂的问题, 随着特性分支存在的时间加长, 合并的难度会指数上升。 所以很多人认为, 应该尽量缩短特性分支的生存周期, 比如只有一两天。 还有一些人(比如我本人) 认为特性分支的生命还应该更短, 我们采用的方法叫作持续集成(Continuous Integration, CI) , 也叫“基于主干开发”(Trunk-Based Development) 在使用CI时, 每个团队成员每天至少向主线集成一次。 不过CI也有其代价: 你必须使用相关的实践以确保主线随时处于健康状态, 必须学会将大功能拆分成小块, 还必须使用特性开关(feature toggle, 也叫特性旗标, feature flag) 将尚未完成又无法拆小的功能隐藏掉。
  • 不会改变程序可观察的行为, 这是重构的一个重要特征。如果每个重构都是很小的修改, 即便真的造成了破坏, 我也只需要检查最后一步的小修改——就算找不到出错的原因, 只要回滚到版本控制中最后一个可用的版本就行了。这里的关键就在于“快速发现错误”。 要做到这一点, 我的代码应该有一套完备的测试套件, 并且运行速度要快, 否则我会不愿意频繁运行它。 (文中还提到了另外保障重构质量的方法:1. 重构工具;2. 只使用验证过的重构方法;)
  • 对于没有自测的遗留代码,秉持的原则是 “没测试就加测试”。而加测试的时候主要在于去找到代码的衔接处,如果没有衔接的地方则可能需要使用重构手法来创建衔接处。 但此时的重构是非常危险的,需要格外小心。鉴于此种情况,如果能在代码最开始的时候编写测试程序是极好的。 不过就算有了测试, 我也不建议你尝试一鼓作气把复杂而混乱的遗留代码重构成漂亮的代码。 我更愿意随时重构相关的代码: 每次触碰一块代码时, 我会尝试把它变好一点点——至少要让营地比我到达时更干净。 如果是一个大系统, 越是频繁使用的代码, 改善其可理解性的努力就能得到越丰厚的回报。
  • 与常规的重构不同, 很多时候, 数据库重构最好是分散到多次生产发布来完成, 这样即便某次修改在生产数据库上造成了问题, 也比较容易回滚。 比如, 要改名一个字段, 我的第一次提交会新添一个字段, 但暂时不使用它。 然后我会修改数据写入的逻辑, 使其同时写入新旧两个字段。 随后我就可以修改读取数据的地方, 将它们逐个改为使用新字段。 这步修改完成之后, 我会暂停一小段时间,看看是否有bug冒出来。 确定没有bug之后, 我再删除已经没人使用的旧字段。 这种修改数据库的方式是并行修改(Parallel Change, 也叫扩展协议/expandcontract) [mf-pc]的一个实例。
  • 要真正以敏捷的方式运作项目, 团队成员必须在重构上有能力、 有热情, 他们采用的开发过程必须与常规的、 持续的重构相匹配。
  • 重构的第一块基石是自测试代码。 我应该有一套自动化的测试, 我可以频繁地运行它们, 并且我有信心: 如果我在编程过程中犯了任何错误, 会有测试失败。 这块基石如此重要, 我会专门用一章篇幅来讨论它。
  • 自测试代码、 持续集成、 重构——彼此之间有着很强的协同效应。有这三大核心实践打下的基础, 才谈得上运用敏捷思想的其他部分。 不管采用什么方法, 软件开发都是一件复杂而微妙的事, 涉及人与人之间、 人与机器之间的复杂交互。我在这里描述的方法已经被证明可以应对这些复杂性, 但——就跟其他所有方法一样——对使用者的实践和技能有要求。
  • 今天的大多数重构功能都依附于强大的IDE, 因为这些IDE原本就在语法树上实现了代码导航、 静态检查等功能,自然也可以用于重构。 不仅能处理文本, 还能处理语法树, 这是IDE相比于文本编辑器更先进的地方.
  • 坏代码的指标有:神秘命名,重复代码,过长函数,过长参数列表,全局数据,可变数据,发散式变化,霰弹式修改,
  • 重构手法:函数调用代替函数名称,多态代替分支,循环拆分,参数对象取代冗长的参数列表,单例只包含只读或者不存储状态的函数,
  • 编写优良的测试程序, 可以极大提高我的编程速度, 即使不进行重构也一样如此。
  • 测试驱动开发的编程方式依赖于下面这个短循环: 先编写一个(失败的) 测试, 编写代码使测试通过, 然后进行重构以保证代码整洁。 这个“测试、 编码、 重构”的循环应该在每个小时内都完成很多次。 这种良好的节奏感可使编程工作以更加高效、 有条不紊的方式开展。
  • 一旦业务逻辑的部分开始变复杂, 我就会把它与UI分离开, 以便能更好地理解和测试它。
  • 当我为类似的既有代码编写测试时, 发现一切正常工作固然是好, 但我天然持怀疑精神。 特别是有很多测试在运行时, 我总会担心测试没有按我期望的方式检查结果, 从而没法在实际出错的时候抓到bug。 因此编写测试时, 我想看到每个测试都至少失败一遍。 我最爱的方式莫过于在代码中暂时引入一个错误,总是确保测试不该通过时真的会失败。
  • 测试的重点应该是那些我最担心出错的部分, 这样就能从测试工作中得到最大利。
  • 测试的阶段:配置-检查-验证(setup-exercise-verify),given-when-then,准备-行为-断言(arrange-act-assert). 除此之外还有一个拆除阶段,即此阶段可将测试夹具移除, 以确保不同测试之间不会产生交互。 因为有时创建缓慢等原因, 我们会在不同的测试间共享测试夹具, 此时, 显式地声明一个拆除操作就是很重要的。
  • 测试覆盖率的分析只能识别出那些未被测试覆盖到的代码, 而不能用来衡量一个测试集的质量高低。
  • 单元测试的缺失不仅会意味着较低的工程质量,而且意味着重构的难以进行,一个有单元测试的项目尚且不能够保证重构前后的逻辑完全相同,一个没有单元测试的项目很可能本身的项目质量就堪忧,更不用说如何在不丢失业务逻辑的情况下进行重构了。
CMake
  1. How to study?

    我之介绍一下我的学习路径,因为我现在正在尝试将 AVS 的 SDK 移植到我们的平台上,发现他同了 google 的单元测试工具和 CMake 工具,真的是把我触动了一把,觉得自己的工作虽然赶,但不能马虎。想仔细的学习一下。我的学习路径基本上都是,先对之有个大概的了解,去 Wikipedia, 百度百科,知乎上了解之,然后从最简单的例子进行分析学习,然后在将官方提供的文档理一遍,基本上就到一个阶段了,然后就是在实战中仔细的雕琢了。

    至于我学习过程中接触到的知识点,我就不在这里记录了,因为前人已经总结得很好了。所以我建议读者先把参考资料中的 LINK6, LINK7 熟悉一遍,然后再把 LINK8 理一遍就可以开始研究别人的代码了。

ReactiveX ( C++, java, python lua ... )

ReactiveX 是一个库,用于通过使用可观察的序列来组成异步和基于事件的程序。支持的语言有 JAVA, C++, Python, PHP, JS, Net, Scale, Switft ...., 请参考 http://reactivex.io/languages.html

ReactiveX 大致的可以从 Observer, Operation, Single, Subject, Scheduler 这几个方面进行阐述。

JS
  • 浅拷贝
    result = Object.assign({},target)
GO
  • 构建 Go 项目工程骨架
    #!/usr/bin/env bash
    
    if [ "$1" = "" ]; then
        echo "Please input project name"
        exit -1
    fi
    
    if [ -f $1 ] || [ -d $1 ]; then
        echo "File ( directory ) aready exist, please try another name"
        exit -1
    fi
    
    mkdir $1
    cd $1
    
    echo "dir: pkg 共有依赖存放目录"
    mkdir pkg
    
    echo "dir: internal/pkg 私有依赖存放目录"
    mkdir -p internal/pkg
    
    echo "dir: internal/app 私有代码存放目录"
    mkdir -p internal/app
    
    echo "dir: cmd 可执行文件存放目录"
    mkdir -p cmd
    
    echo "dir: api 对外 API 接口定义文件存放目录"
    mkdir -p api
    
    echo "dir: scripts 工具脚本存放目录"
    mkdir -p scripts
    
    echo "dir: tests 测试代码存放目录"
    mkdir -p tests
    
    echo "dir: examples 事例代码存放目录"
    mkdir -p examples
    
    echo "dir: configs 配置文件存放目录"
    mkdir -p configs
    
    echo "dir: docs 工程文档存放目录"
    mkdir -p docs
    
    echo "dir: assets 工程依赖物料(图片,配置,...)存放目录"
    mkdir -p assets
    
    echo "dir: tools 辅助开发工具存放目录"
    mkdir -p tools
    
    echo "file: Makefile"
    touch Makefile
    
    echo "file: README.md"
    touch README.md
    
参考资料
参考文档
函数库

    请等待更新...

基础库
  1. 常见库的数据类型定义
    • stdlib.h
          system()
      
    • unistd.h
          sleep()
      
    • fcntl.h
          O_RDWR
          O_CREATE
          ...
      
      
    • sys/stat.h
          mkfifo
      
    • inttypes.h
          // 包含定义的数据类型
          int8_t
          int16_t
          int32_t
          int64_t
          uint8_t
          uint16_t
          uint32_t
          uint64_t
      
  2. BLAS

     

    libcurl 交叉编译

    选择合适的 libcurl 版本 下载,解压之后,将以下的代码复制到 build.sh 中,更具自己的需求修改成自己的配置,然后执行 make -f build.sh 即可。

    include ../../env.mk
    
    CONF_OPTION += --with-mbedtls=${MBEDTLS_INSTALL_PATH}
    
    all: config build
    
    config:export CROSS_COMPILE=mipsel-linux
    config:export ROOTDIR=$(DEPENDS_DIR)
    config:export PKG_CONFIG_PATH=$(ROOTDIR)/lib/pkgconfig
    config:export CPPFLAGS=-I${ROOTDIR}/include
    config:export LDFLAGS=-L${ROOTDIR}/lib
    config:export LIBS=-lmbedtls -lmbedx509 -lmbedcrypto
    config:
        configure --prefix=${ROOTDIR} --host=mipsel-linux $(CONF_OPTION)
    
    build:export CROSS_COMPILE=mipsel-linux
    build:export ROOTDIR=$(DEPENDS_DIR)
    build:export PKG_CONFIG_PATH=$(ROOTDIR)/lib/pkgconfig
    build:export CPPFLAGS=-I${ROOTDIR}/include
    build:export LDFLAGS=-L${ROOTDIR}/lib
    build:export LIBS=-lmbedtls -lmbedx509 -lmbedcrypto
    build:export AR=${CROSS_COMPILE}-ar
    build:export AS=${CROSS_COMPILE}-as
    build:export LD=${CROSS_COMPILE}-ld
    build:export RANLIB=${CROSS_COMPILE}-ranlib
    build:export CC=${CROSS_COMPILE}-gcc
    build:export NM=${CROSS_COMPILE}-nm
    build:export PKGCONFIG=${CROSS_COMPILE}-pkg-config
    build:
        make
        make install
    
参考文档
libav
libav 支持打开文件的类型

crypto, ffrtmpcrypt, ffrtmphttp, file, gopher, hls, http, httpproxy, https, icecast, mmsh, mmst, md5, pipe, rtmp, rtmpe, rtmps, rtmpt, rtmpte, rtmpts, rtp, srtp, tcp, tls, udp, unix

libav 输入文件
  1. 打开输入文件

    avconv.c ( main ), --> avconv_opt.c ( avconv_parse_options ) , --> avconv_opt.c ( open_files ) , --> avconv_opt.c ( open_input_file ) , --> libavformat/utils.c ( avformat_open_input ) , --> libavformat/utils.c ( init_input ) , --> libavformat/utils.c( AVFormatContext->io_open ) == libavformat/options.c ( io_open_default ) , --> libavformat/aviobuf.c ( avio_open2 ) , --> libavformat/avio.c ( ffurl_open ) , --> libavformat/avio.c ( ffurl_connect ) ,

软件安装
caffe 安装
      这里只介绍 ubuntu 上的安装,如果想在 docker, mac ... 请查看
参考连接
    .
  1. 安装 gcc, g++, python

    caffe是主要是C/C++和Python编写的。所以需要先把 gcc, g++, python 安装好。

  2. 安装 cuda
  3. 安装 caffe 其他依赖
  4. opencv
  5. caffe 安装编译配置
docker

docker attach container_name 登陆 docker 的容器后,怎么退出勒?我们可以用 ctrl + d, 但是这种方法会导致容器内部运行的工作都停止,因此,这里还介绍另外一种方法:Ctrl + p -> Ctrl + q

apache2
  1. 监听端口

    如需要修改 apache 的监听端口,可在 /etc/apache2/ports.conf 中配置,语法为 Listen port_num (such as: Listen 8080)

  2. IfDefine & IfModule

    Apache 允许使用 IfDefine 以及 IfModule 指令来快速而且更容易的更改配置。IfDefine 标签允许在命令行中使用某个标志来指定 IfDefine 内的配置选项否出于启用状态, IfModuole 标签具有类似的效果,其作用是先检查当前模块是否已经加载,若已经加载,则标签中的配置有效,若没有加载,则标签中的配置无效。

nginx
  1. 反向代理

    个人理解反向代理及将接收到的请求分发给在其他可访问到的服务器,方便实现服务器的负载均衡,也可减少主服务器的压力。详细概念在这里就不再赘述了,请参考参考文档。这里只是记录一下自己在搭建反向代理中需要主要的一些问题。

        1. 以集群域名创建的 upstream 对象应该放在 html 对象下;
        2. 在 server 对象中设置目录反向代理的 proxy_pass 成员时,一个目录只能设置一个 proxy_pass;
        3. proxy_pass 的参数为 upstream 对象的名字;
        4. 如果你为 /abc/ 目录指定了反向代理服务器 www.test.com ,那么在 www.test.com 这个服务器集群的跟目录下一定要有 abc 这个文件夹。
        5. /etc/nginx/site-avaliable 目录下只是可以用来配置的服务,启动 nginx 时真正引用的是 /etc/nginx/site-enabled 目录下的配置文件(apache2 也一样)。
        
node.js
  1. npm 报告 https://registry.npmjs.org/ 访问不到
    可替换成谷内的源,比如在使用 npm 的时候可以这样使用 npm install xxx --registry=https://registry.npm.taobao.org
    或者你可以直接将 taobao 的源配置到当前用户,如 npm config set registry https://registry.npm.taobao.org
    如果想使用默认的则使用如下指令删除即可: npm config delete registry
    配置之后可以通过如下指令来验证: npm config get registry 或者 npm info express
  2. npm 如何检测一个包是否安装
    运行 npm ls ${PACKAGE} 或者 npm ls -g ${PACKAGE}, 然后用 $? 判断是否成功安装
LDAP -- Lightweight Directory Access Protocol -- 轻量目录访问协议

LDAP 是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。LDAP的一个常用用途是单点登录,用户可以在多个服务中使用同一个密码,通常用于公司内部网站的登录中(这样他们可以在公司电脑上登入一次,便可以自动在公司内部网上登入)。

  1. LDAP特点
    • LDAP的结构用树来表示,而不是用表格。正因为这样,就不能用SQL语句了
    • LDAP可以很快地得到查询结果,不过在写方面,就慢得多
    • LDAP提供了静态数据的快速查询方式
    • Client/server模型,Server 用于存储数据,Client提供操作目录信息树的工具
    • 这些工具可以将数据库的内容以文本格式(LDAP 数据交换格式,LDIF)呈现在您的面前
    • LDAP是一种开放Internet标准,LDAP协议是跨平台的Interent协议
  2. 需要了解的知识点

    增删改查, 条目(Entry), 属性(Attribute), schema, 对象类(ObjectClass), 必要属性(Must, Required), 可选属性(May, Optional), 对象的三种类型结构类型(Structural) 抽象类型(Abstract) 辅助类型(Auxiliary)slapd, backends, database, openldap, bdb, TLS & SASL, LDIF

    schema 一般在 /etc/ldap/schema/ 目录。它一般由条目、属性和值组成,这些成员又由对象类(ObjectClass)、属性类型(AttributeType)、语法(Syntax)来限制。

  3. 安装步骤

    这里只介绍在 ubuntu 下的安装,以及配置过程中出现的问题,主要是参考 [6] 来搭建的,为了更好的阅读体验,建议读者直接产看 [6] 的安装步骤,如果遇到什么问题,再回头看看,这里是否有解决方法,或者思路。

        // 安装 openldap
        apt-get install sladp ldap-utils openldap-utils phpldapadmin apache2
    
        // apache2 主要是来做做 phpldapadmin 的 web 服务器的。
        // 安装过程中会提示输入admin密码
        // 从 2.4.x 版本开始 slapd.conf 配置形式官方已经废弃了但如果要用还是可以的,需要在 /etc/default/slapd 中指定 slapd.conf 配置文档路径,
        // 其中 SLAPD_CONF= 这个全局命令即是配置项目, 可以用一下命令配置你的 slapd.conf 所在的路径。
        sed 's#SLAPD_CONF=#&/home/username/.config/slapd.conf#' -i /etc/default/slapd
    
        // 如果在想把你的 slapd.conf 转换成 slapd.d 配置目录的形式可以用以下命令,(注意用以下命令前先备份原始目录)
        slaptest -f /home/username/.config/slapd.conf -F /etc/ldap/slapd.d/
        chown -R openldap:openldap /etc/ldap/slapd.d/
    
        // 我们这个例子中使用默认的 slapd.d 配置目录的形式
        // 如果需要重新配置 slapd 可以运行以下命令,需要管理员权限
        dpkg-reconfigure slapd
        // 具体的配置过程中应该怎么选择可以参考 [6]
        // 需要注意的是,我在 docker 中搭建的 ldap, 用的是 ubuntu 1604,需要注意的是每次我调用 /etc/init.d/slapd restart 之后 ldap 都 fail
        // 用 ps 查看之后发现 slapd 并没有在 stop 的时候真正退出,因此当我重新配置了 slapd.d 之后我都会执行
        ps -aux > /tmp/ps;
        cat /tmp/ps | grep "/usr/sbin/slapd" | awk '{print $2}' | xargs kill -9;
        /etc/ini.d/slapd start
    
        // 需要注意的是 https 搭建好之后,用浏览器打开会遇到网站安全性方面的问题,但不用担心,因为在这个例子中,网站的证书是我们自己颁发的。直接信任就可以了。
    
        // 需要注意的是 /etc/ldap/slapd.d 下配置 (或用 dpkg-reconfigure 重新配置的)的用户名最好与 phpldapadmin 中的 servers->base, servers->bind_id 保持一致。
        // 如果你的用户名是 admin 域名是 test.com 的话,需要修改 /etc/phpldapadmin/config.php 中的内容,如下:
        $servers->setValue('server','base',array('dc=test,dc=com'));
        $servers->setValue('login','bind_id','cn=admin,dc=test,dc=com');
    
        // 创建 ssl 证书以及配置 apache2 支持 https 这个都比较容易,这里就不用再赘述了。
        
参考文档
  • [0]openldap 官网
  • [1]LDAP 维基百科
  • [2]LDAP服务器的概念和原理简单介绍 -- segmentfault
  • [3]LDAP服务器的搭建 -- csdn
  • [4]LDIF修改ldap记录或配置示例
  • [5]OpenLDAP(2.4.3x)服务器搭建及配置说明
  • [6]如何在Ubuntu 14.04服务器上配置并安装OpenLDAP和phpLDAPadmin
  • [7]如何在Ubuntu 12.04 VPS上安装和配置基本LDAP服务器
  • [8]OpenLdap 简易教程
  • [9]Linux/UNIX OpenLDAP实战指南
  • [10]Installation instructions
  • [11]caffe 安装
  • [12]cuda 安装
  • [13]nodejs & npm 安装
  • [14]nodejs 中文网站
  • [15]nodejs 官方网站
  • [16]mongoose 官方网站(文档)
WWW
前端图形库 Chat.js

Chart.js 官网: https://www.chartjs.org

  • 关闭动效
    在配置的时候将 options.animation.duration 设置成 0 即可
  • 如歌更新
    首先替换数据集 myChart.data.datasets = [{...},{...}], 然后再调用 myChart.update()
前端图形库 D3.js
参考资料
  • bootstrap 的图形库
  • searxsearX 是一个开源的搜索引擎,汇总来自70多个搜索服务(包括谷歌)的结果,不跟踪也不分析用户。使用时,需要自己架设实例。这个网站列出了世界各地现有的实例,以及访问速度的实时统计。
  • 在线工具秘籍 -- 这个中文仓库收集各种好用的在线小工具。
  • Moment.js JavaScript 日期处理类库
  • visxAirBnb 开源的一套可视化组建库
算法
寻路算法
  • [link_1]关于寻路算法的一些思考1

    名词解释:Dijkstra单元最短路径算法的一种,思想就是从起始点开始,层层遍历与之相近的节点,直到搜索到目标节点,终止搜索过程。其中经常与这个算法一并提起的还有Floyd 算法『最短路径—Dijkstra算法和Floyd算法』 这篇文章有两个算法的简单对比。贪心最优优先搜索(greedy best first search):针对目标节点,给出一个估计方位(启发值),在搜索开始时选取偏向目标的节点优先搜索,直到发现目标节点,终止搜索。详细说明可以[link_1],里面有详细的图解。

    A*算法结合了贪心最好优先搜索算法和Dijsktra算法的优点。A*算法不仅拥有发式算法的快速,同时,A*算法建立在启发式之上,能够保证在启发值无法保证最优的情况下,生成确定的最短路径。

  • [link_1]关于寻路算法的一些思考2
  • [link_1]关于寻路算法的一些思考3
  • [link_1]关于寻路算法的一些思考4
  • [link_1]关于寻路算法的一些思考5
  • [link_1]关于寻路算法的一些思考6
  • [link_1]关于寻路算法的一些思考7
  • [link_1]关于寻路算法的一些思考8
  • [link_1]关于寻路算法的一些思考9
  • [link_1]关于寻路算法的一些思考10
  • [link_1]关于寻路算法的一些思考11
  • [link_1]关于寻路算法的一些思考12
  • [link_1_Attachments]Map representations
  • [link_1_Attachments]Grids and Graphs
  • [link_1_Attachments]Amit’s Thoughts on Grids
Andrew NG ML 课程课后习题

停止更新,停止更新,停止更新(重要的事情说三遍)。这个课程已经学完,开始的时候自己英语还有点问题,但到后面专业词汇也都熟悉了,感觉没那么难了。总的来说吴恩达讲的还是很生动易懂的,因此也就没觉得必要记录了。相比较而言以后还是记录一些调参的心得吧!

  1. 梯度下降算法

    下面是基于 Andrew NG's ML 视频示例中的公式实现的代码,需要注意的是,根据输入参数要特别小心的选择 Alpha 的值, 初始化 Theta0 和 Theta1 的值, 同时 Alpha 的选择会影响最终收敛的速度,所以 Count 可能也会需要相应的改变。

     alpha=0.01
     theta_0 = 0
     theta_1 = 0
    
     dataSet0 = [
            [3,4],
            [2,1],
            [4,3],
            [0,1] ]
    
     dataSet1 = [
            [1,0.5],
            [2,1],
            [4,2],
            [0,0]
            ]
    
     dataSet2 = [
            [1,-890],
            [2,-1411],
            [2,-1560],
            [3,-2220],
            [3,-2091],
            [4,-2878],
            [5,-3537],
            [6,-3268],
            [6,-3920],
            [6,-4163],
            [8,-5471],
            [10,-5157],
            ]
    
     predictX = 2
    
     def gradient_convergence( theta0, theta1, dataSet ):
         m = len( dataSet )
         tvTheta0MinusSum = 0
         tvTheta1MinusSum = 0
         for v in dataSet:
             # print( "k=",v[0], " v=" ,v[1])
             tvTheta0MinusSum += theta0 + theta1*v[0] - v[1]
             tvTheta1MinusSum += ( theta0 + theta1*v[0] - v[1] ) * v[0]
         # print( "tvTheta0MinusSum = ", tvTheta0MinusSum, "tvTheta1MinusSum = ", tvTheta1MinusSum )
         tvTheta0 = theta0 - tvTheta0MinusSum * alpha / m
         tvTheta1 = theta1 - tvTheta1MinusSum * alpha / m
         # print( "newTheta0=", tvTheta0, "newTheta1=", tvTheta1 )
         # predictY = tvTheta0 + predictX*tvTheta1
         # print( "predictY=", predictY )
         # time.sleep(2)
         return tvTheta0, tvTheta1
    
     count = 0
     while count < 10000:
         count += 1
         # theta_0, theta_1 = gradient_convergence( theta_0, theta_1, dataSet0)
         # theta_0, theta_1 = gradient_convergence( theta_0, theta_1, dataSet1)
         theta_0, theta_1 = gradient_convergence( theta_0, theta_1, dataSet2)
    
     print( "newTheta0=", theta_0, "newTheta1=", theta_1 )
    
    
  2.  octave 求 dataset 的 costfunction

    X 为因的集合,Y 为果的集合,theta 为线性参量,J 为成本果与线性函数的平均离差平方和。

        function J = computeCost(X, y, theta)
            m = length(y);
            % J=sum( ( X * theta - y ) .^ 2 ) / ( 2 * m );
            J = (X * theta - y)' * (X * theta - y )/m/2;
        end
    
  3. octave 梯度下降法求拟合度最大的线性参量

    X 为因的集合,Y 为果的集合,theta 为线性参量,alpha 为梯度下降的速度权重,num_iters 迭代次数,J_history 梯度下降时每次的期望与实际结果的鸿沟宽度。

        function [theta, J_history] = gradientDescent(X, y, theta, alpha, num_iters)
            m = length(y); % number of training examples
            J_history = zeros(num_iters, 1);
            for iter = 1:num_iters
                theta = theta - ( alpha / m ) .* ( X' * ( X * theta  - y ));
                J_history(iter) = computeCostMulti(X, y, theta);
            end
        end
        
  4. octave 其矩阵各个元素的归一化值。

    X m * n 的矩阵,mean 针对 X 中的每一列求平均值,std 求 X 每一列的标准差。

            X_normal = (X-mean(X))/std(X);
        
  5. octave 利用正规方程求预测线性函数各参量的精确解。
            theta = pinv(X'*X) * X' * y;
        
参考资料
  1. [link]二叉堆【1】(PS:图文分析, C 实现)
  2. [link]二叉堆【2】(PS:C++ 实现)
  3. [link]二叉堆【3】(PS:JAVA 实现)
  4. [link]左倾堆【1】(PS:图文分析, C 实现)
  5. [link]左倾堆【2】(PS:C++ 实现)
  6. [link]左倾堆【3】(PS:JAVA 实现)
  7. [link]斜堆【1】(PS:图文分析, C 实现)
  8. [link]斜堆【2】(PS:C++ 实现)
  9. [link]斜堆【3】(PS:JAVA 实现)
  10. [link]二项堆【1】(PS:图文分析, C 实现)
  11. [link]二项堆【2】(PS:C++ 实现)
  12. [link]二项堆【3】(PS:JAVA 实现)
  13. [link]斐波那契堆【1】(PS:图文分析, C 实现)
  14. [link]斐波那契堆【2】(PS:C++ 实现)
  15. [link]斐波那契堆【3】(PS:JAVA 实现)
  16. [link]BP神经网络模型与学习算法(PS:先读读这个入门)
  17. [link]脉络清晰的BP神经网络讲解,赞(PS:然后再读读这个梳理一下逻辑,加深一下理解,这个人写的其他东西也可看看,很多干货)
  18. [link]神经网络编程入门(PS:可以开始在 Matlab 上演练演练了)
  19. [link]传统激活函数、脑神经元激活频率研究、稀疏激活性(PS:写这货的人也是个牛人,博客里面其他的东西都可以看看)
  20. [link]HTK 隐马尔可夫模型工具包
  21. [link]声纹识别,听声辨人(注:对语音识别方面的一些理解)
  22. [link]中国科学院语言声学与内容理解重点实验室
  23. [link]高斯混合模型(声纹识别)
  24. [link]文本无关的声纹识别 验证
  25. [link]开源的声纹识别
  26. [link]CodeForge 声纹识别源代码
  27. [link]几个常见的语音交互平台的简介和比较
  28. [link]中文普通话拼音全集
  29. [link]ALIZE
  30. [link]RASR
  31. [link]Ekho 中文文本转语音引擎
  32. [link]Audacity 官网
  33. [link]Audacity 教程
  34. [link]人工智能中的常用搜索策略(衡量一个搜索策略的好坏,我们需要从四个方面对其进行判断:完备性、时间复杂度、空间复杂度和最优性)
安全相关
安全编程
  1. 缓冲区溢出 是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。 传送门
  2. 内存泄露 是指申请了内存资源之后忘记释放,或者释放失败,从而导致内存泄露,其他程序不能申请该内存,也使用不了, 传送门
  3. 差一错误 是在计数时由于边界条件判断失误导致结果多了一或少了一的错误,通常指计算机编程中循环多了一次或者少了一次的程序错误,属于逻辑错误的一种。 传送门
  4. Secure Programming HOWTO - Creating Secure Software 传送门
Android 安全
  1. Android 官方安全文档 1 (最佳实践都在其中) 传送门
  2. Android 官方安全文档 2 (最佳实践都在其中) 传送门
  3. Blacklight 用于检查网站跟踪用户使用到的种类
测试

为什么要写测试: 1. 保证代码的稳定性,在后期重构代码的时候知道输入的范围,输入对应的输出是什么,输入错误会发生什么事。 2. 保证和外界代码交互的稳定性。 3. 侧面验证设备的稳定性,如果代码不变,但是在不同机器上跑出了不同的测试结果,要么是测试样例设计不完善,要么是机器环境产生了未知的 Bug。

什么样的代码是可测试的

首先,在我们隔离了待测试方法中一些依赖之后,当函数的入参确定时,就应该得到期望的返回值。 如何控制待测试方法中依赖的模块是写单元测试时至关重要的,控制依赖也就是对目标函数的依赖进行 Mock 消灭不确定性,为了减少每一个单元测试的复杂度,我们需要: 1)尽可能减少目标方法的依赖,让目标方法只依赖必要的模块;2)依赖的模块也应该非常容易地进行 Mock;

单元测试的执行不应该依赖于任何的外部模块,无论是调用外部的 HTTP 请求还是数据库中的数据,我们都应该想尽办法模拟可能出现的情况,因为单元测试不是集成测试的, 它的运行不应该依赖除项目代码外的其他任何系统。

JUnit - 入门( JUnit )

第一步,在 Gradle 工程中配置依赖,如下:

// 在 Mudule 的 build.gradle 中添加
dependencies {
    testImplementation 'junit:junit:4.12'
}

假设我们有一个待测试类如下:

package com.example.test.junit;
public class Fibonacci {
    public static int compute(int n) {
        int result = 0;

        if (n <= 1) {
            result = n;
        } else {
            result = compute(n - 1) + compute(n - 2);
        }

        return result;
    }
}

然后创建该类的测试类,此处有两个方法。方法一:右击类名 -> Goto -> Test -> 选择目标测试工具,以及默认要生成的测试方法 -> 选择保存测试类的路径 -> 创建成功, 因为我 AS 在我的 Linux 上存在一些兼容性问题,无法出现创建的菜单栏,此处不再演示。而方法二是直接在想要保存测试路径里创建该测试类即可,比如我的被测试类保存在 src/java/com/example/test/junit, 那 junit 的测试类一般保存在 test/java/com/example/test/junit

然后编写测试文件,如下:

package com.example.mojies.mojies;

import org.junit.Test;
import static org.junit.Assert.*;


public class FibonacciTest {
    @Test
    public void test() {
        assertEquals(0, Fibonacci.compute(0));
        assertEquals(1, Fibonacci.compute(1));
        assertEquals(1, Fibonacci.compute(2));
    }
}

然后右击该测试类,然后点击 Run 'FibonacciTest' 则可以直接运行该测试类

当然如果你嫌弃一个一个写 assert 麻烦则可以采用以下这种写法:

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class FibonacciTest {
    @Parameters
    public static Collection<object[]> data() {
        return Arrays.asList(new Object[][] {     
                    { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }  
            });
    }

    private int fInput;

    private int fExpected;

    public FibonacciTest(int input, int expected) {
        this.fInput = input;
        this.fExpected = expected;
    }

    @Test
    public void test() {
        assertEquals(fExpected, Fibonacci.compute(fInput));
    }
}

JUnit 在判断测试是否通过主要是根据 assert 的执行结果来判断的,但在 @Test Annotation 指定的函数中可以执行除 assert 的判断函数外,还可以执行其他操作, 上叙采用 @Parameterized 方式麻烦的话写成下面这样也是可以的。

package com.example.mojies.mojies;

import org.junit.Test;
import static org.junit.Assert.*;

public class FibonacciTest {

    @Test
    public void test() {
        int data[][] =  {
                { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
        };
        for( int[] tc: data ){
            assertEquals(tc[1], Fibonacci.compute(tc[0]));
        }
    }
}

但需要注意的是采用上面的方式如果 test() 中发生了异常, 那么该测试会直接退出。比如说如果 data[4] = {4,4}, 此时 data[5] data[6] 就不会测试了。

此外一个测试类中还能包含多个测试方法,而测试的顺序取决于你在文档中编排的顺序,当然前一个测试方法测试失败也并不会影响下一个测试方法的执行。

关于 JUnit 提供的 Assert 列表请参考: https://github.com/junit-team/junit4/wiki/Assertions

关于 JUnit 提供的 Annotation 方法请参考: JUnit 对 Annotation 的测试文档

关于单元测试

在安卓中, 测试大致可以分为小/中/大形测试,而 小/中/大 的占比大约是 7:2:1, 其中小型测试主要以本地单元测试和插桩测试为主。 中形测试的测试范围大致包括:1)视图和视图模型之间的互动;2)应用的代码库层中的测试;3)测试特定屏幕上的互动;4)多 Fragment 测试,评估应用的特定区域。 而大型测试主要负责测试端到端的功能,交互流程是否和预期设计一致。

 
测试的结构在金字塔模型之前,流行的是冰淇淋模型。
包含了大量的手工测试、端到端的自动化测试及少量的单元测试。
造成的后果是,随着产品壮大,手工回归测试时间越来越长,质量很难把控;自动化case频频失败,
每一个失败对应着一个长长的函数调用,到底哪里出了问题?
单元测试少的可怜,基本没作用。

Mike Cohn 在他的著作《Succeeding with Agile》一书中提出了“测试金字塔”这个概念。
这个比喻非常形象,它让你一眼就知道测试是需要分层的。它还告诉你每一层需要写多少测试。

同时,我们对金字塔的理解绝不能止步于此,要进一步理解:我把金字塔模型理解为——冰激凌融化了。
就是指,最顶部的“手工测试”理论上全部要自动化,向下融化,优先全部考虑融化成单元测试,单元测试覆盖不了的 放在中间层(分层测试),再覆盖不了的才会放到UI层。
因此,UI层的case,能没有就不要有,跑的慢还不稳定。
按照乔帮主的说法,我不分单元测试还是分层测试,统一都叫自动化测试,
那就应该把所有的自动化case看做一个整体,case不要冗余,单元测试能覆盖,就要把这个case从分层或ui中去掉。

越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广。
比如单元测试,它的关注点只有一个单元,而没有其它任何东西。
所以,只要一个单元写好了,测试就是可以通过的;而集成测试则要把好几个单元组装到一起才能测试,测试通过的前提条件是,
所有这些单元都写好了,这个周期就明显比单元测试要长;系统测试则要把整个系统的各个模块都连在一起,各种数据都准备好,才可能通过。

另外,因为涉及到的模块过多,任何一个模块做了调整,都有可能破坏高层测试,所以,高层测试通常是相对比较脆弱的,在实际的工作中,
有些高层测试会牵扯到外部系统,这样一来,复杂度又在不断地提升。

-- 摘自 谈有效的单元测试 腾讯效能团队
什么时候适合mock
如果一个对象具有以下特征,比较适合使用mock对象:
1. 该对象提供非确定的结果(比如当前的时间或者当前的温度)
2. 对象的某些状态难以创建或者重现(比如网络错误或者文件读写错误)
3. 对象方法上的执行太慢(比如在测试开始之前初始化数据库)
4. 该对象还不存在或者其行为可能发生变化(比如测试驱动开发中驱动创建新的类)
5. 该对象必须包含一些专门为测试准备的数据或者方法(后者不适用于静态类型的语言,流行的Mock框架不能为对象添加新的方法。Stub是可以的。)

-- 摘自 谈有效的单元测试 腾讯效能团队
Mockito

关于 Mockito 的使用方法可以先参考 Mockito 官网首页中的实例,好有直观的认识。最好是把首页玩玩整整看一遍,因为 Guide 基本都在首页里面了。https://site.mockito.org/

首先是依赖,在你的 build.gradle 中添加 androidTestImplementation 'org.mockito:mockito-android:2.23.0', 现在的 mockito 版本已经到 3.x 了,但是 Mocktito 3.0 需要 JAVA8。

Mockito 的一般使用方法如下:

Handler handler = Mockito.mock(Handler.class);
Mockito.when(handler.sendMessageDelayed( Mockito.any(Message.class), Mockito.anyLong()))
    .thenAnswer(new Answer() {

    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        // Object mock = invocation.getMock();
        // do something
        return null;
    }
});
yourClassInstance.setHandle( handler );

但你可能想要 mock void 方法,此时的使用方法如下: 以下这个例子在我的工程中没有跑成功,原因我现在也没找到,但是这个使用模式是对的, 如果你把 handle.removeMessages 换成其他 return void 的方法,在这里使用这个方法仅仅是为了介绍使用模式

Handler handler = Mockito.mock(Handler.class);

// 如果你啥也不想处理
Mockito.doNothing().when(handler).removeMessages( 1 ); // 如果你不在乎匹配参数为 1 的调用也可以是 Mockito.anyInt()

// 如果你有异常想要抛出
Mockito.doThrow(new Exception()).doNothing().when(handler).removeMessages(anyInt(), Mockito.any(), Mockito.any());

// 如果你想处理一些什么
Mockito.doAnswer( new Answer(){
    @Override
    public Void answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        // Object mock = invocation.getMock();
        // do something
        return null;
    }
} ).when( handler ).removeMessages(Mockito.anyInt());

yourClassInstance.setHandle( handler );

关于上叙 doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 之类的使用方法请参考: Mockito javadoc 中的说明

你可能注意到了,上面我使用 Mockito 去匹配 Handler.removeMessages 失败了,但是我用下面的方法成功了。 虽然可能需要在逻辑上判断 removeMessages 是否使用成功。

private class MockHandler extends Handler {
    MockHandler() {
        super(Looper.getMainLooper());
    }

    @Override
    public void handleMessage(Message msg) {
        // check your msg
    }
}

{
    ...
    Handler handler = new MockHandler();
    yourClassInstance.setHandle( handler );
    ...
}
如何测试 Http 请求

小节主要参考 stackover flow 上的一篇 帖子。 可能还有更好的办法,大家可以在评论区提出自己宝贵的建议。

一个通用的办法是用 square okhttp mockwebserver,这是 okhttp 使用的一个模拟 http server 的一个 mock 库。 如果你对如何使用该库有任何细节问题可以参考 okhttp 自己的 测试代码

下面是一个测试的例子:

// YOUR HTTP REQUEST CLASS BEING TESTED
public class HttpClient {
    public String getBody() {
        return body;
    }

    private String body;

    public void getData( String url ) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url( url )
                .build();
        try( Response response = client.newCall( request ).execute() ){
            body = response.body().string();
        }
    }
}

// YOUR UNIT TEST CLASS
public class HttpClientTest {
    @Test
    public void getDataTest() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setBody("success mojies return"));

        HttpClient httpClient = new HttpClient();
        httpClient.getData( mockWebServer.url("/").toString() );

        System.out.println( httpClient.getBody() );

    }
}

注意: 如果你的测试样例中包含 Url 组装部分,则这部分要单独测试。 mockWebServer.url("/").toString() 会返回你测试请求时候需要访问的 Url, 你必须通过这个 URL 去访问才能得到 Mock 的 response。 将该 URL 打印出来可以得到基本上是一个类似于这样的字符串 http://localhost.localdomain:59509/, 因此也可一推测 MockWebServer 的原理应该是在本地开一个 HTTP server, 同时引导测试代码访问该 Url,从而实现 HTTP Server Mock。

另外 stackoverflow 的那个例子中说必须要加上 @RunWith 注解是错误的,okhttp 并没有依赖 android 原生 API, 应该是作者在使用的时候用到了一些 Android 的 API。

而且 MockWebServer 还可以模拟以下情况,重定向,请求超时,请求失败(头信息错误,连接断开,发送响应消息过程中失败),延迟响应 ... (具体请参考上面提到的 MockWebServer 的自己的测试例子)

另外在测试的时候,你可能需要在 Test Case 里面等待整个请求的完成(因为 JUnit 一般认为程序执行跳出测试方法的作用域之后测试就完成了,而网络请求是需要时间的), 一个可行的方法是利用 mockwebserver 的请求同步方法,但是如果你的方法封装了很多东西,此方法可能不太适用。 另外一个方法是调用 await 库中的同步方法,该方法会周期性查询你设置的退出条件,在满足之后会停止阻塞,使程序往下走(参考 awaitility/awaitility )。

如何生成测试覆盖报告
powermock
参考资料
Andoird 开发小技巧
  • Android Studio 4.0 Run 窗口中文乱码
    Help->Edit Custom VM Options -> 确认( 或者按 shift 两下激活全局搜索,然后搜索 Edit Custom VM Options )
    增加 -Dfile.encoding=UTF-8 一行
    File -> Invalidate Caches / Restart -> Just Restart
  • 内存分析
    Memory Analyzer (MAT)
    官方参考文档
    注意从 Android studio 导出的 hprof 文件在 MAT 上打开需要用以下命令转换一下: hprof-conv heap-original.hprof heap-converted.hprof
参考资料
参考文档
优秀的博客
图像处理
原创文章,版权所有,转载请获得作者本人允许并注明出处
我是留白;我是留白;我是留白;(重要的事情说三遍)

发表评论

0/200
330 点赞
0 评论
收藏